Skip to content

User Configuration

Many MCP servers need configuration like API keys. The user_config field in your manifest lets you declare what users need to provide.

In your manifest.json:

{
"name": "@yourorg/my-api-server",
"version": "1.0.0",
"user_config": {
"api_key": {
"type": "string",
"title": "API Key",
"description": "Your API key from example.com/dashboard",
"sensitive": true,
"required": true
},
"base_url": {
"type": "string",
"title": "API Base URL",
"description": "Override the default API endpoint",
"default": "https://api.example.com"
}
},
"server": {
"type": "python",
"mcp_config": {
"command": "python",
"args": ["-m", "my_server"],
"env": {
"API_KEY": "${user_config.api_key}",
"API_BASE_URL": "${user_config.base_url}"
}
}
}
}
FieldTypeDescription
typestringstring, number, boolean
titlestringHuman-readable label
descriptionstringHelp text for users
sensitivebooleanMask in output (default: false)
requiredbooleanMust be provided (default: false)
defaultanyDefault value if not provided

The mpak SDK’s gatherUserConfig walks these tiers for each user_config field. First non-empty value wins:

  1. Caller overrideuserConfig passed to prepareServer({ userConfig }) by a host runtime (SDK 0.3.0+).
  2. Stored config — values set via mpak config set, read from ~/.mpak/config.json.
  3. Env alias — the manifest’s mcp_config.env declaration, read in reverse. If you mapped "API_KEY": "${user_config.api_key}", a host API_KEY env var satisfies api_key (SDK 0.4.0+). Only whole-value substitutions participate; composite templates like "prefix-${user_config.a}" don’t.
  4. Manifest default — the field’s default.

If a required field is still unresolved, the SDK throws MpakConfigError. Each missing field carries its envAliases list so callers can render a specific export VAR=... hint (SDK 0.5.0+).

Terminal window
# Store configuration
mpak config set @yourorg/my-api-server api_key=sk-xxx
# Run uses stored config automatically
mpak bundle run @yourorg/my-api-server

Option 2: Claude Desktop (environment variables)

Section titled “Option 2: Claude Desktop (environment variables)”
{
"mcpServers": {
"my-api": {
"command": "mpak",
"args": ["bundle", "run", "@yourorg/my-api-server"],
"env": {
"API_KEY": "sk-xxx"
}
}
}
}

The flow from user config to environment variable:

manifest.user_config.api_key → mpak config set ... api_key=xxx → env API_KEY
^^^^^^^ ^^^^^^^ ^^^^^^^
user_config key config key env var

Users provide the user_config key name. Your manifest maps it to an environment variable.

In your README, list what users need to configure:

## Configuration
This server requires an API key from [example.com](https://example.com/dashboard).
### Claude Desktop
\`\`\`json
{
"mcpServers": {
"my-api": {
"command": "mpak",
"args": ["bundle", "run", "@yourorg/my-api-server"],
"env": {
"API_KEY": "your-api-key"
}
}
}
}
\`\`\`
### CLI
\`\`\`bash
mpak config set @yourorg/my-api-server api_key=your-api-key
mpak bundle run @yourorg/my-api-server
\`\`\`

Always set sensitive: true for API keys, passwords, and tokens:

{
"user_config": {
"api_key": {
"type": "string",
"sensitive": true
}
}
}

This masks the value in mpak config get output.

Your mcp_config.env name is also the host env alias (tier 3 above). Pick the name the upstream service’s own SDK uses so users who already have it exported get zero-config startup:

{
"mcp_config": {
"env": { "ANTHROPIC_API_KEY": "${user_config.anthropic_api_key}" }
}
}

A user with ANTHROPIC_API_KEY already in their shell — which Anthropic’s own SDK reads — runs your bundle with no extra configuration. Renaming to MYBUNDLE_ANTHROPIC_KEY would force them to re-export the same secret for no benefit.

When the upstream has no de-facto standard, use the name the service itself calls it:

UpstreamEnv var
AnthropicANTHROPIC_API_KEY
OpenAIOPENAI_API_KEY
GitHubGITHUB_TOKEN
Google GeminiGOOGLE_GENERATIVE_AI_API_KEY
StripeSTRIPE_API_KEY
AWSAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
PostgresDATABASE_URL, or PGHOST/PGUSER/etc.

For multiple keys into the same service, keep the service prefix and disambiguate with a suffix: SLACK_BOT_TOKEN and SLACK_USER_TOKEN, not MYBUNDLE_SLACK_BOT.

For optional configuration, provide defaults:

{
"user_config": {
"timeout": {
"type": "number",
"title": "Request Timeout",
"description": "Timeout in seconds",
"default": 30
}
}
}