@code-yeongyu/senpi 2026.10.25-2 → 2026.10.25-3
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/CHANGELOG.md +20 -0
- package/dist/bundle/chunks/{args-JXULI4EH.js → args-B444OQBK.js} +1 -1
- package/dist/bundle/chunks/{auth-4NUNUSTF.js → auth-C77UQA55.js} +1 -1
- package/dist/bundle/chunks/chunk-6O4SEW2W.js +5 -0
- package/dist/bundle/chunks/{chunk-Y5OTHMXO.js → chunk-7CNOCJCN.js} +1 -1
- package/dist/bundle/chunks/{chunk-JRXKLIEQ.js → chunk-7W7OJDIY.js} +4 -4
- package/dist/bundle/chunks/chunk-AX72VNRS.js +27 -0
- package/dist/bundle/chunks/{chunk-LYGAV6KU.js → chunk-C5UFJ7B3.js} +1 -1
- package/dist/bundle/chunks/{chunk-G2UETS2R.js → chunk-CNL6QSUB.js} +4 -4
- package/dist/bundle/chunks/{chunk-2EFVQWXZ.js → chunk-FFQOEC76.js} +3 -3
- package/dist/bundle/chunks/chunk-H3HNMY2W.js +13 -0
- package/dist/bundle/chunks/{chunk-W2BKZBYJ.js → chunk-HVJI5T75.js} +1 -1
- package/dist/bundle/chunks/{chunk-YN7ELQ43.js → chunk-JA2R2ZSE.js} +1 -1
- package/dist/bundle/chunks/chunk-JPGT64RV.js +2 -0
- package/dist/bundle/chunks/chunk-KXB5WIS5.js +4 -0
- package/dist/bundle/chunks/chunk-L73UYAH5.js +3 -0
- package/dist/bundle/chunks/chunk-LTU6UFEX.js +2 -0
- package/dist/bundle/chunks/{chunk-KUE4AQN6.js → chunk-MJ5NNWX4.js} +1 -1
- package/dist/bundle/chunks/{chunk-WFXUCVIR.js → chunk-O3Q2ET6W.js} +2 -5
- package/dist/bundle/chunks/{chunk-MXZPAUFB.js → chunk-ODTGC6YU.js} +1 -1
- package/dist/bundle/chunks/chunk-P3M2PF6K.js +27 -0
- package/dist/bundle/chunks/{chunk-VNN2V74Y.js → chunk-SWJXRC2I.js} +1 -1
- package/dist/bundle/chunks/{chunk-RXKNOEQQ.js → chunk-UQNRW3VU.js} +1 -1
- package/dist/bundle/chunks/{chunk-U67ML6CQ.js → chunk-WD6WBYC5.js} +1 -1
- package/dist/bundle/chunks/chunk-WJOT3DFL.js +2 -0
- package/dist/bundle/chunks/{chunk-XBUSVKWC.js → chunk-WLHNEUGD.js} +1 -1
- package/dist/bundle/chunks/chunk-YI7SPFDN.js +5 -0
- package/dist/bundle/chunks/{chunk-J2U5VDG7.js → chunk-YRF4I43T.js} +28 -28
- package/dist/bundle/chunks/chunk-Z3UYNSVR.js +3 -0
- package/dist/bundle/chunks/cli-main-HEKFFB5S.js +3 -0
- package/dist/bundle/chunks/{client-KWTLTKVV.js → client-WTYEICLH.js} +3 -3
- package/dist/bundle/chunks/dispatch-NYAULNVW.js +9 -0
- package/dist/bundle/chunks/{help-fast-path-ZHZGXAUG.js → help-fast-path-3Z26DQSV.js} +1 -1
- package/dist/bundle/chunks/host-command-Z3WPHJOW.js +2 -0
- package/dist/bundle/chunks/host-lifecycle-ONQIMWMX.js +3 -0
- package/dist/bundle/chunks/host-lifecycle.js +7 -5
- package/dist/bundle/chunks/host-session-command-J65ENSV5.js +20 -0
- package/dist/bundle/chunks/{host-socket-request-AS2OCRLP.js → host-socket-request-3CWE4265.js} +1 -1
- package/dist/bundle/chunks/interactive-mode-DEPQC6TX.js +2 -0
- package/dist/bundle/chunks/mcp-presenter-bootstrap-XUSCQVRC.js +2 -0
- package/dist/bundle/chunks/{multi-session-host-VTTY5MFT.js → multi-session-host-YI2WZOET.js} +12 -9
- package/dist/bundle/chunks/{package-manager-cli-DZFOC3MF.js → package-manager-cli-GHZPJMRF.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-U7YQMC7H.js → rotation-stream-ZPVCFOVD.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-ORHBGBBV.js +2 -0
- package/dist/bundle/chunks/{rpc-monitor-source-B5NQ4ZVH.js → rpc-monitor-source-FFEQS744.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-SLISTG4V.js → schedule-command-YWG6D6ZF.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-36HAQSQO.js +3 -0
- package/dist/bundle/chunks/{session-picker-465FVXQG.js → session-picker-25H5KNYP.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +278 -264
- package/dist/bundle/chunks/{stdio-IEC635SY.js → stdio-YXDKKKQZ.js} +1 -1
- package/dist/bundle/chunks/{streamableHttp-KJI5KE4K.js → streamableHttp-DKIBUWVY.js} +1 -1
- package/dist/bundle/chunks/{types-LLNV23LW.js → types-BJUEQ4II.js} +1 -1
- package/dist/bundle/chunks/zod-compat-RJDXCU7S.js +2 -0
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +7 -5
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.js +9 -0
- package/dist/cli/host-command-args.d.ts +1 -0
- package/dist/cli/host-command-args.js +7 -1
- package/dist/cli/host-command.js +3 -1
- package/dist/core/agent-session.js +22 -7
- package/dist/core/extensions/builtin/goal/index.js +1 -0
- package/dist/core/extensions/builtin/goal/lifecycle-facts.d.ts +3 -0
- package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/catalog.js +2 -0
- package/dist/core/extensions/builtin/mcp/connection.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/connection.js +46 -1
- package/dist/core/extensions/builtin/mcp/elicitation.d.ts +6 -1
- package/dist/core/extensions/builtin/mcp/elicitation.js +56 -2
- package/dist/core/extensions/builtin/mcp/expose/call.js +6 -2
- package/dist/core/extensions/builtin/mcp/expose/session.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/expose/session.js +7 -0
- package/dist/core/extensions/builtin/mcp/health.js +5 -0
- package/dist/core/extensions/builtin/mcp/host-registry.js +7 -1
- package/dist/core/extensions/builtin/mcp/index.js +2 -0
- package/dist/core/extensions/builtin/mcp/modern/client.d.ts +5 -0
- package/dist/core/extensions/builtin/mcp/modern/client.js +15 -0
- package/dist/core/extensions/builtin/mcp/modern/consent-queue.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/modern/consent-queue.js +59 -0
- package/dist/core/extensions/builtin/mcp/modern/diagnostics.d.ts +4 -0
- package/dist/core/extensions/builtin/mcp/modern/diagnostics.js +39 -0
- package/dist/core/extensions/builtin/mcp/modern/dispatch.js +21 -11
- package/dist/core/extensions/builtin/mcp/modern/elicitation.d.ts +14 -0
- package/dist/core/extensions/builtin/mcp/modern/elicitation.js +145 -0
- package/dist/core/extensions/builtin/mcp/modern/errors.d.ts +21 -0
- package/dist/core/extensions/builtin/mcp/modern/errors.js +23 -0
- package/dist/core/extensions/builtin/mcp/modern/form-schema.d.ts +20 -0
- package/dist/core/extensions/builtin/mcp/modern/form-schema.js +153 -0
- package/dist/core/extensions/builtin/mcp/modern/http-fetch.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/modern/http-fetch.js +8 -2
- package/dist/core/extensions/builtin/mcp/modern/inputs.d.ts +11 -0
- package/dist/core/extensions/builtin/mcp/modern/inputs.js +65 -0
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +11 -0
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +109 -0
- package/dist/core/extensions/builtin/mcp/modern/request.d.ts +20 -0
- package/dist/core/extensions/builtin/mcp/modern/request.js +292 -0
- package/dist/core/extensions/builtin/mcp/modern/stdio-probe.d.ts +1 -1
- package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +54 -6
- package/dist/core/extensions/builtin/mcp/modern/transport.js +80 -14
- package/dist/core/extensions/builtin/mcp/modern/wire.js +11 -7
- package/dist/core/extensions/builtin/mcp/prompts.d.ts +5 -1
- package/dist/core/extensions/builtin/mcp/prompts.js +6 -1
- package/dist/core/extensions/builtin/mcp/request-origin.d.ts +47 -0
- package/dist/core/extensions/builtin/mcp/request-origin.js +59 -0
- package/dist/core/extensions/builtin/mcp/resources.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/resources.js +19 -4
- package/dist/core/extensions/builtin/mcp/sdk.lazy.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/sdk.lazy.js +3 -1
- package/dist/core/extensions/builtin/mcp/service-register.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/service-register.js +3 -0
- package/dist/core/extensions/builtin/mcp/service-types.d.ts +2 -1
- package/dist/core/extensions/builtin/mcp/service.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/service.js +79 -4
- package/dist/core/extensions/builtin/mcp/shared-connection.js +36 -2
- package/dist/core/extensions/builtin/mcp/shared-lease.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/shared-lease.js +9 -0
- package/dist/core/extensions/builtin/mcp/transport-sdk.d.ts +3 -2
- package/dist/core/extensions/builtin/mcp/transport-sdk.js +56 -25
- package/dist/core/extensions/builtin/mcp/wrap.d.ts +7 -0
- package/dist/core/extensions/builtin/mcp/wrap.js +6 -0
- package/dist/core/extensions/runner.js +3 -2
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/lifecycle-ledger.d.ts +1 -0
- package/dist/core/lifecycle-ledger.js +7 -2
- package/dist/core/lifecycle-record.js +3 -0
- package/dist/core/lifecycle-session-binding.d.ts +11 -1
- package/dist/core/lifecycle-session-binding.js +29 -2
- package/dist/core/lifecycle-stop.d.ts +41 -0
- package/dist/core/lifecycle-stop.js +92 -0
- package/dist/core/mcp-form-types.d.ts +89 -0
- package/dist/core/mcp-form-types.js +23 -0
- package/dist/core/mcp-mrtr-presentations.d.ts +15 -0
- package/dist/core/mcp-mrtr-presentations.js +17 -0
- package/dist/core/session-lifecycle-types.d.ts +15 -0
- package/dist/core/session-lifecycle.d.ts +17 -1
- package/dist/core/session-lifecycle.js +40 -2
- package/dist/main.js +5 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
- package/dist/modes/interactive/components/extension-selector.js +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.js +34 -8
- package/dist/modes/interactive/mcp-local-presenter.d.ts +5 -0
- package/dist/modes/interactive/mcp-local-presenter.js +109 -0
- package/dist/modes/rpc/connection-handler.d.ts +1 -0
- package/dist/modes/rpc/connection-handler.js +7 -1
- package/dist/modes/rpc/host-capabilities.d.ts +1 -0
- package/dist/modes/rpc/host-capabilities.js +2 -1
- package/dist/modes/rpc/host-ensure-start.js +13 -2
- package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
- package/dist/modes/rpc/host-ensure.js +1 -0
- package/dist/modes/rpc/host-handoff.d.ts +2 -0
- package/dist/modes/rpc/host-handover-wire.d.ts +2 -0
- package/dist/modes/rpc/host-handover-wire.js +1 -0
- package/dist/modes/rpc/host-lifecycle-launch.d.ts +1 -0
- package/dist/modes/rpc/host-lifecycle-launch.js +43 -2
- package/dist/modes/rpc/host-runner.d.ts +2 -0
- package/dist/modes/rpc/host-runner.js +8 -6
- package/dist/modes/rpc/host-successor.js +11 -1
- package/dist/modes/rpc/mcp-form-dialog.d.ts +45 -0
- package/dist/modes/rpc/mcp-form-dialog.js +159 -0
- package/dist/modes/rpc/mcp-form-worker.d.ts +14 -0
- package/dist/modes/rpc/mcp-form-worker.js +167 -0
- package/dist/modes/rpc/mcp-presenter-authority.d.ts +53 -0
- package/dist/modes/rpc/mcp-presenter-authority.js +309 -0
- package/dist/modes/rpc/mcp-presenter-bootstrap.d.ts +8 -0
- package/dist/modes/rpc/mcp-presenter-bootstrap.js +111 -0
- package/dist/modes/rpc/mcp-presenter-control.d.ts +24 -0
- package/dist/modes/rpc/mcp-presenter-control.js +330 -0
- package/dist/modes/rpc/multi-session-host.d.ts +3 -0
- package/dist/modes/rpc/multi-session-host.js +36 -1
- package/dist/modes/rpc/session-binding.js +6 -1
- package/dist/modes/rpc/session-event-writer.d.ts +14 -0
- package/dist/modes/rpc/session-event-writer.js +128 -1
- package/dist/modes/rpc/session-router-routing.js +1 -0
- package/dist/modes/rpc/session-worker-client.d.ts +3 -0
- package/dist/modes/rpc/session-worker-client.js +89 -0
- package/dist/modes/rpc/session-worker-protocol.d.ts +41 -0
- package/dist/modes/rpc/session-worker.js +9 -0
- package/docs/extensions.md +5 -0
- package/docs/mcp.md +20 -3
- package/docs/rpc.md +111 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-B6TPQDBW.js +0 -5
- package/dist/bundle/chunks/chunk-CDRFFHLL.js +0 -4
- package/dist/bundle/chunks/chunk-HPCS4HM4.js +0 -27
- package/dist/bundle/chunks/chunk-KJZAY5YH.js +0 -27
- package/dist/bundle/chunks/chunk-OI4NMCKP.js +0 -3
- package/dist/bundle/chunks/chunk-SCVRKIFK.js +0 -2
- package/dist/bundle/chunks/cli-main-SWKFMTZQ.js +0 -3
- package/dist/bundle/chunks/dispatch-DEP4PEDZ.js +0 -10
- package/dist/bundle/chunks/host-command-BC6UWP33.js +0 -2
- package/dist/bundle/chunks/host-lifecycle-TN5NEGNF.js +0 -3
- package/dist/bundle/chunks/host-session-command-MGVBLBRV.js +0 -20
- package/dist/bundle/chunks/interactive-mode-SCSROHD5.js +0 -2
- package/dist/bundle/chunks/rpc-mode-TOXYD4XA.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-HFFMBALG.js +0 -3
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { isRecord, validateMrtrForm } from "./form-schema.js";
|
|
2
|
+
import { McpMrtrError } from "./errors.js";
|
|
3
|
+
const own = (value, key) => Object.hasOwn(value, key);
|
|
4
|
+
const byteSize = (value) => Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
5
|
+
export function parseInputs(result) {
|
|
6
|
+
if (!own(result, "requestState") && !own(result, "inputRequests"))
|
|
7
|
+
throw new McpMrtrError("MCP_MRTR_INVALID_INPUT");
|
|
8
|
+
if (own(result, "requestState") && typeof result.requestState !== "string")
|
|
9
|
+
throw new McpMrtrError("MCP_MRTR_INVALID_INPUT");
|
|
10
|
+
if (byteSize(result) > 512 * 1024 ||
|
|
11
|
+
(typeof result.requestState === "string" && Buffer.byteLength(result.requestState) > 64 * 1024))
|
|
12
|
+
throw new McpMrtrError("MCP_MRTR_INPUT_LIMIT");
|
|
13
|
+
if (!own(result, "inputRequests"))
|
|
14
|
+
return undefined;
|
|
15
|
+
if (!isRecord(result.inputRequests))
|
|
16
|
+
throw new McpMrtrError("MCP_MRTR_INVALID_INPUT");
|
|
17
|
+
const entries = Object.entries(result.inputRequests);
|
|
18
|
+
if (entries.length > 32)
|
|
19
|
+
throw new McpMrtrError("MCP_MRTR_INPUT_LIMIT");
|
|
20
|
+
// Scan the complete batch before collecting anything, including unsupported capability requests.
|
|
21
|
+
for (const [, input] of entries)
|
|
22
|
+
if (isRecord(input) && ["roots/list", "sampling/createMessage"].includes(String(input.method)))
|
|
23
|
+
throw new McpMrtrError("MCP_MRTR_UNSUPPORTED_INPUT");
|
|
24
|
+
const inputs = new Map();
|
|
25
|
+
const checkStrings = (value) => {
|
|
26
|
+
if (typeof value === "string" && Buffer.byteLength(value) > 16 * 1024)
|
|
27
|
+
throw new McpMrtrError("MCP_MRTR_INPUT_LIMIT");
|
|
28
|
+
if (Array.isArray(value))
|
|
29
|
+
for (const entry of value)
|
|
30
|
+
checkStrings(entry);
|
|
31
|
+
else if (isRecord(value))
|
|
32
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
33
|
+
checkStrings(key);
|
|
34
|
+
checkStrings(entry);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
for (const [key, input] of entries) {
|
|
38
|
+
checkStrings(key);
|
|
39
|
+
if (!isRecord(input) ||
|
|
40
|
+
input.method !== "elicitation/create" ||
|
|
41
|
+
!isRecord(input.params) ||
|
|
42
|
+
typeof input.params.message !== "string")
|
|
43
|
+
throw new McpMrtrError("MCP_MRTR_INVALID_INPUT");
|
|
44
|
+
const params = input.params;
|
|
45
|
+
checkStrings(params);
|
|
46
|
+
if (params.mode === "url") {
|
|
47
|
+
if (typeof params.url !== "string" || typeof params.elicitationId !== "string")
|
|
48
|
+
throw new McpMrtrError("MCP_MRTR_INVALID_INPUT");
|
|
49
|
+
throw new McpMrtrError("MCP_MRTR_UNSUPPORTED_INPUT");
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
if (params.mode !== undefined && params.mode !== "form")
|
|
53
|
+
throw new McpMrtrError("MCP_MRTR_INVALID_INPUT");
|
|
54
|
+
if (isRecord(params.requestedSchema) &&
|
|
55
|
+
isRecord(params.requestedSchema.properties) &&
|
|
56
|
+
Object.keys(params.requestedSchema.properties).length > 64)
|
|
57
|
+
throw new McpMrtrError("MCP_MRTR_INPUT_LIMIT");
|
|
58
|
+
if (!validateMrtrForm(params.requestedSchema))
|
|
59
|
+
throw new McpMrtrError("MCP_MRTR_INVALID_INPUT");
|
|
60
|
+
}
|
|
61
|
+
inputs.set(key, input);
|
|
62
|
+
}
|
|
63
|
+
return inputs;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=inputs.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** RFC 9110 field values, preserving whitespace and literal sentinel strings. */
|
|
2
|
+
export declare function encodeMcpHeaderValue(value: string): string;
|
|
3
|
+
/** Configured headers must never supply parameter metadata absent from the request body. */
|
|
4
|
+
export declare function clearMcpParameterHeaders(headers: Headers): void;
|
|
5
|
+
/** Connection-local catalog: never shared with another server or transport generation. */
|
|
6
|
+
export declare class ModernParameterHeaders {
|
|
7
|
+
#private;
|
|
8
|
+
filterTools(tools: readonly unknown[], firstPage: boolean): unknown[];
|
|
9
|
+
append(headers: Headers, name: unknown, args: unknown): void;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=parameter-headers.d.ts.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
/** RFC 9110 field values, preserving whitespace and literal sentinel strings. */
|
|
4
|
+
export function encodeMcpHeaderValue(value) {
|
|
5
|
+
return /^[\x09\x20-\x7e]*$/.test(value) &&
|
|
6
|
+
value.trim() === value &&
|
|
7
|
+
!(value.startsWith("=?base64?") && value.endsWith("?="))
|
|
8
|
+
? value
|
|
9
|
+
: `=?base64?${Buffer.from(value, "utf8").toString("base64")}?=`;
|
|
10
|
+
}
|
|
11
|
+
/** Configured headers must never supply parameter metadata absent from the request body. */
|
|
12
|
+
export function clearMcpParameterHeaders(headers) {
|
|
13
|
+
for (const name of [...headers.keys()]) {
|
|
14
|
+
if (name.toLowerCase().startsWith("mcp-param-"))
|
|
15
|
+
headers.delete(name);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function parseParameterHeaders(schema) {
|
|
19
|
+
const headers = [];
|
|
20
|
+
const names = new Set();
|
|
21
|
+
const visit = (node, path) => {
|
|
22
|
+
if (!isRecord(node))
|
|
23
|
+
return true;
|
|
24
|
+
if (Object.hasOwn(node, "x-mcp-header")) {
|
|
25
|
+
const name = node["x-mcp-header"];
|
|
26
|
+
const type = node.type;
|
|
27
|
+
if (!path?.length ||
|
|
28
|
+
typeof name !== "string" ||
|
|
29
|
+
!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(name) ||
|
|
30
|
+
names.has(name.toLowerCase()) ||
|
|
31
|
+
(type !== "string" && type !== "integer" && type !== "boolean"))
|
|
32
|
+
return false;
|
|
33
|
+
names.add(name.toLowerCase());
|
|
34
|
+
headers.push({ name, path, type });
|
|
35
|
+
}
|
|
36
|
+
// Only schema-valued keywords are traversed: examples/default/const are instance data.
|
|
37
|
+
for (const [keyword, value] of Object.entries(node)) {
|
|
38
|
+
if (["properties", "patternProperties", "$defs", "definitions", "dependentSchemas", "dependencies"].includes(keyword)) {
|
|
39
|
+
if (!isRecord(value))
|
|
40
|
+
continue;
|
|
41
|
+
for (const [key, child] of Object.entries(value)) {
|
|
42
|
+
const childPath = keyword === "properties" && path !== undefined ? [...path, key] : undefined;
|
|
43
|
+
if (!visit(child, childPath))
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if ([
|
|
48
|
+
"items",
|
|
49
|
+
"prefixItems",
|
|
50
|
+
"allOf",
|
|
51
|
+
"anyOf",
|
|
52
|
+
"oneOf",
|
|
53
|
+
"additionalItems",
|
|
54
|
+
"additionalProperties",
|
|
55
|
+
"unevaluatedItems",
|
|
56
|
+
"unevaluatedProperties",
|
|
57
|
+
"contains",
|
|
58
|
+
"propertyNames",
|
|
59
|
+
"not",
|
|
60
|
+
"if",
|
|
61
|
+
"then",
|
|
62
|
+
"else",
|
|
63
|
+
"contentSchema",
|
|
64
|
+
].includes(keyword)) {
|
|
65
|
+
for (const child of Array.isArray(value) ? value : [value]) {
|
|
66
|
+
if (!visit(child, undefined))
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
};
|
|
73
|
+
return visit(schema, []) ? headers : undefined;
|
|
74
|
+
}
|
|
75
|
+
/** Connection-local catalog: never shared with another server or transport generation. */
|
|
76
|
+
export class ModernParameterHeaders {
|
|
77
|
+
#tools = new Map();
|
|
78
|
+
filterTools(tools, firstPage) {
|
|
79
|
+
if (firstPage)
|
|
80
|
+
this.#tools.clear();
|
|
81
|
+
return tools.filter((tool) => {
|
|
82
|
+
if (!isRecord(tool) || typeof tool.name !== "string")
|
|
83
|
+
return true;
|
|
84
|
+
const headers = parseParameterHeaders(tool.inputSchema);
|
|
85
|
+
if (headers === undefined) {
|
|
86
|
+
this.#tools.delete(tool.name);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
this.#tools.set(tool.name, headers);
|
|
90
|
+
return true;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
append(headers, name, args) {
|
|
94
|
+
if (typeof name !== "string")
|
|
95
|
+
return;
|
|
96
|
+
for (const parameter of this.#tools.get(name) ?? []) {
|
|
97
|
+
let value = args;
|
|
98
|
+
for (const key of parameter.path) {
|
|
99
|
+
value = isRecord(value) && Object.hasOwn(value, key) ? value[key] : undefined;
|
|
100
|
+
}
|
|
101
|
+
const valid = (parameter.type === "string" && typeof value === "string") ||
|
|
102
|
+
(parameter.type === "boolean" && typeof value === "boolean") ||
|
|
103
|
+
(parameter.type === "integer" && typeof value === "number" && Number.isSafeInteger(value));
|
|
104
|
+
if (valid)
|
|
105
|
+
headers.set(`Mcp-Param-${parameter.name}`, encodeMcpHeaderValue(String(value)));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=parameter-headers.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { type McpClock } from "../wrap.ts";
|
|
3
|
+
export declare const MAX_MCP_MRTR_ROUNDS = 8;
|
|
4
|
+
/** Local dispatch guard survives SDK object spreads but is never JSON serialized. */
|
|
5
|
+
export declare const modernDispatchGuard: unique symbol;
|
|
6
|
+
export interface ModernProtocolState {
|
|
7
|
+
privateForms?: boolean;
|
|
8
|
+
legacyFormEligible?: boolean;
|
|
9
|
+
version?: string;
|
|
10
|
+
legacy?: boolean;
|
|
11
|
+
resolveStdioDiagnostics?: (modern: boolean) => void;
|
|
12
|
+
activateRequests?: () => void;
|
|
13
|
+
}
|
|
14
|
+
export type MrtrClock = McpClock;
|
|
15
|
+
export declare const isModernMcpClient: (client: Client) => boolean;
|
|
16
|
+
export declare const legacyMcpFormsAdvertised: (client: Client) => boolean | undefined;
|
|
17
|
+
export declare function getModernDispatchGuard(params: unknown): ((credential?: string) => void) | undefined;
|
|
18
|
+
/** One instance-local adapter. Captured SDK requests retain ID allocation and final output-schema validation. */
|
|
19
|
+
export declare function installModernRequestAdapter(client: Client, protocol: ModernProtocolState, clock?: MrtrClock, readCredential?: () => string | undefined): void;
|
|
20
|
+
//# sourceMappingURL=request.d.ts.map
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { beginLegacyMcpOperation, physicalMcpClient, takeMcpRequestOrigin, } from "../request-origin.js";
|
|
2
|
+
import { AuthError } from "../errors.js";
|
|
3
|
+
import { loadMcpSdkTypes, loadMcpSdkZodCompat } from "../sdk.lazy.js";
|
|
4
|
+
import { mcpElapsedClock } from "../wrap.js";
|
|
5
|
+
import { collectMrtrForm } from "./elicitation.js";
|
|
6
|
+
import { isRecord, validateMrtrForm } from "./form-schema.js";
|
|
7
|
+
import { McpMrtrError } from "./errors.js";
|
|
8
|
+
import { queuedForm } from "./consent-queue.js";
|
|
9
|
+
import { parseInputs } from "./inputs.js";
|
|
10
|
+
export const MAX_MCP_MRTR_ROUNDS = 8;
|
|
11
|
+
const capableMethods = new Set(["tools/call", "resources/read", "prompts/get"]);
|
|
12
|
+
const capabilitiesKey = "io.modelcontextprotocol/clientCapabilities";
|
|
13
|
+
/** Local dispatch guard survives SDK object spreads but is never JSON serialized. */
|
|
14
|
+
export const modernDispatchGuard = Symbol("mcp-modern-dispatch");
|
|
15
|
+
const states = new WeakMap();
|
|
16
|
+
export const isModernMcpClient = (client) => states.get(physicalMcpClient(client))?.version !== undefined;
|
|
17
|
+
export const legacyMcpFormsAdvertised = (client) => states.get(physicalMcpClient(client))?.legacyFormEligible;
|
|
18
|
+
const own = (value, key) => Object.hasOwn(value, key);
|
|
19
|
+
const byteSize = (value) => Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
20
|
+
export function getModernDispatchGuard(params) {
|
|
21
|
+
return isRecord(params)
|
|
22
|
+
? params[modernDispatchGuard]
|
|
23
|
+
: undefined;
|
|
24
|
+
}
|
|
25
|
+
/** One instance-local adapter. Captured SDK requests retain ID allocation and final output-schema validation. */
|
|
26
|
+
export function installModernRequestAdapter(client, protocol, clock = mcpElapsedClock, readCredential = () => undefined) {
|
|
27
|
+
const sdkRequest = client.request.bind(client);
|
|
28
|
+
states.set(client, protocol);
|
|
29
|
+
const requestWithMrtr = async (request, schema, options) => {
|
|
30
|
+
if (request.method === "initialize")
|
|
31
|
+
return sdkRequest(request, schema, options);
|
|
32
|
+
const { origin, sdkOptions } = takeMcpRequestOrigin(options, client);
|
|
33
|
+
if (protocol.version === undefined) {
|
|
34
|
+
const end = request.method === "tools/call" && origin
|
|
35
|
+
? beginLegacyMcpOperation(client, origin, sdkOptions.signal)
|
|
36
|
+
: undefined;
|
|
37
|
+
try {
|
|
38
|
+
return await sdkRequest(request, schema, sdkOptions);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
end?.();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (own(request.params ?? {}, "requestState") || own(request.params ?? {}, "inputResponses") || options?.task)
|
|
45
|
+
throw new McpMrtrError("MCP_MRTR_INVALID_INPUT");
|
|
46
|
+
const params = structuredClone(request.params ?? {});
|
|
47
|
+
const start = clock.now();
|
|
48
|
+
const timeout = sdkOptions.timeout ?? 60_000;
|
|
49
|
+
const duration = Math.min(sdkOptions.maxTotalTimeout ?? Infinity, 9 * timeout + 300_000);
|
|
50
|
+
const deadline = start + duration;
|
|
51
|
+
const operation = new AbortController();
|
|
52
|
+
const credential = readCredential();
|
|
53
|
+
let authority;
|
|
54
|
+
let authorityCaptured = false;
|
|
55
|
+
const controller = origin?.connection;
|
|
56
|
+
const generation = controller?.generation;
|
|
57
|
+
let presentationLost = origin?.presentation === undefined || origin.presentation.signal.aborted;
|
|
58
|
+
let consentSpent = 0;
|
|
59
|
+
let continuation = {};
|
|
60
|
+
let failure;
|
|
61
|
+
const stop = (code) => {
|
|
62
|
+
failure ??= new McpMrtrError(code);
|
|
63
|
+
operation.abort(failure);
|
|
64
|
+
};
|
|
65
|
+
const onCallerAbort = () => stop("MCP_MRTR_CANCELLED");
|
|
66
|
+
const onOwnerAbort = () => stop("MCP_MRTR_CONTEXT_CHANGED");
|
|
67
|
+
const onPresentationLost = () => {
|
|
68
|
+
presentationLost = true;
|
|
69
|
+
};
|
|
70
|
+
const check = () => {
|
|
71
|
+
if (clock.now() >= deadline)
|
|
72
|
+
stop("MCP_MRTR_TIMEOUT");
|
|
73
|
+
if (origin &&
|
|
74
|
+
(!origin.isCurrent() ||
|
|
75
|
+
origin.signal.aborted ||
|
|
76
|
+
controller?.generation !== generation ||
|
|
77
|
+
controller?.state !== "connected"))
|
|
78
|
+
stop("MCP_MRTR_CONTEXT_CHANGED");
|
|
79
|
+
if (failure)
|
|
80
|
+
throw failure;
|
|
81
|
+
};
|
|
82
|
+
const resolveAuthority = async () => {
|
|
83
|
+
check();
|
|
84
|
+
if (origin) {
|
|
85
|
+
await origin.privateForm?.synchronize?.();
|
|
86
|
+
const snapshot = JSON.stringify(await interrupted(origin.resolveAuthority(operation.signal), operation.signal));
|
|
87
|
+
if (!authorityCaptured) {
|
|
88
|
+
authority = snapshot;
|
|
89
|
+
authorityCaptured = true;
|
|
90
|
+
}
|
|
91
|
+
else if (authority !== snapshot)
|
|
92
|
+
stop("MCP_MRTR_CONTEXT_CHANGED");
|
|
93
|
+
}
|
|
94
|
+
check();
|
|
95
|
+
};
|
|
96
|
+
const off = controller?.onStateChange(() => {
|
|
97
|
+
try {
|
|
98
|
+
check();
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
/* Abort reaches the active attempt and consent. */
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
sdkOptions.signal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
105
|
+
origin?.signal.addEventListener("abort", onOwnerAbort, { once: true });
|
|
106
|
+
origin?.presentation?.signal.addEventListener("abort", onPresentationLost, { once: true });
|
|
107
|
+
const timer = clock.schedule(() => stop("MCP_MRTR_TIMEOUT"), Math.max(0, duration));
|
|
108
|
+
if (sdkOptions.signal?.aborted)
|
|
109
|
+
onCallerAbort();
|
|
110
|
+
if (origin?.signal.aborted)
|
|
111
|
+
onOwnerAbort();
|
|
112
|
+
try {
|
|
113
|
+
const { safeParse } = await interrupted(loadMcpSdkZodCompat(), operation.signal);
|
|
114
|
+
// Use the SDK's own Zod identity, including when npm installs another copy for this package.
|
|
115
|
+
const { ResultSchema } = await interrupted(loadMcpSdkTypes(), operation.signal);
|
|
116
|
+
for (let round = 0;; round++) {
|
|
117
|
+
await resolveAuthority();
|
|
118
|
+
const remaining = deadline - clock.now();
|
|
119
|
+
const attempt = new AbortController();
|
|
120
|
+
const forward = () => attempt.abort("MCP request canceled");
|
|
121
|
+
operation.signal.addEventListener("abort", forward, { once: true });
|
|
122
|
+
const metadata = isRecord(params._meta) ? params._meta : {};
|
|
123
|
+
const capabilities = origin?.privateFormsEnforced
|
|
124
|
+
? origin.privateForm?.ready()
|
|
125
|
+
? { elicitation: {} }
|
|
126
|
+
: {}
|
|
127
|
+
: !presentationLost && origin?.presentation
|
|
128
|
+
? { elicitation: {} }
|
|
129
|
+
: {};
|
|
130
|
+
const outgoing = {
|
|
131
|
+
...params,
|
|
132
|
+
...continuation,
|
|
133
|
+
_meta: { ...metadata, [capabilitiesKey]: capabilities },
|
|
134
|
+
[modernDispatchGuard]: (snapshot) => {
|
|
135
|
+
check();
|
|
136
|
+
if ((snapshot ?? readCredential()) !== credential)
|
|
137
|
+
throw new McpMrtrError("MCP_MRTR_CONTEXT_CHANGED");
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
let result;
|
|
141
|
+
try {
|
|
142
|
+
check();
|
|
143
|
+
result = await sdkRequest({ method: request.method, params: outgoing }, ResultSchema, {
|
|
144
|
+
...sdkOptions,
|
|
145
|
+
signal: attempt.signal,
|
|
146
|
+
timeout: Math.min(timeout, remaining),
|
|
147
|
+
maxTotalTimeout: remaining,
|
|
148
|
+
onprogress: sdkOptions.onprogress
|
|
149
|
+
? (progress) => {
|
|
150
|
+
check();
|
|
151
|
+
if (!Number.isFinite(progress.progress) ||
|
|
152
|
+
(progress.total !== undefined && !Number.isFinite(progress.total)))
|
|
153
|
+
return;
|
|
154
|
+
sdkOptions.onprogress?.({
|
|
155
|
+
progress: progress.progress,
|
|
156
|
+
...(progress.total === undefined ? {} : { total: progress.total }),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
: undefined,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
operation.signal.removeEventListener("abort", forward);
|
|
164
|
+
}
|
|
165
|
+
check();
|
|
166
|
+
const type = own(result, "resultType") ? result.resultType : "complete";
|
|
167
|
+
if (type === "complete") {
|
|
168
|
+
if (["requestState", "inputRequests", "inputResponses"].some((key) => own(result, key)))
|
|
169
|
+
throw new McpMrtrError("MCP_MRTR_INVALID_RESULT");
|
|
170
|
+
const parsed = safeParse(schema, result);
|
|
171
|
+
if (!parsed.success)
|
|
172
|
+
throw new McpMrtrError("MCP_MRTR_INVALID_RESULT");
|
|
173
|
+
return parsed.data;
|
|
174
|
+
}
|
|
175
|
+
if (type !== "input_required" || !capableMethods.has(request.method))
|
|
176
|
+
throw new McpMrtrError("MCP_MRTR_INVALID_RESULT");
|
|
177
|
+
if (round >= MAX_MCP_MRTR_ROUNDS)
|
|
178
|
+
throw new McpMrtrError("MCP_MRTR_ROUND_LIMIT");
|
|
179
|
+
const inputs = parseInputs(result);
|
|
180
|
+
continuation = own(result, "requestState") ? { requestState: result.requestState } : {};
|
|
181
|
+
if (inputs) {
|
|
182
|
+
const responses = Object.create(null);
|
|
183
|
+
let cancelled = false;
|
|
184
|
+
for (const [key, input] of inputs) {
|
|
185
|
+
await resolveAuthority();
|
|
186
|
+
if (input.params.mode === "url") {
|
|
187
|
+
throw new McpMrtrError("MCP_MRTR_UNSUPPORTED_INPUT");
|
|
188
|
+
}
|
|
189
|
+
else if (cancelled) {
|
|
190
|
+
// A human stopped this batch, not necessarily the caller's AbortSignal.
|
|
191
|
+
// Keep the cancellation code; never fabricate results for unshown inputs.
|
|
192
|
+
throw new McpMrtrError("MCP_MRTR_CANCELLED");
|
|
193
|
+
}
|
|
194
|
+
else if (consentSpent >= 300_000) {
|
|
195
|
+
throw new McpMrtrError("MCP_MRTR_PROCESSING_UNAVAILABLE");
|
|
196
|
+
}
|
|
197
|
+
else if (origin?.privateFormsEnforced) {
|
|
198
|
+
const presenter = origin.privateForm;
|
|
199
|
+
const schema = input.params.requestedSchema;
|
|
200
|
+
if (!presenter?.ready() || !validateMrtrForm(schema))
|
|
201
|
+
throw new McpMrtrError("MCP_MRTR_PROCESSING_UNAVAILABLE");
|
|
202
|
+
const began = clock.now();
|
|
203
|
+
const outcome = await presenter.present({
|
|
204
|
+
operationId: String(start),
|
|
205
|
+
inputId: key,
|
|
206
|
+
round,
|
|
207
|
+
server: { label: origin.serverLabel, source: origin.serverSource ?? "user" },
|
|
208
|
+
message: input.params.message,
|
|
209
|
+
schema,
|
|
210
|
+
operationSignal: operation.signal,
|
|
211
|
+
presentationSignal: presenter.signal,
|
|
212
|
+
deadlineMs: Math.max(0, 300_000 - consentSpent),
|
|
213
|
+
});
|
|
214
|
+
consentSpent += clock.now() - began;
|
|
215
|
+
check();
|
|
216
|
+
if (outcome.kind !== "result")
|
|
217
|
+
throw new McpMrtrError(outcome.kind === "operation_aborted"
|
|
218
|
+
? "MCP_MRTR_CANCELLED"
|
|
219
|
+
: "MCP_MRTR_PROCESSING_UNAVAILABLE");
|
|
220
|
+
responses[key] = outcome.result;
|
|
221
|
+
cancelled = outcome.result.action !== "accept";
|
|
222
|
+
}
|
|
223
|
+
else if (presentationLost || !origin?.presentation) {
|
|
224
|
+
throw new McpMrtrError("MCP_MRTR_PROCESSING_UNAVAILABLE");
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
const consent = new AbortController();
|
|
228
|
+
const began = clock.now();
|
|
229
|
+
const consentTimer = clock.schedule(() => consent.abort(), Math.max(0, 300_000 - consentSpent));
|
|
230
|
+
const signal = AbortSignal.any([operation.signal, origin.presentation.signal, consent.signal]);
|
|
231
|
+
let presented = false;
|
|
232
|
+
try {
|
|
233
|
+
const answer = await interrupted(queuedForm(origin.binding, signal, () => {
|
|
234
|
+
presented = true;
|
|
235
|
+
return collectMrtrForm(origin.presentation, origin.serverLabel, { message: input.params.message, requestedSchema: input.params.requestedSchema }, signal);
|
|
236
|
+
}), operation.signal);
|
|
237
|
+
check();
|
|
238
|
+
if (!presented)
|
|
239
|
+
throw new McpMrtrError("MCP_MRTR_PROCESSING_UNAVAILABLE");
|
|
240
|
+
responses[key] = answer;
|
|
241
|
+
cancelled = answer.action !== "accept";
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
consentSpent += clock.now() - began;
|
|
245
|
+
clock.cancel(consentTimer);
|
|
246
|
+
consent.abort();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (byteSize(responses) > 512 * 1024)
|
|
251
|
+
throw new McpMrtrError("MCP_MRTR_INPUT_LIMIT");
|
|
252
|
+
continuation.inputResponses = responses;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
check();
|
|
258
|
+
if (error instanceof McpMrtrError)
|
|
259
|
+
throw error;
|
|
260
|
+
if (error instanceof AuthError)
|
|
261
|
+
throw error;
|
|
262
|
+
const remoteCode = isRecord(error) && typeof error.code === "number" ? error.code : undefined;
|
|
263
|
+
throw new McpMrtrError("MCP_MRTR_TRANSPORT_FAILED", remoteCode);
|
|
264
|
+
}
|
|
265
|
+
finally {
|
|
266
|
+
continuation = {};
|
|
267
|
+
clock.cancel(timer);
|
|
268
|
+
off?.();
|
|
269
|
+
sdkOptions.signal?.removeEventListener("abort", onCallerAbort);
|
|
270
|
+
origin?.signal.removeEventListener("abort", onOwnerAbort);
|
|
271
|
+
origin?.presentation?.signal.removeEventListener("abort", onPresentationLost);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
// Preserve the SDK method itself until negotiation positively selects modern.
|
|
275
|
+
protocol.activateRequests = () => {
|
|
276
|
+
client.request = requestWithMrtr;
|
|
277
|
+
};
|
|
278
|
+
// Default legacy clients retain the SDK method and its original admission behavior.
|
|
279
|
+
// Enforced legacy calls need private operation ownership before the first tools/call.
|
|
280
|
+
if (protocol.privateForms || protocol.version !== undefined)
|
|
281
|
+
protocol.activateRequests();
|
|
282
|
+
}
|
|
283
|
+
function interrupted(work, signal) {
|
|
284
|
+
return new Promise((resolve, reject) => {
|
|
285
|
+
const onabort = () => reject(signal.reason);
|
|
286
|
+
signal.addEventListener("abort", onabort, { once: true });
|
|
287
|
+
if (signal.aborted)
|
|
288
|
+
onabort();
|
|
289
|
+
void work.then(resolve, reject).finally(() => signal.removeEventListener("abort", onabort));
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=request.js.map
|
|
@@ -10,5 +10,5 @@ export declare const STDIO_PROBE_MS = 2000;
|
|
|
10
10
|
* keeps the same process for the legacy handshake; a discovery reply that arrives late still upgrades it to modern.
|
|
11
11
|
* Only a child that exited or wrote non-JSON to stdout is replaced by a fresh process for the legacy handshake.
|
|
12
12
|
*/
|
|
13
|
-
export declare function probeStdioTransport(transport: Transport, options: ProbeOptions, logger: McpLogger, timeoutMs: number, serverName: string, onModern?: (version: string) => void, probeMs?: number): void;
|
|
13
|
+
export declare function probeStdioTransport(transport: Transport, options: ProbeOptions, logger: McpLogger, timeoutMs: number, serverName: string, onModern?: (version: string) => void, probeMs?: number, onLegacy?: () => void): void;
|
|
14
14
|
//# sourceMappingURL=stdio-probe.d.ts.map
|
|
@@ -3,6 +3,9 @@ import { reapProcessTree } from "../process-tree.js";
|
|
|
3
3
|
import { discoveryReply, probeStdioEra, STDIO_PROBE_ID, StdioProbeSilence, } from "./era.js";
|
|
4
4
|
import { modernInitializeResult, normalizeModernResult } from "./sdk-bridge.js";
|
|
5
5
|
import { ModernTransportError } from "./transport.js";
|
|
6
|
+
import { sanitizeModernMessage } from "./diagnostics.js";
|
|
7
|
+
import { McpMrtrError } from "./errors.js";
|
|
8
|
+
import { getModernDispatchGuard } from "./request.js";
|
|
6
9
|
import { MODERN_PROTOCOL_VERSION, modernizeRequest } from "./wire.js";
|
|
7
10
|
/** How long a stdio child gets to answer discovery (at most a quarter of the connect timeout) before it is treated as legacy. */
|
|
8
11
|
export const STDIO_PROBE_MS = 2_000;
|
|
@@ -13,7 +16,7 @@ export const STDIO_PROBE_MS = 2_000;
|
|
|
13
16
|
* keeps the same process for the legacy handshake; a discovery reply that arrives late still upgrades it to modern.
|
|
14
17
|
* Only a child that exited or wrote non-JSON to stdout is replaced by a fresh process for the legacy handshake.
|
|
15
18
|
*/
|
|
16
|
-
export function probeStdioTransport(transport, options, logger, timeoutMs, serverName, onModern, probeMs = STDIO_PROBE_MS) {
|
|
19
|
+
export function probeStdioTransport(transport, options, logger, timeoutMs, serverName, onModern, probeMs = STDIO_PROBE_MS, onLegacy) {
|
|
17
20
|
const start = transport.start.bind(transport);
|
|
18
21
|
const close = transport.close.bind(transport);
|
|
19
22
|
const send = transport.send.bind(transport);
|
|
@@ -31,6 +34,8 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
|
|
|
31
34
|
await restart();
|
|
32
35
|
else if (outcome.silent)
|
|
33
36
|
awaitLateDiscovery();
|
|
37
|
+
if (!outcome.silent)
|
|
38
|
+
onLegacy?.();
|
|
34
39
|
return;
|
|
35
40
|
}
|
|
36
41
|
if (outcome.kind === "indeterminate" || !outcome.discovery) {
|
|
@@ -161,8 +166,10 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
|
|
|
161
166
|
}, extra);
|
|
162
167
|
return;
|
|
163
168
|
}
|
|
164
|
-
if (isInitializeReply)
|
|
169
|
+
if (isInitializeReply) {
|
|
165
170
|
settle();
|
|
171
|
+
onLegacy?.();
|
|
172
|
+
}
|
|
166
173
|
deliver?.(message, extra);
|
|
167
174
|
};
|
|
168
175
|
transport.send = async (message, sendOptions) => {
|
|
@@ -178,8 +185,34 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
|
|
|
178
185
|
function speakModern(discovery, version) {
|
|
179
186
|
const initializeResult = modernInitializeResult(discovery, version);
|
|
180
187
|
const deliver = transport.onmessage;
|
|
188
|
+
const live = new Set();
|
|
181
189
|
let initialized = false;
|
|
182
|
-
transport.
|
|
190
|
+
transport.onerror = () => {
|
|
191
|
+
// A parser error may quote a wire body; never forward it.
|
|
192
|
+
const failure = new McpMrtrError("MCP_MRTR_TRANSPORT_FAILED");
|
|
193
|
+
logger.warn(failure.message);
|
|
194
|
+
void transport.close();
|
|
195
|
+
};
|
|
196
|
+
transport.onmessage = (message, extra) => {
|
|
197
|
+
if ("id" in message && "method" in message) {
|
|
198
|
+
void send({
|
|
199
|
+
jsonrpc: "2.0",
|
|
200
|
+
id: message.id,
|
|
201
|
+
error: { code: -32601, message: "Unsupported MCP method." },
|
|
202
|
+
}).catch(() => { });
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if ("id" in message && message.id !== undefined && !live.delete(message.id))
|
|
206
|
+
return;
|
|
207
|
+
if ("method" in message && message.method === "notifications/progress") {
|
|
208
|
+
const token = message.params?.progressToken;
|
|
209
|
+
if ((typeof token !== "string" && typeof token !== "number") || !live.has(token))
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const safe = sanitizeModernMessage(message);
|
|
213
|
+
if (safe)
|
|
214
|
+
deliver?.(normalizeModernResult(safe), extra);
|
|
215
|
+
};
|
|
183
216
|
transport.send = async (message, sendOptions) => {
|
|
184
217
|
if (!("method" in message))
|
|
185
218
|
return send(message, sendOptions);
|
|
@@ -188,14 +221,29 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
|
|
|
188
221
|
throw new ModernTransportError("Modern MCP stdio has already initialized");
|
|
189
222
|
}
|
|
190
223
|
initialized = true;
|
|
191
|
-
|
|
224
|
+
deliver?.({ jsonrpc: "2.0", id: message.id, result: initializeResult });
|
|
192
225
|
return;
|
|
193
226
|
}
|
|
194
227
|
if (message.method === "notifications/initialized" && !("id" in message))
|
|
195
228
|
return;
|
|
229
|
+
if (message.method === "notifications/cancelled" && !("id" in message)) {
|
|
230
|
+
const id = message.params?.requestId;
|
|
231
|
+
if ((typeof id !== "string" && typeof id !== "number") || !live.delete(id))
|
|
232
|
+
return;
|
|
233
|
+
return send({ jsonrpc: "2.0", method: message.method, params: { requestId: id, reason: "MCP request canceled" } }, sendOptions);
|
|
234
|
+
}
|
|
235
|
+
getModernDispatchGuard(message.params)?.();
|
|
196
236
|
const request = modernizeRequest("id" in message ? message.id : 0, message.method, options.clientInfo, options.clientCapabilities, version, message.params);
|
|
197
|
-
if ("id" in message)
|
|
198
|
-
|
|
237
|
+
if ("id" in message) {
|
|
238
|
+
live.add(message.id);
|
|
239
|
+
try {
|
|
240
|
+
return await send(request, sendOptions);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
live.delete(message.id);
|
|
244
|
+
throw new McpMrtrError("MCP_MRTR_TRANSPORT_FAILED");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
199
247
|
const { id: _id, ...notification } = request;
|
|
200
248
|
return send(notification, sendOptions);
|
|
201
249
|
};
|