stitchkit 0.103.13 → 0.104.1
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 +120 -0
- package/darwin-native.cjs +1 -3
- package/dist/agent-runtime/store-conformance-lifecycle.d.ts +10 -10
- package/dist/agent-runtime/store-conformance-support.d.ts +5 -5
- package/dist/application/directory-inbox-accept.d.ts +13 -0
- package/dist/application/directory-inbox-accept.d.ts.map +1 -0
- package/dist/application/directory-inbox-contract.d.ts +42 -5
- package/dist/application/directory-inbox-contract.d.ts.map +1 -1
- package/dist/application/directory-inbox-delivery.d.ts +14 -0
- package/dist/application/directory-inbox-delivery.d.ts.map +1 -0
- package/dist/application/directory-inbox-files.d.ts +28 -0
- package/dist/application/directory-inbox-files.d.ts.map +1 -0
- package/dist/application/directory-inbox-state.d.ts +14 -0
- package/dist/application/directory-inbox-state.d.ts.map +1 -0
- package/dist/application/directory-inbox.d.ts +2 -1
- package/dist/application/directory-inbox.d.ts.map +1 -1
- package/dist/application/file-state-store.d.ts +2 -8
- package/dist/application/file-state-store.d.ts.map +1 -1
- package/dist/application/state-store.d.ts +6 -1
- package/dist/application/state-store.d.ts.map +1 -1
- package/dist/browser/client-arguments.d.ts +4 -0
- package/dist/browser/client-arguments.d.ts.map +1 -0
- package/dist/browser/client-executors.d.ts.map +1 -1
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/contract-stream.d.ts.map +1 -1
- package/dist/browser/http.d.ts +2 -1
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/browser/native-fetch.d.ts +8 -0
- package/dist/browser/native-fetch.d.ts.map +1 -0
- package/dist/contract/errors.d.ts +16 -12
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/normalize.d.ts +5 -1
- package/dist/contract/normalize.d.ts.map +1 -1
- package/dist/contract/stream.d.ts +1 -0
- package/dist/contract/stream.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime/browser.js +3 -3
- package/dist/entrypoints/agent-runtime/coding-tools.js +6 -6
- package/dist/entrypoints/agent-runtime/harness-tools.js +8 -8
- package/dist/entrypoints/agent-runtime/harness.js +11 -11
- package/dist/entrypoints/agent-runtime/realtime.js +3 -3
- package/dist/entrypoints/agent-runtime/sandbox.js +6 -6
- package/dist/entrypoints/agent-runtime.js +13 -13
- package/dist/entrypoints/application/diagnostic-journal.js +1 -1
- package/dist/entrypoints/application/directory-inbox.js +424 -119
- package/dist/entrypoints/application/grammy.js +1 -1
- package/dist/entrypoints/application.d.ts +2 -2
- package/dist/entrypoints/application.d.ts.map +1 -1
- package/dist/entrypoints/application.js +7 -3
- package/dist/entrypoints/cli.d.ts +2 -0
- package/dist/entrypoints/cli.d.ts.map +1 -1
- package/dist/entrypoints/cli.js +757 -136
- package/dist/entrypoints/contract.js +3 -3
- package/dist/entrypoints/files/packaging.d.ts +3 -0
- package/dist/entrypoints/files/packaging.d.ts.map +1 -0
- package/dist/entrypoints/files/packaging.js +133 -0
- package/dist/entrypoints/files.js +10 -107
- package/dist/entrypoints/index.js +7 -7
- package/dist/entrypoints/node.js +8 -8
- package/dist/entrypoints/observability.js +2 -2
- package/dist/entrypoints/react.js +1 -1
- package/dist/entrypoints/remote.js +19 -6
- package/dist/entrypoints/server.d.ts +1 -1
- package/dist/entrypoints/server.d.ts.map +1 -1
- package/dist/entrypoints/server.js +23 -17
- package/dist/entrypoints/telegram.d.ts +1 -0
- package/dist/entrypoints/telegram.d.ts.map +1 -1
- package/dist/entrypoints/telegram.js +214 -98
- package/dist/entrypoints/testing.js +7 -7
- package/dist/entrypoints/tools/connections.d.ts +2 -2
- package/dist/entrypoints/tools/connections.d.ts.map +1 -1
- package/dist/entrypoints/tools/connections.js +427 -294
- package/dist/entrypoints/tools/invoker.js +6 -6
- package/dist/entrypoints/tools/mcp.d.ts +12 -0
- package/dist/entrypoints/tools/mcp.d.ts.map +1 -0
- package/dist/entrypoints/tools/mcp.js +45 -0
- package/dist/entrypoints/tools.d.ts +1 -0
- package/dist/entrypoints/tools.d.ts.map +1 -1
- package/dist/entrypoints/tools.js +82 -1147
- package/dist/entrypoints/tracking.js +1 -1
- package/dist/files/boundary.d.ts +2 -8
- package/dist/files/boundary.d.ts.map +1 -1
- package/dist/files/file-io.d.ts +0 -5
- package/dist/files/file-io.d.ts.map +1 -1
- package/dist/files/native-packaging-layout.d.ts +12 -0
- package/dist/files/native-packaging-layout.d.ts.map +1 -0
- package/dist/files/native-packaging.d.ts +72 -0
- package/dist/files/native-packaging.d.ts.map +1 -0
- package/dist/files/read.d.ts +2 -2
- package/dist/files/read.d.ts.map +1 -1
- package/dist/index-1a6jtnd9.js +88 -0
- package/dist/index-269zqj8m.js +77 -0
- package/dist/{index-r34mvse1.js → index-27a2pj09.js} +1 -1
- package/dist/{index-r956rndn.js → index-2hyavjjj.js} +3 -3
- package/dist/{index-m2xqkv55.js → index-2vnh05w4.js} +83 -14
- package/dist/{index-pw99k9h5.js → index-43p08g7t.js} +1 -1
- package/dist/{index-xwq9j1sf.js → index-4e0ah203.js} +9 -7
- package/dist/{index-qapzv4p5.js → index-5z6jn897.js} +1 -1
- package/dist/{index-ahvsksw5.js → index-6d9xaxw1.js} +1 -1
- package/dist/{index-8zfsgar0.js → index-6drst9sg.js} +11 -5
- package/dist/{index-x87j7csh.js → index-77nxk6ay.js} +2 -2
- package/dist/{index-dnn9z96q.js → index-a0zzh8ak.js} +3 -3
- package/dist/{index-kmpzj1cq.js → index-a3wma1hk.js} +1 -1
- package/dist/{index-j8aw2c0e.js → index-bakgedh8.js} +35 -7
- package/dist/index-bhb950qs.js +1145 -0
- package/dist/{index-tgzhq07n.js → index-cvda079a.js} +1 -1
- package/dist/{index-n2gre124.js → index-fa4wg312.js} +4 -4
- package/dist/{index-zp4jpkp0.js → index-fk18k56k.js} +2 -2
- package/dist/{index-afjvjyck.js → index-g2j7pnak.js} +2 -134
- package/dist/index-g95fk660.js +116 -0
- package/dist/{index-r5c4fedv.js → index-h2fhjqy7.js} +2 -1
- package/dist/{index-sybedc8b.js → index-hbd32c6a.js} +23 -94
- package/dist/{index-fyphpknh.js → index-hp9d77wk.js} +1 -1
- package/dist/index-jd470k6q.js +109 -0
- package/dist/{index-b2bz0kdm.js → index-kb8styz3.js} +4 -3
- package/dist/{index-0c419yx7.js → index-kemm0ej5.js} +12 -9
- package/dist/{index-db2spkv1.js → index-ptn0g0gj.js} +3 -3
- package/dist/{index-w575evhq.js → index-r4r70zmh.js} +1 -1
- package/dist/{index-12m2v2a9.js → index-s4evrmp5.js} +1 -1
- package/dist/{index-1khy8y7n.js → index-t1sr4aks.js} +3 -3
- package/dist/{index-45tdvqa3.js → index-wh0ecvyk.js} +1 -1
- package/dist/{index-n53mswt3.js → index-x928baz2.js} +8 -1
- package/dist/index-yk4g8805.js +133 -0
- package/dist/{index-ygkka6zn.js → index-ywppsaby.js} +28 -5
- package/dist/{index-9853ymwb.js → index-za6j7nwy.js} +18 -4
- package/dist/internal/bounded-file-read.d.ts +41 -0
- package/dist/internal/bounded-file-read.d.ts.map +1 -0
- package/dist/internal/exclusive-lock.d.ts +2 -0
- package/dist/internal/exclusive-lock.d.ts.map +1 -1
- package/dist/internal/with-exclusive-lock.d.ts +4 -2
- package/dist/internal/with-exclusive-lock.d.ts.map +1 -1
- package/dist/telegram/broadcast-delivery.d.ts +23 -0
- package/dist/telegram/broadcast-delivery.d.ts.map +1 -0
- package/dist/telegram/broadcast-failure.d.ts +22 -0
- package/dist/telegram/broadcast-failure.d.ts.map +1 -0
- package/dist/telegram/broadcast-state.d.ts +6 -3
- package/dist/telegram/broadcast-state.d.ts.map +1 -1
- package/dist/telegram/broadcast.d.ts +10 -5
- package/dist/telegram/broadcast.d.ts.map +1 -1
- package/dist/tools/cli/format.d.ts.map +1 -1
- package/dist/tools/cli/invoke.d.ts +2 -2
- package/dist/tools/cli/invoke.d.ts.map +1 -1
- package/dist/tools/cli/publication-assets.d.ts +10 -0
- package/dist/tools/cli/publication-assets.d.ts.map +1 -0
- package/dist/tools/cli/publication-contract.d.ts +75 -0
- package/dist/tools/cli/publication-contract.d.ts.map +1 -0
- package/dist/tools/cli/publication-control.d.ts +4 -0
- package/dist/tools/cli/publication-control.d.ts.map +1 -0
- package/dist/tools/cli/publication-storage.d.ts +32 -0
- package/dist/tools/cli/publication-storage.d.ts.map +1 -0
- package/dist/tools/cli/publication.d.ts +35 -0
- package/dist/tools/cli/publication.d.ts.map +1 -0
- package/dist/tools/cli/update.d.ts.map +1 -1
- package/dist/tools/connections/define.d.ts.map +1 -1
- package/dist/tools/connections/error-projection.d.ts +4 -0
- package/dist/tools/connections/error-projection.d.ts.map +1 -0
- package/dist/tools/connections/errors.d.ts +24 -8
- package/dist/tools/connections/errors.d.ts.map +1 -1
- package/dist/tools/connections/limits.d.ts +11 -31
- package/dist/tools/connections/limits.d.ts.map +1 -1
- package/dist/tools/connections/mcp-client.d.ts +6 -4
- package/dist/tools/connections/mcp-client.d.ts.map +1 -1
- package/dist/tools/connections/mcp.d.ts.map +1 -1
- package/dist/tools/connections/openapi.d.ts.map +1 -1
- package/dist/tools/connections/operation-limits.d.ts +25 -0
- package/dist/tools/connections/operation-limits.d.ts.map +1 -0
- package/dist/tools/connections/sse-frames.d.ts +11 -2
- package/dist/tools/connections/sse-frames.d.ts.map +1 -1
- package/dist/tools/connections/sse-session.d.ts +15 -2
- package/dist/tools/connections/sse-session.d.ts.map +1 -1
- package/dist/tools/connections/types.d.ts +19 -1
- package/dist/tools/connections/types.d.ts.map +1 -1
- package/dist/tools/execute-result.d.ts.map +1 -1
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/internal/observe-hook.d.ts +3 -0
- package/dist/tools/internal/observe-hook.d.ts.map +1 -0
- package/dist/tools/internal/surface-projector.d.ts +2 -1
- package/dist/tools/internal/surface-projector.d.ts.map +1 -1
- package/dist/tools/mcp/mount.d.ts +2 -2
- package/dist/tools/mcp/mount.d.ts.map +1 -1
- package/dist/tools/mcp/prepare.d.ts +4 -5
- package/dist/tools/mcp/prepare.d.ts.map +1 -1
- package/dist/tools/mcp/register.d.ts +4 -1
- package/dist/tools/mcp/register.d.ts.map +1 -1
- package/dist/tools/mcp/runtime-tools.d.ts.map +1 -1
- package/dist/tools/remote.d.ts.map +1 -1
- package/dist/tools/runtime-tool-execution.d.ts +65 -0
- package/dist/tools/runtime-tool-execution.d.ts.map +1 -0
- package/dist/tools/runtime-tool-mcp.d.ts +27 -0
- package/dist/tools/runtime-tool-mcp.d.ts.map +1 -0
- package/dist/tools/runtime-tool.d.ts +9 -60
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/surface.d.ts +12 -14
- package/dist/tools/surface.d.ts.map +1 -1
- package/llms/application.2.txt +221 -44
- package/llms/application.3.txt +3 -1
- package/llms/application.txt +1 -1
- package/llms/cli.2.txt +144 -1
- package/llms/cli.txt +123 -104
- package/llms/contract.txt +1 -1
- package/llms/files-packaging.txt +27 -0
- package/llms/process.txt +63 -1
- package/llms/server.4.txt +4 -1
- package/llms/server.5.txt +1 -0
- package/llms/stitchkit.2.txt +14 -0
- package/llms/stitchkit.txt +6 -4
- package/llms/telegram.txt +64 -2
- package/llms/tools-connections.txt +3 -0
- package/llms/tools-mcp.txt +29 -0
- package/llms/tools.2.txt +59 -0
- package/llms/tools.3.txt +1 -0
- package/llms/tools.txt +11 -52
- package/llms/upgrading-0.37.0-0.44.0.txt +444 -0
- package/llms/{upgrading-0.44.0-0.60.0.txt → upgrading-0.44.0-0.61.0.txt} +140 -271
- package/llms/{upgrading-0.61.0-0.74.0.txt → upgrading-0.62.0-0.80.0.txt} +217 -140
- package/llms/{upgrading-0.75.0-0.103.0.txt → upgrading-0.81.0-0.104.0.txt} +154 -217
- package/llms/upgrading.txt +4 -4
- package/llms-full.txt +745 -60
- package/llms.txt +14 -12
- package/native-assets.json +8 -0
- package/package.json +12 -3
- package/dist/index-wykebpn9.js +0 -221
- package/llms/upgrading-0.37.0.txt +0 -176
- package/dist/{index-69nvze1q.js → index-pptyv13k.js} +3 -3
|
@@ -0,0 +1,1145 @@
|
|
|
1
|
+
import {
|
|
2
|
+
toJsonSchema
|
|
3
|
+
} from "./index-ct75c3at.js";
|
|
4
|
+
import {
|
|
5
|
+
AppError2
|
|
6
|
+
} from "./index-x928baz2.js";
|
|
7
|
+
import {
|
|
8
|
+
resolvePropagationContext2
|
|
9
|
+
} from "./index-yex7eg1v.js";
|
|
10
|
+
import {
|
|
11
|
+
toolSurfaceOutputSchema,
|
|
12
|
+
staticInputRounds,
|
|
13
|
+
validateMcpRoundPolicy,
|
|
14
|
+
validateResolvedInputRounds,
|
|
15
|
+
presentationMetadata,
|
|
16
|
+
prepareProjectedMcpTools
|
|
17
|
+
} from "./index-77nxk6ay.js";
|
|
18
|
+
import {
|
|
19
|
+
parseToolCallArguments,
|
|
20
|
+
toolResultFromError,
|
|
21
|
+
observed
|
|
22
|
+
} from "./index-2vnh05w4.js";
|
|
23
|
+
import {
|
|
24
|
+
collectTools2,
|
|
25
|
+
createToolRunner,
|
|
26
|
+
formatToolError
|
|
27
|
+
} from "./index-2hyavjjj.js";
|
|
28
|
+
import {
|
|
29
|
+
redact2
|
|
30
|
+
} from "./index-e6c2spsd.js";
|
|
31
|
+
import {
|
|
32
|
+
DEFAULT_CORS_ALLOW_HEADERS2,
|
|
33
|
+
DEFAULT_PROCESS_SIGNALS,
|
|
34
|
+
defaultSignalSource,
|
|
35
|
+
reportSignalError,
|
|
36
|
+
guardSignalCallback
|
|
37
|
+
} from "./index-yk4g8805.js";
|
|
38
|
+
import {
|
|
39
|
+
collectToolSurface
|
|
40
|
+
} from "./index-a0zzh8ak.js";
|
|
41
|
+
import {
|
|
42
|
+
argumentsDigest
|
|
43
|
+
} from "./index-r7a7r7ha.js";
|
|
44
|
+
import {
|
|
45
|
+
runWithRequestContext2,
|
|
46
|
+
getRequestContext2
|
|
47
|
+
} from "./index-7n68nmc6.js";
|
|
48
|
+
import {
|
|
49
|
+
compareCodeUnits,
|
|
50
|
+
serializeCanonicalJson
|
|
51
|
+
} from "./index-zsvzhjtk.js";
|
|
52
|
+
import {
|
|
53
|
+
isRecord
|
|
54
|
+
} from "./index-9jfdm5tg.js";
|
|
55
|
+
|
|
56
|
+
// src/server/oauth/metadata.ts
|
|
57
|
+
var PROTECTED_RESOURCE_PATH2 = "/.well-known/oauth-protected-resource";
|
|
58
|
+
function metadataPath(resource) {
|
|
59
|
+
const { pathname } = new URL(resource);
|
|
60
|
+
return pathname === "/" ? PROTECTED_RESOURCE_PATH2 : `${PROTECTED_RESOURCE_PATH2}${pathname}`;
|
|
61
|
+
}
|
|
62
|
+
function protectedResourceMetadataUrl2(resource) {
|
|
63
|
+
return `${new URL(resource).origin}${metadataPath(resource)}`;
|
|
64
|
+
}
|
|
65
|
+
function wwwAuthenticateHeader2(resource) {
|
|
66
|
+
return `Bearer resource_metadata="${protectedResourceMetadataUrl2(resource)}"`;
|
|
67
|
+
}
|
|
68
|
+
var PUBLIC_CORS = {
|
|
69
|
+
"Access-Control-Allow-Origin": "*",
|
|
70
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
71
|
+
"Access-Control-Allow-Headers": DEFAULT_CORS_ALLOW_HEADERS2
|
|
72
|
+
};
|
|
73
|
+
function oauthProtectedResourceRoute2(config) {
|
|
74
|
+
const body = JSON.stringify({
|
|
75
|
+
resource: config.resource,
|
|
76
|
+
authorization_servers: config.authorizationServers,
|
|
77
|
+
...config.scopesSupported && { scopes_supported: config.scopesSupported },
|
|
78
|
+
bearer_methods_supported: ["header"]
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
method: "ALL",
|
|
82
|
+
path: metadataPath(config.resource),
|
|
83
|
+
handler: (req) => {
|
|
84
|
+
if (req.method === "OPTIONS")
|
|
85
|
+
return new Response(null, { status: 204, headers: PUBLIC_CORS });
|
|
86
|
+
if (req.method !== "GET")
|
|
87
|
+
return new Response("Method Not Allowed", { status: 405 });
|
|
88
|
+
return new Response(body, {
|
|
89
|
+
status: 200,
|
|
90
|
+
headers: { "Content-Type": "application/json", ...PUBLIC_CORS }
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/tools/mcp/app.ts
|
|
97
|
+
import { readFileSync } from "node:fs";
|
|
98
|
+
import { fileURLToPath } from "node:url";
|
|
99
|
+
var RESOURCE_MIME_TYPE2 = "text/html;profile=mcp-app";
|
|
100
|
+
var EXT_APPS_BUNDLE_PLACEHOLDER2 = "/*__EXT_APPS_BUNDLE__*/";
|
|
101
|
+
function inlineMcpAppBundle2(html) {
|
|
102
|
+
if (!html.includes(EXT_APPS_BUNDLE_PLACEHOLDER2))
|
|
103
|
+
return html;
|
|
104
|
+
let bundlePath;
|
|
105
|
+
try {
|
|
106
|
+
bundlePath = fileURLToPath(import.meta.resolve("@modelcontextprotocol/ext-apps/app-with-deps"));
|
|
107
|
+
} catch {
|
|
108
|
+
throw new Error("[stitchkit] inlineMcpAppBundle: '@modelcontextprotocol/ext-apps' is not installed. " + "Add it as a dependency to serve MCP App widgets.");
|
|
109
|
+
}
|
|
110
|
+
const bundle = readFileSync(bundlePath, "utf8").replace(/export\{([^}]+)\};?\s*$/, (_match, body) => `globalThis.ExtApps={${body.split(",").map((pair) => {
|
|
111
|
+
const [local, exported] = pair.split(" as ").map((s) => s.trim());
|
|
112
|
+
return `${exported ?? local}:${local}`;
|
|
113
|
+
}).join(",")}};`);
|
|
114
|
+
return html.replace(EXT_APPS_BUNDLE_PLACEHOLDER2, () => bundle);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/tools/mcp/catalog.ts
|
|
118
|
+
import { createHash } from "node:crypto";
|
|
119
|
+
var MCP_CATALOG_META_KEY2 = "stitchkit/catalog";
|
|
120
|
+
var stampCache = new WeakMap;
|
|
121
|
+
function toolFingerprint(descriptor) {
|
|
122
|
+
const { mountable } = descriptor;
|
|
123
|
+
return {
|
|
124
|
+
name: mountable.name,
|
|
125
|
+
description: mountable.method.desc,
|
|
126
|
+
input: descriptor.inputSchema,
|
|
127
|
+
output: descriptor.outputSchema ? toJsonSchema(descriptor.outputSchema, "output", "any") : null,
|
|
128
|
+
annotations: mountable.method.annotations ?? null
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function mcpCatalogStamp(surface) {
|
|
132
|
+
const cached = stampCache.get(surface);
|
|
133
|
+
if (cached)
|
|
134
|
+
return cached;
|
|
135
|
+
const descriptors = [
|
|
136
|
+
...surface.contractTools,
|
|
137
|
+
...surface.runtimeTools.map((entry) => entry.descriptor)
|
|
138
|
+
].sort((left, right) => compareCodeUnits(left.mountable.name, right.mountable.name));
|
|
139
|
+
const bytes = serializeCanonicalJson(descriptors.map(toolFingerprint));
|
|
140
|
+
const stamp = {
|
|
141
|
+
digest: createHash("sha256").update(bytes).digest("hex").slice(0, 16),
|
|
142
|
+
tools: descriptors.length
|
|
143
|
+
};
|
|
144
|
+
stampCache.set(surface, stamp);
|
|
145
|
+
return stamp;
|
|
146
|
+
}
|
|
147
|
+
function mcpCatalogMeta2(stamp) {
|
|
148
|
+
return { [MCP_CATALOG_META_KEY2]: { ...stamp } };
|
|
149
|
+
}
|
|
150
|
+
function readMcpCatalogStamp2(meta) {
|
|
151
|
+
if (!isRecord(meta))
|
|
152
|
+
return null;
|
|
153
|
+
const value = meta[MCP_CATALOG_META_KEY2];
|
|
154
|
+
if (!isRecord(value))
|
|
155
|
+
return null;
|
|
156
|
+
const { digest, tools } = value;
|
|
157
|
+
if (typeof digest !== "string" || digest.length === 0)
|
|
158
|
+
return null;
|
|
159
|
+
if (typeof tools !== "number" || !Number.isInteger(tools) || tools < 0)
|
|
160
|
+
return null;
|
|
161
|
+
return { digest, tools };
|
|
162
|
+
}
|
|
163
|
+
function stampToolRegistration(config, stamp) {
|
|
164
|
+
if (!stamp)
|
|
165
|
+
return config;
|
|
166
|
+
return { ...config, _meta: { ...config._meta, ...mcpCatalogMeta2(stamp) } };
|
|
167
|
+
}
|
|
168
|
+
function stampToolResult(result, stamp) {
|
|
169
|
+
if (!stamp)
|
|
170
|
+
return result;
|
|
171
|
+
return { ...result, _meta: { ...result._meta, ...mcpCatalogMeta2(stamp) } };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/tools/mcp/mount.ts
|
|
175
|
+
import {
|
|
176
|
+
createRequestStateCodec,
|
|
177
|
+
McpServer
|
|
178
|
+
} from "@modelcontextprotocol/server";
|
|
179
|
+
|
|
180
|
+
// src/tools/mcp/prepare.ts
|
|
181
|
+
function textBlock(text) {
|
|
182
|
+
return [{ type: "text", text }];
|
|
183
|
+
}
|
|
184
|
+
function formatMcpResult(result, mode, toolName, errorHint) {
|
|
185
|
+
if (result.ok) {
|
|
186
|
+
if (mode === "none")
|
|
187
|
+
return { content: [] };
|
|
188
|
+
let serialized;
|
|
189
|
+
try {
|
|
190
|
+
serialized = JSON.stringify(result.data, null, 2);
|
|
191
|
+
} catch {
|
|
192
|
+
serialized = undefined;
|
|
193
|
+
}
|
|
194
|
+
if (serialized === undefined) {
|
|
195
|
+
const safe = redact2(result.data, { sensitiveKeys: /(?!)/ });
|
|
196
|
+
return { content: textBlock(JSON.stringify(safe, null, 2)), structuredContent: safe };
|
|
197
|
+
}
|
|
198
|
+
return { content: textBlock(serialized), structuredContent: result.data };
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
content: textBlock(JSON.stringify(formatToolError(result, toolName, errorHint), null, 2)),
|
|
202
|
+
isError: true
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function prepareMcpSurface(services, config = {}) {
|
|
206
|
+
const serviceList = Array.isArray(services) ? services : [services];
|
|
207
|
+
const tools = serviceList.flatMap((service) => collectTools2(service, "MCP", config));
|
|
208
|
+
return prepareMcpTools(tools, config);
|
|
209
|
+
}
|
|
210
|
+
function prepareMcpTools(tools, config = {}) {
|
|
211
|
+
const prepared = prepareProjectedMcpTools(tools.map((mountable) => {
|
|
212
|
+
const outputSchema = toolSurfaceOutputSchema(mountable.method);
|
|
213
|
+
return {
|
|
214
|
+
tool: mountable,
|
|
215
|
+
name: mountable.name,
|
|
216
|
+
...mountable.method.paramsSchema !== undefined && {
|
|
217
|
+
paramsSchema: mountable.method.paramsSchema
|
|
218
|
+
},
|
|
219
|
+
...mountable.method.inputSchema !== undefined && {
|
|
220
|
+
inputSchema: mountable.method.inputSchema
|
|
221
|
+
},
|
|
222
|
+
...mountable.method.toolView?.defaults !== undefined && {
|
|
223
|
+
inputDefaults: mountable.method.toolView.defaults
|
|
224
|
+
},
|
|
225
|
+
...outputSchema !== undefined && { outputSchema },
|
|
226
|
+
shouldExtend: mountable.shouldExtend,
|
|
227
|
+
...mountable.method.mcp !== undefined && { mcp: mountable.method.mcp }
|
|
228
|
+
};
|
|
229
|
+
}), config).map((entry) => {
|
|
230
|
+
Object.freeze(entry.tool);
|
|
231
|
+
return Object.freeze({
|
|
232
|
+
mountable: entry.tool,
|
|
233
|
+
inputSchema: entry.inputSchema,
|
|
234
|
+
...entry.outputSchema !== undefined && { outputSchema: entry.outputSchema },
|
|
235
|
+
outputMode: entry.outputSchema === undefined ? "none" : "direct"
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
return Object.freeze(prepared);
|
|
239
|
+
}
|
|
240
|
+
function prepareMcpServerSurface(surface, config = {}) {
|
|
241
|
+
const contractMountables = [];
|
|
242
|
+
const definitions = [];
|
|
243
|
+
const runtimeMountables = [];
|
|
244
|
+
for (const entry of collectToolSurface({
|
|
245
|
+
surface,
|
|
246
|
+
transport: "MCP",
|
|
247
|
+
extend: config.extend,
|
|
248
|
+
flattenUnionInput: config.flattenUnionInput
|
|
249
|
+
})) {
|
|
250
|
+
if (entry.kind === "contract") {
|
|
251
|
+
contractMountables.push(entry.mountable);
|
|
252
|
+
} else {
|
|
253
|
+
definitions.push(entry.definition);
|
|
254
|
+
runtimeMountables.push(entry.mountable);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const contractTools = prepareMcpTools(contractMountables, config);
|
|
258
|
+
const runtimeDescriptors = prepareMcpTools(runtimeMountables, {
|
|
259
|
+
schemaValidation: config.schemaValidation,
|
|
260
|
+
logger: config.logger,
|
|
261
|
+
multiRound: config.multiRound
|
|
262
|
+
});
|
|
263
|
+
const descriptorsByName = new Map(runtimeDescriptors.map((descriptor) => [descriptor.mountable.name, descriptor]));
|
|
264
|
+
const runtimeTools = [];
|
|
265
|
+
for (const definition of definitions) {
|
|
266
|
+
const descriptor = descriptorsByName.get(definition.name);
|
|
267
|
+
if (descriptor)
|
|
268
|
+
runtimeTools.push(Object.freeze({ definition, descriptor }));
|
|
269
|
+
}
|
|
270
|
+
return Object.freeze({ contractTools, runtimeTools: Object.freeze(runtimeTools) });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/tools/mcp/register.ts
|
|
274
|
+
import {
|
|
275
|
+
isCallToolResult
|
|
276
|
+
} from "@modelcontextprotocol/server";
|
|
277
|
+
import { z as z2 } from "zod";
|
|
278
|
+
|
|
279
|
+
// src/tools/mcp/round.ts
|
|
280
|
+
import {
|
|
281
|
+
acceptedContent,
|
|
282
|
+
inputRequired,
|
|
283
|
+
inputResponse
|
|
284
|
+
} from "@modelcontextprotocol/server";
|
|
285
|
+
import { z } from "zod";
|
|
286
|
+
|
|
287
|
+
// src/tools/mcp/round-context.ts
|
|
288
|
+
import {
|
|
289
|
+
CLIENT_INFO_META_KEY,
|
|
290
|
+
PROTOCOL_VERSION_META_KEY
|
|
291
|
+
} from "@modelcontextprotocol/server";
|
|
292
|
+
|
|
293
|
+
// src/tools/mcp/progress.ts
|
|
294
|
+
function mcpProgressToken(context) {
|
|
295
|
+
const token = context.mcpReq._meta?.progressToken;
|
|
296
|
+
return typeof token === "string" || typeof token === "number" ? token : undefined;
|
|
297
|
+
}
|
|
298
|
+
function createMcpProgressReporter(context) {
|
|
299
|
+
const token = mcpProgressToken(context);
|
|
300
|
+
if (token === undefined)
|
|
301
|
+
return async () => {
|
|
302
|
+
return;
|
|
303
|
+
};
|
|
304
|
+
let sent = 0;
|
|
305
|
+
let highWater = 0;
|
|
306
|
+
return async (update) => {
|
|
307
|
+
sent += 1;
|
|
308
|
+
const value = update.progress ?? Math.max(sent, highWater + 1);
|
|
309
|
+
highWater = Math.max(highWater, value);
|
|
310
|
+
try {
|
|
311
|
+
await context.mcpReq.notify({
|
|
312
|
+
method: "notifications/progress",
|
|
313
|
+
params: {
|
|
314
|
+
progressToken: token,
|
|
315
|
+
progress: value,
|
|
316
|
+
...update.total !== undefined && { total: update.total },
|
|
317
|
+
...update.message !== undefined && { message: update.message }
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
} catch {}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/tools/mcp/round-context.ts
|
|
325
|
+
function transportContext(context, toolName, outcome, round) {
|
|
326
|
+
const protocolVersionValue = isRecord(context.mcpReq.envelope) ? Reflect.get(context.mcpReq.envelope, PROTOCOL_VERSION_META_KEY) : undefined;
|
|
327
|
+
const protocolVersion = typeof protocolVersionValue === "string" ? protocolVersionValue : undefined;
|
|
328
|
+
const clientInfoValue = isRecord(context.mcpReq.envelope) ? Reflect.get(context.mcpReq.envelope, CLIENT_INFO_META_KEY) : undefined;
|
|
329
|
+
const clientInfo = isRecord(clientInfoValue) && typeof clientInfoValue.name === "string" && typeof clientInfoValue.version === "string" ? { name: clientInfoValue.name, version: clientInfoValue.version } : undefined;
|
|
330
|
+
const token = mcpProgressToken(context);
|
|
331
|
+
return {
|
|
332
|
+
signal: context.mcpReq.signal,
|
|
333
|
+
mcp: {
|
|
334
|
+
era: context.mcpReq.envelope ? "modern" : "legacy",
|
|
335
|
+
method: context.mcpReq.method,
|
|
336
|
+
toolName,
|
|
337
|
+
...protocolVersion !== undefined && { protocolVersion },
|
|
338
|
+
...clientInfo !== undefined && { clientInfo },
|
|
339
|
+
...outcome !== undefined && { outcome },
|
|
340
|
+
...round !== undefined && { round },
|
|
341
|
+
...token !== undefined && { progressToken: token }
|
|
342
|
+
},
|
|
343
|
+
reportProgress: createMcpProgressReporter(context)
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function operationIdentity(tool) {
|
|
347
|
+
return {
|
|
348
|
+
toolName: tool.name,
|
|
349
|
+
serviceName: tool.method.serviceName,
|
|
350
|
+
action: tool.method.key,
|
|
351
|
+
method: tool.method.method,
|
|
352
|
+
...tool.method.scope !== undefined && { scope: tool.method.scope }
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function sameIdentity(left, right) {
|
|
356
|
+
return left.toolName === right.toolName && left.serviceName === right.serviceName && left.action === right.action && left.method === right.method && left.scope === right.scope;
|
|
357
|
+
}
|
|
358
|
+
async function runRoundSuccess(tool, rawArgs, runTool, context) {
|
|
359
|
+
return runTool({
|
|
360
|
+
...tool,
|
|
361
|
+
method: {
|
|
362
|
+
...tool.method,
|
|
363
|
+
outputSchema: undefined,
|
|
364
|
+
handler: () => {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}, rawArgs, context);
|
|
369
|
+
}
|
|
370
|
+
async function runRoundFailure(tool, rawArgs, runTool, context, code, message) {
|
|
371
|
+
return runTool({
|
|
372
|
+
...tool,
|
|
373
|
+
method: {
|
|
374
|
+
...tool.method,
|
|
375
|
+
outputSchema: undefined,
|
|
376
|
+
handler: () => {
|
|
377
|
+
throw new AppError2(code, message, 400);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}, rawArgs, context);
|
|
381
|
+
}
|
|
382
|
+
async function failedResolution(options) {
|
|
383
|
+
const result = await runRoundFailure(options.tool, options.rawArgs, options.runTool, options.context, options.code, options.message);
|
|
384
|
+
return { kind: "response", response: options.formatFailure(result) };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/tools/mcp/round.ts
|
|
388
|
+
function roundPlanDigest(requests) {
|
|
389
|
+
return argumentsDigest({
|
|
390
|
+
plan: requests.map((request) => ({
|
|
391
|
+
key: request.key,
|
|
392
|
+
message: request.message,
|
|
393
|
+
schema: z.toJSONSchema(request.schema, { io: "input", unrepresentable: "any" })
|
|
394
|
+
}))
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
async function resolveRequests(options, policy) {
|
|
398
|
+
const declared = staticInputRounds(policy);
|
|
399
|
+
if (declared)
|
|
400
|
+
return { requests: declared, guarded: false };
|
|
401
|
+
const parsed = parseToolCallArguments(options.tool.method, options.rawArgs, {
|
|
402
|
+
coerceJson: options.coerceJsonArgs ?? true,
|
|
403
|
+
toolSurface: true
|
|
404
|
+
});
|
|
405
|
+
if (!parsed.ok) {
|
|
406
|
+
return { requests: [], guarded: false };
|
|
407
|
+
}
|
|
408
|
+
const resolver = policy.inputRequired;
|
|
409
|
+
const resolved = await resolver({ params: parsed.params, input: parsed.input });
|
|
410
|
+
validateResolvedInputRounds(options.tool, resolved, options.runtime?.maxRounds ?? 1);
|
|
411
|
+
return { requests: resolved, guarded: false };
|
|
412
|
+
}
|
|
413
|
+
async function askRound(runtime, context, request, state) {
|
|
414
|
+
const requestState = await runtime.codec.mint(state, context);
|
|
415
|
+
return {
|
|
416
|
+
kind: "response",
|
|
417
|
+
response: inputRequired({
|
|
418
|
+
inputRequests: {
|
|
419
|
+
[request.key]: inputRequired.elicit({
|
|
420
|
+
message: request.message,
|
|
421
|
+
requestedSchema: request.schema
|
|
422
|
+
})
|
|
423
|
+
},
|
|
424
|
+
requestState
|
|
425
|
+
})
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function readRoundAnswer(context, request) {
|
|
429
|
+
const view = inputResponse(context.mcpReq.inputResponses, request.key);
|
|
430
|
+
if (view.kind !== "elicit") {
|
|
431
|
+
return {
|
|
432
|
+
refusal: {
|
|
433
|
+
outcome: "invalid",
|
|
434
|
+
code: "INVALID_INPUT_RESPONSE",
|
|
435
|
+
message: "Expected an elicitation response for the current round"
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
if (view.action !== "accept") {
|
|
440
|
+
const declined = view.action === "decline";
|
|
441
|
+
return {
|
|
442
|
+
refusal: {
|
|
443
|
+
outcome: declined ? "declined" : "cancelled",
|
|
444
|
+
code: declined ? "INPUT_DECLINED" : "INPUT_CANCELLED",
|
|
445
|
+
message: declined ? "Required input was declined" : "Required input was cancelled"
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
const content = acceptedContent(context.mcpReq.inputResponses, request.key, request.schema);
|
|
450
|
+
if (!content) {
|
|
451
|
+
return {
|
|
452
|
+
refusal: {
|
|
453
|
+
outcome: "invalid",
|
|
454
|
+
code: "INVALID_INPUT_RESPONSE",
|
|
455
|
+
message: "Accepted input failed its declared schema"
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
return { content };
|
|
460
|
+
}
|
|
461
|
+
async function resolveMcpRound(options) {
|
|
462
|
+
const { policy } = options;
|
|
463
|
+
if (!policy) {
|
|
464
|
+
return {
|
|
465
|
+
kind: "continue",
|
|
466
|
+
context: transportContext(options.context, options.tool.name)
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
if (!options.runtime) {
|
|
470
|
+
throw new Error(`[stitchkit] MCP tool "${options.tool.name}" declares inputRequired but no multiRound.state key is configured`);
|
|
471
|
+
}
|
|
472
|
+
validateMcpRoundPolicy(options.tool, policy, {
|
|
473
|
+
stateConfigured: true,
|
|
474
|
+
maxRounds: options.runtime.maxRounds
|
|
475
|
+
});
|
|
476
|
+
const state = options.context.mcpReq.requestState();
|
|
477
|
+
const digest = argumentsDigest(options.rawArgs);
|
|
478
|
+
if (state) {
|
|
479
|
+
const mismatched = !sameIdentity(state.identity, operationIdentity(options.tool)) || state.argumentsDigest !== digest || !Number.isInteger(state.round) || state.round < 0 || state.round >= options.runtime.maxRounds || !isRecord(state.accepted);
|
|
480
|
+
if (mismatched) {
|
|
481
|
+
return failedResolution({
|
|
482
|
+
...options,
|
|
483
|
+
context: transportContext(options.context, options.tool.name, "invalid", Number.isInteger(state.round) ? state.round : 0),
|
|
484
|
+
code: "INVALID_REQUEST_STATE",
|
|
485
|
+
message: "Continuation state does not match this operation, arguments or round"
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
const dynamic = staticInputRounds(policy) === undefined;
|
|
490
|
+
if (dynamic) {
|
|
491
|
+
const guarded = await runRoundSuccess(options.tool, options.rawArgs, options.runTool, transportContext(options.context, options.tool.name, "input_required", state?.round ?? 0));
|
|
492
|
+
if (!guarded.ok)
|
|
493
|
+
return { kind: "response", response: options.formatFailure(guarded) };
|
|
494
|
+
}
|
|
495
|
+
const resolution = await resolveRequests(options, policy);
|
|
496
|
+
if ("failure" in resolution)
|
|
497
|
+
return resolution.failure;
|
|
498
|
+
const requests = resolution.requests;
|
|
499
|
+
const alreadyGuarded = dynamic;
|
|
500
|
+
const planDigest = roundPlanDigest(requests);
|
|
501
|
+
if (requests.length === 0 && !state) {
|
|
502
|
+
return {
|
|
503
|
+
kind: "continue",
|
|
504
|
+
context: transportContext(options.context, options.tool.name)
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
if (!state) {
|
|
508
|
+
if (isRecord(options.context.mcpReq.inputResponses)) {
|
|
509
|
+
return failedResolution({
|
|
510
|
+
...options,
|
|
511
|
+
context: transportContext(options.context, options.tool.name, "invalid", 0),
|
|
512
|
+
code: "INVALID_REQUEST_STATE",
|
|
513
|
+
message: "Input responses require a valid continuation state"
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
if (!alreadyGuarded) {
|
|
517
|
+
const guarded = await runRoundSuccess(options.tool, options.rawArgs, options.runTool, transportContext(options.context, options.tool.name, "input_required", 0));
|
|
518
|
+
if (!guarded.ok) {
|
|
519
|
+
return { kind: "response", response: options.formatFailure(guarded) };
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const request = requests[0];
|
|
523
|
+
if (!request)
|
|
524
|
+
throw new Error("[stitchkit] validated MRTR policy has no first round");
|
|
525
|
+
return askRound(options.runtime, options.context, request, {
|
|
526
|
+
identity: operationIdentity(options.tool),
|
|
527
|
+
argumentsDigest: digest,
|
|
528
|
+
round: 0,
|
|
529
|
+
accepted: {},
|
|
530
|
+
planDigest
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
if (state.planDigest !== planDigest || state.round >= requests.length) {
|
|
534
|
+
return failedResolution({
|
|
535
|
+
...options,
|
|
536
|
+
context: transportContext(options.context, options.tool.name, "invalid", state.round),
|
|
537
|
+
code: "INVALID_REQUEST_STATE",
|
|
538
|
+
message: state.round >= requests.length ? "Continuation state does not match this operation, arguments or round" : "The questions for this call changed between rounds"
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
const request = requests[state.round];
|
|
542
|
+
if (!request)
|
|
543
|
+
throw new Error("[stitchkit] validated MRTR state points outside its policy");
|
|
544
|
+
const answer = readRoundAnswer(options.context, request);
|
|
545
|
+
if ("refusal" in answer) {
|
|
546
|
+
return failedResolution({
|
|
547
|
+
...options,
|
|
548
|
+
context: transportContext(options.context, options.tool.name, answer.refusal.outcome, state.round),
|
|
549
|
+
code: answer.refusal.code,
|
|
550
|
+
message: answer.refusal.message
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
const { content } = answer;
|
|
554
|
+
const accepted = { ...state.accepted, [request.key]: content };
|
|
555
|
+
const nextRound = state.round + 1;
|
|
556
|
+
const nextRequest = requests[nextRound];
|
|
557
|
+
if (nextRequest) {
|
|
558
|
+
if (!alreadyGuarded) {
|
|
559
|
+
const guarded = await runRoundSuccess(options.tool, options.rawArgs, options.runTool, transportContext(options.context, options.tool.name, "input_required", nextRound));
|
|
560
|
+
if (!guarded.ok) {
|
|
561
|
+
return { kind: "response", response: options.formatFailure(guarded) };
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return askRound(options.runtime, options.context, nextRequest, {
|
|
565
|
+
identity: state.identity,
|
|
566
|
+
argumentsDigest: state.argumentsDigest,
|
|
567
|
+
round: nextRound,
|
|
568
|
+
accepted,
|
|
569
|
+
planDigest
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
kind: "continue",
|
|
574
|
+
context: {
|
|
575
|
+
...transportContext(options.context, options.tool.name, "complete", state.round),
|
|
576
|
+
mcpInput: accepted
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/tools/mcp/trace.ts
|
|
582
|
+
import {
|
|
583
|
+
BAGGAGE_META_KEY,
|
|
584
|
+
TRACEPARENT_META_KEY,
|
|
585
|
+
TRACESTATE_META_KEY
|
|
586
|
+
} from "@modelcontextprotocol/server";
|
|
587
|
+
function runInMcpRequestContext(context, toolName, body) {
|
|
588
|
+
const ambient = getRequestContext2();
|
|
589
|
+
const metadata = context.mcpReq._meta;
|
|
590
|
+
const propagation = metadata ? {
|
|
591
|
+
traceparent: metadata[TRACEPARENT_META_KEY],
|
|
592
|
+
tracestate: metadata[TRACESTATE_META_KEY],
|
|
593
|
+
baggage: metadata[BAGGAGE_META_KEY]
|
|
594
|
+
} : undefined;
|
|
595
|
+
const request = {
|
|
596
|
+
...ambient ?? {
|
|
597
|
+
source: "mcp",
|
|
598
|
+
method: "MCP",
|
|
599
|
+
path: `/mcp/${toolName}`,
|
|
600
|
+
startedAt: process.hrtime.bigint()
|
|
601
|
+
},
|
|
602
|
+
trace: resolvePropagationContext2(propagation, ambient?.trace),
|
|
603
|
+
source: "mcp",
|
|
604
|
+
method: "MCP",
|
|
605
|
+
path: `/mcp/${toolName}`,
|
|
606
|
+
startedAt: process.hrtime.bigint(),
|
|
607
|
+
dimensions: ambient?.dimensions ? { ...ambient.dimensions } : undefined,
|
|
608
|
+
error: undefined
|
|
609
|
+
};
|
|
610
|
+
return runWithRequestContext2(request, body);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/tools/mcp/register.ts
|
|
614
|
+
function registerMcpTool(server, presentation, descriptor, config) {
|
|
615
|
+
const { mountable } = descriptor;
|
|
616
|
+
const toolConfig = {
|
|
617
|
+
description: presentation.description,
|
|
618
|
+
inputSchema: z2.looseObject({}).meta(presentationMetadata(descriptor.inputSchema))
|
|
619
|
+
};
|
|
620
|
+
if (descriptor.outputSchema)
|
|
621
|
+
toolConfig.outputSchema = descriptor.outputSchema;
|
|
622
|
+
if (presentation.annotations)
|
|
623
|
+
toolConfig.annotations = presentation.annotations;
|
|
624
|
+
if (presentation.ui) {
|
|
625
|
+
toolConfig._meta = {
|
|
626
|
+
ui: presentation.ui,
|
|
627
|
+
"ui/resourceUri": presentation.ui.resourceUri
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
const { name } = presentation;
|
|
631
|
+
const format = config.formatResult;
|
|
632
|
+
server.registerTool(name, stampToolRegistration(toolConfig, config.catalog), async (rawArgs, mcpContext) => stampToolResult(await runInMcpRequestContext(mcpContext, name, async () => {
|
|
633
|
+
const args = isRecord(rawArgs) ? rawArgs : {};
|
|
634
|
+
const startedAt = Date.now();
|
|
635
|
+
try {
|
|
636
|
+
const round = await resolveMcpRound({
|
|
637
|
+
tool: mountable,
|
|
638
|
+
rawArgs: args,
|
|
639
|
+
context: mcpContext,
|
|
640
|
+
policy: mountable.method.mcp,
|
|
641
|
+
runtime: config.multiRoundRuntime,
|
|
642
|
+
runTool: config.runTool,
|
|
643
|
+
formatFailure: (result) => format(result, "none", name)
|
|
644
|
+
});
|
|
645
|
+
if (round.kind === "response")
|
|
646
|
+
return round.response;
|
|
647
|
+
const { present } = presentation;
|
|
648
|
+
if (!present) {
|
|
649
|
+
const result = await config.runTool(mountable, args, round.context);
|
|
650
|
+
return format(result, descriptor.outputMode, name);
|
|
651
|
+
}
|
|
652
|
+
const result = await config.runTool(mountable, args, round.context, {
|
|
653
|
+
finalizeOutput: async (data) => {
|
|
654
|
+
const presented = await present(data);
|
|
655
|
+
if (!isRecord(presented)) {
|
|
656
|
+
throw new AppError2("INTERNAL_SERVER_ERROR", "Runtime MCP presenter did not return a result", 500);
|
|
657
|
+
}
|
|
658
|
+
if ("structuredContent" in presented || "isError" in presented) {
|
|
659
|
+
throw new AppError2("INTERNAL_SERVER_ERROR", "Runtime MCP presenter cannot set framework-owned structuredContent or isError", 500);
|
|
660
|
+
}
|
|
661
|
+
const structured = format({ ok: true, data }, descriptor.outputMode, name).structuredContent;
|
|
662
|
+
const response = structured === undefined ? presented : { ...presented, structuredContent: structured };
|
|
663
|
+
if (!isCallToolResult(response)) {
|
|
664
|
+
throw new AppError2("INTERNAL_SERVER_ERROR", "Runtime MCP presenter returned an invalid CallToolResult", 500);
|
|
665
|
+
}
|
|
666
|
+
return response;
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
if (!result.ok)
|
|
670
|
+
return format(result, "none", name);
|
|
671
|
+
if (!isCallToolResult(result.data)) {
|
|
672
|
+
throw new Error("[stitchkit] Runtime MCP presenter invariant failed");
|
|
673
|
+
}
|
|
674
|
+
return result.data;
|
|
675
|
+
} catch (err) {
|
|
676
|
+
const context = {
|
|
677
|
+
...config.context,
|
|
678
|
+
...transportContext(mcpContext, name),
|
|
679
|
+
source: "mcp"
|
|
680
|
+
};
|
|
681
|
+
const result = toolResultFromError(err);
|
|
682
|
+
await observed("onToolError", () => config.hooks?.onToolError?.({
|
|
683
|
+
toolName: name,
|
|
684
|
+
error: err,
|
|
685
|
+
context,
|
|
686
|
+
endpoint: mountable.method
|
|
687
|
+
}));
|
|
688
|
+
await observed("afterToolCall", () => config.hooks?.afterToolCall?.({
|
|
689
|
+
toolName: name,
|
|
690
|
+
args,
|
|
691
|
+
result,
|
|
692
|
+
durationMs: Date.now() - startedAt,
|
|
693
|
+
error: err,
|
|
694
|
+
context,
|
|
695
|
+
endpoint: mountable.method
|
|
696
|
+
}));
|
|
697
|
+
return format(result, "none", name);
|
|
698
|
+
}
|
|
699
|
+
}), config.catalog));
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/tools/mcp/runtime-tools.ts
|
|
703
|
+
function mountPreparedRuntimeMcp(server, tools, config) {
|
|
704
|
+
const runTool = createToolRunner({
|
|
705
|
+
source: "mcp",
|
|
706
|
+
toolSurface: true,
|
|
707
|
+
context: config.context,
|
|
708
|
+
hooks: config.hooks,
|
|
709
|
+
lifecycle: config.lifecycle,
|
|
710
|
+
coerceJsonArgs: config.coerceJsonArgs,
|
|
711
|
+
onOutputStrip: config.onOutputStrip
|
|
712
|
+
});
|
|
713
|
+
for (const { definition, descriptor } of tools) {
|
|
714
|
+
const present = definition.present?.mcp;
|
|
715
|
+
registerMcpTool(server, {
|
|
716
|
+
name: definition.name,
|
|
717
|
+
description: definition.description,
|
|
718
|
+
annotations: definition.annotations,
|
|
719
|
+
ui: definition.ui,
|
|
720
|
+
...present && { present }
|
|
721
|
+
}, descriptor, {
|
|
722
|
+
runTool,
|
|
723
|
+
formatResult: config.formatResult,
|
|
724
|
+
catalog: config.catalog,
|
|
725
|
+
multiRoundRuntime: config.multiRoundRuntime,
|
|
726
|
+
hooks: config.hooks,
|
|
727
|
+
context: config.context
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// src/tools/mcp/mount.ts
|
|
733
|
+
function validateMcpSchemas2(config) {
|
|
734
|
+
prepareMcpSurface(config.services, {
|
|
735
|
+
extend: config.extend,
|
|
736
|
+
flattenUnionInput: config.flattenUnionInput,
|
|
737
|
+
schemaValidation: config,
|
|
738
|
+
logger: config.logger
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
function mountMcp2(mcpServer, services, config = {}) {
|
|
742
|
+
const prepared = prepareMcpSurface(services, {
|
|
743
|
+
extend: config.extend,
|
|
744
|
+
flattenUnionInput: config.flattenUnionInput,
|
|
745
|
+
schemaValidation: config.schemaValidation,
|
|
746
|
+
logger: config.logger
|
|
747
|
+
});
|
|
748
|
+
mountPreparedMcp(mcpServer, prepared, {
|
|
749
|
+
...config,
|
|
750
|
+
catalog: mcpCatalogStamp({ contractTools: prepared, runtimeTools: [] })
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
function mountPreparedMcp(mcpServer, prepared, config = {}) {
|
|
754
|
+
const runTool = createToolRunner({
|
|
755
|
+
source: "mcp",
|
|
756
|
+
toolSurface: true,
|
|
757
|
+
extend: config.extend,
|
|
758
|
+
context: config.context,
|
|
759
|
+
hooks: config.hooks,
|
|
760
|
+
lifecycle: config.lifecycle,
|
|
761
|
+
coerceJsonArgs: config.coerceJsonArgs,
|
|
762
|
+
onOutputStrip: config.onOutputStrip
|
|
763
|
+
});
|
|
764
|
+
for (const descriptor of prepared) {
|
|
765
|
+
const { method, name } = descriptor.mountable;
|
|
766
|
+
registerMcpTool(mcpServer, {
|
|
767
|
+
name,
|
|
768
|
+
description: method.desc,
|
|
769
|
+
annotations: method.annotations,
|
|
770
|
+
ui: method.ui
|
|
771
|
+
}, descriptor, {
|
|
772
|
+
runTool,
|
|
773
|
+
formatResult: (result, mode, toolName) => formatMcpResult(result, mode, toolName, config.errorHint),
|
|
774
|
+
catalog: config.catalog,
|
|
775
|
+
multiRoundRuntime: config.multiRoundRuntime,
|
|
776
|
+
hooks: config.hooks,
|
|
777
|
+
context: config.context
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
function buildMcpServer2(config, auth) {
|
|
782
|
+
if (!isMcpServerBuildConfig(config)) {
|
|
783
|
+
throw new TypeError("[stitchkit] buildMcpServer requires a valid config object");
|
|
784
|
+
}
|
|
785
|
+
let surface;
|
|
786
|
+
if (config.surfaces) {
|
|
787
|
+
const key = config.selectSurface(auth);
|
|
788
|
+
if (!Object.hasOwn(config.surfaces, key)) {
|
|
789
|
+
throw new Error(`[stitchkit] Unknown MCP surface "${key}"`);
|
|
790
|
+
}
|
|
791
|
+
const selected = config.surfaces[key];
|
|
792
|
+
if (!selected)
|
|
793
|
+
throw new Error(`[stitchkit] Unknown MCP surface "${key}"`);
|
|
794
|
+
surface = selected;
|
|
795
|
+
} else {
|
|
796
|
+
const services = typeof config.services === "function" ? config.services(auth) : config.services;
|
|
797
|
+
const runtimeTools = typeof config.runtimeTools === "function" ? config.runtimeTools(auth) : config.runtimeTools;
|
|
798
|
+
surface = { services, runtimeTools };
|
|
799
|
+
}
|
|
800
|
+
const prepared = prepareMcpServerSurface(surface, {
|
|
801
|
+
extend: config.extend,
|
|
802
|
+
flattenUnionInput: config.flattenUnionInput,
|
|
803
|
+
schemaValidation: config.schemaValidation,
|
|
804
|
+
logger: config.logger,
|
|
805
|
+
multiRound: {
|
|
806
|
+
stateConfigured: config.multiRound !== undefined,
|
|
807
|
+
maxRounds: config.multiRound?.serving?.maxRounds ?? 10
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
return buildMcpServerFromPrepared(config, auth, prepared);
|
|
811
|
+
}
|
|
812
|
+
function isMcpServerBuildConfig(value) {
|
|
813
|
+
return isRecord(value) && isRecord(value.serverInfo);
|
|
814
|
+
}
|
|
815
|
+
function buildMcpServerFromPrepared(config, auth, prepared) {
|
|
816
|
+
let roundCodec;
|
|
817
|
+
if (config.multiRound) {
|
|
818
|
+
const principal = config.multiRound.state.principal(auth);
|
|
819
|
+
roundCodec = createRequestStateCodec({
|
|
820
|
+
key: config.multiRound.state.key,
|
|
821
|
+
ttlSeconds: config.multiRound.state.ttlSeconds,
|
|
822
|
+
bind: (mcpContext) => `${mcpContext.mcpReq.method}\x00${principal}`
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
const dynamicSurface = !config.surfaces && (typeof config.services === "function" || typeof config.runtimeTools === "function");
|
|
826
|
+
const serverOptions = {
|
|
827
|
+
...config.instructions !== undefined && { instructions: config.instructions },
|
|
828
|
+
...!dynamicSurface && config.cache?.operations !== undefined && {
|
|
829
|
+
cacheHints: config.cache.operations
|
|
830
|
+
},
|
|
831
|
+
...config.multiRound?.serving !== undefined && {
|
|
832
|
+
inputRequired: config.multiRound.serving
|
|
833
|
+
},
|
|
834
|
+
...roundCodec !== undefined && {
|
|
835
|
+
requestState: { verify: roundCodec.verify }
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
const server = new McpServer(config.serverInfo, serverOptions);
|
|
839
|
+
const context = config.context?.(auth);
|
|
840
|
+
const catalog = mcpCatalogStamp(prepared);
|
|
841
|
+
mountPreparedMcp(server, prepared.contractTools, {
|
|
842
|
+
catalog,
|
|
843
|
+
context,
|
|
844
|
+
hooks: config.hooks,
|
|
845
|
+
lifecycle: config.lifecycle,
|
|
846
|
+
extend: config.extend,
|
|
847
|
+
schemaValidation: config.schemaValidation,
|
|
848
|
+
logger: config.logger,
|
|
849
|
+
coerceJsonArgs: config.coerceJsonArgs,
|
|
850
|
+
flattenUnionInput: config.flattenUnionInput,
|
|
851
|
+
errorHint: config.errorHint,
|
|
852
|
+
onOutputStrip: config.onOutputStrip,
|
|
853
|
+
...roundCodec !== undefined && {
|
|
854
|
+
multiRoundRuntime: {
|
|
855
|
+
codec: roundCodec,
|
|
856
|
+
maxRounds: config.multiRound?.serving?.maxRounds ?? 10
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
mountPreparedRuntimeMcp(server, prepared.runtimeTools, {
|
|
861
|
+
catalog,
|
|
862
|
+
context,
|
|
863
|
+
hooks: config.hooks,
|
|
864
|
+
lifecycle: config.lifecycle,
|
|
865
|
+
coerceJsonArgs: config.coerceJsonArgs,
|
|
866
|
+
onOutputStrip: config.onOutputStrip,
|
|
867
|
+
formatResult: (result, mode, toolName) => formatMcpResult(result, mode, toolName, config.errorHint),
|
|
868
|
+
...roundCodec !== undefined && {
|
|
869
|
+
multiRoundRuntime: {
|
|
870
|
+
codec: roundCodec,
|
|
871
|
+
maxRounds: config.multiRound?.serving?.maxRounds ?? 10
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
config.rawTools?.(server, auth);
|
|
876
|
+
for (const resource of config.resources ?? []) {
|
|
877
|
+
mountMcpResource2(server, resource);
|
|
878
|
+
}
|
|
879
|
+
return server;
|
|
880
|
+
}
|
|
881
|
+
function mountMcpResource2(server, resource) {
|
|
882
|
+
server.registerResource(resource.name, resource.uri, {
|
|
883
|
+
mimeType: resource.mimeType ?? RESOURCE_MIME_TYPE2,
|
|
884
|
+
...resource.cacheHint !== undefined && { cacheHint: resource.cacheHint }
|
|
885
|
+
}, async () => ({
|
|
886
|
+
contents: [
|
|
887
|
+
{
|
|
888
|
+
uri: resource.uri,
|
|
889
|
+
mimeType: resource.mimeType ?? RESOURCE_MIME_TYPE2,
|
|
890
|
+
text: await resource.read(),
|
|
891
|
+
...resource.ui && { _meta: { ui: resource.ui } }
|
|
892
|
+
}
|
|
893
|
+
]
|
|
894
|
+
}));
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// src/tools/mcp/handler.ts
|
|
898
|
+
import {
|
|
899
|
+
createMcpHandler as createSdkMcpHandler,
|
|
900
|
+
hostHeaderValidationResponse,
|
|
901
|
+
localhostAllowedHostnames,
|
|
902
|
+
localhostAllowedOrigins,
|
|
903
|
+
originValidationResponse
|
|
904
|
+
} from "@modelcontextprotocol/server";
|
|
905
|
+
function jsonRpcError(code, message, status, headers) {
|
|
906
|
+
return Response.json({ jsonrpc: "2.0", error: { code, message }, id: null }, { status, headers });
|
|
907
|
+
}
|
|
908
|
+
function hostValidationResponse(request, allowedHosts) {
|
|
909
|
+
if (request.headers.has("host")) {
|
|
910
|
+
return hostHeaderValidationResponse(request, [...allowedHosts]);
|
|
911
|
+
}
|
|
912
|
+
const hostname = new URL(request.url).hostname;
|
|
913
|
+
if (allowedHosts.includes(hostname))
|
|
914
|
+
return;
|
|
915
|
+
return jsonRpcError(-32000, "Invalid Host header", 403);
|
|
916
|
+
}
|
|
917
|
+
function prepareStaticSurface(config) {
|
|
918
|
+
const preparation = {
|
|
919
|
+
logger: config.logger,
|
|
920
|
+
extend: config.extend,
|
|
921
|
+
flattenUnionInput: config.flattenUnionInput,
|
|
922
|
+
schemaValidation: config.schemaValidation,
|
|
923
|
+
multiRound: {
|
|
924
|
+
stateConfigured: config.multiRound !== undefined,
|
|
925
|
+
maxRounds: config.multiRound?.serving?.maxRounds ?? 10
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
if (config.surfaces && config.selectSurface) {
|
|
929
|
+
const preparedByKey = new Map;
|
|
930
|
+
const preparedBySurface = new WeakMap;
|
|
931
|
+
for (const key in config.surfaces) {
|
|
932
|
+
if (!Object.hasOwn(config.surfaces, key))
|
|
933
|
+
continue;
|
|
934
|
+
const surface = config.surfaces[key];
|
|
935
|
+
if (!surface)
|
|
936
|
+
continue;
|
|
937
|
+
const existing = preparedBySurface.get(surface);
|
|
938
|
+
const prepared = existing ?? prepareMcpServerSurface(surface, preparation);
|
|
939
|
+
if (!existing)
|
|
940
|
+
preparedBySurface.set(surface, prepared);
|
|
941
|
+
preparedByKey.set(key, prepared);
|
|
942
|
+
}
|
|
943
|
+
return (auth) => {
|
|
944
|
+
const key = config.selectSurface(auth);
|
|
945
|
+
const prepared = preparedByKey.get(key);
|
|
946
|
+
if (!prepared)
|
|
947
|
+
throw new Error(`[stitchkit] Unknown MCP surface "${key}"`);
|
|
948
|
+
return buildMcpServerFromPrepared(config, auth, prepared);
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
if (config.services && typeof config.services !== "function" && typeof config.runtimeTools !== "function") {
|
|
952
|
+
const prepared = prepareMcpServerSurface({ services: config.services, runtimeTools: config.runtimeTools }, preparation);
|
|
953
|
+
return (auth) => buildMcpServerFromPrepared(config, auth, prepared);
|
|
954
|
+
}
|
|
955
|
+
return null;
|
|
956
|
+
}
|
|
957
|
+
function createMcpHandler2(config) {
|
|
958
|
+
const buildPrepared = prepareStaticSurface(config);
|
|
959
|
+
const authByCarrier = new WeakMap;
|
|
960
|
+
let closed = false;
|
|
961
|
+
let resolveClosed;
|
|
962
|
+
const closedSignal = new Promise((resolve) => {
|
|
963
|
+
resolveClosed = resolve;
|
|
964
|
+
});
|
|
965
|
+
const sdkHandler = createSdkMcpHandler(({ authInfo }) => {
|
|
966
|
+
const resolved = authInfo ? authByCarrier.get(authInfo) : undefined;
|
|
967
|
+
if (!resolved) {
|
|
968
|
+
throw new Error("[stitchkit] MCP request reached the SDK without resolved auth");
|
|
969
|
+
}
|
|
970
|
+
const auth = resolved.value;
|
|
971
|
+
return buildPrepared ? buildPrepared(auth) : buildMcpServer2(config, auth);
|
|
972
|
+
}, {
|
|
973
|
+
legacy: config.legacy === "reject" ? "reject" : "stateless",
|
|
974
|
+
maxSubscriptions: 0
|
|
975
|
+
});
|
|
976
|
+
const unauthorized = () => {
|
|
977
|
+
const headers = config.protectedResource ? { "WWW-Authenticate": wwwAuthenticateHeader2(config.protectedResource.resource) } : undefined;
|
|
978
|
+
return jsonRpcError(-32001, "Authorization required", 401, headers);
|
|
979
|
+
};
|
|
980
|
+
const observeRejection = (request, response) => {
|
|
981
|
+
if (response.status < 400 || !config.onTransportRejected)
|
|
982
|
+
return response;
|
|
983
|
+
Promise.resolve(config.onTransportRejected({ request, response: response.clone() })).catch(() => {
|
|
984
|
+
return;
|
|
985
|
+
});
|
|
986
|
+
return response;
|
|
987
|
+
};
|
|
988
|
+
return {
|
|
989
|
+
fetch: async (request) => {
|
|
990
|
+
if (closed) {
|
|
991
|
+
return observeRejection(request, jsonRpcError(-32000, "MCP handler is closed", 503));
|
|
992
|
+
}
|
|
993
|
+
const allowedHosts = config.security?.allowedHosts ?? localhostAllowedHostnames();
|
|
994
|
+
const hostRejection = hostValidationResponse(request, allowedHosts);
|
|
995
|
+
if (hostRejection)
|
|
996
|
+
return observeRejection(request, hostRejection);
|
|
997
|
+
const allowedOrigins = config.security?.allowedOrigins ?? localhostAllowedOrigins();
|
|
998
|
+
const originRejection = originValidationResponse(request, [...allowedOrigins]);
|
|
999
|
+
if (originRejection)
|
|
1000
|
+
return observeRejection(request, originRejection);
|
|
1001
|
+
const auth = await Promise.race([
|
|
1002
|
+
Promise.resolve(config.auth(request)),
|
|
1003
|
+
closedSignal.then(() => null)
|
|
1004
|
+
]);
|
|
1005
|
+
if (closed) {
|
|
1006
|
+
return observeRejection(request, jsonRpcError(-32000, "MCP handler is closed", 503));
|
|
1007
|
+
}
|
|
1008
|
+
if (auth == null)
|
|
1009
|
+
return observeRejection(request, unauthorized());
|
|
1010
|
+
const authCarrier = {
|
|
1011
|
+
token: "",
|
|
1012
|
+
clientId: config.multiRound?.state.principal(auth) ?? "stitchkit",
|
|
1013
|
+
scopes: []
|
|
1014
|
+
};
|
|
1015
|
+
authByCarrier.set(authCarrier, { value: auth });
|
|
1016
|
+
try {
|
|
1017
|
+
return observeRejection(request, await sdkHandler.fetch(request, { authInfo: authCarrier }));
|
|
1018
|
+
} finally {
|
|
1019
|
+
authByCarrier.delete(authCarrier);
|
|
1020
|
+
}
|
|
1021
|
+
},
|
|
1022
|
+
close: async () => {
|
|
1023
|
+
if (closed)
|
|
1024
|
+
return;
|
|
1025
|
+
closed = true;
|
|
1026
|
+
resolveClosed?.();
|
|
1027
|
+
await sdkHandler.close();
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
function createMcpHttpRoute2(config) {
|
|
1032
|
+
return {
|
|
1033
|
+
method: "ALL",
|
|
1034
|
+
path: config.path,
|
|
1035
|
+
handler: (request) => config.handler.fetch(request)
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// src/tools/mcp/stdio.ts
|
|
1040
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
1041
|
+
async function createStdioMcpServer2(config) {
|
|
1042
|
+
const auth = await config.auth;
|
|
1043
|
+
return serveStdio(() => buildMcpServer2(config, auth), {
|
|
1044
|
+
legacy: config.legacy ?? "serve",
|
|
1045
|
+
maxSubscriptions: 0
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// src/tools/mcp/stdio-signals.ts
|
|
1050
|
+
var bound = new WeakSet;
|
|
1051
|
+
function bindStdioProcessSignals2(handle, options = {}) {
|
|
1052
|
+
if (bound.has(handle)) {
|
|
1053
|
+
throw new Error("[stitchkit] bindStdioProcessSignals: this stdio handle is already bound; close the first binding before creating another");
|
|
1054
|
+
}
|
|
1055
|
+
bound.add(handle);
|
|
1056
|
+
const signals = [...new Set(options.signals ?? DEFAULT_PROCESS_SIGNALS)];
|
|
1057
|
+
const source = options.signalSource ?? defaultSignalSource;
|
|
1058
|
+
const handlers = [];
|
|
1059
|
+
let started = false;
|
|
1060
|
+
let sameTurnAsStart = false;
|
|
1061
|
+
let closed = false;
|
|
1062
|
+
let resolveChain = () => {
|
|
1063
|
+
return;
|
|
1064
|
+
};
|
|
1065
|
+
let rejectChain = () => {
|
|
1066
|
+
return;
|
|
1067
|
+
};
|
|
1068
|
+
const promise = new Promise((resolve, reject) => {
|
|
1069
|
+
resolveChain = resolve;
|
|
1070
|
+
rejectChain = reject;
|
|
1071
|
+
});
|
|
1072
|
+
promise.catch(() => {
|
|
1073
|
+
return;
|
|
1074
|
+
});
|
|
1075
|
+
const removeListeners = () => {
|
|
1076
|
+
if (closed)
|
|
1077
|
+
return;
|
|
1078
|
+
closed = true;
|
|
1079
|
+
for (const [signal, handler] of handlers)
|
|
1080
|
+
source.off(signal, handler);
|
|
1081
|
+
if (!started)
|
|
1082
|
+
bound.delete(handle);
|
|
1083
|
+
};
|
|
1084
|
+
const close = () => {
|
|
1085
|
+
const hadStarted = started;
|
|
1086
|
+
removeListeners();
|
|
1087
|
+
if (!hadStarted)
|
|
1088
|
+
resolveChain();
|
|
1089
|
+
};
|
|
1090
|
+
const run = async (signal) => {
|
|
1091
|
+
try {
|
|
1092
|
+
await options.onClose?.(signal);
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
reportSignalError("prepare", error, options.onError);
|
|
1095
|
+
}
|
|
1096
|
+
try {
|
|
1097
|
+
await handle.close();
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
rejectChain(error);
|
|
1100
|
+
reportSignalError("close", error, options.onError);
|
|
1101
|
+
removeListeners();
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
resolveChain();
|
|
1105
|
+
try {
|
|
1106
|
+
await options.onComplete?.();
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
reportSignalError("complete", error, options.onError);
|
|
1109
|
+
} finally {
|
|
1110
|
+
removeListeners();
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
const onSignal = (signal) => {
|
|
1114
|
+
if (!started) {
|
|
1115
|
+
started = true;
|
|
1116
|
+
sameTurnAsStart = true;
|
|
1117
|
+
queueMicrotask(() => {
|
|
1118
|
+
sameTurnAsStart = false;
|
|
1119
|
+
});
|
|
1120
|
+
run(signal).catch((error) => reportSignalError("close", error, options.onError));
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
if (sameTurnAsStart)
|
|
1124
|
+
return;
|
|
1125
|
+
removeListeners();
|
|
1126
|
+
guardSignalCallback(() => options.onRepeatedSignal?.(signal), "close", options.onError);
|
|
1127
|
+
let restored = false;
|
|
1128
|
+
try {
|
|
1129
|
+
restored = source.raiseDefault(signal);
|
|
1130
|
+
} catch (error) {
|
|
1131
|
+
reportSignalError("close", error, options.onError);
|
|
1132
|
+
}
|
|
1133
|
+
if (!restored) {
|
|
1134
|
+
guardSignalCallback(() => options.onEscalationBlocked?.(signal), "close", options.onError);
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
for (const signal of signals) {
|
|
1138
|
+
const handler = () => onSignal(signal);
|
|
1139
|
+
handlers.push([signal, handler]);
|
|
1140
|
+
source.on(signal, handler);
|
|
1141
|
+
}
|
|
1142
|
+
return { promise, close };
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
export { PROTECTED_RESOURCE_PATH2, protectedResourceMetadataUrl2, wwwAuthenticateHeader2, oauthProtectedResourceRoute2, RESOURCE_MIME_TYPE2, EXT_APPS_BUNDLE_PLACEHOLDER2, inlineMcpAppBundle2, MCP_CATALOG_META_KEY2, mcpCatalogMeta2, readMcpCatalogStamp2, createMcpProgressReporter, validateMcpSchemas2, mountMcp2, buildMcpServer2, mountMcpResource2, createMcpHandler2, createMcpHttpRoute2, createStdioMcpServer2, bindStdioProcessSignals2 };
|