@code-yeongyu/senpi 2026.10.25-1 → 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 +36 -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-RHHBXIRN.js → chunk-4WZRVZNX.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-YDCRA5ES.js → chunk-7W7OJDIY.js} +4 -4
- package/dist/bundle/chunks/chunk-AX72VNRS.js +27 -0
- package/dist/bundle/chunks/{chunk-FWXMMMCV.js → chunk-C5UFJ7B3.js} +1 -1
- package/dist/bundle/chunks/{chunk-7IRLFYNM.js → chunk-CNL6QSUB.js} +4 -4
- package/dist/bundle/chunks/{chunk-BTNYLNWZ.js → chunk-FFQOEC76.js} +5 -5
- package/dist/bundle/chunks/{chunk-VOBD6FQG.js → chunk-FHXR4ZAC.js} +1 -1
- package/dist/bundle/chunks/chunk-H3HNMY2W.js +13 -0
- package/dist/bundle/chunks/{chunk-BMYNMUVZ.js → chunk-HVJI5T75.js} +1 -1
- package/dist/bundle/chunks/{chunk-EWOEVVQT.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-EYBDBR7V.js → chunk-MJ5NNWX4.js} +1 -1
- package/dist/bundle/chunks/{chunk-RVYSJBZ7.js → chunk-O3Q2ET6W.js} +2 -5
- package/dist/bundle/chunks/chunk-O5RYI3IW.js +2 -0
- package/dist/bundle/chunks/{chunk-SGDOZT6B.js → chunk-ODTGC6YU.js} +1 -1
- package/dist/bundle/chunks/chunk-P3M2PF6K.js +27 -0
- package/dist/bundle/chunks/chunk-SWJXRC2I.js +5 -0
- package/dist/bundle/chunks/{chunk-QH2HSCMX.js → chunk-TTU2RNVX.js} +3 -3
- package/dist/bundle/chunks/{chunk-PXKF2RHD.js → chunk-UFJ2K7Q7.js} +1 -1
- package/dist/bundle/chunks/{chunk-7UUOGZKR.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-7WAJCESX.js → chunk-WLHNEUGD.js} +1 -1
- package/dist/bundle/chunks/chunk-YI7SPFDN.js +5 -0
- package/dist/bundle/chunks/{chunk-URBJS24R.js → chunk-YRF4I43T.js} +35 -35
- 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 +9 -7
- package/dist/bundle/chunks/host-session-command-J65ENSV5.js +20 -0
- package/dist/bundle/chunks/{host-socket-request-ZDJGSSZO.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-M7HVUMRH.js → multi-session-host-YI2WZOET.js} +12 -9
- package/dist/bundle/chunks/{package-manager-cli-FSR5P6YL.js → package-manager-cli-GHZPJMRF.js} +1 -1
- package/dist/bundle/chunks/{role-M2MTVACJ.js → role-JVSBNKGE.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-OL5KU6NO.js → rpc-monitor-source-FFEQS744.js} +1 -1
- package/dist/bundle/chunks/{runtime-session-CLNODAYE.js → runtime-session-V5OO7ZRM.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-XBW4EOG5.js → schedule-command-YWG6D6ZF.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-36HAQSQO.js +3 -0
- package/dist/bundle/chunks/{session-picker-KQUFWDQC.js → session-picker-25H5KNYP.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +285 -271
- 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 +9 -7
- 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.d.ts +1 -0
- package/dist/core/agent-session.js +50 -16
- package/dist/core/engine-turn-limit.d.ts +2 -0
- package/dist/core/engine-turn-limit.js +7 -1
- package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.js +5 -0
- 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/goal/stop-pause.js +2 -1
- package/dist/core/extensions/builtin/loop/session-hold.js +2 -1
- 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-runtime-facts.js +3 -0
- package/dist/core/lifecycle-session-binding.d.ts +11 -1
- package/dist/core/lifecycle-session-binding.js +35 -4
- 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/run-resume-turn.d.ts +1 -0
- package/dist/core/run-resume-turn.js +4 -3
- 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/core/stop-resume-directive.d.ts +1 -1
- package/dist/core/stop-resume-directive.js +10 -1
- 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 +35 -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/interactive/pause-footer.d.ts +5 -0
- package/dist/modes/interactive/pause-footer.js +2 -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-reservations.js +3 -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-stalled-evidence.js +2 -1
- 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/dist/modes/rpc/tool-child/group-ownership.js +4 -2
- package/dist/utils/process-identity.d.ts +7 -0
- package/dist/utils/process-identity.js +32 -5
- 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-3VOG6WFU.js +0 -27
- package/dist/bundle/chunks/chunk-7ASLQ243.js +0 -2
- package/dist/bundle/chunks/chunk-CDRFFHLL.js +0 -4
- package/dist/bundle/chunks/chunk-HPCS4HM4.js +0 -27
- package/dist/bundle/chunks/chunk-II22E3CV.js +0 -3
- package/dist/bundle/chunks/chunk-J5P2VL3Y.js +0 -5
- package/dist/bundle/chunks/chunk-SCVRKIFK.js +0 -2
- package/dist/bundle/chunks/chunk-XUFUKDKJ.js +0 -5
- package/dist/bundle/chunks/cli-main-TABBP55A.js +0 -3
- package/dist/bundle/chunks/dispatch-DEP4PEDZ.js +0 -10
- package/dist/bundle/chunks/host-command-OQR4RDTM.js +0 -2
- package/dist/bundle/chunks/host-lifecycle-U36U7JTE.js +0 -3
- package/dist/bundle/chunks/host-session-command-IGB27ZPJ.js +0 -20
- package/dist/bundle/chunks/interactive-mode-DGQNSXUA.js +0 -2
- package/dist/bundle/chunks/rpc-mode-SZAJNKKL.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-WEF3XTJR.js +0 -3
|
@@ -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
|
};
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { JSONRPCMessageSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
import { deriveModernHeaders, MODERN_PROTOCOL_VERSION, modernizeRequest } from "./wire.js";
|
|
3
|
+
import { AuthError } from "../errors.js";
|
|
4
|
+
import { sanitizeModernMessage } from "./diagnostics.js";
|
|
5
|
+
import { McpMrtrError } from "./errors.js";
|
|
6
|
+
import { getModernDispatchGuard, modernDispatchGuard } from "./request.js";
|
|
7
|
+
import { clearMcpParameterHeaders, ModernParameterHeaders } from "./parameter-headers.js";
|
|
3
8
|
export class ModernTransportError extends Error {
|
|
4
9
|
constructor(message) {
|
|
5
10
|
super(message);
|
|
@@ -10,6 +15,9 @@ export class ModernTransportError extends Error {
|
|
|
10
15
|
export class ModernHttpTransport {
|
|
11
16
|
#options;
|
|
12
17
|
#abort = new AbortController();
|
|
18
|
+
#attempts = new Map();
|
|
19
|
+
#toolLists = new Map();
|
|
20
|
+
#parameterHeaders = new ModernParameterHeaders();
|
|
13
21
|
#started = false;
|
|
14
22
|
#closed = false;
|
|
15
23
|
constructor(options) {
|
|
@@ -30,9 +38,24 @@ export class ModernHttpTransport {
|
|
|
30
38
|
async send(message) {
|
|
31
39
|
if (!this.#started || this.#closed)
|
|
32
40
|
throw new ModernTransportError("Modern MCP transport is not open");
|
|
41
|
+
if ("method" in message && message.method === "notifications/cancelled") {
|
|
42
|
+
const id = message.params?.requestId;
|
|
43
|
+
if (typeof id === "string" || typeof id === "number")
|
|
44
|
+
this.#attempts.get(id)?.abort();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const id = "method" in message && "id" in message ? message.id : undefined;
|
|
48
|
+
if (id !== undefined && "method" in message && message.method === "tools/list") {
|
|
49
|
+
this.#toolLists.set(id, message.params?.cursor === undefined);
|
|
50
|
+
}
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
if (id !== undefined)
|
|
53
|
+
this.#attempts.set(id, controller);
|
|
54
|
+
const signal = AbortSignal.any([this.#abort.signal, controller.signal]);
|
|
33
55
|
try {
|
|
34
56
|
const version = this.#options.protocolVersion ?? MODERN_PROTOCOL_VERSION;
|
|
35
57
|
const headers = new Headers(this.#options.headers);
|
|
58
|
+
clearMcpParameterHeaders(headers);
|
|
36
59
|
// Auth headers pass through, but protocol routing is exclusively derived from the body.
|
|
37
60
|
for (const name of ["Mcp-Session-Id", "Mcp-Name", "Mcp-Method", "MCP-Protocol-Version"])
|
|
38
61
|
headers.delete(name);
|
|
@@ -43,6 +66,9 @@ export class ModernHttpTransport {
|
|
|
43
66
|
const request = modernizeRequest("id" in message ? message.id : 0, message.method, this.#options.clientInfo, this.#options.clientCapabilities, version, message.params);
|
|
44
67
|
for (const [name, value] of Object.entries(deriveModernHeaders(request)))
|
|
45
68
|
headers.set(name, value);
|
|
69
|
+
if (request.method === "tools/call") {
|
|
70
|
+
this.#parameterHeaders.append(headers, request.params.name, request.params.arguments);
|
|
71
|
+
}
|
|
46
72
|
if ("id" in message) {
|
|
47
73
|
body = request;
|
|
48
74
|
}
|
|
@@ -55,13 +81,24 @@ export class ModernHttpTransport {
|
|
|
55
81
|
// JSON-RPC replies have no method or params; do not turn them into requests.
|
|
56
82
|
headers.set("MCP-Protocol-Version", version);
|
|
57
83
|
}
|
|
58
|
-
const
|
|
84
|
+
const guard = "method" in message ? getModernDispatchGuard(message.params) : undefined;
|
|
85
|
+
guard?.();
|
|
86
|
+
const init = {
|
|
59
87
|
method: "POST",
|
|
60
88
|
headers,
|
|
61
89
|
body: JSON.stringify(body),
|
|
62
|
-
signal
|
|
63
|
-
|
|
64
|
-
|
|
90
|
+
signal,
|
|
91
|
+
[modernDispatchGuard]: guard,
|
|
92
|
+
};
|
|
93
|
+
const response = await this.#options.fetch(this.#options.url, init);
|
|
94
|
+
signal.throwIfAborted();
|
|
95
|
+
if (response.status === 401 || response.status === 403) {
|
|
96
|
+
await response.body?.cancel();
|
|
97
|
+
throw new AuthError("MCP server needs authentication; use /mcp auth-start.", {
|
|
98
|
+
phase: "auth",
|
|
99
|
+
retriable: false,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
65
102
|
if (response.status === 202 || response.status === 204)
|
|
66
103
|
return;
|
|
67
104
|
const mediaType = response.headers.get("Content-Type")?.split(";")[0].trim().toLowerCase();
|
|
@@ -71,35 +108,64 @@ export class ModernHttpTransport {
|
|
|
71
108
|
if (!response.ok && !("error" in incoming)) {
|
|
72
109
|
throw new ModernTransportError(`Modern MCP POST failed with HTTP ${response.status}`);
|
|
73
110
|
}
|
|
74
|
-
|
|
75
|
-
this
|
|
111
|
+
signal.throwIfAborted();
|
|
112
|
+
this.#deliver(incoming, id);
|
|
76
113
|
}
|
|
77
114
|
else if (response.ok && mediaType === "text/event-stream" && response.body) {
|
|
78
|
-
await this.#consumeSse(response.body,
|
|
115
|
+
await this.#consumeSse(response.body, id, signal);
|
|
79
116
|
}
|
|
80
117
|
else {
|
|
81
118
|
throw new ModernTransportError(`Modern MCP POST failed with HTTP ${response.status} (${mediaType ?? "no content type"})`);
|
|
82
119
|
}
|
|
83
120
|
}
|
|
84
121
|
catch (error) {
|
|
85
|
-
const failure = error instanceof
|
|
122
|
+
const failure = error instanceof McpMrtrError || error instanceof AuthError
|
|
123
|
+
? error
|
|
124
|
+
: new McpMrtrError("MCP_MRTR_TRANSPORT_FAILED");
|
|
86
125
|
this.onerror?.(failure);
|
|
87
126
|
throw failure;
|
|
88
127
|
}
|
|
128
|
+
finally {
|
|
129
|
+
if (id !== undefined)
|
|
130
|
+
this.#attempts.delete(id);
|
|
131
|
+
if (id !== undefined)
|
|
132
|
+
this.#toolLists.delete(id);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
#deliver(incoming, expected) {
|
|
136
|
+
if ("id" in incoming && (expected === undefined || incoming.id !== expected))
|
|
137
|
+
throw new McpMrtrError("MCP_MRTR_INVALID_RESULT");
|
|
138
|
+
if ("id" in incoming && "result" in incoming && this.#toolLists.has(incoming.id)) {
|
|
139
|
+
const result = incoming.result;
|
|
140
|
+
if (Array.isArray(result.tools)) {
|
|
141
|
+
incoming = {
|
|
142
|
+
...incoming,
|
|
143
|
+
result: {
|
|
144
|
+
...result,
|
|
145
|
+
tools: this.#parameterHeaders.filterTools(result.tools, this.#toolLists.get(incoming.id) === true),
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const safe = sanitizeModernMessage(incoming);
|
|
151
|
+
if (safe)
|
|
152
|
+
this.onmessage?.(safe);
|
|
89
153
|
}
|
|
90
|
-
async #consumeSse(stream, requestId) {
|
|
154
|
+
async #consumeSse(stream, requestId, signal) {
|
|
91
155
|
const reader = stream.getReader();
|
|
92
156
|
const decoder = new TextDecoder();
|
|
93
157
|
const aborted = Promise.withResolvers();
|
|
94
|
-
const onabort = () => aborted.reject(
|
|
95
|
-
|
|
158
|
+
const onabort = () => aborted.reject(signal.reason);
|
|
159
|
+
signal.addEventListener("abort", onabort, { once: true });
|
|
160
|
+
if (signal.aborted)
|
|
161
|
+
onabort();
|
|
96
162
|
let buffer = "";
|
|
97
163
|
let data = [];
|
|
98
164
|
let event = "";
|
|
99
165
|
try {
|
|
100
166
|
while (true) {
|
|
101
167
|
const { value, done } = await Promise.race([reader.read(), aborted.promise]);
|
|
102
|
-
|
|
168
|
+
signal.throwIfAborted();
|
|
103
169
|
buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
|
|
104
170
|
while (true) {
|
|
105
171
|
const match = /[\r\n]/.exec(buffer);
|
|
@@ -114,7 +180,7 @@ export class ModernHttpTransport {
|
|
|
114
180
|
if (line === "") {
|
|
115
181
|
if (data.length > 0 && (event === "" || event === "message")) {
|
|
116
182
|
const incoming = JSONRPCMessageSchema.parse(JSON.parse(data.join("\n")));
|
|
117
|
-
this
|
|
183
|
+
this.#deliver(incoming, requestId);
|
|
118
184
|
if ("id" in incoming && !("method" in incoming) && incoming.id === requestId)
|
|
119
185
|
return;
|
|
120
186
|
}
|
|
@@ -139,7 +205,7 @@ export class ModernHttpTransport {
|
|
|
139
205
|
}
|
|
140
206
|
}
|
|
141
207
|
finally {
|
|
142
|
-
|
|
208
|
+
signal.removeEventListener("abort", onabort);
|
|
143
209
|
await reader.cancel();
|
|
144
210
|
reader.releaseLock();
|
|
145
211
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { encodeMcpHeaderValue } from "./parameter-headers.js";
|
|
2
2
|
export const MODERN_PROTOCOL_VERSION = "2026-07-28";
|
|
3
3
|
export const MODERN_ERROR_CODES = {
|
|
4
4
|
HeaderMismatch: -32020,
|
|
@@ -24,9 +24,17 @@ export function buildModernRequest(id, method, clientInfo, clientCapabilities, p
|
|
|
24
24
|
/** Rewrite an outgoing SDK request or notification into the modern wire shape at the negotiated version. */
|
|
25
25
|
export function modernizeRequest(id, method, clientInfo, clientCapabilities, version, params) {
|
|
26
26
|
const built = buildModernRequest(id, method, clientInfo, clientCapabilities, params);
|
|
27
|
+
const capabilities = params?._meta?.["io.modelcontextprotocol/clientCapabilities"];
|
|
27
28
|
return {
|
|
28
29
|
...built,
|
|
29
|
-
params: {
|
|
30
|
+
params: {
|
|
31
|
+
...built.params,
|
|
32
|
+
_meta: {
|
|
33
|
+
...built.params._meta,
|
|
34
|
+
"io.modelcontextprotocol/clientCapabilities": isRecord(capabilities) ? capabilities : clientCapabilities,
|
|
35
|
+
"io.modelcontextprotocol/protocolVersion": version,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
30
38
|
};
|
|
31
39
|
}
|
|
32
40
|
/** Derive protocol headers from the same request that will be serialized. */
|
|
@@ -41,11 +49,7 @@ export function deriveModernHeaders(request) {
|
|
|
41
49
|
? request.params.name
|
|
42
50
|
: undefined;
|
|
43
51
|
if (typeof name === "string") {
|
|
44
|
-
|
|
45
|
-
headers["Mcp-Name"] =
|
|
46
|
-
/^[\x20-\x7E]*$/.test(name) && !name.startsWith("base64:")
|
|
47
|
-
? name
|
|
48
|
-
: `base64:${Buffer.from(name, "utf8").toString("base64url")}`;
|
|
52
|
+
headers["Mcp-Name"] = encodeMcpHeaderValue(name);
|
|
49
53
|
}
|
|
50
54
|
return headers;
|
|
51
55
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "../../types.ts";
|
|
1
|
+
import type { ExtensionAPI, ExtensionUIContext } from "../../types.ts";
|
|
2
2
|
import type { McpCachedServerCatalog } from "./catalog-cache.ts";
|
|
3
3
|
import type { ServerConnection } from "./connection.ts";
|
|
4
4
|
import type { McpService } from "./service.ts";
|
|
5
|
+
import { type McpRequestOrigin } from "./request-origin.ts";
|
|
5
6
|
export interface McpPromptServer {
|
|
7
|
+
readonly ensureFresh?: () => Promise<void>;
|
|
8
|
+
readonly requestOrigin?: () => McpRequestOrigin | undefined;
|
|
9
|
+
readonly bindPresentation?: (ui: ExtensionUIContext) => void;
|
|
6
10
|
readonly server: string;
|
|
7
11
|
readonly connection: ServerConnection;
|
|
8
12
|
readonly requestTimeoutMs?: number;
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
// last-wins, and prompts removed server-side keep a stale command until the
|
|
11
11
|
// next session — invoking one surfaces the server error as a notice.
|
|
12
12
|
import { createMcpLogger } from "./log.js";
|
|
13
|
+
import { mcpRequestOptions } from "./request-origin.js";
|
|
14
|
+
import { ensureMcpToolCallConnection } from "./health.js";
|
|
13
15
|
const registeredCommandNames = new Set();
|
|
14
16
|
/** Test seam: forget which commands were registered (module-level last-wins). */
|
|
15
17
|
export function resetMcpPromptCommandsForTests() {
|
|
@@ -34,10 +36,13 @@ export function registerMcpPromptCommands(serviceOrPi, piOrServers, serversArg)
|
|
|
34
36
|
description: prompt.description ?? `MCP prompt ${prompt.name} from ${server.server}`,
|
|
35
37
|
handler: async (_args, ctx) => {
|
|
36
38
|
try {
|
|
39
|
+
server.bindPresentation?.(ctx.ui);
|
|
37
40
|
const collected = await collectPromptArguments(prompt.arguments ?? [], ctx);
|
|
38
41
|
if (collected === undefined)
|
|
39
42
|
return;
|
|
40
|
-
|
|
43
|
+
if (server.requestOrigin)
|
|
44
|
+
await ensureMcpToolCallConnection(server.connection, server.ensureFresh);
|
|
45
|
+
const result = await server.connection.client.getPrompt({ arguments: collected, name: prompt.name }, mcpRequestOptions(server.requestOrigin?.(), { timeout: server.requestTimeoutMs }));
|
|
41
46
|
ctx.ui.setEditorText(flattenPromptMessages(result.messages));
|
|
42
47
|
}
|
|
43
48
|
catch (error) {
|