Skip to content

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:

  1. Environment Variables - Persistent credentials and settings stored in NCP profiles
  2. Parameters - Runtime values passed when calling tools
  3. 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 account
  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY - Your AWS credentials
  • GITHUB_TOKEN - Your GitHub authentication
  • SMTP_HOST, SMTP_USER, SMTP_PASS - Your email server
  • DATABASE_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 ​

typescript
// 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 ​

typescript
// 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 ​

typescript
// 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:

typescript
/**
 * 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):

json
{
  "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:

  1. Detect this placeholder
  2. Prompt you to enter the actual value
  3. Store it securely in OS keychain
  4. Load it at runtime

Option B: Use Environment Variables Directly ​

SimpleMCPs can also read from your shell environment:

bash
# Add to ~/.bashrc or ~/.zshrc
export OPENAI_API_KEY="sk-..."
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."

Then run NCP:

bash
ncp run ai:complete prompt="Hello" provider=openai

Step 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_":

  1. First run prompts for the actual value
  2. Value is stored in OS keychain
  3. Profile JSON never contains plain-text credentials
  4. NCP loads credentials at runtime

Step 4: Verify Configuration ​

Check that your MCP can access credentials:

bash
# 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

json
// Profile config
{
  "ai": {
    "type": "internal",
    "env": {
      "OPENAI_API_KEY": "_USE_SECURE_STORAGE_",
      "ANTHROPIC_API_KEY": "_USE_SECURE_STORAGE_"
    }
  }
}
bash
# 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=anthropic

Service-Heavy MCPs (Notify) ​

Configure:

  • SMTP server settings
  • Default email sender

Pass as parameters:

  • Webhook URLs (different channels)
  • Recipients
  • Message content

Example: Notify MCP

json
// 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"
    }
  }
}
bash
# 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

typescript
// 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

bash
# 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=encode

Security Best Practices ​

1. Never Hardcode Secrets ​

typescript
// ❌ 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 ​

json
// ❌ 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:

typescript
/**
 * 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 ​

typescript
// ✅ 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 ​

typescript
// ✅ 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
bash
# Location
~/.ncp/all.json

# Available to all projects
ncp run ai:complete prompt="Hello" provider=openai

Project-Local Configuration (.ncp/) ​

Use for:

  • Project-specific credentials (test accounts, staging keys)
  • Project-specific MCPs
  • Team shared settings (checked into git with placeholders)
bash
# Location
.ncp/all.json

# Only available in this project directory
cd /path/to/project
ncp run ai:complete prompt="Hello" provider=openai

Configuration Priority ​

NCP merges configurations in this order (last wins):

  1. Global config (~/.ncp/all.json)
  2. Project-local config (.ncp/all.json)
  3. Shell environment variables (export VAR=value)

Validation and Error Handling ​

Validate Configuration at Runtime ​

typescript
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 ​

typescript
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 ​

json
{
  "mcpServers": {
    "ai": {
      "type": "internal",
      "env": {
        "OPENAI_API_KEY": "_USE_SECURE_STORAGE_",
        "ANTHROPIC_API_KEY": "_USE_SECURE_STORAGE_"
      }
    }
  }
}
bash
# 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) ​

json
{
  "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_"
      }
    }
  }
}
bash
# 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 ​

json
{
  "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"
      }
    }
  }
}
bash
# 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:

  1. Check your profile JSON has the env var defined
  2. Verify you're using the correct profile (ncp profile list, ncp profile use <name>)
  3. If using _USE_SECURE_STORAGE_, make sure you entered the value when prompted

Credentials not loading ​

Problem: _USE_SECURE_STORAGE_ not working

Solution:

  1. Check secure credential store is accessible
  2. Try setting plain-text value temporarily (then NCP auto-migrates to secure storage)
  3. Check NCP logs for credential loading errors

Wrong profile being used ​

Problem: Config defined but not being used

Solution:

bash
# Check active profile
ncp profile list

# Switch to correct profile
ncp profile use all

# Verify config
cat ~/.ncp/all.json  # or .ncp/all.json

Parameter 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 @env tags)
  • [ ] Add env vars to NCP profile (~/.ncp/all.json or .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 ​

ConcernUse ConfigUse ParameterUse Elicitation
WhatCredentials, persistent settingsOperation inputsInteractive/sensitive
WhenSet once, use many timesDifferent each callWhen needed
WhereNCP profile JSON + OS keychainTool call parametersRuntime dialog
ExampleAPI keys, SMTP serverPrompts, file pathsMissing API key

Happy configuring! 🔒

Released under the Elastic License 2.0.