@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.
- package/.rush/temp/5d207610a84e494acaefe7ed13447cda74ddf7da.tar.log +90 -0
- package/.rush/temp/chunked-rush-logs/ts-extras-mcp.build.chunks.jsonl +9 -0
- package/.rush/temp/operation/build/all.log +9 -0
- package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
- package/.rush/temp/operation/build/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +734 -0
- package/CHANGELOG.json +4 -0
- package/README.md +133 -0
- package/config/api-extractor.json +38 -0
- package/config/jest.config.json +13 -0
- package/config/rig.json +6 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/packlets/mcp/adapter.js +122 -0
- package/dist/packlets/mcp/adapter.js.map +1 -0
- package/dist/packlets/mcp/index.js +33 -0
- package/dist/packlets/mcp/index.js.map +1 -0
- package/dist/packlets/mcp/model.js +23 -0
- package/dist/packlets/mcp/model.js.map +1 -0
- package/dist/packlets/mcp/operations.js +119 -0
- package/dist/packlets/mcp/operations.js.map +1 -0
- package/dist/packlets/mcp/sdk.js +67 -0
- package/dist/packlets/mcp/sdk.js.map +1 -0
- package/dist/packlets/mcp/session.js +109 -0
- package/dist/packlets/mcp/session.js.map +1 -0
- package/dist/packlets/mcp/transports.js +96 -0
- package/dist/packlets/mcp/transports.js.map +1 -0
- package/dist/test/unit/endToEnd.test.js +220 -0
- package/dist/test/unit/endToEnd.test.js.map +1 -0
- package/dist/test/unit/index.test.js +41 -0
- package/dist/test/unit/index.test.js.map +1 -0
- package/dist/test/unit/mcp.test.js +381 -0
- package/dist/test/unit/mcp.test.js.map +1 -0
- package/dist/test/unit/sdk.test.js +68 -0
- package/dist/test/unit/sdk.test.js.map +1 -0
- package/dist/ts-extras-mcp.d.ts +290 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/eslint.config.js +15 -0
- package/etc/ts-extras-mcp.api.md +104 -0
- package/lib/index.d.ts +28 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +65 -0
- package/lib/index.js.map +1 -0
- package/lib/packlets/mcp/adapter.d.ts +33 -0
- package/lib/packlets/mcp/adapter.d.ts.map +1 -0
- package/lib/packlets/mcp/adapter.js +125 -0
- package/lib/packlets/mcp/adapter.js.map +1 -0
- package/lib/packlets/mcp/index.d.ts +10 -0
- package/lib/packlets/mcp/index.d.ts.map +1 -0
- package/lib/packlets/mcp/index.js +57 -0
- package/lib/packlets/mcp/index.js.map +1 -0
- package/lib/packlets/mcp/model.d.ts +155 -0
- package/lib/packlets/mcp/model.d.ts.map +1 -0
- package/lib/packlets/mcp/model.js +24 -0
- package/lib/packlets/mcp/model.js.map +1 -0
- package/lib/packlets/mcp/operations.d.ts +36 -0
- package/lib/packlets/mcp/operations.d.ts.map +1 -0
- package/lib/packlets/mcp/operations.js +123 -0
- package/lib/packlets/mcp/operations.js.map +1 -0
- package/lib/packlets/mcp/sdk.d.ts +91 -0
- package/lib/packlets/mcp/sdk.d.ts.map +1 -0
- package/lib/packlets/mcp/sdk.js +72 -0
- package/lib/packlets/mcp/sdk.js.map +1 -0
- package/lib/packlets/mcp/session.d.ts +43 -0
- package/lib/packlets/mcp/session.d.ts.map +1 -0
- package/lib/packlets/mcp/session.js +115 -0
- package/lib/packlets/mcp/session.js.map +1 -0
- package/lib/packlets/mcp/transports.d.ts +49 -0
- package/lib/packlets/mcp/transports.d.ts.map +1 -0
- package/lib/packlets/mcp/transports.js +102 -0
- package/lib/packlets/mcp/transports.js.map +1 -0
- package/lib/test/unit/endToEnd.test.d.ts +13 -0
- package/lib/test/unit/endToEnd.test.d.ts.map +1 -0
- package/lib/test/unit/endToEnd.test.js +222 -0
- package/lib/test/unit/endToEnd.test.js.map +1 -0
- package/lib/test/unit/index.test.d.ts +2 -0
- package/lib/test/unit/index.test.d.ts.map +1 -0
- package/lib/test/unit/index.test.js +76 -0
- package/lib/test/unit/index.test.js.map +1 -0
- package/lib/test/unit/mcp.test.d.ts +2 -0
- package/lib/test/unit/mcp.test.d.ts.map +1 -0
- package/lib/test/unit/mcp.test.js +416 -0
- package/lib/test/unit/mcp.test.js.map +1 -0
- package/lib/test/unit/sdk.test.d.ts +2 -0
- package/lib/test/unit/sdk.test.d.ts.map +1 -0
- package/lib/test/unit/sdk.test.js +70 -0
- package/lib/test/unit/sdk.test.js.map +1 -0
- package/package.json +86 -0
- package/rush-logs/ts-extras-mcp.build.cache.log +3 -0
- package/rush-logs/ts-extras-mcp.build.log +9 -0
- package/src/index.ts +50 -0
- package/src/packlets/mcp/adapter.ts +152 -0
- package/src/packlets/mcp/index.ts +34 -0
- package/src/packlets/mcp/model.ts +204 -0
- package/src/packlets/mcp/operations.ts +138 -0
- package/src/packlets/mcp/sdk.ts +148 -0
- package/src/packlets/mcp/session.ts +137 -0
- package/src/packlets/mcp/transports.ts +111 -0
- package/src/test/unit/endToEnd.test.ts +254 -0
- package/src/test/unit/index.test.ts +43 -0
- package/src/test/unit/mcp.test.ts +473 -0
- package/src/test/unit/sdk.test.ts +73 -0
- package/temp/build/lint/_eslint-5eVG3S6w.json +54 -0
- package/temp/build/typescript/ts_8nwakTlr.json +1 -0
- package/temp/ts-extras-mcp.api.json +1662 -0
- package/temp/ts-extras-mcp.api.md +104 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,222 @@
|
|
|
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
|
+
/**
|
|
25
|
+
* End-to-end verification against a REAL in-memory MCP server (the SDK's `Server` +
|
|
26
|
+
* `InMemoryTransport.createLinkedPair()`) — NOT a mocked SDK. This is the durable proof that the
|
|
27
|
+
* brief's two load-bearing constraints actually flow through the full stack: the real SDK `Client`,
|
|
28
|
+
* the real initialize handshake, the real `listTools`/`callTool` round-trips, the `sdk.ts`
|
|
29
|
+
* projection casts, and `adaptMcpTools`' `JsonSchema.fromJson` gate. It is the class of gap that
|
|
30
|
+
* 100%-line-coverage of a mocked seam cannot catch (e.g. a projection-shape mismatch in `sdk.ts`).
|
|
31
|
+
*
|
|
32
|
+
* The fixture server advertises a MIX of tools: two adaptable, and one for each JSON Schema feature
|
|
33
|
+
* `JsonSchema.fromJson` rejects ($ref, oneOf, anyOf, pattern, union `type[]`).
|
|
34
|
+
*/
|
|
35
|
+
require("@fgv/ts-utils-jest");
|
|
36
|
+
const ts_utils_1 = require("@fgv/ts-utils");
|
|
37
|
+
// Real MCP SDK server side — exercised over an in-process linked transport pair. Subpath imports
|
|
38
|
+
// resolve via the tsconfig `paths` mapping (the same seam the package's sdk.ts uses).
|
|
39
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
40
|
+
const inMemory_js_1 = require("@modelcontextprotocol/sdk/inMemory.js");
|
|
41
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
42
|
+
const mcp_1 = require("../../packlets/mcp");
|
|
43
|
+
// Internal: the opaque-handle class. The package exposes only stdio/http transport factories, so a
|
|
44
|
+
// real in-process e2e (which must inject the SDK's InMemoryTransport) wraps it here. Tests may
|
|
45
|
+
// import internal modules directly (TESTING_GUIDELINES § Testing Internal Code).
|
|
46
|
+
// eslint-disable-next-line @rushstack/packlets/mechanics
|
|
47
|
+
const transports_1 = require("../../packlets/mcp/transports");
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Fixture server — a real SDK Server advertising a mix of adaptable + un-adaptable tools.
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
/** The two adaptable tools (in the supported JSON Schema subset). */
|
|
52
|
+
const ADAPTABLE_TOOLS = [
|
|
53
|
+
{
|
|
54
|
+
name: 'echo',
|
|
55
|
+
description: 'Echoes the supplied message back.',
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: { msg: { type: 'string', description: 'message to echo' } },
|
|
59
|
+
required: ['msg']
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'ping',
|
|
64
|
+
description: 'Health check with no required args.',
|
|
65
|
+
inputSchema: { type: 'object', properties: {} }
|
|
66
|
+
}
|
|
67
|
+
];
|
|
68
|
+
/**
|
|
69
|
+
* One tool per JSON Schema feature `JsonSchema.fromJson` rejects. Each carries the forbidden
|
|
70
|
+
* keyword NESTED inside a property (the real-world shape: the top-level inputSchema is a valid
|
|
71
|
+
* `type: 'object'`, so the MCP SDK's own result validation lets it through, and the rejection
|
|
72
|
+
* happens in `adaptMcpTools` exactly as a real server would surface it).
|
|
73
|
+
*/
|
|
74
|
+
const UNADAPTABLE_TOOLS = [
|
|
75
|
+
{
|
|
76
|
+
name: 'ref_tool',
|
|
77
|
+
inputSchema: { type: 'object', properties: { x: { $ref: '#/$defs/Foo' } } }
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'oneof_tool',
|
|
81
|
+
inputSchema: { type: 'object', properties: { x: { oneOf: [{ type: 'string' }, { type: 'number' }] } } }
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'anyof_tool',
|
|
85
|
+
inputSchema: { type: 'object', properties: { x: { anyOf: [{ type: 'string' }, { type: 'number' }] } } }
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'pattern_tool',
|
|
89
|
+
inputSchema: { type: 'object', properties: { code: { type: 'string', pattern: '^[A-Z]+$' } } }
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'union_tool',
|
|
93
|
+
inputSchema: { type: 'object', properties: { x: { type: ['string', 'null'] } } }
|
|
94
|
+
}
|
|
95
|
+
];
|
|
96
|
+
const ALL_TOOLS = [...ADAPTABLE_TOOLS, ...UNADAPTABLE_TOOLS];
|
|
97
|
+
/** Builds and connects a real in-memory MCP server; returns its server-side handle for teardown. */
|
|
98
|
+
async function startFixtureServer() {
|
|
99
|
+
const server = new index_js_1.Server({ name: 'fixture-mcp-server', version: '0.0.1' }, { capabilities: { tools: {} } });
|
|
100
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: ALL_TOOLS }));
|
|
101
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
102
|
+
const { name, arguments: args } = request.params;
|
|
103
|
+
if (name === 'echo') {
|
|
104
|
+
return { content: [{ type: 'text', text: `echo: ${JSON.stringify(args)}` }] };
|
|
105
|
+
}
|
|
106
|
+
if (name === 'ping') {
|
|
107
|
+
return { content: [{ type: 'text', text: 'pong' }] };
|
|
108
|
+
}
|
|
109
|
+
return { content: [{ type: 'text', text: `unknown tool: ${name}` }], isError: true };
|
|
110
|
+
});
|
|
111
|
+
const [clientTransport, serverTransport] = inMemory_js_1.InMemoryTransport.createLinkedPair();
|
|
112
|
+
await server.connect(serverTransport);
|
|
113
|
+
return { server, clientTransport };
|
|
114
|
+
}
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// End-to-end suite
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
describe('@fgv/ts-extras-mcp end-to-end against a real in-memory MCP server', () => {
|
|
119
|
+
let server;
|
|
120
|
+
let session;
|
|
121
|
+
beforeEach(async () => {
|
|
122
|
+
const fixture = await startFixtureServer();
|
|
123
|
+
server = fixture.server;
|
|
124
|
+
// Wrap the SDK in-memory client transport in the package's opaque handle, then connect through
|
|
125
|
+
// the PUBLIC connectMcpSession (real Client + real initialize handshake). On a connect failure,
|
|
126
|
+
// tear the fixture server down before throwing so a failed handshake can't leak open handles.
|
|
127
|
+
const transport = new transports_1.McpTransport('http', fixture.clientTransport);
|
|
128
|
+
const connectResult = await (0, mcp_1.connectMcpSession)({ transport, clientName: 'e2e', clientVersion: '0.0.0' });
|
|
129
|
+
if (connectResult.isFailure()) {
|
|
130
|
+
await server.close();
|
|
131
|
+
throw new Error(`fixture connect failed: ${connectResult.message}`);
|
|
132
|
+
}
|
|
133
|
+
session = connectResult.value;
|
|
134
|
+
});
|
|
135
|
+
afterEach(async () => {
|
|
136
|
+
// Assert teardown succeeds — a regression in close/transport would otherwise pass silently.
|
|
137
|
+
// `server.close()` runs in `finally` so the fixture is always torn down even if the assertion
|
|
138
|
+
// throws (otherwise a failed close would leak handles and make later tests flaky).
|
|
139
|
+
try {
|
|
140
|
+
expect(await (0, mcp_1.closeMcpSession)(session)).toSucceedWith(true);
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
await server.close();
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
test('the initialize handshake reports the real server identity', () => {
|
|
147
|
+
expect(session.serverInfo).toEqual({ name: 'fixture-mcp-server', version: '0.0.1' });
|
|
148
|
+
});
|
|
149
|
+
test('listMcpTools returns the full catalog over the real transport', async () => {
|
|
150
|
+
expect(await (0, mcp_1.listMcpTools)(session)).toSucceedAndSatisfy((tools) => {
|
|
151
|
+
expect(tools.map((t) => t.name).sort()).toEqual(['anyof_tool', 'echo', 'oneof_tool', 'pattern_tool', 'ping', 'ref_tool', 'union_tool'].sort());
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
test('callMcpTool round-trips a real tool result, and maps isError to Failure', async () => {
|
|
155
|
+
expect(await (0, mcp_1.callMcpTool)(session, 'echo', { msg: 'hi' })).toSucceedWith({
|
|
156
|
+
content: 'echo: {"msg":"hi"}'
|
|
157
|
+
});
|
|
158
|
+
expect(await (0, mcp_1.callMcpTool)(session, 'nope', {})).toFailWith(/unknown tool: nope/);
|
|
159
|
+
});
|
|
160
|
+
// =========================================================================
|
|
161
|
+
// CONSTRAINT 1 — graceful degradation with NOISY, schema-bearing warnings.
|
|
162
|
+
// =========================================================================
|
|
163
|
+
describe('Constraint 1 — adaptMcpTools graceful + NOISY degradation', () => {
|
|
164
|
+
test('excludes EVERY un-adaptable tool from `tools`, surfaces each on `skipped`, warns with the raw schema', async () => {
|
|
165
|
+
const logger = new ts_utils_1.Logging.InMemoryLogger('all');
|
|
166
|
+
const expectedReasonFragment = {
|
|
167
|
+
ref_tool: /\$ref/,
|
|
168
|
+
oneof_tool: /oneOf/,
|
|
169
|
+
anyof_tool: /anyOf/,
|
|
170
|
+
pattern_tool: /pattern/,
|
|
171
|
+
union_tool: /union 'type'/
|
|
172
|
+
};
|
|
173
|
+
expect(await (0, mcp_1.adaptMcpTools)(session, { logger })).toSucceedAndSatisfy((result) => {
|
|
174
|
+
// (a) Only the adaptable tools are offered to the model; none of the rejected ones leak.
|
|
175
|
+
expect(result.tools.map((t) => t.config.name).sort()).toEqual(['echo', 'ping']);
|
|
176
|
+
const offeredNames = new Set(result.tools.map((t) => t.config.name));
|
|
177
|
+
for (const t of UNADAPTABLE_TOOLS) {
|
|
178
|
+
expect(offeredNames.has(t.name)).toBe(false);
|
|
179
|
+
}
|
|
180
|
+
// (b) Each un-adaptable tool is surfaced structurally with name + JSON-pointer reason + the
|
|
181
|
+
// RAW failing schema (verbatim, so the subset can be widened later).
|
|
182
|
+
expect(result.skipped).toHaveLength(UNADAPTABLE_TOOLS.length);
|
|
183
|
+
const skippedByName = new Map(result.skipped.map((s) => [s.name, s]));
|
|
184
|
+
for (const tool of UNADAPTABLE_TOOLS) {
|
|
185
|
+
const skipped = skippedByName.get(tool.name);
|
|
186
|
+
expect(skipped).toBeDefined();
|
|
187
|
+
// JSON-pointer path into the offending node.
|
|
188
|
+
expect(skipped === null || skipped === void 0 ? void 0 : skipped.reason).toMatch(/#\/properties\//);
|
|
189
|
+
expect(skipped === null || skipped === void 0 ? void 0 : skipped.reason).toMatch(expectedReasonFragment[tool.name]);
|
|
190
|
+
// The RAW failing schema, verbatim.
|
|
191
|
+
expect(skipped === null || skipped === void 0 ? void 0 : skipped.schema).toEqual(tool.inputSchema);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
// (c) A NOISY warning per skip that INCLUDES the raw failing schema text + the reason. The
|
|
195
|
+
// schema-in-the-warning is the whole "so we can extend fromJson later" requirement — a
|
|
196
|
+
// warn WITHOUT the schema would fail this assertion.
|
|
197
|
+
const warned = logger.logged.join('\n');
|
|
198
|
+
for (const tool of UNADAPTABLE_TOOLS) {
|
|
199
|
+
expect(warned).toContain(`skipping tool '${tool.name}'`);
|
|
200
|
+
// The raw schema, serialized exactly as adaptMcpTools emits it, must appear in the warn.
|
|
201
|
+
expect(warned).toContain(JSON.stringify(tool.inputSchema));
|
|
202
|
+
}
|
|
203
|
+
// And the reason fragments are present in the warnings too.
|
|
204
|
+
expect(warned).toMatch(/\$ref/);
|
|
205
|
+
expect(warned).toMatch(/union 'type'/);
|
|
206
|
+
});
|
|
207
|
+
test('an adapted tool executes end-to-end against the real server', async () => {
|
|
208
|
+
// Result-matcher-driven: extract the adapted `echo` tool inside the success satisfier (where
|
|
209
|
+
// its presence is asserted), then drive its execute callback — which round-trips through
|
|
210
|
+
// callMcpTool to the real server — with another matcher.
|
|
211
|
+
let echoExecute;
|
|
212
|
+
expect(await (0, mcp_1.adaptMcpTools)(session)).toSucceedAndSatisfy((result) => {
|
|
213
|
+
const echo = result.tools.find((t) => t.config.name === 'echo');
|
|
214
|
+
expect(echo).toBeDefined();
|
|
215
|
+
echoExecute = echo === null || echo === void 0 ? void 0 : echo.execute;
|
|
216
|
+
});
|
|
217
|
+
expect(echoExecute).toBeDefined();
|
|
218
|
+
expect(await (echoExecute === null || echoExecute === void 0 ? void 0 : echoExecute({ msg: 'roundtrip' }))).toSucceedWith('echo: {"msg":"roundtrip"}');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
//# sourceMappingURL=endToEnd.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endToEnd.test.js","sourceRoot":"","sources":["../../../src/test/unit/endToEnd.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/**\n * End-to-end verification against a REAL in-memory MCP server (the SDK's `Server` +\n * `InMemoryTransport.createLinkedPair()`) — NOT a mocked SDK. This is the durable proof that the\n * brief's two load-bearing constraints actually flow through the full stack: the real SDK `Client`,\n * the real initialize handshake, the real `listTools`/`callTool` round-trips, the `sdk.ts`\n * projection casts, and `adaptMcpTools`' `JsonSchema.fromJson` gate. It is the class of gap that\n * 100%-line-coverage of a mocked seam cannot catch (e.g. a projection-shape mismatch in `sdk.ts`).\n *\n * The fixture server advertises a MIX of tools: two adaptable, and one for each JSON Schema feature\n * `JsonSchema.fromJson` rejects ($ref, oneOf, anyOf, pattern, union `type[]`).\n */\n\nimport '@fgv/ts-utils-jest';\nimport { Logging, type Result } from '@fgv/ts-utils';\n\n// Real MCP SDK server side — exercised over an in-process linked transport pair. Subpath imports\n// resolve via the tsconfig `paths` mapping (the same seam the package's sdk.ts uses).\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\n\nimport {\n type IMcpSession,\n adaptMcpTools,\n callMcpTool,\n closeMcpSession,\n connectMcpSession,\n listMcpTools\n} from '../../packlets/mcp';\n// Internal: the opaque-handle class. The package exposes only stdio/http transport factories, so a\n// real in-process e2e (which must inject the SDK's InMemoryTransport) wraps it here. Tests may\n// import internal modules directly (TESTING_GUIDELINES § Testing Internal Code).\n// eslint-disable-next-line @rushstack/packlets/mechanics\nimport { McpTransport } from '../../packlets/mcp/transports';\n\n// ---------------------------------------------------------------------------\n// Fixture server — a real SDK Server advertising a mix of adaptable + un-adaptable tools.\n// ---------------------------------------------------------------------------\n\n/** The two adaptable tools (in the supported JSON Schema subset). */\nconst ADAPTABLE_TOOLS = [\n {\n name: 'echo',\n description: 'Echoes the supplied message back.',\n inputSchema: {\n type: 'object',\n properties: { msg: { type: 'string', description: 'message to echo' } },\n required: ['msg']\n }\n },\n {\n name: 'ping',\n description: 'Health check with no required args.',\n inputSchema: { type: 'object', properties: {} }\n }\n];\n\n/**\n * One tool per JSON Schema feature `JsonSchema.fromJson` rejects. Each carries the forbidden\n * keyword NESTED inside a property (the real-world shape: the top-level inputSchema is a valid\n * `type: 'object'`, so the MCP SDK's own result validation lets it through, and the rejection\n * happens in `adaptMcpTools` exactly as a real server would surface it).\n */\nconst UNADAPTABLE_TOOLS = [\n {\n name: 'ref_tool',\n inputSchema: { type: 'object', properties: { x: { $ref: '#/$defs/Foo' } } }\n },\n {\n name: 'oneof_tool',\n inputSchema: { type: 'object', properties: { x: { oneOf: [{ type: 'string' }, { type: 'number' }] } } }\n },\n {\n name: 'anyof_tool',\n inputSchema: { type: 'object', properties: { x: { anyOf: [{ type: 'string' }, { type: 'number' }] } } }\n },\n {\n name: 'pattern_tool',\n inputSchema: { type: 'object', properties: { code: { type: 'string', pattern: '^[A-Z]+$' } } }\n },\n {\n name: 'union_tool',\n inputSchema: { type: 'object', properties: { x: { type: ['string', 'null'] } } }\n }\n];\n\nconst ALL_TOOLS = [...ADAPTABLE_TOOLS, ...UNADAPTABLE_TOOLS];\n\n/** Builds and connects a real in-memory MCP server; returns its server-side handle for teardown. */\nasync function startFixtureServer(): Promise<{ server: Server; clientTransport: InMemoryTransport }> {\n const server = new Server(\n { name: 'fixture-mcp-server', version: '0.0.1' },\n { capabilities: { tools: {} } }\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: ALL_TOOLS }));\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n if (name === 'echo') {\n return { content: [{ type: 'text', text: `echo: ${JSON.stringify(args)}` }] };\n }\n if (name === 'ping') {\n return { content: [{ type: 'text', text: 'pong' }] };\n }\n return { content: [{ type: 'text', text: `unknown tool: ${name}` }], isError: true };\n });\n\n const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();\n await server.connect(serverTransport);\n return { server, clientTransport };\n}\n\n// ---------------------------------------------------------------------------\n// End-to-end suite\n// ---------------------------------------------------------------------------\n\ndescribe('@fgv/ts-extras-mcp end-to-end against a real in-memory MCP server', () => {\n let server: Server;\n let session: IMcpSession;\n\n beforeEach(async () => {\n const fixture = await startFixtureServer();\n server = fixture.server;\n // Wrap the SDK in-memory client transport in the package's opaque handle, then connect through\n // the PUBLIC connectMcpSession (real Client + real initialize handshake). On a connect failure,\n // tear the fixture server down before throwing so a failed handshake can't leak open handles.\n const transport = new McpTransport('http', fixture.clientTransport);\n const connectResult = await connectMcpSession({ transport, clientName: 'e2e', clientVersion: '0.0.0' });\n if (connectResult.isFailure()) {\n await server.close();\n throw new Error(`fixture connect failed: ${connectResult.message}`);\n }\n session = connectResult.value;\n });\n\n afterEach(async () => {\n // Assert teardown succeeds — a regression in close/transport would otherwise pass silently.\n // `server.close()` runs in `finally` so the fixture is always torn down even if the assertion\n // throws (otherwise a failed close would leak handles and make later tests flaky).\n try {\n expect(await closeMcpSession(session)).toSucceedWith(true);\n } finally {\n await server.close();\n }\n });\n\n test('the initialize handshake reports the real server identity', () => {\n expect(session.serverInfo).toEqual({ name: 'fixture-mcp-server', version: '0.0.1' });\n });\n\n test('listMcpTools returns the full catalog over the real transport', async () => {\n expect(await listMcpTools(session)).toSucceedAndSatisfy((tools) => {\n expect(tools.map((t) => t.name).sort()).toEqual(\n ['anyof_tool', 'echo', 'oneof_tool', 'pattern_tool', 'ping', 'ref_tool', 'union_tool'].sort()\n );\n });\n });\n\n test('callMcpTool round-trips a real tool result, and maps isError to Failure', async () => {\n expect(await callMcpTool(session, 'echo', { msg: 'hi' })).toSucceedWith({\n content: 'echo: {\"msg\":\"hi\"}'\n });\n expect(await callMcpTool(session, 'nope', {})).toFailWith(/unknown tool: nope/);\n });\n\n // =========================================================================\n // CONSTRAINT 1 — graceful degradation with NOISY, schema-bearing warnings.\n // =========================================================================\n describe('Constraint 1 — adaptMcpTools graceful + NOISY degradation', () => {\n test('excludes EVERY un-adaptable tool from `tools`, surfaces each on `skipped`, warns with the raw schema', async () => {\n const logger = new Logging.InMemoryLogger('all');\n const expectedReasonFragment: Record<string, RegExp> = {\n ref_tool: /\\$ref/,\n oneof_tool: /oneOf/,\n anyof_tool: /anyOf/,\n pattern_tool: /pattern/,\n union_tool: /union 'type'/\n };\n\n expect(await adaptMcpTools(session, { logger })).toSucceedAndSatisfy((result) => {\n // (a) Only the adaptable tools are offered to the model; none of the rejected ones leak.\n expect(result.tools.map((t) => t.config.name).sort()).toEqual(['echo', 'ping']);\n const offeredNames = new Set(result.tools.map((t) => t.config.name));\n for (const t of UNADAPTABLE_TOOLS) {\n expect(offeredNames.has(t.name)).toBe(false);\n }\n\n // (b) Each un-adaptable tool is surfaced structurally with name + JSON-pointer reason + the\n // RAW failing schema (verbatim, so the subset can be widened later).\n expect(result.skipped).toHaveLength(UNADAPTABLE_TOOLS.length);\n const skippedByName = new Map(result.skipped.map((s) => [s.name, s]));\n for (const tool of UNADAPTABLE_TOOLS) {\n const skipped = skippedByName.get(tool.name);\n expect(skipped).toBeDefined();\n // JSON-pointer path into the offending node.\n expect(skipped?.reason).toMatch(/#\\/properties\\//);\n expect(skipped?.reason).toMatch(expectedReasonFragment[tool.name]);\n // The RAW failing schema, verbatim.\n expect(skipped?.schema).toEqual(tool.inputSchema);\n }\n });\n\n // (c) A NOISY warning per skip that INCLUDES the raw failing schema text + the reason. The\n // schema-in-the-warning is the whole \"so we can extend fromJson later\" requirement — a\n // warn WITHOUT the schema would fail this assertion.\n const warned = logger.logged.join('\\n');\n for (const tool of UNADAPTABLE_TOOLS) {\n expect(warned).toContain(`skipping tool '${tool.name}'`);\n // The raw schema, serialized exactly as adaptMcpTools emits it, must appear in the warn.\n expect(warned).toContain(JSON.stringify(tool.inputSchema));\n }\n // And the reason fragments are present in the warnings too.\n expect(warned).toMatch(/\\$ref/);\n expect(warned).toMatch(/union 'type'/);\n });\n\n test('an adapted tool executes end-to-end against the real server', async () => {\n // Result-matcher-driven: extract the adapted `echo` tool inside the success satisfier (where\n // its presence is asserted), then drive its execute callback — which round-trips through\n // callMcpTool to the real server — with another matcher.\n let echoExecute: ((args: unknown) => Promise<Result<unknown>>) | undefined;\n expect(await adaptMcpTools(session)).toSucceedAndSatisfy((result) => {\n const echo = result.tools.find((t) => t.config.name === 'echo');\n expect(echo).toBeDefined();\n echoExecute = echo?.execute;\n });\n expect(echoExecute).toBeDefined();\n expect(await echoExecute?.({ msg: 'roundtrip' })).toSucceedWith('echo: {\"msg\":\"roundtrip\"}');\n });\n });\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../src/test/unit/index.test.ts"],"names":[],"mappings":"AAsBA,OAAO,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
35
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
36
|
+
}) : function(o, v) {
|
|
37
|
+
o["default"] = v;
|
|
38
|
+
});
|
|
39
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
40
|
+
var ownKeys = function(o) {
|
|
41
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
42
|
+
var ar = [];
|
|
43
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
44
|
+
return ar;
|
|
45
|
+
};
|
|
46
|
+
return ownKeys(o);
|
|
47
|
+
};
|
|
48
|
+
return function (mod) {
|
|
49
|
+
if (mod && mod.__esModule) return mod;
|
|
50
|
+
var result = {};
|
|
51
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
52
|
+
__setModuleDefault(result, mod);
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
})();
|
|
56
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
|
+
require("@fgv/ts-utils-jest");
|
|
58
|
+
const mcp = __importStar(require("../../index"));
|
|
59
|
+
describe('@fgv/ts-extras-mcp package barrel', () => {
|
|
60
|
+
test('re-exports the public boundary primitives', () => {
|
|
61
|
+
expect(typeof mcp.createStdioTransport).toBe('function');
|
|
62
|
+
expect(typeof mcp.createHttpTransport).toBe('function');
|
|
63
|
+
expect(typeof mcp.connectMcpSession).toBe('function');
|
|
64
|
+
expect(typeof mcp.closeMcpSession).toBe('function');
|
|
65
|
+
expect(typeof mcp.listMcpTools).toBe('function');
|
|
66
|
+
expect(typeof mcp.callMcpTool).toBe('function');
|
|
67
|
+
expect(typeof mcp.adaptMcpTools).toBe('function');
|
|
68
|
+
});
|
|
69
|
+
test('the re-exported transport factories are the real (behaving) implementations', () => {
|
|
70
|
+
// Behavioral signal beyond `typeof`: a renamed-but-still-a-function export would not behave
|
|
71
|
+
// this way. (The synchronous transport factories validate without touching the SDK/network.)
|
|
72
|
+
expect(mcp.createStdioTransport({ command: '' })).toFailWith(/command must be a non-empty string/);
|
|
73
|
+
expect(mcp.createHttpTransport({ url: 'not a url' })).toFailWith(/invalid url/);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
//# sourceMappingURL=index.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../../src/test/unit/index.test.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,8BAA4B;AAC5B,iDAAmC;AAEnC,QAAQ,CAAC,mCAAmC,EAAE,GAAG,EAAE;IACjD,IAAI,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACrD,MAAM,CAAC,OAAO,GAAG,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACzD,MAAM,CAAC,OAAO,GAAG,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACxD,MAAM,CAAC,OAAO,GAAG,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACtD,MAAM,CAAC,OAAO,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpD,MAAM,CAAC,OAAO,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACjD,MAAM,CAAC,OAAO,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAChD,MAAM,CAAC,OAAO,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,6EAA6E,EAAE,GAAG,EAAE;QACvF,4FAA4F;QAC5F,6FAA6F;QAC7F,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,oCAAoC,CAAC,CAAC;QACnG,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,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\nimport '@fgv/ts-utils-jest';\nimport * as mcp from '../../index';\n\ndescribe('@fgv/ts-extras-mcp package barrel', () => {\n test('re-exports the public boundary primitives', () => {\n expect(typeof mcp.createStdioTransport).toBe('function');\n expect(typeof mcp.createHttpTransport).toBe('function');\n expect(typeof mcp.connectMcpSession).toBe('function');\n expect(typeof mcp.closeMcpSession).toBe('function');\n expect(typeof mcp.listMcpTools).toBe('function');\n expect(typeof mcp.callMcpTool).toBe('function');\n expect(typeof mcp.adaptMcpTools).toBe('function');\n });\n\n test('the re-exported transport factories are the real (behaving) implementations', () => {\n // Behavioral signal beyond `typeof`: a renamed-but-still-a-function export would not behave\n // this way. (The synchronous transport factories validate without touching the SDK/network.)\n expect(mcp.createStdioTransport({ command: '' })).toFailWith(/command must be a non-empty string/);\n expect(mcp.createHttpTransport({ url: 'not a url' })).toFailWith(/invalid url/);\n });\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.test.d.ts","sourceRoot":"","sources":["../../../src/test/unit/mcp.test.ts"],"names":[],"mappings":"AA+BA,OAAO,oBAAoB,CAAC"}
|