@fgv/ts-extras-mcp 5.1.0-37 → 5.1.0-39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.rush/temp/{8523ec7cdfdd100d7242d368a2a014f4283131ad.tar.log → 28abef25194b96c733b5ec1c8816ab85532589aa.tar.log} +2 -2
- package/.rush/temp/chunked-rush-logs/ts-extras-mcp.build.chunks.jsonl +4 -4
- package/.rush/temp/operation/build/all.log +4 -4
- package/.rush/temp/operation/build/log-chunks.jsonl +4 -4
- package/.rush/temp/operation/build/state.json +1 -1
- package/.rush/temp/shrinkwrap-deps.json +227 -226
- package/config/jest.config.json +1 -1
- package/dist/packlets/mcp/adapter.js +2 -7
- package/dist/packlets/mcp/adapter.js.map +1 -1
- package/dist/packlets/mcp/model.js.map +1 -1
- package/dist/packlets/mcp/operations.js +31 -6
- package/dist/packlets/mcp/operations.js.map +1 -1
- package/dist/packlets/mcp/sdk.js.map +1 -1
- package/dist/test/unit/mcp.test.js +113 -0
- package/dist/test/unit/mcp.test.js.map +1 -1
- package/dist/ts-extras-mcp.d.ts +31 -0
- package/dist/tsdoc-metadata.json +1 -1
- package/etc/ts-extras-mcp.api.md +10 -0
- package/lib/packlets/mcp/adapter.d.ts.map +1 -1
- package/lib/packlets/mcp/adapter.js +2 -7
- package/lib/packlets/mcp/adapter.js.map +1 -1
- package/lib/packlets/mcp/model.d.ts +30 -0
- package/lib/packlets/mcp/model.d.ts.map +1 -1
- package/lib/packlets/mcp/model.js.map +1 -1
- package/lib/packlets/mcp/operations.d.ts.map +1 -1
- package/lib/packlets/mcp/operations.js +30 -5
- package/lib/packlets/mcp/operations.js.map +1 -1
- package/lib/packlets/mcp/sdk.d.ts +6 -0
- package/lib/packlets/mcp/sdk.d.ts.map +1 -1
- package/lib/packlets/mcp/sdk.js.map +1 -1
- package/lib/test/unit/mcp.test.js +113 -0
- package/lib/test/unit/mcp.test.js.map +1 -1
- package/package.json +9 -9
- package/rush-logs/ts-extras-mcp.build.cache.log +1 -1
- package/rush-logs/ts-extras-mcp.build.log +4 -4
- package/src/packlets/mcp/adapter.ts +5 -2
- package/src/packlets/mcp/model.ts +31 -0
- package/src/packlets/mcp/operations.ts +46 -3
- package/src/packlets/mcp/sdk.ts +6 -0
- package/src/test/unit/mcp.test.ts +128 -0
- package/temp/build/lint/_eslint-5eVG3S6w.json +6 -6
- package/temp/build/typescript/ts_8nwakTlr.json +1 -1
- package/temp/ts-extras-mcp.api.json +182 -1
- package/temp/ts-extras-mcp.api.md +10 -0
package/config/jest.config.json
CHANGED
|
@@ -50,7 +50,7 @@ function _makeExecute(session, name) {
|
|
|
50
50
|
* tool's `inputSchema` is not a JSON object or is outside the `JsonSchema.fromJson` subset.
|
|
51
51
|
*/
|
|
52
52
|
function _adaptOne(session, descriptor) {
|
|
53
|
-
const { name, description, inputSchema } = descriptor;
|
|
53
|
+
const { name, description, inputSchema, annotations } = descriptor;
|
|
54
54
|
const skip = (reason, schema) => ({
|
|
55
55
|
kind: 'skipped',
|
|
56
56
|
skipped: { name, reason, schema }
|
|
@@ -63,12 +63,7 @@ function _adaptOne(session, descriptor) {
|
|
|
63
63
|
if (schemaResult.isFailure()) {
|
|
64
64
|
return skip(schemaResult.message, inputSchema);
|
|
65
65
|
}
|
|
66
|
-
const config = {
|
|
67
|
-
type: 'client_tool',
|
|
68
|
-
name,
|
|
69
|
-
description: description !== null && description !== void 0 ? description : name,
|
|
70
|
-
parametersSchema: schemaResult.value
|
|
71
|
-
};
|
|
66
|
+
const config = Object.assign({ type: 'client_tool', name, description: description !== null && description !== void 0 ? description : name, parametersSchema: schemaResult.value }, (annotations !== undefined ? { annotations } : {}));
|
|
72
67
|
return {
|
|
73
68
|
kind: 'tool',
|
|
74
69
|
tool: { config, execute: _makeExecute(session, name) }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../src/packlets/mcp/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;GAKG;AAEH,OAAO,EAAe,OAAO,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAkB,MAAM,mBAAmB,CAAC;AAS3E,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAUzD;;;;;GAKG;AACH,SAAS,YAAY,CAAC,OAAoB,EAAE,IAAY;IACtD,OAAO,KAAK,EAAE,IAAa,EAA4B,EAAE;QACvD,MAAM,SAAS,GAAG,UAAU,CAAC,UAAU;aACpC,OAAO,CAAC,IAAI,CAAC;aACb,eAAe,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,IAAI,uCAAuC,GAAG,EAAE,CAAC,CAAC;QACvF,IAAI,SAAS,CAAC,SAAS,EAAE,EAAE,CAAC;YAC1B,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,CAAC,MAAM,WAAW,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5G,CAAC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,SAAS,CAAC,OAAoB,EAAE,UAA8B;IACrE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../src/packlets/mcp/adapter.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * The headline adapter — discovers an MCP server's tools and adapts each into an\n * `AiAssist.IAiClientTool`, gracefully degrading (Constraint 1) on any tool whose `inputSchema`\n * is outside the JSON Schema subset that `JsonSchema.fromJson` supports.\n * @packageDocumentation\n */\n\nimport { type Result, succeed } from '@fgv/ts-utils';\nimport { AiAssist } from '@fgv/ts-extras';\nimport { Converters, JsonSchema, type JsonValue } from '@fgv/ts-json-base';\n\nimport {\n type IAdaptMcpToolsOptions,\n type IAdaptMcpToolsResult,\n type IMcpSession,\n type IMcpSkippedTool,\n type IMcpToolDescriptor\n} from './model';\nimport { callMcpTool, listMcpTools } from './operations';\n\n/**\n * Per-tool adaptation outcome: either an adapted client tool, or a structurally-surfaced skip.\n * `adaptMcpTools` never short-circuits on a skip — degradation is structural, not a failure.\n */\ntype IAdaptOutcome =\n | { readonly kind: 'tool'; readonly tool: AiAssist.IAiClientTool }\n | { readonly kind: 'skipped'; readonly skipped: IMcpSkippedTool };\n\n/**\n * Builds the `execute` callback for an adapted MCP tool. Args arrive already validated against\n * the tool's `parametersSchema` by `executeClientToolTurn`; here we narrow to a `JsonObject`\n * (MCP arguments are always an object) and forward to {@link callMcpTool}, returning the\n * projected text content. A tool error surfaces as a `Failure` (never swallowed).\n */\nfunction _makeExecute(session: IMcpSession, name: string): (args: unknown) => Promise<Result<unknown>> {\n return async (args: unknown): Promise<Result<unknown>> => {\n const objResult = Converters.jsonObject\n .convert(args)\n .withErrorFormat((msg) => `tool '${name}': arguments must be a JSON object: ${msg}`);\n if (objResult.isFailure()) {\n return objResult;\n }\n return (await callMcpTool(session, name, objResult.value)).onSuccess((called) => succeed(called.content));\n };\n}\n\n/**\n * Adapts a single discovered tool. Returns a `skipped` outcome (never a failure) when the\n * tool's `inputSchema` is not a JSON object or is outside the `JsonSchema.fromJson` subset.\n */\nfunction _adaptOne(session: IMcpSession, descriptor: IMcpToolDescriptor): IAdaptOutcome {\n const { name, description, inputSchema, annotations } = descriptor;\n\n const skip = (reason: string, schema: JsonValue): IAdaptOutcome => ({\n kind: 'skipped',\n skipped: { name, reason, schema }\n });\n\n const asObject = Converters.jsonObject.convert(inputSchema);\n if (asObject.isFailure()) {\n return skip(`inputSchema is not a JSON object (${asObject.message})`, inputSchema);\n }\n\n const schemaResult = JsonSchema.fromJson(asObject.value);\n if (schemaResult.isFailure()) {\n return skip(schemaResult.message, inputSchema);\n }\n\n const config: AiAssist.IAiClientToolConfig = {\n type: 'client_tool',\n name,\n description: description ?? name,\n parametersSchema: schemaResult.value,\n // IMcpToolAnnotations maps 1:1 onto AiAssist.IAiToolAnnotations. Only set the field when the\n // server provided usable annotations, so a tool without them stays exactly as before.\n ...(annotations !== undefined ? { annotations } : {})\n };\n return {\n kind: 'tool',\n tool: { config, execute: _makeExecute(session, name) }\n };\n}\n\n/**\n * Discovers an MCP server's tools and adapts each into an `AiAssist.IAiClientTool` that drops\n * directly into `AiAssist.executeClientToolTurn`.\n *\n * @remarks\n * **Constraint 1 — graceful degradation with NOISY warnings.** A tool whose `inputSchema` is\n * outside the JSON Schema subset supported by `JsonSchema.fromJson` is NOT adapted (the model\n * must never be offered a tool whose args we can't validate). Instead it is:\n * 1. excluded from {@link IAdaptMcpToolsResult.tools};\n * 2. surfaced structurally on {@link IAdaptMcpToolsResult.skipped} with the tool name, the\n * JSON-pointer reason, and the raw failing schema; and\n * 3. logged as a NOISY `warning` (name + reason + raw schema) when an `options.logger` is\n * supplied — so pointing this at a new server immediately reveals every subset feature to\n * extend, with the schema in hand.\n *\n * The whole catalog never fails on a single bad schema; the only failure mode is an upstream\n * `listMcpTools` error.\n *\n * @param session - A connected session from `connectMcpSession`.\n * @param options - Optional logger for the NOISY skip warnings.\n * @returns `Success` with `{ tools, skipped }`, or `Failure` only if tool discovery fails.\n * @public\n */\nexport async function adaptMcpTools(\n session: IMcpSession,\n options?: IAdaptMcpToolsOptions\n): Promise<Result<IAdaptMcpToolsResult>> {\n return (await listMcpTools(session))\n .onSuccess((descriptors) => {\n const tools: AiAssist.IAiClientTool[] = [];\n const skipped: IMcpSkippedTool[] = [];\n\n for (const descriptor of descriptors) {\n const outcome = _adaptOne(session, descriptor);\n if (outcome.kind === 'tool') {\n tools.push(outcome.tool);\n } else {\n skipped.push(outcome.skipped);\n options?.logger?.warn(\n `mcp: skipping tool '${outcome.skipped.name}': inputSchema is outside the supported ` +\n `JSON Schema subset: ${outcome.skipped.reason}. ` +\n `Raw schema: ${JSON.stringify(outcome.skipped.schema)}`\n );\n }\n }\n\n return succeed<IAdaptMcpToolsResult>({ tools, skipped });\n })\n .withErrorFormat((msg) => `adaptMcpTools: ${msg}`);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/packlets/mcp/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Public types for the MCP → ai-assist client-tools boundary.\n * @packageDocumentation\n */\n\nimport { type Logging } from '@fgv/ts-utils';\nimport { type AiAssist } from '@fgv/ts-extras';\nimport { type JsonValue } from '@fgv/ts-json-base';\n\n// ============================================================================\n// Transport\n// ============================================================================\n\n/**\n * Parameters for {@link createStdioTransport}. The transport spawns `command` as a\n * subprocess and speaks MCP over its stdin/stdout.\n *\n * @remarks\n * **Security:** stdio transport executes a consumer-supplied command. Treat `command`/`args`\n * as a trust boundary — never source them from untrusted input. See the package README.\n *\n * @public\n */\nexport interface IMcpStdioTransportParams {\n /** Executable to spawn (e.g. `'npx'`, `'node'`, an absolute path). */\n readonly command: string;\n /** Arguments passed to the command. */\n readonly args?: ReadonlyArray<string>;\n /** Environment variables for the spawned process. When omitted, the SDK's safe default set is used. */\n readonly env?: Record<string, string>;\n /** Working directory for the spawned process. */\n readonly cwd?: string;\n}\n\n/**\n * Parameters for {@link createHttpTransport}. The transport connects to a Streamable-HTTP MCP\n * endpoint.\n * @public\n */\nexport interface IMcpHttpTransportParams {\n /** Absolute `http`/`https` URL of the MCP server endpoint. */\n readonly url: string;\n /** Optional static headers (e.g. an `Authorization` bearer token). OAuth/managed auth is out of scope at v0.1. */\n readonly headers?: Record<string, string>;\n}\n\n/**\n * Opaque handle to an MCP transport produced by {@link createStdioTransport} or\n * {@link createHttpTransport}. Hand it to {@link connectMcpSession}; do not construct directly.\n * @public\n */\nexport interface IMcpTransport {\n /** Which transport kind this handle wraps. */\n readonly transportKind: 'stdio' | 'http';\n}\n\n// ============================================================================\n// Session\n// ============================================================================\n\n/**\n * Parameters for {@link connectMcpSession}.\n * @public\n */\nexport interface IConnectMcpSessionParams {\n /** A transport produced by {@link createStdioTransport} / {@link createHttpTransport}. */\n readonly transport: IMcpTransport;\n /** Client name advertised to the server during the initialize handshake. Default `'@fgv/ts-extras-mcp'`. */\n readonly clientName?: string;\n /** Client version advertised to the server. Default the package version. */\n readonly clientVersion?: string;\n /** Optional logger for connection diagnostics. */\n readonly logger?: Logging.ILogger;\n}\n\n/**\n * Server identity reported during the MCP initialize handshake.\n * @public\n */\nexport interface IMcpServerInfo {\n /** Server-advertised name. */\n readonly name: string;\n /** Server-advertised version. */\n readonly version: string;\n}\n\n/**\n * Opaque handle to a connected MCP session. Pass it to {@link listMcpTools},\n * {@link callMcpTool}, {@link adaptMcpTools}, and {@link closeMcpSession}.\n * @public\n */\nexport interface IMcpSession {\n /** Client name advertised during the handshake. */\n readonly clientName: string;\n /** Client version advertised during the handshake. */\n readonly clientVersion: string;\n /** Server identity reported by the handshake, when the server provided one. */\n readonly serverInfo: IMcpServerInfo | undefined;\n}\n\n// ============================================================================\n// Tool discovery + invocation\n// ============================================================================\n\n/**\n * A tool descriptor discovered from an MCP server via {@link listMcpTools}.\n * @public\n */\nexport interface IMcpToolDescriptor {\n /** Tool name (unique within a server). */\n readonly name: string;\n /** Human-readable description, when the server provided one. */\n readonly description: string | undefined;\n /**\n * The tool's declared input schema as raw JSON. Per the MCP spec this is normally a JSON\n * Schema object; carried verbatim so {@link adaptMcpTools} can run it through\n * `JsonSchema.fromJson` (and surface it on {@link IMcpSkippedTool} when it is outside the\n * supported subset).\n */\n readonly inputSchema: JsonValue;\n}\n\n/**\n * Successful projection of an MCP `CallToolResult` produced by {@link callMcpTool}.\n *\n * @remarks\n * `content` is the text concatenation of the result's `text` blocks; non-text blocks\n * (image / audio / resource) are projected to a one-line `[<type> block]` summary\n * (multimodal passthrough is out of scope at v0.1). A result with `isError: true` is mapped\n * to `Result.fail(content)` rather than returned here, so it is never silently swallowed.\n *\n * @public\n */\nexport interface IMcpToolCallResult {\n /** The projected text content of the tool result. */\n readonly content: string;\n}\n\n// ============================================================================\n// Adapter (Constraint 1)\n// ============================================================================\n\n/**\n * A tool that was discovered but could NOT be adapted into an `AiAssist.IAiClientTool`,\n * because its `inputSchema` is outside the JSON Schema subset supported by\n * `JsonSchema.fromJson`. Surfaced structurally so callers/the probe can enumerate exactly which\n * subset features a server needs (with the raw schema in hand to extend `fromJson` later).\n * @public\n */\nexport interface IMcpSkippedTool {\n /** The tool's name. */\n readonly name: string;\n /** Why the tool was skipped — the JSON-pointer reason from `JsonSchema.fromJson`, or a structural reason. */\n readonly reason: string;\n /** The raw failing input schema, verbatim, in hand for additively widening `JsonSchema.fromJson`. */\n readonly schema: JsonValue;\n}\n\n/**\n * Options for {@link adaptMcpTools}.\n * @public\n */\nexport interface IAdaptMcpToolsOptions {\n /**\n * Logger for the NOISY per-tool skip warnings. When a tool is skipped, a `warning` is emitted\n * including the tool name, the JSON-pointer reason, and the raw failing schema. When omitted,\n * skips are still surfaced structurally on {@link IAdaptMcpToolsResult.skipped}.\n */\n readonly logger?: Logging.ILogger;\n}\n\n/**\n * The result of {@link adaptMcpTools}: cleanly-adapted client tools plus the structurally-surfaced\n * set of tools that could not be adapted (graceful degradation, Constraint 1).\n * @public\n */\nexport interface IAdaptMcpToolsResult {\n /** Tools adapted into `IAiClientTool` — safe to hand to `AiAssist.executeClientToolTurn`. */\n readonly tools: ReadonlyArray<AiAssist.IAiClientTool>;\n /** Tools excluded because their `inputSchema` is outside the supported JSON Schema subset. */\n readonly skipped: ReadonlyArray<IMcpSkippedTool>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/packlets/mcp/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Public types for the MCP → ai-assist client-tools boundary.\n * @packageDocumentation\n */\n\nimport { type Logging } from '@fgv/ts-utils';\nimport { type AiAssist } from '@fgv/ts-extras';\nimport { type JsonValue } from '@fgv/ts-json-base';\n\n// ============================================================================\n// Transport\n// ============================================================================\n\n/**\n * Parameters for {@link createStdioTransport}. The transport spawns `command` as a\n * subprocess and speaks MCP over its stdin/stdout.\n *\n * @remarks\n * **Security:** stdio transport executes a consumer-supplied command. Treat `command`/`args`\n * as a trust boundary — never source them from untrusted input. See the package README.\n *\n * @public\n */\nexport interface IMcpStdioTransportParams {\n /** Executable to spawn (e.g. `'npx'`, `'node'`, an absolute path). */\n readonly command: string;\n /** Arguments passed to the command. */\n readonly args?: ReadonlyArray<string>;\n /** Environment variables for the spawned process. When omitted, the SDK's safe default set is used. */\n readonly env?: Record<string, string>;\n /** Working directory for the spawned process. */\n readonly cwd?: string;\n}\n\n/**\n * Parameters for {@link createHttpTransport}. The transport connects to a Streamable-HTTP MCP\n * endpoint.\n * @public\n */\nexport interface IMcpHttpTransportParams {\n /** Absolute `http`/`https` URL of the MCP server endpoint. */\n readonly url: string;\n /** Optional static headers (e.g. an `Authorization` bearer token). OAuth/managed auth is out of scope at v0.1. */\n readonly headers?: Record<string, string>;\n}\n\n/**\n * Opaque handle to an MCP transport produced by {@link createStdioTransport} or\n * {@link createHttpTransport}. Hand it to {@link connectMcpSession}; do not construct directly.\n * @public\n */\nexport interface IMcpTransport {\n /** Which transport kind this handle wraps. */\n readonly transportKind: 'stdio' | 'http';\n}\n\n// ============================================================================\n// Session\n// ============================================================================\n\n/**\n * Parameters for {@link connectMcpSession}.\n * @public\n */\nexport interface IConnectMcpSessionParams {\n /** A transport produced by {@link createStdioTransport} / {@link createHttpTransport}. */\n readonly transport: IMcpTransport;\n /** Client name advertised to the server during the initialize handshake. Default `'@fgv/ts-extras-mcp'`. */\n readonly clientName?: string;\n /** Client version advertised to the server. Default the package version. */\n readonly clientVersion?: string;\n /** Optional logger for connection diagnostics. */\n readonly logger?: Logging.ILogger;\n}\n\n/**\n * Server identity reported during the MCP initialize handshake.\n * @public\n */\nexport interface IMcpServerInfo {\n /** Server-advertised name. */\n readonly name: string;\n /** Server-advertised version. */\n readonly version: string;\n}\n\n/**\n * Opaque handle to a connected MCP session. Pass it to {@link listMcpTools},\n * {@link callMcpTool}, {@link adaptMcpTools}, and {@link closeMcpSession}.\n * @public\n */\nexport interface IMcpSession {\n /** Client name advertised during the handshake. */\n readonly clientName: string;\n /** Client version advertised during the handshake. */\n readonly clientVersion: string;\n /** Server identity reported by the handshake, when the server provided one. */\n readonly serverInfo: IMcpServerInfo | undefined;\n}\n\n// ============================================================================\n// Tool discovery + invocation\n// ============================================================================\n\n/**\n * Normalized MCP `Tool.annotations` — the five known host-advisory behavior hints.\n *\n * @remarks\n * Field names mirror MCP's `ToolAnnotations` 1:1 (and map straight onto\n * `AiAssist.IAiToolAnnotations`). Per the MCP spec these are hints only, and per the\n * untrusted-server warning they are validated/normalized (known fields kept, malformed\n * or unknown fields dropped) before they reach {@link IMcpToolDescriptor} — the raw\n * server blob is never propagated.\n *\n * @public\n */\nexport interface IMcpToolAnnotations {\n /** Optional human-readable display title for the tool. */\n readonly title?: string;\n /** Hint: the tool does not modify its environment (read-only). */\n readonly readOnlyHint?: boolean;\n /** Hint: the tool may perform destructive updates (only meaningful when not read-only). */\n readonly destructiveHint?: boolean;\n /** Hint: repeated calls with the same arguments have no additional effect. */\n readonly idempotentHint?: boolean;\n /** Hint: the tool interacts with an open world of external entities. */\n readonly openWorldHint?: boolean;\n}\n\n/**\n * A tool descriptor discovered from an MCP server via {@link listMcpTools}.\n * @public\n */\nexport interface IMcpToolDescriptor {\n /** Tool name (unique within a server). */\n readonly name: string;\n /** Human-readable description, when the server provided one. */\n readonly description: string | undefined;\n /**\n * The tool's declared input schema as raw JSON. Per the MCP spec this is normally a JSON\n * Schema object; carried verbatim so {@link adaptMcpTools} can run it through\n * `JsonSchema.fromJson` (and surface it on {@link IMcpSkippedTool} when it is outside the\n * supported subset).\n */\n readonly inputSchema: JsonValue;\n /**\n * The tool's normalized behavior annotations, when the server provided any usable ones.\n * Absent when the server declared no annotations (or only malformed/unknown fields).\n * See {@link IMcpToolAnnotations}.\n */\n readonly annotations?: IMcpToolAnnotations;\n}\n\n/**\n * Successful projection of an MCP `CallToolResult` produced by {@link callMcpTool}.\n *\n * @remarks\n * `content` is the text concatenation of the result's `text` blocks; non-text blocks\n * (image / audio / resource) are projected to a one-line `[<type> block]` summary\n * (multimodal passthrough is out of scope at v0.1). A result with `isError: true` is mapped\n * to `Result.fail(content)` rather than returned here, so it is never silently swallowed.\n *\n * @public\n */\nexport interface IMcpToolCallResult {\n /** The projected text content of the tool result. */\n readonly content: string;\n}\n\n// ============================================================================\n// Adapter (Constraint 1)\n// ============================================================================\n\n/**\n * A tool that was discovered but could NOT be adapted into an `AiAssist.IAiClientTool`,\n * because its `inputSchema` is outside the JSON Schema subset supported by\n * `JsonSchema.fromJson`. Surfaced structurally so callers/the probe can enumerate exactly which\n * subset features a server needs (with the raw schema in hand to extend `fromJson` later).\n * @public\n */\nexport interface IMcpSkippedTool {\n /** The tool's name. */\n readonly name: string;\n /** Why the tool was skipped — the JSON-pointer reason from `JsonSchema.fromJson`, or a structural reason. */\n readonly reason: string;\n /** The raw failing input schema, verbatim, in hand for additively widening `JsonSchema.fromJson`. */\n readonly schema: JsonValue;\n}\n\n/**\n * Options for {@link adaptMcpTools}.\n * @public\n */\nexport interface IAdaptMcpToolsOptions {\n /**\n * Logger for the NOISY per-tool skip warnings. When a tool is skipped, a `warning` is emitted\n * including the tool name, the JSON-pointer reason, and the raw failing schema. When omitted,\n * skips are still surfaced structurally on {@link IAdaptMcpToolsResult.skipped}.\n */\n readonly logger?: Logging.ILogger;\n}\n\n/**\n * The result of {@link adaptMcpTools}: cleanly-adapted client tools plus the structurally-surfaced\n * set of tools that could not be adapted (graceful degradation, Constraint 1).\n * @public\n */\nexport interface IAdaptMcpToolsResult {\n /** Tools adapted into `IAiClientTool` — safe to hand to `AiAssist.executeClientToolTurn`. */\n readonly tools: ReadonlyArray<AiAssist.IAiClientTool>;\n /** Tools excluded because their `inputSchema` is outside the supported JSON Schema subset. */\n readonly skipped: ReadonlyArray<IMcpSkippedTool>;\n}\n"]}
|
|
@@ -24,20 +24,45 @@
|
|
|
24
24
|
* projection), converting the throwing SDK calls into `Result<T>`.
|
|
25
25
|
* @packageDocumentation
|
|
26
26
|
*/
|
|
27
|
-
import { captureAsyncResult, fail, succeed } from '@fgv/ts-utils';
|
|
27
|
+
import { Converters as UtilsConverters, captureAsyncResult, fail, succeed } from '@fgv/ts-utils';
|
|
28
28
|
import { Converters } from '@fgv/ts-json-base';
|
|
29
29
|
import { McpSession } from './session';
|
|
30
|
+
/**
|
|
31
|
+
* Validates/normalizes a raw (untrusted) MCP `Tool.annotations` blob into an
|
|
32
|
+
* {@link IMcpToolAnnotations}, keeping only the five known fields that convert cleanly.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* Each known field is converted independently with its own Converter, so a present-but-malformed
|
|
36
|
+
* field is dropped (never failing the whole blob) and unknown keys are ignored. Returns
|
|
37
|
+
* `undefined` when the blob is absent, not a JSON object, or normalizes to no usable known
|
|
38
|
+
* fields — so a tool with no (usable) annotations leaves {@link IMcpToolDescriptor.annotations}
|
|
39
|
+
* absent. The raw server value is never propagated verbatim (per the MCP untrusted-server
|
|
40
|
+
* warning).
|
|
41
|
+
*/
|
|
42
|
+
function _normalizeAnnotations(raw) {
|
|
43
|
+
const objResult = Converters.jsonObject.convert(raw);
|
|
44
|
+
if (objResult.isFailure()) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const obj = objResult.value;
|
|
48
|
+
const title = UtilsConverters.string.convert(obj.title).orDefault();
|
|
49
|
+
const readOnlyHint = UtilsConverters.boolean.convert(obj.readOnlyHint).orDefault();
|
|
50
|
+
const destructiveHint = UtilsConverters.boolean.convert(obj.destructiveHint).orDefault();
|
|
51
|
+
const idempotentHint = UtilsConverters.boolean.convert(obj.idempotentHint).orDefault();
|
|
52
|
+
const openWorldHint = UtilsConverters.boolean.convert(obj.openWorldHint).orDefault();
|
|
53
|
+
const normalized = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (title !== undefined ? { title } : {})), (readOnlyHint !== undefined ? { readOnlyHint } : {})), (destructiveHint !== undefined ? { destructiveHint } : {})), (idempotentHint !== undefined ? { idempotentHint } : {})), (openWorldHint !== undefined ? { openWorldHint } : {}));
|
|
54
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
55
|
+
}
|
|
30
56
|
/**
|
|
31
57
|
* Projects a single SDK tool descriptor into a public {@link IMcpToolDescriptor}. The
|
|
32
58
|
* `inputSchema` field is validated as a `JsonValue`; an absent/non-JSON schema becomes `null`
|
|
33
59
|
* (which {@link adaptMcpTools} then reports as un-adaptable rather than offering it to the model).
|
|
60
|
+
* The raw `annotations` blob is validated/normalized (never propagated raw) per the MCP
|
|
61
|
+
* untrusted-server warning.
|
|
34
62
|
*/
|
|
35
63
|
function _toDescriptor(tool) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
description: tool.description,
|
|
39
|
-
inputSchema: Converters.jsonValue.convert(tool.inputSchema).orDefault(null)
|
|
40
|
-
};
|
|
64
|
+
const annotations = _normalizeAnnotations(tool.annotations);
|
|
65
|
+
return Object.assign({ name: tool.name, description: tool.description, inputSchema: Converters.jsonValue.convert(tool.inputSchema).orDefault(null) }, (annotations !== undefined ? { annotations } : {}));
|
|
41
66
|
}
|
|
42
67
|
/**
|
|
43
68
|
* Projects the SDK `CallToolResult` content blocks to a single string: `text` blocks are
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../../../src/packlets/mcp/operations.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Tool discovery (`listMcpTools`, paginated) and invocation (`callMcpTool`, with `CallToolResult`\n * projection), converting the throwing SDK calls into `Result<T>`.\n * @packageDocumentation\n */\n\nimport { type Result, captureAsyncResult, fail, succeed } from '@fgv/ts-utils';\nimport { Converters, type JsonObject } from '@fgv/ts-json-base';\n\nimport { type IMcpSession, type IMcpToolCallResult, type IMcpToolDescriptor } from './model';\nimport { type ISdkContentBlock, type ISdkToolDescriptor } from './sdk';\nimport { McpSession } from './session';\n\n/**\n * Projects a single SDK tool descriptor into a public {@link IMcpToolDescriptor}. The\n * `inputSchema` field is validated as a `JsonValue`; an absent/non-JSON schema becomes `null`\n * (which {@link adaptMcpTools} then reports as un-adaptable rather than offering it to the model).\n */\nfunction _toDescriptor(tool: ISdkToolDescriptor): IMcpToolDescriptor {\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: Converters.jsonValue.convert(tool.inputSchema).orDefault(null)\n };\n}\n\n/**\n * Projects the SDK `CallToolResult` content blocks to a single string: `text` blocks are\n * concatenated; every other block type becomes a one-line `[<type> block]` summary.\n */\nfunction _projectContent(blocks: ReadonlyArray<ISdkContentBlock> | undefined): string {\n if (blocks === undefined || blocks.length === 0) {\n return '';\n }\n return blocks\n .map((block) =>\n block.type === 'text' && block.text !== undefined ? block.text : `[${block.type} block]`\n )\n .join('\\n');\n}\n\n/**\n * Lists every tool a connected MCP server advertises, following the SDK's `nextCursor`\n * pagination until the full catalog is accumulated.\n *\n * @param session - A session from `connectMcpSession`.\n * @returns `Success` with the full tool catalog, or `Failure` on a foreign handle or a\n * transport/protocol error.\n * @public\n */\nexport async function listMcpTools(session: IMcpSession): Promise<Result<ReadonlyArray<IMcpToolDescriptor>>> {\n const sessionResult = McpSession.fromHandle(session);\n if (sessionResult.isFailure()) {\n return fail(`listMcpTools: ${sessionResult.message}`);\n }\n const { client } = sessionResult.value;\n\n const all: IMcpToolDescriptor[] = [];\n let cursor: string | undefined;\n\n // Cursor-paginated loop: fetch each page, accumulate, advance until nextCursor is absent.\n do {\n const pageResult = await captureAsyncResult(() =>\n client.listTools(cursor !== undefined ? { cursor } : undefined)\n ).withErrorFormat((msg) => `listMcpTools: ${msg}`);\n if (pageResult.isFailure()) {\n return fail(pageResult.message);\n }\n for (const tool of pageResult.value.tools) {\n all.push(_toDescriptor(tool));\n }\n cursor = pageResult.value.nextCursor;\n } while (cursor !== undefined);\n\n return succeed(all);\n}\n\n/**\n * Calls a named tool on a connected MCP server.\n *\n * @remarks\n * The SDK `CallToolResult` is projected to {@link IMcpToolCallResult} (text-block concatenation;\n * non-text blocks summarized). A result flagged `isError: true` is mapped to `Result.fail` with\n * the projected content — it is never swallowed, so `executeClientToolTurn` routes it back to the\n * model as a provider-native error tool-result.\n *\n * @param session - A session from `connectMcpSession`.\n * @param name - The tool name to invoke.\n * @param args - The tool arguments (a JSON object).\n * @returns `Success` with the projected content, or `Failure` on tool error / transport error /\n * foreign handle.\n * @public\n */\nexport async function callMcpTool(\n session: IMcpSession,\n name: string,\n args: JsonObject\n): Promise<Result<IMcpToolCallResult>> {\n const sessionResult = McpSession.fromHandle(session);\n if (sessionResult.isFailure()) {\n return fail(`callMcpTool: ${sessionResult.message}`);\n }\n // The `withErrorFormat` wraps only transport/throw failures (it transforms the failure flowing\n // out of `captureAsyncResult`); the `isError` failure created in the later `onSuccess` is\n // downstream and stays clean, so the model-facing tool-error text is the server's verbatim\n // content rather than a doubly-prefixed message.\n return captureAsyncResult(() => sessionResult.value.client.callTool({ name, arguments: args }))\n .withErrorFormat((msg) => `callMcpTool '${name}': ${msg}`)\n .onSuccess((raw): Result<IMcpToolCallResult> => {\n const content = _projectContent(raw.content);\n if (raw.isError === true) {\n return fail(content.length > 0 ? content : `tool '${name}' reported an error`);\n }\n return succeed({ content });\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../../../src/packlets/mcp/operations.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Tool discovery (`listMcpTools`, paginated) and invocation (`callMcpTool`, with `CallToolResult`\n * projection), converting the throwing SDK calls into `Result<T>`.\n * @packageDocumentation\n */\n\nimport { Converters as UtilsConverters, type Result, captureAsyncResult, fail, succeed } from '@fgv/ts-utils';\nimport { Converters, type JsonObject } from '@fgv/ts-json-base';\n\nimport {\n type IMcpSession,\n type IMcpToolAnnotations,\n type IMcpToolCallResult,\n type IMcpToolDescriptor\n} from './model';\nimport { type ISdkContentBlock, type ISdkToolDescriptor } from './sdk';\nimport { McpSession } from './session';\n\n/**\n * Validates/normalizes a raw (untrusted) MCP `Tool.annotations` blob into an\n * {@link IMcpToolAnnotations}, keeping only the five known fields that convert cleanly.\n *\n * @remarks\n * Each known field is converted independently with its own Converter, so a present-but-malformed\n * field is dropped (never failing the whole blob) and unknown keys are ignored. Returns\n * `undefined` when the blob is absent, not a JSON object, or normalizes to no usable known\n * fields — so a tool with no (usable) annotations leaves {@link IMcpToolDescriptor.annotations}\n * absent. The raw server value is never propagated verbatim (per the MCP untrusted-server\n * warning).\n */\nfunction _normalizeAnnotations(raw: unknown): IMcpToolAnnotations | undefined {\n const objResult = Converters.jsonObject.convert(raw);\n if (objResult.isFailure()) {\n return undefined;\n }\n const obj = objResult.value;\n const title = UtilsConverters.string.convert(obj.title).orDefault();\n const readOnlyHint = UtilsConverters.boolean.convert(obj.readOnlyHint).orDefault();\n const destructiveHint = UtilsConverters.boolean.convert(obj.destructiveHint).orDefault();\n const idempotentHint = UtilsConverters.boolean.convert(obj.idempotentHint).orDefault();\n const openWorldHint = UtilsConverters.boolean.convert(obj.openWorldHint).orDefault();\n\n const normalized: IMcpToolAnnotations = {\n ...(title !== undefined ? { title } : {}),\n ...(readOnlyHint !== undefined ? { readOnlyHint } : {}),\n ...(destructiveHint !== undefined ? { destructiveHint } : {}),\n ...(idempotentHint !== undefined ? { idempotentHint } : {}),\n ...(openWorldHint !== undefined ? { openWorldHint } : {})\n };\n return Object.keys(normalized).length > 0 ? normalized : undefined;\n}\n\n/**\n * Projects a single SDK tool descriptor into a public {@link IMcpToolDescriptor}. The\n * `inputSchema` field is validated as a `JsonValue`; an absent/non-JSON schema becomes `null`\n * (which {@link adaptMcpTools} then reports as un-adaptable rather than offering it to the model).\n * The raw `annotations` blob is validated/normalized (never propagated raw) per the MCP\n * untrusted-server warning.\n */\nfunction _toDescriptor(tool: ISdkToolDescriptor): IMcpToolDescriptor {\n const annotations = _normalizeAnnotations(tool.annotations);\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: Converters.jsonValue.convert(tool.inputSchema).orDefault(null),\n ...(annotations !== undefined ? { annotations } : {})\n };\n}\n\n/**\n * Projects the SDK `CallToolResult` content blocks to a single string: `text` blocks are\n * concatenated; every other block type becomes a one-line `[<type> block]` summary.\n */\nfunction _projectContent(blocks: ReadonlyArray<ISdkContentBlock> | undefined): string {\n if (blocks === undefined || blocks.length === 0) {\n return '';\n }\n return blocks\n .map((block) =>\n block.type === 'text' && block.text !== undefined ? block.text : `[${block.type} block]`\n )\n .join('\\n');\n}\n\n/**\n * Lists every tool a connected MCP server advertises, following the SDK's `nextCursor`\n * pagination until the full catalog is accumulated.\n *\n * @param session - A session from `connectMcpSession`.\n * @returns `Success` with the full tool catalog, or `Failure` on a foreign handle or a\n * transport/protocol error.\n * @public\n */\nexport async function listMcpTools(session: IMcpSession): Promise<Result<ReadonlyArray<IMcpToolDescriptor>>> {\n const sessionResult = McpSession.fromHandle(session);\n if (sessionResult.isFailure()) {\n return fail(`listMcpTools: ${sessionResult.message}`);\n }\n const { client } = sessionResult.value;\n\n const all: IMcpToolDescriptor[] = [];\n let cursor: string | undefined;\n\n // Cursor-paginated loop: fetch each page, accumulate, advance until nextCursor is absent.\n do {\n const pageResult = await captureAsyncResult(() =>\n client.listTools(cursor !== undefined ? { cursor } : undefined)\n ).withErrorFormat((msg) => `listMcpTools: ${msg}`);\n if (pageResult.isFailure()) {\n return fail(pageResult.message);\n }\n for (const tool of pageResult.value.tools) {\n all.push(_toDescriptor(tool));\n }\n cursor = pageResult.value.nextCursor;\n } while (cursor !== undefined);\n\n return succeed(all);\n}\n\n/**\n * Calls a named tool on a connected MCP server.\n *\n * @remarks\n * The SDK `CallToolResult` is projected to {@link IMcpToolCallResult} (text-block concatenation;\n * non-text blocks summarized). A result flagged `isError: true` is mapped to `Result.fail` with\n * the projected content — it is never swallowed, so `executeClientToolTurn` routes it back to the\n * model as a provider-native error tool-result.\n *\n * @param session - A session from `connectMcpSession`.\n * @param name - The tool name to invoke.\n * @param args - The tool arguments (a JSON object).\n * @returns `Success` with the projected content, or `Failure` on tool error / transport error /\n * foreign handle.\n * @public\n */\nexport async function callMcpTool(\n session: IMcpSession,\n name: string,\n args: JsonObject\n): Promise<Result<IMcpToolCallResult>> {\n const sessionResult = McpSession.fromHandle(session);\n if (sessionResult.isFailure()) {\n return fail(`callMcpTool: ${sessionResult.message}`);\n }\n // The `withErrorFormat` wraps only transport/throw failures (it transforms the failure flowing\n // out of `captureAsyncResult`); the `isError` failure created in the later `onSuccess` is\n // downstream and stays clean, so the model-facing tool-error text is the server's verbatim\n // content rather than a doubly-prefixed message.\n return captureAsyncResult(() => sessionResult.value.client.callTool({ name, arguments: args }))\n .withErrorFormat((msg) => `callMcpTool '${name}': ${msg}`)\n .onSuccess((raw): Result<IMcpToolCallResult> => {\n const content = _projectContent(raw.content);\n if (raw.isError === true) {\n return fail(content.length > 0 ? content : `tool '${name}' reported an error`);\n }\n return succeed({ content });\n });\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk.js","sourceRoot":"","sources":["../../../src/packlets/mcp/sdk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;
|
|
1
|
+
{"version":3,"file":"sdk.js","sourceRoot":"","sources":["../../../src/packlets/mcp/sdk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAqFnG;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,OAAe;IACtD,OAAO,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAA0B,CAAC;AACtF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAuB;IACxD,OAAO,IAAI,oBAAoB,CAAC;QAC9B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;QAChD,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,GAAG,EAAE,MAAM,CAAC,GAAG;KAChB,CAA6B,CAAC;AACjC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAQ,EAAE,OAAgC;IAC1E,OAAO,IAAI,6BAA6B,CAAC,GAAG,EAAE;QAC5C,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS;KAC/C,CAA6B,CAAC;AACjC,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * **The single `@modelcontextprotocol/sdk` import site for the entire package.**\n *\n * Every other module in `@fgv/ts-extras-mcp` depends only on the minimal local projection\n * exported here (`ISdkClient`, `ISdkTransport`, the three `make*` factories) — never on the SDK\n * directly. This keeps the announced v2 client-package rename a one-file change and lets unit\n * tests mock this module instead of the SDK internals (no live server required).\n *\n * The only `as unknown as` casts in the package live here, at the boundary where the real SDK\n * objects are narrowed to the local projection.\n *\n * @internal\n */\n\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';\n\n/**\n * Opaque transport handle. The concrete value is an SDK `Transport`; consumers of this module\n * treat it as opaque and only ever hand it back to {@link makeClient}-produced clients.\n * @internal\n */\nexport type ISdkTransport = object;\n\n/**\n * A single content block in an MCP `CallToolResult`. Only `text` blocks carry inline content;\n * all other block types are projected to a structural summary by the operations layer.\n * @internal\n */\nexport interface ISdkContentBlock {\n readonly type: string;\n readonly text?: string;\n}\n\n/**\n * Projection of the SDK's `CallToolResult`.\n * @internal\n */\nexport interface ISdkCallToolResult {\n readonly content?: ReadonlyArray<ISdkContentBlock>;\n readonly isError?: boolean;\n}\n\n/**\n * Projection of a single tool descriptor in the SDK's `ListToolsResult`.\n * @internal\n */\nexport interface ISdkToolDescriptor {\n readonly name: string;\n readonly description?: string;\n readonly inputSchema?: unknown;\n /**\n * Raw MCP `Tool.annotations` (untrusted). Declared as `unknown` so the SDK field survives\n * narrowing; the operations layer validates/normalizes it (never propagates raw) per the MCP\n * spec's untrusted-server warning.\n */\n readonly annotations?: unknown;\n}\n\n/**\n * Projection of the SDK's `ListToolsResult` (one page).\n * @internal\n */\nexport interface ISdkListToolsResult {\n readonly tools: ReadonlyArray<ISdkToolDescriptor>;\n readonly nextCursor?: string;\n}\n\n/**\n * Server identity returned by the SDK's `getServerVersion()` after the initialize handshake.\n * @internal\n */\nexport interface ISdkImplementation {\n readonly name: string;\n readonly version: string;\n}\n\n/**\n * Minimal projection of the SDK `Client` surface the package depends on.\n * @internal\n */\nexport interface ISdkClient {\n connect(transport: ISdkTransport): Promise<void>;\n getServerVersion(): ISdkImplementation | undefined;\n listTools(params?: { cursor?: string }): Promise<ISdkListToolsResult>;\n callTool(params: { name: string; arguments?: Record<string, unknown> }): Promise<ISdkCallToolResult>;\n close(): Promise<void>;\n}\n\n/**\n * Parameters for the stdio transport factory.\n * @internal\n */\nexport interface ISdkStdioParams {\n readonly command: string;\n readonly args?: ReadonlyArray<string>;\n readonly env?: Record<string, string>;\n readonly cwd?: string;\n}\n\n/**\n * Constructs an MCP client. The `as unknown as` cast is the package's single SDK-type\n * bridge — the runtime object is a real SDK `Client`.\n * @internal\n */\nexport function makeClient(name: string, version: string): ISdkClient {\n return new Client({ name, version }, { capabilities: {} }) as unknown as ISdkClient;\n}\n\n/**\n * Constructs a stdio transport that spawns the given command as a subprocess.\n * @internal\n */\nexport function makeStdioTransport(params: ISdkStdioParams): ISdkTransport {\n return new StdioClientTransport({\n command: params.command,\n args: params.args ? [...params.args] : undefined,\n env: params.env,\n cwd: params.cwd\n }) as unknown as ISdkTransport;\n}\n\n/**\n * Constructs a Streamable-HTTP transport for the given URL, with optional static headers.\n * @internal\n */\nexport function makeHttpTransport(url: URL, headers?: Record<string, string>): ISdkTransport {\n return new StreamableHTTPClientTransport(url, {\n requestInit: headers ? { headers } : undefined\n }) as unknown as ISdkTransport;\n}\n"]}
|
|
@@ -377,5 +377,118 @@ describe('adaptMcpTools', () => {
|
|
|
377
377
|
expect(await execute({ q: 'hello' })).toFailWith(/denied/);
|
|
378
378
|
});
|
|
379
379
|
});
|
|
380
|
+
// ==========================================================================
|
|
381
|
+
// C2 — MCP Tool.annotations passthrough (normalized, never raw)
|
|
382
|
+
// ==========================================================================
|
|
383
|
+
describe('annotations passthrough (C2)', () => {
|
|
384
|
+
const goodSchema2 = { type: 'object', properties: { q: { type: 'string' } }, required: ['q'] };
|
|
385
|
+
test('all five hints round-trip onto descriptor.annotations and config.annotations', async () => {
|
|
386
|
+
const annotations = {
|
|
387
|
+
title: 'Recall Memory',
|
|
388
|
+
readOnlyHint: true,
|
|
389
|
+
destructiveHint: false,
|
|
390
|
+
idempotentHint: true,
|
|
391
|
+
openWorldHint: false
|
|
392
|
+
};
|
|
393
|
+
const fake = makeFakeClient({
|
|
394
|
+
listTools: jest.fn(async () => ({
|
|
395
|
+
tools: [{ name: 'recall', description: 'r', inputSchema: goodSchema2, annotations }]
|
|
396
|
+
}))
|
|
397
|
+
});
|
|
398
|
+
const session = await connectWith(fake);
|
|
399
|
+
expect(await listMcpTools(session)).toSucceedAndSatisfy((tools) => {
|
|
400
|
+
expect(tools[0].annotations).toEqual(annotations);
|
|
401
|
+
});
|
|
402
|
+
const session2 = await connectWith(makeFakeClient({
|
|
403
|
+
listTools: jest.fn(async () => ({
|
|
404
|
+
tools: [{ name: 'recall', description: 'r', inputSchema: goodSchema2, annotations }]
|
|
405
|
+
}))
|
|
406
|
+
}));
|
|
407
|
+
expect(await adaptMcpTools(session2)).toSucceedAndSatisfy((result) => {
|
|
408
|
+
expect(result.tools[0].config.annotations).toEqual(annotations);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
test('a tool with no annotations leaves the field absent', async () => {
|
|
412
|
+
const fake = makeFakeClient({
|
|
413
|
+
listTools: jest.fn(async () => ({ tools: [{ name: 'plain', inputSchema: goodSchema2 }] }))
|
|
414
|
+
});
|
|
415
|
+
const session = await connectWith(fake);
|
|
416
|
+
expect(await listMcpTools(session)).toSucceedAndSatisfy((tools) => {
|
|
417
|
+
expect(tools[0]).not.toHaveProperty('annotations');
|
|
418
|
+
});
|
|
419
|
+
const session2 = await connectWith(makeFakeClient({
|
|
420
|
+
listTools: jest.fn(async () => ({ tools: [{ name: 'plain', inputSchema: goodSchema2 }] }))
|
|
421
|
+
}));
|
|
422
|
+
expect(await adaptMcpTools(session2)).toSucceedAndSatisfy((result) => {
|
|
423
|
+
expect(result.tools[0].config).not.toHaveProperty('annotations');
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
test('a malformed annotations blob is normalized (known fields kept, junk dropped), never raw-propagated', async () => {
|
|
427
|
+
// readOnlyHint is the wrong type (dropped); bogusField is unknown (dropped); title is valid (kept).
|
|
428
|
+
const rawAnnotations = {
|
|
429
|
+
title: 'Keep Me',
|
|
430
|
+
readOnlyHint: 'yes-please',
|
|
431
|
+
destructiveHint: true,
|
|
432
|
+
bogusField: { nested: 42 }
|
|
433
|
+
};
|
|
434
|
+
const fake = makeFakeClient({
|
|
435
|
+
listTools: jest.fn(async () => ({
|
|
436
|
+
tools: [{ name: 'messy', inputSchema: goodSchema2, annotations: rawAnnotations }]
|
|
437
|
+
}))
|
|
438
|
+
});
|
|
439
|
+
const session = await connectWith(fake);
|
|
440
|
+
expect(await listMcpTools(session)).toSucceedAndSatisfy((tools) => {
|
|
441
|
+
// Only the well-typed known fields survive; the malformed readOnlyHint and unknown
|
|
442
|
+
// bogusField are dropped; the raw blob is never propagated.
|
|
443
|
+
expect(tools[0].annotations).toEqual({ title: 'Keep Me', destructiveHint: true });
|
|
444
|
+
expect(tools[0].annotations).not.toHaveProperty('readOnlyHint');
|
|
445
|
+
expect(tools[0].annotations).not.toHaveProperty('bogusField');
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
test('an all-junk annotations blob normalizes to an absent field (not an empty object)', async () => {
|
|
449
|
+
const fake = makeFakeClient({
|
|
450
|
+
listTools: jest.fn(async () => ({
|
|
451
|
+
tools: [
|
|
452
|
+
{ name: 'junk', inputSchema: goodSchema2, annotations: { unknownOnly: 1, readOnlyHint: 3 } }
|
|
453
|
+
]
|
|
454
|
+
}))
|
|
455
|
+
});
|
|
456
|
+
const session = await connectWith(fake);
|
|
457
|
+
expect(await listMcpTools(session)).toSucceedAndSatisfy((tools) => {
|
|
458
|
+
expect(tools[0]).not.toHaveProperty('annotations');
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
test('a non-object annotations value is dropped', async () => {
|
|
462
|
+
const fake = makeFakeClient({
|
|
463
|
+
listTools: jest.fn(async () => ({
|
|
464
|
+
tools: [{ name: 'weird', inputSchema: goodSchema2, annotations: 'not-an-object' }]
|
|
465
|
+
}))
|
|
466
|
+
});
|
|
467
|
+
const session = await connectWith(fake);
|
|
468
|
+
expect(await listMcpTools(session)).toSucceedAndSatisfy((tools) => {
|
|
469
|
+
expect(tools[0]).not.toHaveProperty('annotations');
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
test('the skipped-tool path is unaffected by annotations', async () => {
|
|
473
|
+
// A tool with annotations but an unsupported inputSchema is still skipped; annotations do
|
|
474
|
+
// not rescue it or leak onto the skip record.
|
|
475
|
+
const forbidden = {
|
|
476
|
+
type: 'object',
|
|
477
|
+
properties: { name: { type: 'string', pattern: '^a' } }
|
|
478
|
+
};
|
|
479
|
+
const fake = makeFakeClient({
|
|
480
|
+
listTools: jest.fn(async () => ({
|
|
481
|
+
tools: [{ name: 'blocked', inputSchema: forbidden, annotations: { destructiveHint: true } }]
|
|
482
|
+
}))
|
|
483
|
+
});
|
|
484
|
+
const session = await connectWith(fake);
|
|
485
|
+
expect(await adaptMcpTools(session)).toSucceedAndSatisfy((result) => {
|
|
486
|
+
expect(result.tools).toHaveLength(0);
|
|
487
|
+
expect(result.skipped).toHaveLength(1);
|
|
488
|
+
expect(result.skipped[0].name).toBe('blocked');
|
|
489
|
+
expect(result.skipped[0]).not.toHaveProperty('annotations');
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
});
|
|
380
493
|
});
|
|
381
494
|
//# sourceMappingURL=mcp.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.test.js","sourceRoot":"","sources":["../../../src/test/unit/mcp.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n// Mock the SDK-isolation seam so no live MCP server / subprocess is required. The factories\n// return controllable fakes; `makeClient` is configured per-test. The jest.mock call must\n// precede the regular imports (hoist-jest-mock).\njest.mock('../../packlets/mcp/sdk', () => ({\n makeClient: jest.fn(),\n makeStdioTransport: jest.fn(() => ({ sentinel: 'stdio' })),\n makeHttpTransport: jest.fn(() => ({ sentinel: 'http' }))\n}));\n\nimport '@fgv/ts-utils-jest';\nimport { Logging, type Result } from '@fgv/ts-utils';\nimport { type JsonObject, type JsonValue } from '@fgv/ts-json-base';\n\n// eslint-disable-next-line @rushstack/packlets/mechanics\nimport * as sdk from '../../packlets/mcp/sdk';\nimport {\n type IAdaptMcpToolsResult,\n type IMcpSession,\n type IMcpToolDescriptor,\n type IMcpTransport,\n adaptMcpTools,\n callMcpTool,\n closeMcpSession,\n connectMcpSession,\n createHttpTransport,\n createStdioTransport,\n listMcpTools\n} from '../../packlets/mcp';\n\nconst mockSdk = sdk as jest.Mocked<typeof sdk>;\n\ninterface IFakeClient {\n connect: jest.Mock;\n getServerVersion: jest.Mock;\n listTools: jest.Mock;\n callTool: jest.Mock;\n close: jest.Mock;\n}\n\nfunction makeFakeClient(overrides: Partial<IFakeClient> = {}): IFakeClient {\n return {\n connect: jest.fn(async () => undefined),\n getServerVersion: jest.fn(() => ({ name: 'fake-server', version: '1.2.3' })),\n listTools: jest.fn(async () => ({ tools: [] })),\n callTool: jest.fn(async () => ({ content: [] })),\n close: jest.fn(async () => undefined),\n ...overrides\n };\n}\n\n/** Connect a session backed by the given fake client (and a stdio transport). */\nasync function connectWith(fake: IFakeClient): Promise<IMcpSession> {\n mockSdk.makeClient.mockReturnValueOnce(fake as unknown as sdk.ISdkClient);\n const transport = createStdioTransport({ command: 'node' }).orThrow();\n return (await connectMcpSession({ transport })).orThrow();\n}\n\n/** A foreign (non-package) handle, to exercise the loud narrowing-failure paths. */\nconst FOREIGN_TRANSPORT = { transportKind: 'stdio' } as unknown as IMcpTransport;\nconst FOREIGN_SESSION = {\n clientName: 'x',\n clientVersion: 'y',\n serverInfo: undefined\n} as unknown as IMcpSession;\n\nbeforeEach(() => {\n jest.clearAllMocks();\n mockSdk.makeStdioTransport.mockReturnValue({ sentinel: 'stdio' } as unknown as sdk.ISdkTransport);\n mockSdk.makeHttpTransport.mockReturnValue({ sentinel: 'http' } as unknown as sdk.ISdkTransport);\n});\n\n// ===========================================================================\n// Transports\n// ===========================================================================\n\ndescribe('createStdioTransport', () => {\n test('succeeds for a valid command', () => {\n expect(createStdioTransport({ command: 'node', args: ['x'] })).toSucceedAndSatisfy((t: IMcpTransport) => {\n expect(t.transportKind).toBe('stdio');\n });\n expect(mockSdk.makeStdioTransport).toHaveBeenCalledWith(\n expect.objectContaining({ command: 'node', args: ['x'] })\n );\n });\n\n test('fails for an empty/whitespace command', () => {\n expect(createStdioTransport({ command: '' })).toFailWith(/command must be a non-empty string/);\n expect(createStdioTransport({ command: ' ' })).toFailWith(/command must be a non-empty string/);\n });\n\n test('fails (Result) when the SDK constructor throws', () => {\n mockSdk.makeStdioTransport.mockImplementationOnce(() => {\n throw new Error('spawn boom');\n });\n expect(createStdioTransport({ command: 'node' })).toFailWith(/createStdioTransport:.*spawn boom/);\n });\n});\n\ndescribe('createHttpTransport', () => {\n test('succeeds for http and https urls and forwards headers', () => {\n expect(createHttpTransport({ url: 'http://localhost:9000/mcp' })).toSucceedAndSatisfy(\n (t: IMcpTransport) => {\n expect(t.transportKind).toBe('http');\n }\n );\n expect(\n createHttpTransport({ url: 'https://example.com/mcp', headers: { authorization: 'Bearer t' } })\n ).toSucceed();\n expect(mockSdk.makeHttpTransport).toHaveBeenCalledWith(expect.any(URL), { authorization: 'Bearer t' });\n });\n\n test('fails for an unparseable url', () => {\n expect(createHttpTransport({ url: 'not a url' })).toFailWith(/invalid url 'not a url'/);\n });\n\n test('fails for a non-http(s) protocol', () => {\n expect(createHttpTransport({ url: 'ftp://example.com' })).toFailWith(/must use http or https/);\n });\n\n test('fails (Result) when the SDK constructor throws', () => {\n mockSdk.makeHttpTransport.mockImplementationOnce(() => {\n throw new Error('http boom');\n });\n expect(createHttpTransport({ url: 'http://localhost' })).toFailWith(/createHttpTransport:.*http boom/);\n });\n});\n\n// ===========================================================================\n// Session lifecycle\n// ===========================================================================\n\ndescribe('connectMcpSession', () => {\n test('fails loudly for a foreign transport handle', async () => {\n expect(await connectMcpSession({ transport: FOREIGN_TRANSPORT })).toFailWith(/invalid MCP transport/);\n });\n\n test('connects and reports server identity, logging when a logger is supplied', async () => {\n const logger = new Logging.InMemoryLogger('all');\n const fake = makeFakeClient();\n mockSdk.makeClient.mockReturnValueOnce(fake as unknown as sdk.ISdkClient);\n const transport = createStdioTransport({ command: 'node' }).orThrow();\n\n expect(\n await connectMcpSession({ transport, clientName: 'me', clientVersion: '0.0.1', logger })\n ).toSucceedAndSatisfy((session: IMcpSession) => {\n expect(session.clientName).toBe('me');\n expect(session.clientVersion).toBe('0.0.1');\n expect(session.serverInfo).toEqual({ name: 'fake-server', version: '1.2.3' });\n });\n expect(mockSdk.makeClient).toHaveBeenCalledWith('me', '0.0.1');\n expect(fake.connect).toHaveBeenCalledTimes(1);\n expect(logger.logged.join('\\n')).toMatch(/connected to server fake-server@1.2.3/);\n });\n\n test('connects with default identity and no server info, no logger', async () => {\n const fake = makeFakeClient({ getServerVersion: jest.fn(() => undefined) });\n mockSdk.makeClient.mockReturnValueOnce(fake as unknown as sdk.ISdkClient);\n const transport = createStdioTransport({ command: 'node' }).orThrow();\n\n expect(await connectMcpSession({ transport })).toSucceedAndSatisfy((session: IMcpSession) => {\n expect(session.clientName).toBe('@fgv/ts-extras-mcp');\n expect(session.serverInfo).toBeUndefined();\n });\n });\n\n test('logs the no-identity case when a logger is supplied', async () => {\n const logger = new Logging.InMemoryLogger('all');\n const fake = makeFakeClient({ getServerVersion: jest.fn(() => undefined) });\n mockSdk.makeClient.mockReturnValueOnce(fake as unknown as sdk.ISdkClient);\n const transport = createStdioTransport({ command: 'node' }).orThrow();\n\n expect(await connectMcpSession({ transport, logger })).toSucceed();\n expect(logger.logged.join('\\n')).toMatch(/connected \\(server did not report identity\\)/);\n });\n\n test('fails when the connect handshake throws', async () => {\n const fake = makeFakeClient({\n connect: jest.fn(async () => {\n throw new Error('handshake refused');\n })\n });\n mockSdk.makeClient.mockReturnValueOnce(fake as unknown as sdk.ISdkClient);\n const transport = createStdioTransport({ command: 'node' }).orThrow();\n\n expect(await connectMcpSession({ transport })).toFailWith(/connectMcpSession:.*handshake refused/);\n });\n});\n\ndescribe('closeMcpSession', () => {\n test('fails loudly for a foreign session handle', async () => {\n expect(await closeMcpSession(FOREIGN_SESSION)).toFailWith(/invalid MCP session/);\n });\n\n test('closes a connected session', async () => {\n const fake = makeFakeClient();\n const session = await connectWith(fake);\n expect(await closeMcpSession(session)).toSucceedWith(true);\n expect(fake.close).toHaveBeenCalledTimes(1);\n });\n\n test('fails when close throws', async () => {\n const fake = makeFakeClient({\n close: jest.fn(async () => {\n throw new Error('already closed');\n })\n });\n const session = await connectWith(fake);\n expect(await closeMcpSession(session)).toFailWith(/closeMcpSession:.*already closed/);\n });\n});\n\n// ===========================================================================\n// Tool discovery + invocation\n// ===========================================================================\n\ndescribe('listMcpTools', () => {\n test('fails loudly for a foreign session handle', async () => {\n expect(await listMcpTools(FOREIGN_SESSION)).toFailWith(/invalid MCP session/);\n });\n\n test('returns a single-page catalog', async () => {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => ({\n tools: [{ name: 'a', description: 'tool a', inputSchema: { type: 'object' } }]\n }))\n });\n const session = await connectWith(fake);\n expect(await listMcpTools(session)).toSucceedAndSatisfy((tools: ReadonlyArray<IMcpToolDescriptor>) => {\n expect(tools).toHaveLength(1);\n expect(tools[0]).toEqual({ name: 'a', description: 'tool a', inputSchema: { type: 'object' } });\n });\n });\n\n test('follows nextCursor across pages', async () => {\n const listTools = jest\n .fn()\n .mockResolvedValueOnce({ tools: [{ name: 'a', inputSchema: { type: 'object' } }], nextCursor: 'p2' })\n .mockResolvedValueOnce({ tools: [{ name: 'b', inputSchema: { type: 'object' } }] });\n const fake = makeFakeClient({ listTools });\n const session = await connectWith(fake);\n\n expect(await listMcpTools(session)).toSucceedAndSatisfy((tools: ReadonlyArray<IMcpToolDescriptor>) => {\n expect(tools.map((t) => t.name)).toEqual(['a', 'b']);\n });\n expect(listTools).toHaveBeenNthCalledWith(1, undefined);\n expect(listTools).toHaveBeenNthCalledWith(2, { cursor: 'p2' });\n });\n\n test('coerces a missing/invalid inputSchema to null and a missing description to undefined', async () => {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => ({ tools: [{ name: 'noschema' }] }))\n });\n const session = await connectWith(fake);\n expect(await listMcpTools(session)).toSucceedAndSatisfy((tools: ReadonlyArray<IMcpToolDescriptor>) => {\n expect(tools[0]).toEqual({ name: 'noschema', description: undefined, inputSchema: null });\n });\n });\n\n test('fails when listTools throws', async () => {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => {\n throw new Error('protocol error');\n })\n });\n const session = await connectWith(fake);\n expect(await listMcpTools(session)).toFailWith(/listMcpTools:.*protocol error/);\n });\n});\n\ndescribe('callMcpTool', () => {\n const args: JsonObject = { q: 'hi' };\n\n test('fails loudly for a foreign session handle', async () => {\n expect(await callMcpTool(FOREIGN_SESSION, 'a', args)).toFailWith(/invalid MCP session/);\n });\n\n test('concatenates text blocks', async () => {\n const fake = makeFakeClient({\n callTool: jest.fn(async () => ({\n content: [\n { type: 'text', text: 'line one' },\n { type: 'text', text: 'line two' }\n ]\n }))\n });\n const session = await connectWith(fake);\n expect(await callMcpTool(session, 'a', args)).toSucceedWith({\n content: 'line one\\nline two'\n });\n expect(fake.callTool).toHaveBeenCalledWith({ name: 'a', arguments: args });\n });\n\n test('summarizes non-text blocks', async () => {\n const fake = makeFakeClient({\n callTool: jest.fn(async () => ({\n content: [\n { type: 'text', text: 'caption' },\n { type: 'image', data: 'base64==' }\n ]\n }))\n });\n const session = await connectWith(fake);\n expect(await callMcpTool(session, 'a', args)).toSucceedWith({\n content: 'caption\\n[image block]'\n });\n });\n\n test('projects an empty/absent content list to an empty string', async () => {\n const fake = makeFakeClient({ callTool: jest.fn(async () => ({})) });\n const session = await connectWith(fake);\n expect(await callMcpTool(session, 'a', args)).toSucceedWith({ content: '' });\n });\n\n test('maps isError:true to a Failure carrying the projected content', async () => {\n const fake = makeFakeClient({\n callTool: jest.fn(async () => ({ content: [{ type: 'text', text: 'bad input' }], isError: true }))\n });\n const session = await connectWith(fake);\n expect(await callMcpTool(session, 'a', args)).toFailWith(/^bad input$/);\n });\n\n test('maps isError:true with no content to a generic error', async () => {\n const fake = makeFakeClient({ callTool: jest.fn(async () => ({ isError: true })) });\n const session = await connectWith(fake);\n expect(await callMcpTool(session, 'broken', args)).toFailWith(/tool 'broken' reported an error/);\n });\n\n test('fails when callTool throws', async () => {\n const fake = makeFakeClient({\n callTool: jest.fn(async () => {\n throw new Error('timeout');\n })\n });\n const session = await connectWith(fake);\n expect(await callMcpTool(session, 'a', args)).toFailWith(/callMcpTool 'a':.*timeout/);\n });\n});\n\n// ===========================================================================\n// adaptMcpTools — the headline (Constraint 1)\n// ===========================================================================\n\ndescribe('adaptMcpTools', () => {\n const goodSchema: JsonValue = {\n type: 'object',\n properties: { q: { type: 'string' } },\n required: ['q']\n };\n // `pattern` is outside the JsonSchema.fromJson supported subset → fromJson fails with a pointer.\n const forbiddenSchema: JsonValue = {\n type: 'object',\n properties: { name: { type: 'string', pattern: '^a' } }\n };\n\n function mixedCatalogClient(): IFakeClient {\n return makeFakeClient({\n listTools: jest.fn(async () => ({\n tools: [\n { name: 'good', description: 'a good tool', inputSchema: goodSchema },\n { name: 'forbidden', inputSchema: forbiddenSchema },\n { name: 'nonobject', description: 'bad', inputSchema: 'not-a-schema' }\n ]\n }))\n });\n }\n\n test('propagates a tool-discovery failure', async () => {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => {\n throw new Error('discovery down');\n })\n });\n const session = await connectWith(fake);\n expect(await adaptMcpTools(session)).toFailWith(/adaptMcpTools:.*discovery down/);\n });\n\n test('adapts good tools and surfaces skipped tools structurally + NOISY warn', async () => {\n const logger = new Logging.InMemoryLogger('all');\n const session = await connectWith(mixedCatalogClient());\n\n expect(await adaptMcpTools(session, { logger })).toSucceedAndSatisfy((result: IAdaptMcpToolsResult) => {\n expect(result.tools).toHaveLength(1);\n expect(result.tools[0].config.name).toBe('good');\n expect(result.tools[0].config.description).toBe('a good tool');\n\n expect(result.skipped).toHaveLength(2);\n const byName = new Map(result.skipped.map((s) => [s.name, s]));\n expect(byName.get('forbidden')?.reason).toMatch(/pattern/);\n expect(byName.get('forbidden')?.schema).toEqual(forbiddenSchema);\n expect(byName.get('nonobject')?.reason).toMatch(/not a JSON object/);\n expect(byName.get('nonobject')?.schema).toBe('not-a-schema');\n });\n\n // NOISY warn: tool name + pointer reason + the raw failing schema verbatim.\n const warned = logger.logged.join('\\n');\n expect(warned).toMatch(/skipping tool 'forbidden'/);\n expect(warned).toMatch(/pattern/);\n expect(warned).toContain(JSON.stringify(forbiddenSchema));\n expect(warned).toMatch(/skipping tool 'nonobject'/);\n });\n\n test('surfaces skips structurally even without a logger', async () => {\n const session = await connectWith(mixedCatalogClient());\n expect(await adaptMcpTools(session)).toSucceedAndSatisfy((result: IAdaptMcpToolsResult) => {\n expect(result.tools).toHaveLength(1);\n expect(result.skipped).toHaveLength(2);\n });\n });\n\n test('falls back to the tool name when no description is provided', async () => {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => ({ tools: [{ name: 'nodesc', inputSchema: goodSchema }] }))\n });\n const session = await connectWith(fake);\n expect(await adaptMcpTools(session)).toSucceedAndSatisfy((result: IAdaptMcpToolsResult) => {\n expect(result.tools[0].config.description).toBe('nodesc');\n });\n });\n\n describe('adapted tool execute', () => {\n async function adaptGood(callTool: jest.Mock): Promise<(args: unknown) => Promise<Result<unknown>>> {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => ({ tools: [{ name: 'good', inputSchema: goodSchema }] })),\n callTool\n });\n const session = await connectWith(fake);\n const result = (await adaptMcpTools(session)).orThrow();\n return result.tools[0].execute;\n }\n\n test('forwards validated args to callMcpTool and returns the projected content', async () => {\n const callTool = jest.fn(async () => ({ content: [{ type: 'text', text: 'tool output' }] }));\n const execute = await adaptGood(callTool);\n expect(await execute({ q: 'hello' })).toSucceedWith('tool output');\n expect(callTool).toHaveBeenCalledWith({ name: 'good', arguments: { q: 'hello' } });\n });\n\n test('fails when the model supplies non-object arguments', async () => {\n const callTool = jest.fn();\n const execute = await adaptGood(callTool);\n expect(await execute('not-an-object')).toFailWith(/tool 'good': arguments must be a JSON object/);\n expect(callTool).not.toHaveBeenCalled();\n });\n\n test('propagates an MCP tool error as a Failure', async () => {\n const callTool = jest.fn(async () => ({ content: [{ type: 'text', text: 'denied' }], isError: true }));\n const execute = await adaptGood(callTool);\n expect(await execute({ q: 'hello' })).toFailWith(/denied/);\n });\n });\n});\n"]}
|
|
1
|
+
{"version":3,"file":"mcp.test.js","sourceRoot":"","sources":["../../../src/test/unit/mcp.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n// Mock the SDK-isolation seam so no live MCP server / subprocess is required. The factories\n// return controllable fakes; `makeClient` is configured per-test. The jest.mock call must\n// precede the regular imports (hoist-jest-mock).\njest.mock('../../packlets/mcp/sdk', () => ({\n makeClient: jest.fn(),\n makeStdioTransport: jest.fn(() => ({ sentinel: 'stdio' })),\n makeHttpTransport: jest.fn(() => ({ sentinel: 'http' }))\n}));\n\nimport '@fgv/ts-utils-jest';\nimport { Logging, type Result } from '@fgv/ts-utils';\nimport { type JsonObject, type JsonValue } from '@fgv/ts-json-base';\n\n// eslint-disable-next-line @rushstack/packlets/mechanics\nimport * as sdk from '../../packlets/mcp/sdk';\nimport {\n type IAdaptMcpToolsResult,\n type IMcpSession,\n type IMcpToolDescriptor,\n type IMcpTransport,\n adaptMcpTools,\n callMcpTool,\n closeMcpSession,\n connectMcpSession,\n createHttpTransport,\n createStdioTransport,\n listMcpTools\n} from '../../packlets/mcp';\n\nconst mockSdk = sdk as jest.Mocked<typeof sdk>;\n\ninterface IFakeClient {\n connect: jest.Mock;\n getServerVersion: jest.Mock;\n listTools: jest.Mock;\n callTool: jest.Mock;\n close: jest.Mock;\n}\n\nfunction makeFakeClient(overrides: Partial<IFakeClient> = {}): IFakeClient {\n return {\n connect: jest.fn(async () => undefined),\n getServerVersion: jest.fn(() => ({ name: 'fake-server', version: '1.2.3' })),\n listTools: jest.fn(async () => ({ tools: [] })),\n callTool: jest.fn(async () => ({ content: [] })),\n close: jest.fn(async () => undefined),\n ...overrides\n };\n}\n\n/** Connect a session backed by the given fake client (and a stdio transport). */\nasync function connectWith(fake: IFakeClient): Promise<IMcpSession> {\n mockSdk.makeClient.mockReturnValueOnce(fake as unknown as sdk.ISdkClient);\n const transport = createStdioTransport({ command: 'node' }).orThrow();\n return (await connectMcpSession({ transport })).orThrow();\n}\n\n/** A foreign (non-package) handle, to exercise the loud narrowing-failure paths. */\nconst FOREIGN_TRANSPORT = { transportKind: 'stdio' } as unknown as IMcpTransport;\nconst FOREIGN_SESSION = {\n clientName: 'x',\n clientVersion: 'y',\n serverInfo: undefined\n} as unknown as IMcpSession;\n\nbeforeEach(() => {\n jest.clearAllMocks();\n mockSdk.makeStdioTransport.mockReturnValue({ sentinel: 'stdio' } as unknown as sdk.ISdkTransport);\n mockSdk.makeHttpTransport.mockReturnValue({ sentinel: 'http' } as unknown as sdk.ISdkTransport);\n});\n\n// ===========================================================================\n// Transports\n// ===========================================================================\n\ndescribe('createStdioTransport', () => {\n test('succeeds for a valid command', () => {\n expect(createStdioTransport({ command: 'node', args: ['x'] })).toSucceedAndSatisfy((t: IMcpTransport) => {\n expect(t.transportKind).toBe('stdio');\n });\n expect(mockSdk.makeStdioTransport).toHaveBeenCalledWith(\n expect.objectContaining({ command: 'node', args: ['x'] })\n );\n });\n\n test('fails for an empty/whitespace command', () => {\n expect(createStdioTransport({ command: '' })).toFailWith(/command must be a non-empty string/);\n expect(createStdioTransport({ command: ' ' })).toFailWith(/command must be a non-empty string/);\n });\n\n test('fails (Result) when the SDK constructor throws', () => {\n mockSdk.makeStdioTransport.mockImplementationOnce(() => {\n throw new Error('spawn boom');\n });\n expect(createStdioTransport({ command: 'node' })).toFailWith(/createStdioTransport:.*spawn boom/);\n });\n});\n\ndescribe('createHttpTransport', () => {\n test('succeeds for http and https urls and forwards headers', () => {\n expect(createHttpTransport({ url: 'http://localhost:9000/mcp' })).toSucceedAndSatisfy(\n (t: IMcpTransport) => {\n expect(t.transportKind).toBe('http');\n }\n );\n expect(\n createHttpTransport({ url: 'https://example.com/mcp', headers: { authorization: 'Bearer t' } })\n ).toSucceed();\n expect(mockSdk.makeHttpTransport).toHaveBeenCalledWith(expect.any(URL), { authorization: 'Bearer t' });\n });\n\n test('fails for an unparseable url', () => {\n expect(createHttpTransport({ url: 'not a url' })).toFailWith(/invalid url 'not a url'/);\n });\n\n test('fails for a non-http(s) protocol', () => {\n expect(createHttpTransport({ url: 'ftp://example.com' })).toFailWith(/must use http or https/);\n });\n\n test('fails (Result) when the SDK constructor throws', () => {\n mockSdk.makeHttpTransport.mockImplementationOnce(() => {\n throw new Error('http boom');\n });\n expect(createHttpTransport({ url: 'http://localhost' })).toFailWith(/createHttpTransport:.*http boom/);\n });\n});\n\n// ===========================================================================\n// Session lifecycle\n// ===========================================================================\n\ndescribe('connectMcpSession', () => {\n test('fails loudly for a foreign transport handle', async () => {\n expect(await connectMcpSession({ transport: FOREIGN_TRANSPORT })).toFailWith(/invalid MCP transport/);\n });\n\n test('connects and reports server identity, logging when a logger is supplied', async () => {\n const logger = new Logging.InMemoryLogger('all');\n const fake = makeFakeClient();\n mockSdk.makeClient.mockReturnValueOnce(fake as unknown as sdk.ISdkClient);\n const transport = createStdioTransport({ command: 'node' }).orThrow();\n\n expect(\n await connectMcpSession({ transport, clientName: 'me', clientVersion: '0.0.1', logger })\n ).toSucceedAndSatisfy((session: IMcpSession) => {\n expect(session.clientName).toBe('me');\n expect(session.clientVersion).toBe('0.0.1');\n expect(session.serverInfo).toEqual({ name: 'fake-server', version: '1.2.3' });\n });\n expect(mockSdk.makeClient).toHaveBeenCalledWith('me', '0.0.1');\n expect(fake.connect).toHaveBeenCalledTimes(1);\n expect(logger.logged.join('\\n')).toMatch(/connected to server fake-server@1.2.3/);\n });\n\n test('connects with default identity and no server info, no logger', async () => {\n const fake = makeFakeClient({ getServerVersion: jest.fn(() => undefined) });\n mockSdk.makeClient.mockReturnValueOnce(fake as unknown as sdk.ISdkClient);\n const transport = createStdioTransport({ command: 'node' }).orThrow();\n\n expect(await connectMcpSession({ transport })).toSucceedAndSatisfy((session: IMcpSession) => {\n expect(session.clientName).toBe('@fgv/ts-extras-mcp');\n expect(session.serverInfo).toBeUndefined();\n });\n });\n\n test('logs the no-identity case when a logger is supplied', async () => {\n const logger = new Logging.InMemoryLogger('all');\n const fake = makeFakeClient({ getServerVersion: jest.fn(() => undefined) });\n mockSdk.makeClient.mockReturnValueOnce(fake as unknown as sdk.ISdkClient);\n const transport = createStdioTransport({ command: 'node' }).orThrow();\n\n expect(await connectMcpSession({ transport, logger })).toSucceed();\n expect(logger.logged.join('\\n')).toMatch(/connected \\(server did not report identity\\)/);\n });\n\n test('fails when the connect handshake throws', async () => {\n const fake = makeFakeClient({\n connect: jest.fn(async () => {\n throw new Error('handshake refused');\n })\n });\n mockSdk.makeClient.mockReturnValueOnce(fake as unknown as sdk.ISdkClient);\n const transport = createStdioTransport({ command: 'node' }).orThrow();\n\n expect(await connectMcpSession({ transport })).toFailWith(/connectMcpSession:.*handshake refused/);\n });\n});\n\ndescribe('closeMcpSession', () => {\n test('fails loudly for a foreign session handle', async () => {\n expect(await closeMcpSession(FOREIGN_SESSION)).toFailWith(/invalid MCP session/);\n });\n\n test('closes a connected session', async () => {\n const fake = makeFakeClient();\n const session = await connectWith(fake);\n expect(await closeMcpSession(session)).toSucceedWith(true);\n expect(fake.close).toHaveBeenCalledTimes(1);\n });\n\n test('fails when close throws', async () => {\n const fake = makeFakeClient({\n close: jest.fn(async () => {\n throw new Error('already closed');\n })\n });\n const session = await connectWith(fake);\n expect(await closeMcpSession(session)).toFailWith(/closeMcpSession:.*already closed/);\n });\n});\n\n// ===========================================================================\n// Tool discovery + invocation\n// ===========================================================================\n\ndescribe('listMcpTools', () => {\n test('fails loudly for a foreign session handle', async () => {\n expect(await listMcpTools(FOREIGN_SESSION)).toFailWith(/invalid MCP session/);\n });\n\n test('returns a single-page catalog', async () => {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => ({\n tools: [{ name: 'a', description: 'tool a', inputSchema: { type: 'object' } }]\n }))\n });\n const session = await connectWith(fake);\n expect(await listMcpTools(session)).toSucceedAndSatisfy((tools: ReadonlyArray<IMcpToolDescriptor>) => {\n expect(tools).toHaveLength(1);\n expect(tools[0]).toEqual({ name: 'a', description: 'tool a', inputSchema: { type: 'object' } });\n });\n });\n\n test('follows nextCursor across pages', async () => {\n const listTools = jest\n .fn()\n .mockResolvedValueOnce({ tools: [{ name: 'a', inputSchema: { type: 'object' } }], nextCursor: 'p2' })\n .mockResolvedValueOnce({ tools: [{ name: 'b', inputSchema: { type: 'object' } }] });\n const fake = makeFakeClient({ listTools });\n const session = await connectWith(fake);\n\n expect(await listMcpTools(session)).toSucceedAndSatisfy((tools: ReadonlyArray<IMcpToolDescriptor>) => {\n expect(tools.map((t) => t.name)).toEqual(['a', 'b']);\n });\n expect(listTools).toHaveBeenNthCalledWith(1, undefined);\n expect(listTools).toHaveBeenNthCalledWith(2, { cursor: 'p2' });\n });\n\n test('coerces a missing/invalid inputSchema to null and a missing description to undefined', async () => {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => ({ tools: [{ name: 'noschema' }] }))\n });\n const session = await connectWith(fake);\n expect(await listMcpTools(session)).toSucceedAndSatisfy((tools: ReadonlyArray<IMcpToolDescriptor>) => {\n expect(tools[0]).toEqual({ name: 'noschema', description: undefined, inputSchema: null });\n });\n });\n\n test('fails when listTools throws', async () => {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => {\n throw new Error('protocol error');\n })\n });\n const session = await connectWith(fake);\n expect(await listMcpTools(session)).toFailWith(/listMcpTools:.*protocol error/);\n });\n});\n\ndescribe('callMcpTool', () => {\n const args: JsonObject = { q: 'hi' };\n\n test('fails loudly for a foreign session handle', async () => {\n expect(await callMcpTool(FOREIGN_SESSION, 'a', args)).toFailWith(/invalid MCP session/);\n });\n\n test('concatenates text blocks', async () => {\n const fake = makeFakeClient({\n callTool: jest.fn(async () => ({\n content: [\n { type: 'text', text: 'line one' },\n { type: 'text', text: 'line two' }\n ]\n }))\n });\n const session = await connectWith(fake);\n expect(await callMcpTool(session, 'a', args)).toSucceedWith({\n content: 'line one\\nline two'\n });\n expect(fake.callTool).toHaveBeenCalledWith({ name: 'a', arguments: args });\n });\n\n test('summarizes non-text blocks', async () => {\n const fake = makeFakeClient({\n callTool: jest.fn(async () => ({\n content: [\n { type: 'text', text: 'caption' },\n { type: 'image', data: 'base64==' }\n ]\n }))\n });\n const session = await connectWith(fake);\n expect(await callMcpTool(session, 'a', args)).toSucceedWith({\n content: 'caption\\n[image block]'\n });\n });\n\n test('projects an empty/absent content list to an empty string', async () => {\n const fake = makeFakeClient({ callTool: jest.fn(async () => ({})) });\n const session = await connectWith(fake);\n expect(await callMcpTool(session, 'a', args)).toSucceedWith({ content: '' });\n });\n\n test('maps isError:true to a Failure carrying the projected content', async () => {\n const fake = makeFakeClient({\n callTool: jest.fn(async () => ({ content: [{ type: 'text', text: 'bad input' }], isError: true }))\n });\n const session = await connectWith(fake);\n expect(await callMcpTool(session, 'a', args)).toFailWith(/^bad input$/);\n });\n\n test('maps isError:true with no content to a generic error', async () => {\n const fake = makeFakeClient({ callTool: jest.fn(async () => ({ isError: true })) });\n const session = await connectWith(fake);\n expect(await callMcpTool(session, 'broken', args)).toFailWith(/tool 'broken' reported an error/);\n });\n\n test('fails when callTool throws', async () => {\n const fake = makeFakeClient({\n callTool: jest.fn(async () => {\n throw new Error('timeout');\n })\n });\n const session = await connectWith(fake);\n expect(await callMcpTool(session, 'a', args)).toFailWith(/callMcpTool 'a':.*timeout/);\n });\n});\n\n// ===========================================================================\n// adaptMcpTools — the headline (Constraint 1)\n// ===========================================================================\n\ndescribe('adaptMcpTools', () => {\n const goodSchema: JsonValue = {\n type: 'object',\n properties: { q: { type: 'string' } },\n required: ['q']\n };\n // `pattern` is outside the JsonSchema.fromJson supported subset → fromJson fails with a pointer.\n const forbiddenSchema: JsonValue = {\n type: 'object',\n properties: { name: { type: 'string', pattern: '^a' } }\n };\n\n function mixedCatalogClient(): IFakeClient {\n return makeFakeClient({\n listTools: jest.fn(async () => ({\n tools: [\n { name: 'good', description: 'a good tool', inputSchema: goodSchema },\n { name: 'forbidden', inputSchema: forbiddenSchema },\n { name: 'nonobject', description: 'bad', inputSchema: 'not-a-schema' }\n ]\n }))\n });\n }\n\n test('propagates a tool-discovery failure', async () => {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => {\n throw new Error('discovery down');\n })\n });\n const session = await connectWith(fake);\n expect(await adaptMcpTools(session)).toFailWith(/adaptMcpTools:.*discovery down/);\n });\n\n test('adapts good tools and surfaces skipped tools structurally + NOISY warn', async () => {\n const logger = new Logging.InMemoryLogger('all');\n const session = await connectWith(mixedCatalogClient());\n\n expect(await adaptMcpTools(session, { logger })).toSucceedAndSatisfy((result: IAdaptMcpToolsResult) => {\n expect(result.tools).toHaveLength(1);\n expect(result.tools[0].config.name).toBe('good');\n expect(result.tools[0].config.description).toBe('a good tool');\n\n expect(result.skipped).toHaveLength(2);\n const byName = new Map(result.skipped.map((s) => [s.name, s]));\n expect(byName.get('forbidden')?.reason).toMatch(/pattern/);\n expect(byName.get('forbidden')?.schema).toEqual(forbiddenSchema);\n expect(byName.get('nonobject')?.reason).toMatch(/not a JSON object/);\n expect(byName.get('nonobject')?.schema).toBe('not-a-schema');\n });\n\n // NOISY warn: tool name + pointer reason + the raw failing schema verbatim.\n const warned = logger.logged.join('\\n');\n expect(warned).toMatch(/skipping tool 'forbidden'/);\n expect(warned).toMatch(/pattern/);\n expect(warned).toContain(JSON.stringify(forbiddenSchema));\n expect(warned).toMatch(/skipping tool 'nonobject'/);\n });\n\n test('surfaces skips structurally even without a logger', async () => {\n const session = await connectWith(mixedCatalogClient());\n expect(await adaptMcpTools(session)).toSucceedAndSatisfy((result: IAdaptMcpToolsResult) => {\n expect(result.tools).toHaveLength(1);\n expect(result.skipped).toHaveLength(2);\n });\n });\n\n test('falls back to the tool name when no description is provided', async () => {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => ({ tools: [{ name: 'nodesc', inputSchema: goodSchema }] }))\n });\n const session = await connectWith(fake);\n expect(await adaptMcpTools(session)).toSucceedAndSatisfy((result: IAdaptMcpToolsResult) => {\n expect(result.tools[0].config.description).toBe('nodesc');\n });\n });\n\n describe('adapted tool execute', () => {\n async function adaptGood(callTool: jest.Mock): Promise<(args: unknown) => Promise<Result<unknown>>> {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => ({ tools: [{ name: 'good', inputSchema: goodSchema }] })),\n callTool\n });\n const session = await connectWith(fake);\n const result = (await adaptMcpTools(session)).orThrow();\n return result.tools[0].execute;\n }\n\n test('forwards validated args to callMcpTool and returns the projected content', async () => {\n const callTool = jest.fn(async () => ({ content: [{ type: 'text', text: 'tool output' }] }));\n const execute = await adaptGood(callTool);\n expect(await execute({ q: 'hello' })).toSucceedWith('tool output');\n expect(callTool).toHaveBeenCalledWith({ name: 'good', arguments: { q: 'hello' } });\n });\n\n test('fails when the model supplies non-object arguments', async () => {\n const callTool = jest.fn();\n const execute = await adaptGood(callTool);\n expect(await execute('not-an-object')).toFailWith(/tool 'good': arguments must be a JSON object/);\n expect(callTool).not.toHaveBeenCalled();\n });\n\n test('propagates an MCP tool error as a Failure', async () => {\n const callTool = jest.fn(async () => ({ content: [{ type: 'text', text: 'denied' }], isError: true }));\n const execute = await adaptGood(callTool);\n expect(await execute({ q: 'hello' })).toFailWith(/denied/);\n });\n });\n\n // ==========================================================================\n // C2 — MCP Tool.annotations passthrough (normalized, never raw)\n // ==========================================================================\n\n describe('annotations passthrough (C2)', () => {\n const goodSchema2: JsonValue = { type: 'object', properties: { q: { type: 'string' } }, required: ['q'] };\n\n test('all five hints round-trip onto descriptor.annotations and config.annotations', async () => {\n const annotations = {\n title: 'Recall Memory',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false\n };\n const fake = makeFakeClient({\n listTools: jest.fn(async () => ({\n tools: [{ name: 'recall', description: 'r', inputSchema: goodSchema2, annotations }]\n }))\n });\n const session = await connectWith(fake);\n\n expect(await listMcpTools(session)).toSucceedAndSatisfy((tools: ReadonlyArray<IMcpToolDescriptor>) => {\n expect(tools[0].annotations).toEqual(annotations);\n });\n\n const session2 = await connectWith(\n makeFakeClient({\n listTools: jest.fn(async () => ({\n tools: [{ name: 'recall', description: 'r', inputSchema: goodSchema2, annotations }]\n }))\n })\n );\n expect(await adaptMcpTools(session2)).toSucceedAndSatisfy((result: IAdaptMcpToolsResult) => {\n expect(result.tools[0].config.annotations).toEqual(annotations);\n });\n });\n\n test('a tool with no annotations leaves the field absent', async () => {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => ({ tools: [{ name: 'plain', inputSchema: goodSchema2 }] }))\n });\n const session = await connectWith(fake);\n expect(await listMcpTools(session)).toSucceedAndSatisfy((tools: ReadonlyArray<IMcpToolDescriptor>) => {\n expect(tools[0]).not.toHaveProperty('annotations');\n });\n\n const session2 = await connectWith(\n makeFakeClient({\n listTools: jest.fn(async () => ({ tools: [{ name: 'plain', inputSchema: goodSchema2 }] }))\n })\n );\n expect(await adaptMcpTools(session2)).toSucceedAndSatisfy((result: IAdaptMcpToolsResult) => {\n expect(result.tools[0].config).not.toHaveProperty('annotations');\n });\n });\n\n test('a malformed annotations blob is normalized (known fields kept, junk dropped), never raw-propagated', async () => {\n // readOnlyHint is the wrong type (dropped); bogusField is unknown (dropped); title is valid (kept).\n const rawAnnotations = {\n title: 'Keep Me',\n readOnlyHint: 'yes-please',\n destructiveHint: true,\n bogusField: { nested: 42 }\n };\n const fake = makeFakeClient({\n listTools: jest.fn(async () => ({\n tools: [{ name: 'messy', inputSchema: goodSchema2, annotations: rawAnnotations }]\n }))\n });\n const session = await connectWith(fake);\n expect(await listMcpTools(session)).toSucceedAndSatisfy((tools: ReadonlyArray<IMcpToolDescriptor>) => {\n // Only the well-typed known fields survive; the malformed readOnlyHint and unknown\n // bogusField are dropped; the raw blob is never propagated.\n expect(tools[0].annotations).toEqual({ title: 'Keep Me', destructiveHint: true });\n expect(tools[0].annotations).not.toHaveProperty('readOnlyHint');\n expect(tools[0].annotations).not.toHaveProperty('bogusField');\n });\n });\n\n test('an all-junk annotations blob normalizes to an absent field (not an empty object)', async () => {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => ({\n tools: [\n { name: 'junk', inputSchema: goodSchema2, annotations: { unknownOnly: 1, readOnlyHint: 3 } }\n ]\n }))\n });\n const session = await connectWith(fake);\n expect(await listMcpTools(session)).toSucceedAndSatisfy((tools: ReadonlyArray<IMcpToolDescriptor>) => {\n expect(tools[0]).not.toHaveProperty('annotations');\n });\n });\n\n test('a non-object annotations value is dropped', async () => {\n const fake = makeFakeClient({\n listTools: jest.fn(async () => ({\n tools: [{ name: 'weird', inputSchema: goodSchema2, annotations: 'not-an-object' }]\n }))\n });\n const session = await connectWith(fake);\n expect(await listMcpTools(session)).toSucceedAndSatisfy((tools: ReadonlyArray<IMcpToolDescriptor>) => {\n expect(tools[0]).not.toHaveProperty('annotations');\n });\n });\n\n test('the skipped-tool path is unaffected by annotations', async () => {\n // A tool with annotations but an unsupported inputSchema is still skipped; annotations do\n // not rescue it or leak onto the skip record.\n const forbidden: JsonValue = {\n type: 'object',\n properties: { name: { type: 'string', pattern: '^a' } }\n };\n const fake = makeFakeClient({\n listTools: jest.fn(async () => ({\n tools: [{ name: 'blocked', inputSchema: forbidden, annotations: { destructiveHint: true } }]\n }))\n });\n const session = await connectWith(fake);\n expect(await adaptMcpTools(session)).toSucceedAndSatisfy((result: IAdaptMcpToolsResult) => {\n expect(result.tools).toHaveLength(0);\n expect(result.skipped).toHaveLength(1);\n expect(result.skipped[0].name).toBe('blocked');\n expect(result.skipped[0]).not.toHaveProperty('annotations');\n });\n });\n });\n});\n"]}
|