Core API

Server#

ServerDef is a normalized, transport-polymorphic MCP server descriptor — declared once, rendered into each host's native dialect.

ServerDef
ts
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
}
FieldTypeDefaultNotes
transport
required
"stdio" | "http" | "sse" | "ws"—Selects the dialect each adapter renders.
commandstring—Required for stdio transport.
argsstring[]—Process arguments (stdio).
envRecord<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.
secretEnvRecord<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.
cwdstring—Working directory (stdio).
urlstring—Required for remote transport (http | sse | ws).
headersRecord<string, string>—Sent on remote requests.
authAuthSpec—{ type: "oauth" | "bearerEnv" | "none", bearerEnvVar? }.
toolsToolFilter{ include: ["*"] }Glob/exact include / exclude tool names.
timeoutMsnumber—Per-call timeout.
enabledbooleantrueWhen false, written disabled where the host supports it.
wrapForTelemetrybooleantrue (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 Code
json
// ~/.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