English | 中文
MCP client bridge plugin: connects to external Model Context Protocol servers and registers their tools on ctx.tools, making them available to the model as native tools under server-qualified names (mcp__<serverName>__<rawName>).
One plugin instance per MCP server in cordis.yml:
- id: mcp-github
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: github
transport: stdio
command: npx
args: ['-y', '@modelcontextprotocol/server-github']
env:
GITHUB_TOKEN: !!js process.env.GITHUB_TOKEN
- id: mcp-web
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: web
transport: streamable-http
url: http://localhost:3000/mcp
headers:
Authorization: !!js '`Bearer ${process.env.MCP_TOKEN}`'
The model sees mcp__github__create_issue, mcp__web__search, … — the same server-qualified shape Claude Code and Codex use. HMR hot-swaps: editing the entry triggers disconnect + reconnect without process restart; an unchanged serverName reproduces identical tool names.
| Field | Transport | Required | Description |
|---|---|---|---|
transport |
both | yes | "stdio" or "streamable-http" |
serverName |
both | yes | Namespace for this server's model-facing tool names; [A-Za-z0-9_-]{1,32}, unique inside one registration scope |
command |
stdio | yes | Executable to spawn |
args |
stdio | no | Arguments passed to the command |
env |
stdio | no | Extra env vars merged on top of scrubbed ambient env |
cwd |
stdio | no | Working directory for the child process |
url |
http | yes | MCP server URL |
headers |
http | no | Extra headers (e.g. auth tokens) |
toolCallTimeoutMs |
both | no | Timeout per callTool invocation (default 60000) |
failOnStartupError |
both | no | Reject plugin activation when initial connection or tool synchronization fails (default false) |
reconnect.enabled |
both | no | Reconnect automatically after a lost connection (default true) |
reconnect.initialDelayMs |
both | no | First reconnect delay in ms; doubles per consecutive failed attempt (default 500) |
reconnect.maxDelayMs |
both | no | Backoff ceiling in ms; also the uptime after which the attempt budget resets (default 30000) |
reconnect.maxAttempts |
both | no | Consecutive failed attempts per outage before giving up for good (default 10) |
Every MCP tool has two names: the raw MCP name (sent on the wire in tools/call) and the public name mcp__<serverName>__<rawName> registered on ctx.tools. Public names are normalized to the DeepSeek function-name contract (64 chars, [A-Za-z0-9_-]); when replacement or truncation changes the name, a deterministic 12-hex-char hash of (serverName, rawName) is appended so distinct tools never collapse into one name. Names are pure functions of (serverName, rawName) — connection order, re-syncs, and other servers never rename a tool.
search) coexist under their namespaces.serverName inside one live registration scope fails the later plugin instance at load. Independent Agent scopes may reuse the same namespace because their tools and transports are isolated.listTools() and registers each tool via ctx.tools.register() under its public name before the composition starts its first turn. Initial connection, discovery, or registration failure is always logged; it rejects activation when failOnStartupError is true and otherwise activates with no tools.notifications/tools/list_changed → re-syncs; a fetch-phase failure keeps the previous generation registered, while a registration conflict rolls back the attempted generation and leaves no tools from that server.client.callTool({ name: rawName, arguments }, { signal }) with timeout + abort support—the public name is never sent to the server.{ content: JsonValue[], structuredContent? }; complete JSON MCP blocks survive for programmatic callers. A supported advertised outputSchema validates structuredContent; unsupported schema vocabulary falls back to unconstrained JsonValue.ctx.attachments is mounted and the exact calling model route explicitly declares image input. The whole image batch is decoded and admitted before any member is saved. A malformed/refused image batch, audio, embedded resources, and unsupported blocks become explicit diagnostic text rather than disappearing.reconnect.initialDelayMs doubling up to reconnect.maxDelayMs) and re-runs discovery on success — the recovered generation replaces the previous one, so tools neither duplicate nor leak. During the outage the last good generation stays registered; calls against it fail until recovery.reconnect.maxAttempts consecutive failures the server's tools are unregistered and reconnection stops until an HMR reload or Host restart. A connection that survives past maxDelayMs resets the budget, so an occasionally-crashing server recovers indefinitely while a crash-looping one — even with briefly successful connects — still exhausts the cap instead of restarting forever.reconnect.enabled: false, a lost connection keeps tools registered but failing until a reload — the manual-recovery behavior.| Service | Usage |
|---|---|
ctx.tools |
Register/unregister MCP tools |
ctx.attachments |
Optionally validate and persist image result batches before model projection |
ctx.llm |
Optionally prove the exact calling route explicitly supports image input |
After initial discovery succeeds, each advertised MCP tool appears as a native tool named mcp__<serverName>__<rawName> (or its deterministic normalized form), with the server-provided description and input schema. A successful re-sync — including the one after an automatic reconnect — replaces the generation; plugin disposal or an exhausted reconnect budget removes it.
Data-dependent schema cost is paid on every request while the tools are registered. Re-sync replaces rather than accumulates schemas, and the server-qualified name adds tokens to every tool definition and call.
Prefix-stable while the discovered tool set and schemas are unchanged. A re-sync that adds, removes, renames, or changes a tool replaces definitions and may invalidate reuse from the first changed schema token; a reconnect that recovers an unchanged list reproduces identical definitions and stays prefix-stable.
The public tool name and JSON arguments remain in assistant history. The execution-local canonical value always retains the complete JSON MCP blocks and optional structured content for programmatic and Code Mode callers. In Native context, supported image blocks are durably projected beside text in their original order after exact route-capability proof; Code Mode additionally ferries that settled rich projection through the outer run_code result without changing the canonical binding value. Refused images, audio, embedded resources, resource links, and unknown blocks remain visible as bounded text diagnostics, and MCP isError rejects the call before image persistence.
Arguments, mapped text, and durable image references are retained until compaction. Inline MCP base64 stays only in the execution-local canonical value and is never copied into a session event; the provider reads verified bytes from the attachment store. Audio and embedded-resource payloads stay out of model context.
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
tools/list request uses the SDK's 60-second default, so an unresponsive server or cursor chain can delay both activation and teardown while the initial synchronization settles.structuredContent falls back to JsonValue when the advertised schema uses vocabulary outside the harness subset.