@fgv/ts-extras-mcp 5.1.0-34

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.
Files changed (107) hide show
  1. package/.rush/temp/5d207610a84e494acaefe7ed13447cda74ddf7da.tar.log +90 -0
  2. package/.rush/temp/chunked-rush-logs/ts-extras-mcp.build.chunks.jsonl +9 -0
  3. package/.rush/temp/operation/build/all.log +9 -0
  4. package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
  5. package/.rush/temp/operation/build/state.json +3 -0
  6. package/.rush/temp/shrinkwrap-deps.json +734 -0
  7. package/CHANGELOG.json +4 -0
  8. package/README.md +133 -0
  9. package/config/api-extractor.json +38 -0
  10. package/config/jest.config.json +13 -0
  11. package/config/rig.json +6 -0
  12. package/dist/index.js +49 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/packlets/mcp/adapter.js +122 -0
  15. package/dist/packlets/mcp/adapter.js.map +1 -0
  16. package/dist/packlets/mcp/index.js +33 -0
  17. package/dist/packlets/mcp/index.js.map +1 -0
  18. package/dist/packlets/mcp/model.js +23 -0
  19. package/dist/packlets/mcp/model.js.map +1 -0
  20. package/dist/packlets/mcp/operations.js +119 -0
  21. package/dist/packlets/mcp/operations.js.map +1 -0
  22. package/dist/packlets/mcp/sdk.js +67 -0
  23. package/dist/packlets/mcp/sdk.js.map +1 -0
  24. package/dist/packlets/mcp/session.js +109 -0
  25. package/dist/packlets/mcp/session.js.map +1 -0
  26. package/dist/packlets/mcp/transports.js +96 -0
  27. package/dist/packlets/mcp/transports.js.map +1 -0
  28. package/dist/test/unit/endToEnd.test.js +220 -0
  29. package/dist/test/unit/endToEnd.test.js.map +1 -0
  30. package/dist/test/unit/index.test.js +41 -0
  31. package/dist/test/unit/index.test.js.map +1 -0
  32. package/dist/test/unit/mcp.test.js +381 -0
  33. package/dist/test/unit/mcp.test.js.map +1 -0
  34. package/dist/test/unit/sdk.test.js +68 -0
  35. package/dist/test/unit/sdk.test.js.map +1 -0
  36. package/dist/ts-extras-mcp.d.ts +290 -0
  37. package/dist/tsdoc-metadata.json +11 -0
  38. package/eslint.config.js +15 -0
  39. package/etc/ts-extras-mcp.api.md +104 -0
  40. package/lib/index.d.ts +28 -0
  41. package/lib/index.d.ts.map +1 -0
  42. package/lib/index.js +65 -0
  43. package/lib/index.js.map +1 -0
  44. package/lib/packlets/mcp/adapter.d.ts +33 -0
  45. package/lib/packlets/mcp/adapter.d.ts.map +1 -0
  46. package/lib/packlets/mcp/adapter.js +125 -0
  47. package/lib/packlets/mcp/adapter.js.map +1 -0
  48. package/lib/packlets/mcp/index.d.ts +10 -0
  49. package/lib/packlets/mcp/index.d.ts.map +1 -0
  50. package/lib/packlets/mcp/index.js +57 -0
  51. package/lib/packlets/mcp/index.js.map +1 -0
  52. package/lib/packlets/mcp/model.d.ts +155 -0
  53. package/lib/packlets/mcp/model.d.ts.map +1 -0
  54. package/lib/packlets/mcp/model.js +24 -0
  55. package/lib/packlets/mcp/model.js.map +1 -0
  56. package/lib/packlets/mcp/operations.d.ts +36 -0
  57. package/lib/packlets/mcp/operations.d.ts.map +1 -0
  58. package/lib/packlets/mcp/operations.js +123 -0
  59. package/lib/packlets/mcp/operations.js.map +1 -0
  60. package/lib/packlets/mcp/sdk.d.ts +91 -0
  61. package/lib/packlets/mcp/sdk.d.ts.map +1 -0
  62. package/lib/packlets/mcp/sdk.js +72 -0
  63. package/lib/packlets/mcp/sdk.js.map +1 -0
  64. package/lib/packlets/mcp/session.d.ts +43 -0
  65. package/lib/packlets/mcp/session.d.ts.map +1 -0
  66. package/lib/packlets/mcp/session.js +115 -0
  67. package/lib/packlets/mcp/session.js.map +1 -0
  68. package/lib/packlets/mcp/transports.d.ts +49 -0
  69. package/lib/packlets/mcp/transports.d.ts.map +1 -0
  70. package/lib/packlets/mcp/transports.js +102 -0
  71. package/lib/packlets/mcp/transports.js.map +1 -0
  72. package/lib/test/unit/endToEnd.test.d.ts +13 -0
  73. package/lib/test/unit/endToEnd.test.d.ts.map +1 -0
  74. package/lib/test/unit/endToEnd.test.js +222 -0
  75. package/lib/test/unit/endToEnd.test.js.map +1 -0
  76. package/lib/test/unit/index.test.d.ts +2 -0
  77. package/lib/test/unit/index.test.d.ts.map +1 -0
  78. package/lib/test/unit/index.test.js +76 -0
  79. package/lib/test/unit/index.test.js.map +1 -0
  80. package/lib/test/unit/mcp.test.d.ts +2 -0
  81. package/lib/test/unit/mcp.test.d.ts.map +1 -0
  82. package/lib/test/unit/mcp.test.js +416 -0
  83. package/lib/test/unit/mcp.test.js.map +1 -0
  84. package/lib/test/unit/sdk.test.d.ts +2 -0
  85. package/lib/test/unit/sdk.test.d.ts.map +1 -0
  86. package/lib/test/unit/sdk.test.js +70 -0
  87. package/lib/test/unit/sdk.test.js.map +1 -0
  88. package/package.json +86 -0
  89. package/rush-logs/ts-extras-mcp.build.cache.log +3 -0
  90. package/rush-logs/ts-extras-mcp.build.log +9 -0
  91. package/src/index.ts +50 -0
  92. package/src/packlets/mcp/adapter.ts +152 -0
  93. package/src/packlets/mcp/index.ts +34 -0
  94. package/src/packlets/mcp/model.ts +204 -0
  95. package/src/packlets/mcp/operations.ts +138 -0
  96. package/src/packlets/mcp/sdk.ts +148 -0
  97. package/src/packlets/mcp/session.ts +137 -0
  98. package/src/packlets/mcp/transports.ts +111 -0
  99. package/src/test/unit/endToEnd.test.ts +254 -0
  100. package/src/test/unit/index.test.ts +43 -0
  101. package/src/test/unit/mcp.test.ts +473 -0
  102. package/src/test/unit/sdk.test.ts +73 -0
  103. package/temp/build/lint/_eslint-5eVG3S6w.json +54 -0
  104. package/temp/build/typescript/ts_8nwakTlr.json +1 -0
  105. package/temp/ts-extras-mcp.api.json +1662 -0
  106. package/temp/ts-extras-mcp.api.md +104 -0
  107. package/tsconfig.json +12 -0
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.adaptMcpTools = adaptMcpTools;
25
+ /**
26
+ * The headline adapter — discovers an MCP server's tools and adapts each into an
27
+ * `AiAssist.IAiClientTool`, gracefully degrading (Constraint 1) on any tool whose `inputSchema`
28
+ * is outside the JSON Schema subset that `JsonSchema.fromJson` supports.
29
+ * @packageDocumentation
30
+ */
31
+ const ts_utils_1 = require("@fgv/ts-utils");
32
+ const ts_json_base_1 = require("@fgv/ts-json-base");
33
+ const operations_1 = require("./operations");
34
+ /**
35
+ * Builds the `execute` callback for an adapted MCP tool. Args arrive already validated against
36
+ * the tool's `parametersSchema` by `executeClientToolTurn`; here we narrow to a `JsonObject`
37
+ * (MCP arguments are always an object) and forward to {@link callMcpTool}, returning the
38
+ * projected text content. A tool error surfaces as a `Failure` (never swallowed).
39
+ */
40
+ function _makeExecute(session, name) {
41
+ return async (args) => {
42
+ const objResult = ts_json_base_1.Converters.jsonObject
43
+ .convert(args)
44
+ .withErrorFormat((msg) => `tool '${name}': arguments must be a JSON object: ${msg}`);
45
+ if (objResult.isFailure()) {
46
+ return objResult;
47
+ }
48
+ return (await (0, operations_1.callMcpTool)(session, name, objResult.value)).onSuccess((called) => (0, ts_utils_1.succeed)(called.content));
49
+ };
50
+ }
51
+ /**
52
+ * Adapts a single discovered tool. Returns a `skipped` outcome (never a failure) when the
53
+ * tool's `inputSchema` is not a JSON object or is outside the `JsonSchema.fromJson` subset.
54
+ */
55
+ function _adaptOne(session, descriptor) {
56
+ const { name, description, inputSchema } = descriptor;
57
+ const skip = (reason, schema) => ({
58
+ kind: 'skipped',
59
+ skipped: { name, reason, schema }
60
+ });
61
+ const asObject = ts_json_base_1.Converters.jsonObject.convert(inputSchema);
62
+ if (asObject.isFailure()) {
63
+ return skip(`inputSchema is not a JSON object (${asObject.message})`, inputSchema);
64
+ }
65
+ const schemaResult = ts_json_base_1.JsonSchema.fromJson(asObject.value);
66
+ if (schemaResult.isFailure()) {
67
+ return skip(schemaResult.message, inputSchema);
68
+ }
69
+ const config = {
70
+ type: 'client_tool',
71
+ name,
72
+ description: description !== null && description !== void 0 ? description : name,
73
+ parametersSchema: schemaResult.value
74
+ };
75
+ return {
76
+ kind: 'tool',
77
+ tool: { config, execute: _makeExecute(session, name) }
78
+ };
79
+ }
80
+ /**
81
+ * Discovers an MCP server's tools and adapts each into an `AiAssist.IAiClientTool` that drops
82
+ * directly into `AiAssist.executeClientToolTurn`.
83
+ *
84
+ * @remarks
85
+ * **Constraint 1 — graceful degradation with NOISY warnings.** A tool whose `inputSchema` is
86
+ * outside the JSON Schema subset supported by `JsonSchema.fromJson` is NOT adapted (the model
87
+ * must never be offered a tool whose args we can't validate). Instead it is:
88
+ * 1. excluded from {@link IAdaptMcpToolsResult.tools};
89
+ * 2. surfaced structurally on {@link IAdaptMcpToolsResult.skipped} with the tool name, the
90
+ * JSON-pointer reason, and the raw failing schema; and
91
+ * 3. logged as a NOISY `warning` (name + reason + raw schema) when an `options.logger` is
92
+ * supplied — so pointing this at a new server immediately reveals every subset feature to
93
+ * extend, with the schema in hand.
94
+ *
95
+ * The whole catalog never fails on a single bad schema; the only failure mode is an upstream
96
+ * `listMcpTools` error.
97
+ *
98
+ * @param session - A connected session from `connectMcpSession`.
99
+ * @param options - Optional logger for the NOISY skip warnings.
100
+ * @returns `Success` with `{ tools, skipped }`, or `Failure` only if tool discovery fails.
101
+ * @public
102
+ */
103
+ async function adaptMcpTools(session, options) {
104
+ return (await (0, operations_1.listMcpTools)(session))
105
+ .onSuccess((descriptors) => {
106
+ var _a;
107
+ const tools = [];
108
+ const skipped = [];
109
+ for (const descriptor of descriptors) {
110
+ const outcome = _adaptOne(session, descriptor);
111
+ if (outcome.kind === 'tool') {
112
+ tools.push(outcome.tool);
113
+ }
114
+ else {
115
+ skipped.push(outcome.skipped);
116
+ (_a = options === null || options === void 0 ? void 0 : options.logger) === null || _a === void 0 ? void 0 : _a.warn(`mcp: skipping tool '${outcome.skipped.name}': inputSchema is outside the supported ` +
117
+ `JSON Schema subset: ${outcome.skipped.reason}. ` +
118
+ `Raw schema: ${JSON.stringify(outcome.skipped.schema)}`);
119
+ }
120
+ }
121
+ return (0, ts_utils_1.succeed)({ tools, skipped });
122
+ })
123
+ .withErrorFormat((msg) => `adaptMcpTools: ${msg}`);
124
+ }
125
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
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 } = 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 };\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"]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Public surface of the MCP packlet.
3
+ * @packageDocumentation
4
+ */
5
+ export * from './model';
6
+ export { createHttpTransport, createStdioTransport } from './transports';
7
+ export { closeMcpSession, connectMcpSession } from './session';
8
+ export { callMcpTool, listMcpTools } from './operations';
9
+ export { adaptMcpTools } from './adapter';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/packlets/mcp/index.ts"],"names":[],"mappings":"AAsBA;;;GAGG;AAIH,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
23
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ var desc = Object.getOwnPropertyDescriptor(m, k);
26
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
27
+ desc = { enumerable: true, get: function() { return m[k]; } };
28
+ }
29
+ Object.defineProperty(o, k2, desc);
30
+ }) : (function(o, m, k, k2) {
31
+ if (k2 === undefined) k2 = k;
32
+ o[k2] = m[k];
33
+ }));
34
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
35
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
36
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.adaptMcpTools = exports.listMcpTools = exports.callMcpTool = exports.connectMcpSession = exports.closeMcpSession = exports.createStdioTransport = exports.createHttpTransport = void 0;
39
+ /**
40
+ * Public surface of the MCP packlet.
41
+ * @packageDocumentation
42
+ */
43
+ // `export *` (rather than named type-only re-exports) emits a runtime re-export of the
44
+ // types-only module so it participates in coverage as a loaded (zero-statement) module.
45
+ __exportStar(require("./model"), exports);
46
+ var transports_1 = require("./transports");
47
+ Object.defineProperty(exports, "createHttpTransport", { enumerable: true, get: function () { return transports_1.createHttpTransport; } });
48
+ Object.defineProperty(exports, "createStdioTransport", { enumerable: true, get: function () { return transports_1.createStdioTransport; } });
49
+ var session_1 = require("./session");
50
+ Object.defineProperty(exports, "closeMcpSession", { enumerable: true, get: function () { return session_1.closeMcpSession; } });
51
+ Object.defineProperty(exports, "connectMcpSession", { enumerable: true, get: function () { return session_1.connectMcpSession; } });
52
+ var operations_1 = require("./operations");
53
+ Object.defineProperty(exports, "callMcpTool", { enumerable: true, get: function () { return operations_1.callMcpTool; } });
54
+ Object.defineProperty(exports, "listMcpTools", { enumerable: true, get: function () { return operations_1.listMcpTools; } });
55
+ var adapter_1 = require("./adapter");
56
+ Object.defineProperty(exports, "adaptMcpTools", { enumerable: true, get: function () { return adapter_1.adaptMcpTools; } });
57
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/mcp/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;;;;;;;;;;;;;;;AAEH;;;GAGG;AAEH,uFAAuF;AACvF,wFAAwF;AACxF,0CAAwB;AACxB,2CAAyE;AAAhE,iHAAA,mBAAmB,OAAA;AAAE,kHAAA,oBAAoB,OAAA;AAClD,qCAA+D;AAAtD,0GAAA,eAAe,OAAA;AAAE,4GAAA,iBAAiB,OAAA;AAC3C,2CAAyD;AAAhD,yGAAA,WAAW,OAAA;AAAE,0GAAA,YAAY,OAAA;AAClC,qCAA0C;AAAjC,wGAAA,aAAa,OAAA","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 surface of the MCP packlet.\n * @packageDocumentation\n */\n\n// `export *` (rather than named type-only re-exports) emits a runtime re-export of the\n// types-only module so it participates in coverage as a loaded (zero-statement) module.\nexport * from './model';\nexport { createHttpTransport, createStdioTransport } from './transports';\nexport { closeMcpSession, connectMcpSession } from './session';\nexport { callMcpTool, listMcpTools } from './operations';\nexport { adaptMcpTools } from './adapter';\n"]}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Public types for the MCP → ai-assist client-tools boundary.
3
+ * @packageDocumentation
4
+ */
5
+ import { type Logging } from '@fgv/ts-utils';
6
+ import { type AiAssist } from '@fgv/ts-extras';
7
+ import { type JsonValue } from '@fgv/ts-json-base';
8
+ /**
9
+ * Parameters for {@link createStdioTransport}. The transport spawns `command` as a
10
+ * subprocess and speaks MCP over its stdin/stdout.
11
+ *
12
+ * @remarks
13
+ * **Security:** stdio transport executes a consumer-supplied command. Treat `command`/`args`
14
+ * as a trust boundary — never source them from untrusted input. See the package README.
15
+ *
16
+ * @public
17
+ */
18
+ export interface IMcpStdioTransportParams {
19
+ /** Executable to spawn (e.g. `'npx'`, `'node'`, an absolute path). */
20
+ readonly command: string;
21
+ /** Arguments passed to the command. */
22
+ readonly args?: ReadonlyArray<string>;
23
+ /** Environment variables for the spawned process. When omitted, the SDK's safe default set is used. */
24
+ readonly env?: Record<string, string>;
25
+ /** Working directory for the spawned process. */
26
+ readonly cwd?: string;
27
+ }
28
+ /**
29
+ * Parameters for {@link createHttpTransport}. The transport connects to a Streamable-HTTP MCP
30
+ * endpoint.
31
+ * @public
32
+ */
33
+ export interface IMcpHttpTransportParams {
34
+ /** Absolute `http`/`https` URL of the MCP server endpoint. */
35
+ readonly url: string;
36
+ /** Optional static headers (e.g. an `Authorization` bearer token). OAuth/managed auth is out of scope at v0.1. */
37
+ readonly headers?: Record<string, string>;
38
+ }
39
+ /**
40
+ * Opaque handle to an MCP transport produced by {@link createStdioTransport} or
41
+ * {@link createHttpTransport}. Hand it to {@link connectMcpSession}; do not construct directly.
42
+ * @public
43
+ */
44
+ export interface IMcpTransport {
45
+ /** Which transport kind this handle wraps. */
46
+ readonly transportKind: 'stdio' | 'http';
47
+ }
48
+ /**
49
+ * Parameters for {@link connectMcpSession}.
50
+ * @public
51
+ */
52
+ export interface IConnectMcpSessionParams {
53
+ /** A transport produced by {@link createStdioTransport} / {@link createHttpTransport}. */
54
+ readonly transport: IMcpTransport;
55
+ /** Client name advertised to the server during the initialize handshake. Default `'@fgv/ts-extras-mcp'`. */
56
+ readonly clientName?: string;
57
+ /** Client version advertised to the server. Default the package version. */
58
+ readonly clientVersion?: string;
59
+ /** Optional logger for connection diagnostics. */
60
+ readonly logger?: Logging.ILogger;
61
+ }
62
+ /**
63
+ * Server identity reported during the MCP initialize handshake.
64
+ * @public
65
+ */
66
+ export interface IMcpServerInfo {
67
+ /** Server-advertised name. */
68
+ readonly name: string;
69
+ /** Server-advertised version. */
70
+ readonly version: string;
71
+ }
72
+ /**
73
+ * Opaque handle to a connected MCP session. Pass it to {@link listMcpTools},
74
+ * {@link callMcpTool}, {@link adaptMcpTools}, and {@link closeMcpSession}.
75
+ * @public
76
+ */
77
+ export interface IMcpSession {
78
+ /** Client name advertised during the handshake. */
79
+ readonly clientName: string;
80
+ /** Client version advertised during the handshake. */
81
+ readonly clientVersion: string;
82
+ /** Server identity reported by the handshake, when the server provided one. */
83
+ readonly serverInfo: IMcpServerInfo | undefined;
84
+ }
85
+ /**
86
+ * A tool descriptor discovered from an MCP server via {@link listMcpTools}.
87
+ * @public
88
+ */
89
+ export interface IMcpToolDescriptor {
90
+ /** Tool name (unique within a server). */
91
+ readonly name: string;
92
+ /** Human-readable description, when the server provided one. */
93
+ readonly description: string | undefined;
94
+ /**
95
+ * The tool's declared input schema as raw JSON. Per the MCP spec this is normally a JSON
96
+ * Schema object; carried verbatim so {@link adaptMcpTools} can run it through
97
+ * `JsonSchema.fromJson` (and surface it on {@link IMcpSkippedTool} when it is outside the
98
+ * supported subset).
99
+ */
100
+ readonly inputSchema: JsonValue;
101
+ }
102
+ /**
103
+ * Successful projection of an MCP `CallToolResult` produced by {@link callMcpTool}.
104
+ *
105
+ * @remarks
106
+ * `content` is the text concatenation of the result's `text` blocks; non-text blocks
107
+ * (image / audio / resource) are projected to a one-line `[<type> block]` summary
108
+ * (multimodal passthrough is out of scope at v0.1). A result with `isError: true` is mapped
109
+ * to `Result.fail(content)` rather than returned here, so it is never silently swallowed.
110
+ *
111
+ * @public
112
+ */
113
+ export interface IMcpToolCallResult {
114
+ /** The projected text content of the tool result. */
115
+ readonly content: string;
116
+ }
117
+ /**
118
+ * A tool that was discovered but could NOT be adapted into an `AiAssist.IAiClientTool`,
119
+ * because its `inputSchema` is outside the JSON Schema subset supported by
120
+ * `JsonSchema.fromJson`. Surfaced structurally so callers/the probe can enumerate exactly which
121
+ * subset features a server needs (with the raw schema in hand to extend `fromJson` later).
122
+ * @public
123
+ */
124
+ export interface IMcpSkippedTool {
125
+ /** The tool's name. */
126
+ readonly name: string;
127
+ /** Why the tool was skipped — the JSON-pointer reason from `JsonSchema.fromJson`, or a structural reason. */
128
+ readonly reason: string;
129
+ /** The raw failing input schema, verbatim, in hand for additively widening `JsonSchema.fromJson`. */
130
+ readonly schema: JsonValue;
131
+ }
132
+ /**
133
+ * Options for {@link adaptMcpTools}.
134
+ * @public
135
+ */
136
+ export interface IAdaptMcpToolsOptions {
137
+ /**
138
+ * Logger for the NOISY per-tool skip warnings. When a tool is skipped, a `warning` is emitted
139
+ * including the tool name, the JSON-pointer reason, and the raw failing schema. When omitted,
140
+ * skips are still surfaced structurally on {@link IAdaptMcpToolsResult.skipped}.
141
+ */
142
+ readonly logger?: Logging.ILogger;
143
+ }
144
+ /**
145
+ * The result of {@link adaptMcpTools}: cleanly-adapted client tools plus the structurally-surfaced
146
+ * set of tools that could not be adapted (graceful degradation, Constraint 1).
147
+ * @public
148
+ */
149
+ export interface IAdaptMcpToolsResult {
150
+ /** Tools adapted into `IAiClientTool` — safe to hand to `AiAssist.executeClientToolTurn`. */
151
+ readonly tools: ReadonlyArray<AiAssist.IAiClientTool>;
152
+ /** Tools excluded because their `inputSchema` is outside the supported JSON Schema subset. */
153
+ readonly skipped: ReadonlyArray<IMcpSkippedTool>;
154
+ }
155
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/packlets/mcp/model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ //# sourceMappingURL=model.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Tool discovery (`listMcpTools`, paginated) and invocation (`callMcpTool`, with `CallToolResult`
3
+ * projection), converting the throwing SDK calls into `Result<T>`.
4
+ * @packageDocumentation
5
+ */
6
+ import { type Result } from '@fgv/ts-utils';
7
+ import { type JsonObject } from '@fgv/ts-json-base';
8
+ import { type IMcpSession, type IMcpToolCallResult, type IMcpToolDescriptor } from './model';
9
+ /**
10
+ * Lists every tool a connected MCP server advertises, following the SDK's `nextCursor`
11
+ * pagination until the full catalog is accumulated.
12
+ *
13
+ * @param session - A session from `connectMcpSession`.
14
+ * @returns `Success` with the full tool catalog, or `Failure` on a foreign handle or a
15
+ * transport/protocol error.
16
+ * @public
17
+ */
18
+ export declare function listMcpTools(session: IMcpSession): Promise<Result<ReadonlyArray<IMcpToolDescriptor>>>;
19
+ /**
20
+ * Calls a named tool on a connected MCP server.
21
+ *
22
+ * @remarks
23
+ * The SDK `CallToolResult` is projected to {@link IMcpToolCallResult} (text-block concatenation;
24
+ * non-text blocks summarized). A result flagged `isError: true` is mapped to `Result.fail` with
25
+ * the projected content — it is never swallowed, so `executeClientToolTurn` routes it back to the
26
+ * model as a provider-native error tool-result.
27
+ *
28
+ * @param session - A session from `connectMcpSession`.
29
+ * @param name - The tool name to invoke.
30
+ * @param args - The tool arguments (a JSON object).
31
+ * @returns `Success` with the projected content, or `Failure` on tool error / transport error /
32
+ * foreign handle.
33
+ * @public
34
+ */
35
+ export declare function callMcpTool(session: IMcpSession, name: string, args: JsonObject): Promise<Result<IMcpToolCallResult>>;
36
+ //# sourceMappingURL=operations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../../src/packlets/mcp/operations.ts"],"names":[],"mappings":"AAsBA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,MAAM,EAAqC,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAgC7F;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAyB3G;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAkBrC"}
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.listMcpTools = listMcpTools;
25
+ exports.callMcpTool = callMcpTool;
26
+ /**
27
+ * Tool discovery (`listMcpTools`, paginated) and invocation (`callMcpTool`, with `CallToolResult`
28
+ * projection), converting the throwing SDK calls into `Result<T>`.
29
+ * @packageDocumentation
30
+ */
31
+ const ts_utils_1 = require("@fgv/ts-utils");
32
+ const ts_json_base_1 = require("@fgv/ts-json-base");
33
+ const session_1 = require("./session");
34
+ /**
35
+ * Projects a single SDK tool descriptor into a public {@link IMcpToolDescriptor}. The
36
+ * `inputSchema` field is validated as a `JsonValue`; an absent/non-JSON schema becomes `null`
37
+ * (which {@link adaptMcpTools} then reports as un-adaptable rather than offering it to the model).
38
+ */
39
+ function _toDescriptor(tool) {
40
+ return {
41
+ name: tool.name,
42
+ description: tool.description,
43
+ inputSchema: ts_json_base_1.Converters.jsonValue.convert(tool.inputSchema).orDefault(null)
44
+ };
45
+ }
46
+ /**
47
+ * Projects the SDK `CallToolResult` content blocks to a single string: `text` blocks are
48
+ * concatenated; every other block type becomes a one-line `[<type> block]` summary.
49
+ */
50
+ function _projectContent(blocks) {
51
+ if (blocks === undefined || blocks.length === 0) {
52
+ return '';
53
+ }
54
+ return blocks
55
+ .map((block) => block.type === 'text' && block.text !== undefined ? block.text : `[${block.type} block]`)
56
+ .join('\n');
57
+ }
58
+ /**
59
+ * Lists every tool a connected MCP server advertises, following the SDK's `nextCursor`
60
+ * pagination until the full catalog is accumulated.
61
+ *
62
+ * @param session - A session from `connectMcpSession`.
63
+ * @returns `Success` with the full tool catalog, or `Failure` on a foreign handle or a
64
+ * transport/protocol error.
65
+ * @public
66
+ */
67
+ async function listMcpTools(session) {
68
+ const sessionResult = session_1.McpSession.fromHandle(session);
69
+ if (sessionResult.isFailure()) {
70
+ return (0, ts_utils_1.fail)(`listMcpTools: ${sessionResult.message}`);
71
+ }
72
+ const { client } = sessionResult.value;
73
+ const all = [];
74
+ let cursor;
75
+ // Cursor-paginated loop: fetch each page, accumulate, advance until nextCursor is absent.
76
+ do {
77
+ const pageResult = await (0, ts_utils_1.captureAsyncResult)(() => client.listTools(cursor !== undefined ? { cursor } : undefined)).withErrorFormat((msg) => `listMcpTools: ${msg}`);
78
+ if (pageResult.isFailure()) {
79
+ return (0, ts_utils_1.fail)(pageResult.message);
80
+ }
81
+ for (const tool of pageResult.value.tools) {
82
+ all.push(_toDescriptor(tool));
83
+ }
84
+ cursor = pageResult.value.nextCursor;
85
+ } while (cursor !== undefined);
86
+ return (0, ts_utils_1.succeed)(all);
87
+ }
88
+ /**
89
+ * Calls a named tool on a connected MCP server.
90
+ *
91
+ * @remarks
92
+ * The SDK `CallToolResult` is projected to {@link IMcpToolCallResult} (text-block concatenation;
93
+ * non-text blocks summarized). A result flagged `isError: true` is mapped to `Result.fail` with
94
+ * the projected content — it is never swallowed, so `executeClientToolTurn` routes it back to the
95
+ * model as a provider-native error tool-result.
96
+ *
97
+ * @param session - A session from `connectMcpSession`.
98
+ * @param name - The tool name to invoke.
99
+ * @param args - The tool arguments (a JSON object).
100
+ * @returns `Success` with the projected content, or `Failure` on tool error / transport error /
101
+ * foreign handle.
102
+ * @public
103
+ */
104
+ async function callMcpTool(session, name, args) {
105
+ const sessionResult = session_1.McpSession.fromHandle(session);
106
+ if (sessionResult.isFailure()) {
107
+ return (0, ts_utils_1.fail)(`callMcpTool: ${sessionResult.message}`);
108
+ }
109
+ // The `withErrorFormat` wraps only transport/throw failures (it transforms the failure flowing
110
+ // out of `captureAsyncResult`); the `isError` failure created in the later `onSuccess` is
111
+ // downstream and stays clean, so the model-facing tool-error text is the server's verbatim
112
+ // content rather than a doubly-prefixed message.
113
+ return (0, ts_utils_1.captureAsyncResult)(() => sessionResult.value.client.callTool({ name, arguments: args }))
114
+ .withErrorFormat((msg) => `callMcpTool '${name}': ${msg}`)
115
+ .onSuccess((raw) => {
116
+ const content = _projectContent(raw.content);
117
+ if (raw.isError === true) {
118
+ return (0, ts_utils_1.fail)(content.length > 0 ? content : `tool '${name}' reported an error`);
119
+ }
120
+ return (0, ts_utils_1.succeed)({ content });
121
+ });
122
+ }
123
+ //# sourceMappingURL=operations.js.map
@@ -0,0 +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"]}