Files
deepseek-harness/packages/mcp/mcp-client/src/transport.ts
lintianle 1fbe7c39d4 feat: add MCP client plugin (dsh-mcp-client)
Connects to an external MCP server and registers its tools on
ctx.tools. Supports stdio (child process) and Streamable HTTP
transports. Credential-shaped env vars are scrubbed before forwarding
to child processes.

- Plugin lifecycle: connect, sync tools, re-sync on ToolListChanged,
  dispose unregisters and closes
- Full JSDoc on all exports (@param/@returns on functions)
- 100% per-file coverage (apply lifecycle, args coercion, env scrubbing)
- Config catalog regenerated
2026-07-07 23:22:10 +08:00

57 lines
2.1 KiB
TypeScript

/**
* Transport factory: creates the appropriate MCP transport based on the
* plugin's resolved config. Stdio spawns a child process (with credential
* scrubbing); Streamable HTTP connects to a URL.
*
* @module
*/
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
import type { Config } from './index.ts'
/**
* Credential-shaped ambient env vars are NOT forwarded to the child by default
* (the parent harness's own secrets must not leak into a spawned process
* implicitly). Same pattern as `dsh-subagent-acp`.
*/
const SENSITIVE_ENV_PATTERN = /KEY|SECRET|TOKEN/i
/** The ambient env minus credential-shaped vars, plus the spec's explicit env. */
function buildChildEnv(extra: Record<string, string>): Record<string, string> {
const env: Record<string, string> = {}
for (const [key, value] of Object.entries(process.env)) {
if (value !== undefined && !SENSITIVE_ENV_PATTERN.test(key)) env[key] = value
}
return { ...env, ...extra }
}
/**
* Create an MCP transport from the resolved plugin config.
*
* @param config - Resolved plugin config discriminated on `transport`.
* @returns A connected-ready MCP Transport (stdio or Streamable HTTP).
*/
export function createTransport(config: Config): Transport {
switch (config.transport) {
case 'stdio':
return new StdioClientTransport({
command: config.command,
args: config.args,
env: buildChildEnv(config.env),
cwd: config.cwd,
})
case 'streamable-http':
// The MCP SDK's StreamableHTTPClientTransport has optional callback
// properties typed without `| undefined` (exactOptionalPropertyTypes
// mismatch with the Transport interface). The cast is safe — the SDK
// constructed the object, it simply doesn't declare the optionals
// strictly enough for our tsconfig.
return new StreamableHTTPClientTransport(
new URL(config.url),
{ requestInit: { headers: config.headers } },
) as Transport
}
}