Core API
Server#
ServerDef is a normalized, transport-polymorphic MCP server descriptor — declared once, rendered into each host's native dialect.
interface ServerDef {
transport: "stdio" | "http" | "sse" | "ws";
// stdio transport:
command?: string; // required for stdio
args?: string[];
env?: Record<string, string>; // ${env:VAR} / ${env:VAR:-default}
cwd?: string;
// remote (http | sse | ws) transport:
url?: string; // required for remote
headers?: Record<string, string>;
auth?: AuthSpec; // { type, bearerEnvVar? }
// common:
tools?: ToolFilter; // { include?: string[]; exclude?: string[] }
timeoutMs?: number;
enabled?: boolean; // default true
wrapForTelemetry?: boolean; // default true for stdio
}| Field | Type | Default | Notes |
|---|---|---|---|
transportrequired | "stdio" | "http" | "sse" | "ws" | — | Selects the dialect each adapter renders. |
command | string | — | Required for stdio transport. |
args | string[] | — | Process arguments (stdio). |
env | Record<string, string> | — | Values support ${env:VAR} / ${env:VAR:-default} interpolation. A stdio server may also write ${secret:NAME} to reference a secret the user stored with `secrets set NAME` in the OS keystore (rejected anywhere else, judged by the effective transport; no :-default form). ${env:VAR} around a reference is expanded by the serve wrapper at launch; a secret value never is. A platforms[<id>].server.env override replaces the base env together with its secrets. |
secretEnv | Record<string, string> | — | Set by defineConnector, not by you: every env entry whose value references ${secret:NAME} is moved here. Adapters never see it and no host config carries a value — the serve wrapper gets a --secret-env NAME={secret:NAME} placeholder, resolves it from the OS keystore at launch and injects the value into the server's environment; a missing (or empty) secret aborts the launch. |
cwd | string | — | Working directory (stdio). |
url | string | — | Required for remote transport (http | sse | ws). |
headers | Record<string, string> | — | Sent on remote requests. |
auth | AuthSpec | — | { type: "oauth" | "bearerEnv" | "none", bearerEnvVar? }. |
tools | ToolFilter | { include: ["*"] } | Glob/exact include / exclude tool names. |
timeoutMs | number | — | Per-call timeout. |
enabled | boolean | true | When false, written disabled where the host supports it. |
wrapForTelemetry | boolean | true (stdio) / false (remote) | Wrap with agent-connector serve so per-tool telemetry is captured. Remote transports can't be intercepted. |
Transports & dialects#
The root key and field names differ per host (constant per adapter): mcpServers (Claude Code, Cursor, Copilot CLI, Codebuff, Warp, Antigravity, …), servers (VS Code Copilot), mcp_servers (Codex TOML), mcp (Crush, OpenCode, Kilo), a flat dotted amp.mcpServers (Amp), context_servers (Zed). Field renames like cwd↔working_directory and env↔environment are handled per adapter. An adapter that cannot honor a requested transport downgrades-or-skips and reports it — it never throws.
${env:VAR} / ${env:VAR:-default} interpolation is universal; where a host supports native interpolation the reference is translated rather than baked in.
Per-dialect output#
For the example server, npx @acme/acme-db-mcp install writes each host's native shape (hooks land in a sibling settings file, all pointing back to the one stable home binary):
// ~/.claude.json
// wrapForTelemetry (default for stdio) wraps the real command behind the
// home-bin serve proxy; ${env:VAR} is translated to Claude's native ${VAR}.
{
"mcpServers": {
"acme-db": {
"type": "stdio",
"command": "/home/you/.agent-connector/bin/agent-connector",
"args": ["serve", "--connector", "acme-db", "--scope", "user",
"--host", "claude-code", "--", "npx", "-y", "@acme/acme-db-mcp"],
"env": { "ACME_DB_DSN": "${ACME_DB_DSN}" }
}
}
}
// + hooks registered in ~/.claude/settings.json