SimpleMCP Configuration Management
Overview
SimpleMCP integrates with NCP's robust configuration system to handle credentials, API keys, and persistent settings. This guide explains how to configure your SimpleMCPs properly.
Core Concepts
Configuration Types
SimpleMCP supports three types of configuration:
- Environment Variables - Persistent credentials and settings stored in NCP profiles
- Parameters - Runtime values passed when calling tools
- Elicitation - Interactive prompts for sensitive or dynamic values
Decision Framework: Config vs. Parameter vs. Elicitation
When to Use Configuration (Environment Variables)
Use environment variables in NCP profiles for:
✅ Persistent credentials - API keys, tokens, passwords ✅ Service endpoints - URLs, hostnames, ports ✅ User identity - Account IDs, usernames, regions ✅ Shared settings - Used across multiple tool calls
Mental model: Configuration answers "Who am I?" and "How do I authenticate?"
Examples:
OPENAI_API_KEY- Your OpenAI accountAWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY- Your AWS credentialsGITHUB_TOKEN- Your GitHub authenticationSMTP_HOST,SMTP_USER,SMTP_PASS- Your email serverDATABASE_URL- Your database connection
When to Use Parameters
Use tool parameters for:
✅ Operation-specific values - Different each invocation ✅ User input - What the user wants to do ✅ Data to process - Files, queries, prompts ✅ Destination/target - Where to send, what to modify
Mental model: Parameters answer "What do I want to do?"
Examples:
- Webhook URLs (different Slack channels)
- S3 bucket/key names (different files)
- Database queries
- AI prompts
- Email recipients and content
- Search terms, file paths
When to Use Elicitation
Use elicitation for:
✅ Sensitive values - Shouldn't appear in chat logs ✅ Missing configuration - Prompt if not pre-configured ✅ Interactive choices - User selects from options ✅ Confirmations - Approve destructive operations
Mental model: Elicitation answers "What do I need to ask the user right now?"
Examples:
- API key during first-time setup
- Choosing between multiple accounts
- Confirming "delete all data"
- Entering one-time passwords
Configuration vs. Parameter Examples
❌ Bad: Webhook URL as Config
// DON'T DO THIS - too rigid
export class Notify {
async slack(params: { text: string }) {
// Hardcoded from config - can only send to one channel!
const webhookUrl = process.env.SLACK_WEBHOOK_URL;
await sendToSlack(webhookUrl, params.text);
}
}
// Usage: Can only send to pre-configured channel
ncp run notify:slack text="Deploy complete"Problem: User can't send to different Slack channels without changing config.
✅ Good: Webhook URL as Parameter
// DO THIS - flexible
export class Notify {
async slack(params: { webhookUrl: string; text: string }) {
// User provides webhook URL per invocation
await sendToSlack(params.webhookUrl, params.text);
}
}
// Usage: Flexible - can send to any channel
ncp run notify:slack webhookUrl="https://hooks.slack.com/services/T00/B00/XXX" text="Deploy complete"
ncp run notify:slack webhookUrl="https://hooks.slack.com/services/T00/B00/YYY" text="Build failed"Benefit: User can send to different channels without reconfiguring.
✅ Good: SMTP as Config, Recipients as Parameters
// DO THIS - config for persistent, params for variable
export class Notify {
async email(params: { to: string; subject: string; text: string }) {
// SMTP settings from config (persistent identity)
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT || '587'),
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});
// Recipients and content from parameters (per-operation)
await transporter.sendMail({
from: process.env.SMTP_FROM,
to: params.to,
subject: params.subject,
text: params.text,
});
}
}
// Usage: SMTP configured once, recipients vary per call
ncp run notify:email to="alice@example.com" subject="Alert" text="CPU high"
ncp run notify:email to="bob@example.com" subject="Report" text="Daily summary"How to Configure SimpleMCPs
Step 1: Understand Required Environment Variables
Check the MCP's JSDoc comments for @env tags:
/**
* AI MCP - Multi-provider AI operations
*
* @dependencies openai@^4.0.0, @anthropic-ai/sdk@^0.9.0
*
* @env OPENAI_API_KEY - OpenAI API key (required for OpenAI provider)
* @env ANTHROPIC_API_KEY - Anthropic API key (required for Anthropic provider)
*/
export class AI {
// ...
}Step 2: Add SimpleMCP to NCP Profile
SimpleMCPs are internal MCPs loaded automatically, but you need to configure their environment variables.
Option A: Edit Profile JSON Directly
Edit ~/.ncp/all.json (or .ncp/all.json for project-local):
{
"name": "all",
"mcpServers": {
"ai": {
"type": "internal",
"env": {
"OPENAI_API_KEY": "_USE_SECURE_STORAGE_",
"ANTHROPIC_API_KEY": "_USE_SECURE_STORAGE_"
}
},
"cloud": {
"type": "internal",
"env": {
"AWS_ACCESS_KEY_ID": "_USE_SECURE_STORAGE_",
"AWS_SECRET_ACCESS_KEY": "_USE_SECURE_STORAGE_",
"AWS_REGION": "us-east-1"
}
}
}
}Important: Use "_USE_SECURE_STORAGE_" for sensitive values. NCP will:
- Detect this placeholder
- Prompt you to enter the actual value
- Store it securely in OS keychain
- Load it at runtime
Option B: Use Environment Variables Directly
SimpleMCPs can also read from your shell environment:
# Add to ~/.bashrc or ~/.zshrc
export OPENAI_API_KEY="sk-..."
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."Then run NCP:
ncp run ai:complete prompt="Hello" provider=openaiStep 3: Store Credentials Securely
NCP automatically uses the SecureCredentialStore which:
- macOS: Stores in Keychain
- Windows: Stores in Credential Manager
- Linux: Stores in Secret Service (or encrypted file)
When you use "_USE_SECURE_STORAGE_":
- First run prompts for the actual value
- Value is stored in OS keychain
- Profile JSON never contains plain-text credentials
- NCP loads credentials at runtime
Step 4: Verify Configuration
Check that your MCP can access credentials:
# This will fail with clear error if credentials missing
ncp run ai:complete prompt="Hello" provider=openai
# Error message should guide you:
# "OPENAI_API_KEY not configured. Set it in your NCP profile..."Configuration Patterns by MCP Type
Credentials-Heavy MCPs (AI, Cloud, GitHub)
Configure:
- API keys
- Access tokens
- Service credentials
Pass as parameters:
- Prompts
- File paths
- Resource names
Example: AI MCP
// Profile config
{
"ai": {
"type": "internal",
"env": {
"OPENAI_API_KEY": "_USE_SECURE_STORAGE_",
"ANTHROPIC_API_KEY": "_USE_SECURE_STORAGE_"
}
}
}# Usage - credentials from config, prompt as parameter
ncp run ai:complete prompt="Explain quantum computing" provider=openai
ncp run ai:vision imageUrl="https://..." question="What's this?" provider=anthropicService-Heavy MCPs (Notify)
Configure:
- SMTP server settings
- Default email sender
Pass as parameters:
- Webhook URLs (different channels)
- Recipients
- Message content
Example: Notify MCP
// Profile config - persistent SMTP settings
{
"notify": {
"type": "internal",
"env": {
"SMTP_HOST": "smtp.gmail.com",
"SMTP_PORT": "587",
"SMTP_USER": "bot@company.com",
"SMTP_PASS": "_USE_SECURE_STORAGE_",
"SMTP_FROM": "noreply@company.com"
}
}
}# Email uses SMTP config
ncp run notify:email to="user@example.com" subject="Alert" text="CPU high"
# Slack webhook as parameter (flexible per call)
ncp run notify:slack webhookUrl="https://hooks.slack.com/..." text="Deploy done"Minimal Configuration MCPs (Database, Scraper)
Configure: Optional default connection strings
Pass as parameters: Everything else
Example: Database MCP
// No required config! Everything passed as parameters
ncp run database:query dbPath="./data.db" query="SELECT * FROM users"
ncp run database:postgres connectionString="postgresql://..." query="SELECT 1"Why no config? Database MCP supports multiple databases, so connection info is operation-specific.
Zero Configuration MCPs (Utilities)
Configure: Nothing
Pass as parameters: All inputs
Example: String, Math, Encode MCPs
# No config needed - pure functions
ncp run string:slugify text="Hello World"
ncp run math:random min=1 max=100
ncp run encode:base64 text="Hello" action=encodeSecurity Best Practices
1. Never Hardcode Secrets
// ❌ DON'T DO THIS
const API_KEY = "sk-1234567890abcdef"; // Exposed in source code!
// ✅ DO THIS
const API_KEY = process.env.OPENAI_API_KEY;
if (!API_KEY) {
throw new Error('OPENAI_API_KEY not configured');
}2. Use Secure Credential Storage
// ❌ DON'T DO THIS - plain text in profile
{
"env": {
"OPENAI_API_KEY": "sk-1234567890abcdef"
}
}
// ✅ DO THIS - secure storage reference
{
"env": {
"OPENAI_API_KEY": "_USE_SECURE_STORAGE_"
}
}3. Mark Sensitive Parameters
If you must accept sensitive values as parameters, document it:
/**
* Upload with temporary credentials
* @param accessKeyId Temporary AWS access key (SENSITIVE - do not log)
* @param secretAccessKey Temporary AWS secret key (SENSITIVE - do not log)
*/
async uploadTemp(params: { accessKeyId: string; secretAccessKey: string; ... }) {
// Use params.accessKeyId, params.secretAccessKey
}4. Provide Clear Error Messages
// ✅ Good - guides user to solution
if (!process.env.OPENAI_API_KEY) {
throw new Error(
'OPENAI_API_KEY not configured.\n' +
'Add it to your NCP profile:\n' +
' ~/.ncp/all.json\n' +
' "env": { "OPENAI_API_KEY": "_USE_SECURE_STORAGE_" }'
);
}
// ❌ Bad - cryptic error
if (!process.env.OPENAI_API_KEY) {
throw new Error('API key missing');
}Environment Variable Naming Conventions
Follow Standard Patterns
- API Keys:
{SERVICE}_API_KEY(e.g.,OPENAI_API_KEY,GITHUB_API_KEY) - Tokens:
{SERVICE}_TOKEN(e.g.,GITHUB_TOKEN,SLACK_TOKEN) - Credentials:
{SERVICE}_{CREDENTIAL_TYPE}(e.g.,AWS_ACCESS_KEY_ID,SMTP_USER) - URLs:
{SERVICE}_URL(e.g.,DATABASE_URL,REDIS_URL) - Connection Strings:
{SERVICE}_CONNECTION_STRING(e.g.,AZURE_STORAGE_CONNECTION_STRING)
Use Descriptive Names
// ✅ Good - clear what it's for
OPENAI_API_KEY
AWS_ACCESS_KEY_ID
SMTP_HOST
// ❌ Bad - ambiguous
API_KEY // Which API?
KEY_ID // What kind of key?
HOST // Which service?Global vs. Project-Local Configuration
Global Configuration (~/.ncp/)
Use for:
- Personal credentials (your GitHub token, OpenAI key)
- MCPs used across all projects
- User-level settings
# Location
~/.ncp/all.json
# Available to all projects
ncp run ai:complete prompt="Hello" provider=openaiProject-Local Configuration (.ncp/)
Use for:
- Project-specific credentials (test accounts, staging keys)
- Project-specific MCPs
- Team shared settings (checked into git with placeholders)
# Location
.ncp/all.json
# Only available in this project directory
cd /path/to/project
ncp run ai:complete prompt="Hello" provider=openaiConfiguration Priority
NCP merges configurations in this order (last wins):
- Global config (
~/.ncp/all.json) - Project-local config (
.ncp/all.json) - Shell environment variables (
export VAR=value)
Validation and Error Handling
Validate Configuration at Runtime
export class AI {
private validateConfig(provider: 'openai' | 'anthropic'): string {
const envVar = provider === 'openai' ? 'OPENAI_API_KEY' : 'ANTHROPIC_API_KEY';
const apiKey = process.env[envVar];
if (!apiKey) {
throw new Error(
`${envVar} not configured.\n\n` +
`To configure:\n` +
`1. Edit ~/.ncp/all.json\n` +
`2. Add to env section:\n` +
` "env": {\n` +
` "${envVar}": "_USE_SECURE_STORAGE_"\n` +
` }\n` +
`3. Run any AI tool - you'll be prompted for the key\n` +
`4. Key will be stored securely in OS keychain`
);
}
return apiKey;
}
async complete(params: { prompt: string; provider: 'openai' | 'anthropic' }) {
const apiKey = this.validateConfig(params.provider);
// Use apiKey...
}
}Use Config Helpers
import { requireEnv, optionalEnv } from './config-helpers.js';
export class Cloud {
async s3Upload(params: { bucket: string; key: string; content: string }) {
// Throws clear error if not configured
const accessKeyId = requireEnv('AWS_ACCESS_KEY_ID', 'AWS credentials required for S3 operations');
const secretAccessKey = requireEnv('AWS_SECRET_ACCESS_KEY');
// Optional with default
const region = optionalEnv('AWS_REGION', 'us-east-1');
// Use credentials...
}
}Configuration Examples
Example 1: AI MCP
{
"mcpServers": {
"ai": {
"type": "internal",
"env": {
"OPENAI_API_KEY": "_USE_SECURE_STORAGE_",
"ANTHROPIC_API_KEY": "_USE_SECURE_STORAGE_"
}
}
}
}# Both providers configured
ncp run ai:complete prompt="Hello" provider=openai
ncp run ai:complete prompt="Hello" provider=anthropic
ncp run ai:similarity text1="cat" text2="kitten"Example 2: Cloud MCP (Multi-provider)
{
"mcpServers": {
"cloud": {
"type": "internal",
"env": {
"AWS_ACCESS_KEY_ID": "_USE_SECURE_STORAGE_",
"AWS_SECRET_ACCESS_KEY": "_USE_SECURE_STORAGE_",
"AWS_REGION": "us-east-1",
"GCP_PROJECT_ID": "my-project",
"GCP_CREDENTIALS": "_USE_SECURE_STORAGE_",
"AZURE_STORAGE_CONNECTION_STRING": "_USE_SECURE_STORAGE_"
}
}
}
}# All cloud providers configured
ncp run cloud:s3-upload bucket="my-bucket" key="file.txt" content="..."
ncp run cloud:gcs-upload bucket="my-bucket" filename="file.txt" content="..."
ncp run cloud:azure-upload container="my-container" blobName="file.txt" content="..."Example 3: Notify MCP
{
"mcpServers": {
"notify": {
"type": "internal",
"env": {
"SMTP_HOST": "smtp.gmail.com",
"SMTP_PORT": "587",
"SMTP_USER": "bot@company.com",
"SMTP_PASS": "_USE_SECURE_STORAGE_",
"SMTP_FROM": "noreply@company.com"
}
}
}
}# SMTP configured, webhooks as parameters
ncp run notify:email to="user@example.com" subject="Alert" text="Server down"
ncp run notify:slack webhookUrl="https://..." text="Deploy complete"
ncp run notify:discord webhookUrl="https://..." content="Build #42 succeeded"Troubleshooting
"Environment variable not set" error
Problem: MCP can't find required environment variable
Solution:
- Check your profile JSON has the env var defined
- Verify you're using the correct profile (
ncp profile list,ncp profile use <name>) - If using
_USE_SECURE_STORAGE_, make sure you entered the value when prompted
Credentials not loading
Problem: _USE_SECURE_STORAGE_ not working
Solution:
- Check secure credential store is accessible
- Try setting plain-text value temporarily (then NCP auto-migrates to secure storage)
- Check NCP logs for credential loading errors
Wrong profile being used
Problem: Config defined but not being used
Solution:
# Check active profile
ncp profile list
# Switch to correct profile
ncp profile use all
# Verify config
cat ~/.ncp/all.json # or .ncp/all.jsonParameter vs. config confusion
Problem: Not sure whether to use config or parameter
Ask yourself:
- Does this value change every invocation? → Parameter
- Is this a credential or persistent setting? → Config
- Could the user want to use different values without reconfiguring? → Parameter
Summary
Configuration Checklist
- [ ] Identify required environment variables (check JSDoc
@envtags) - [ ] Add env vars to NCP profile (
~/.ncp/all.jsonor.ncp/all.json) - [ ] Use
_USE_SECURE_STORAGE_for sensitive values - [ ] Test MCP - verify clear error messages if config missing
- [ ] Document configuration in MCP's JSDoc comments
Quick Reference
| Concern | Use Config | Use Parameter | Use Elicitation |
|---|---|---|---|
| What | Credentials, persistent settings | Operation inputs | Interactive/sensitive |
| When | Set once, use many times | Different each call | When needed |
| Where | NCP profile JSON + OS keychain | Tool call parameters | Runtime dialog |
| Example | API keys, SMTP server | Prompts, file paths | Missing API key |
Happy configuring! 🔒