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
57 lines
2.1 KiB
TypeScript
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
|
|
}
|
|
}
|