@code-yeongyu/senpi 2026.10.25-4 → 2026.10.26-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 +23 -0
- package/dist/bundle/chunks/{anthropic-messages-WBASBTIW.js → anthropic-messages-M235FJQV.js} +3 -3
- package/dist/bundle/chunks/{args-B444OQBK.js → args-FBNJOJSR.js} +1 -1
- package/dist/bundle/chunks/azure-openai-responses-YQRTQ7H6.js +2 -0
- package/dist/bundle/chunks/bedrock-converse-stream.js +3 -3
- package/dist/bundle/chunks/{chunk-4MOZXOJX.js → chunk-3A3XCLNH.js} +1 -1
- package/dist/bundle/chunks/{chunk-E6D4RIT7.js → chunk-3LPWILUZ.js} +2 -2
- package/dist/bundle/chunks/{chunk-3BTN337S.js → chunk-3WP5RFQR.js} +1 -1
- package/dist/bundle/chunks/{chunk-2RB5EL4X.js → chunk-5FQMNHXV.js} +1 -1
- package/dist/bundle/chunks/{chunk-STMXIBXF.js → chunk-5KZH6EYQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-4P7WARQZ.js → chunk-5LWUUXMH.js} +1 -1
- package/dist/bundle/chunks/{chunk-5JJDR6XI.js → chunk-6U3TBFBA.js} +1 -1
- package/dist/bundle/chunks/{chunk-BWA4FVFW.js → chunk-7KOUQSBX.js} +2 -2
- package/dist/bundle/chunks/{chunk-7CNOCJCN.js → chunk-ADQAPDPV.js} +1 -1
- package/dist/bundle/chunks/{chunk-ITGYN22E.js → chunk-CDQPA4FP.js} +1 -1
- package/dist/bundle/chunks/{chunk-WDPQ6CZJ.js → chunk-DMFAVE5N.js} +1 -1
- package/dist/bundle/chunks/{chunk-XF3LJC3O.js → chunk-DYWIFF2Q.js} +1 -1
- package/dist/bundle/chunks/{chunk-C2MQ4QXN.js → chunk-EZP6AYW2.js} +35 -29
- package/dist/bundle/chunks/chunk-GQJYNFMO.js +2 -0
- package/dist/bundle/chunks/{chunk-SBGIXMX3.js → chunk-GWGNVYPB.js} +1 -1
- package/dist/bundle/chunks/{chunk-KKD4CYCI.js → chunk-H4WJ4LY3.js} +1 -1
- package/dist/bundle/chunks/{chunk-V2UYXTRR.js → chunk-HWTD4QF3.js} +1 -1
- package/dist/bundle/chunks/{chunk-WD6WBYC5.js → chunk-I7QWTWVZ.js} +1 -1
- package/dist/bundle/chunks/{chunk-BZ7726QO.js → chunk-J2YAYTB2.js} +1 -1
- package/dist/bundle/chunks/{chunk-TTU2RNVX.js → chunk-J5FCH7TI.js} +1 -1
- package/dist/bundle/chunks/chunk-JFQUWEN7.js +13 -0
- package/dist/bundle/chunks/{chunk-KNVNKDER.js → chunk-L2O6K3SJ.js} +1 -1
- package/dist/bundle/chunks/{chunk-4AOMNOGY.js → chunk-LO5MYLGF.js} +1 -1
- package/dist/bundle/chunks/{chunk-N7IN46Q5.js → chunk-MSHRIZGT.js} +8 -6
- package/dist/bundle/chunks/{chunk-T36LJ6MW.js → chunk-MY4ZD3EW.js} +1 -1
- package/dist/bundle/chunks/{chunk-UFJ2K7Q7.js → chunk-OQ3WDMB3.js} +1 -1
- package/dist/bundle/chunks/{chunk-7GZUFOM3.js → chunk-PH3JDRB2.js} +1 -1
- package/dist/bundle/chunks/{chunk-E6WYH3ZL.js → chunk-QREKAXKX.js} +7 -5
- package/dist/bundle/chunks/{chunk-3QCYBOKN.js → chunk-QYFOHFPS.js} +1 -1
- package/dist/bundle/chunks/{chunk-JMOUU75H.js → chunk-SOXQ42VS.js} +1 -1
- package/dist/bundle/chunks/{chunk-FHXR4ZAC.js → chunk-UDM7VO6O.js} +1 -1
- package/dist/bundle/chunks/{chunk-OLCDIITS.js → chunk-UW6M3EXY.js} +1 -1
- package/dist/bundle/chunks/chunk-VICDRG3J.js +410 -0
- package/dist/bundle/chunks/chunk-VL7O5PO4.js +5 -0
- package/dist/bundle/chunks/{chunk-PT6JJUN2.js → chunk-XMNPK472.js} +1 -1
- package/dist/bundle/chunks/{chunk-GSSD3R5X.js → chunk-XQ45JMGN.js} +1 -1
- package/dist/bundle/chunks/{client-WTYEICLH.js → chunk-Y3TMJV3J.js} +3 -3
- package/dist/bundle/chunks/{chunk-KXB5WIS5.js → chunk-Z6SAQ5O3.js} +1 -1
- package/dist/bundle/chunks/{chunk-LOIF6PHI.js → chunk-ZAUVY6ZC.js} +3 -3
- package/dist/bundle/chunks/{chunk-E4LBKOZG.js → chunk-ZLIVZQDY.js} +4 -4
- package/dist/bundle/chunks/{chunk-6AOVUTUR.js → chunk-ZXHLH4MW.js} +2 -2
- package/dist/bundle/chunks/cli-main-55EQA5PS.js +3 -0
- package/dist/bundle/chunks/client-LOS6GSEW.js +2 -0
- package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-K5H6BJP3.js → cloudflare-workers-ai-system-one-ZJQGIIRC.js} +1 -1
- package/dist/bundle/chunks/cursor-agent.js +3 -3
- package/dist/bundle/chunks/devin-agent.js +3 -3
- package/dist/bundle/chunks/dispatch-C2IBP5VV.js +9 -0
- package/dist/bundle/chunks/google-generative-ai-5G4RIQA6.js +2 -0
- package/dist/bundle/chunks/google-vertex-ALD4P27O.js +2 -0
- package/dist/bundle/chunks/{help-fast-path-3Z26DQSV.js → help-fast-path-PEIKGCKZ.js} +1 -1
- package/dist/bundle/chunks/host-command-657VCQ34.js +2 -0
- package/dist/bundle/chunks/{host-lifecycle-3VHHIGDL.js → host-lifecycle-HX3EGRRW.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +2 -2
- package/dist/bundle/chunks/{host-session-command-56XF6ACS.js → host-session-command-OV5XDOJK.js} +1 -1
- package/dist/bundle/chunks/{host-socket-request-QL2WEZTN.js → host-socket-request-K4LCUOUD.js} +1 -1
- package/dist/bundle/chunks/interactive-mode-MLXSYK2H.js +2 -0
- package/dist/bundle/chunks/{mcp-presenter-bootstrap-XUSCQVRC.js → mcp-presenter-bootstrap-FOMKF4TU.js} +1 -1
- package/dist/bundle/chunks/{mistral-conversations-DH44NOYV.js → mistral-conversations-RAE4K2XY.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-Z2CW4SDM.js → multi-session-host-774MACJU.js} +5 -6
- package/dist/bundle/chunks/{openai-codex-responses-SERAFKAS.js → openai-codex-responses-3CW4R7TG.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-BZB5O45A.js → openai-completions-5UIJXQUF.js} +1 -1
- package/dist/bundle/chunks/openai-responses-2BO44MRW.js +3 -0
- package/dist/bundle/chunks/{package-manager-cli-N55U6PEO.js → package-manager-cli-MZHVCX2S.js} +1 -1
- package/dist/bundle/chunks/{pi-messages-VXATX45B.js → pi-messages-RF6GER4R.js} +2 -2
- package/dist/bundle/chunks/{role-JVSBNKGE.js → role-ETSFSDTS.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-CBGCBYHV.js → rotation-stream-ZMQB4GPO.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-UJXOIZCW.js +2 -0
- package/dist/bundle/chunks/{rpc-monitor-source-AE3C6EQV.js → rpc-monitor-source-DRZXJHTL.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-VS7CZC2J.js → schedule-command-KA7LFFNK.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-MFWXUR7Z.js +3 -0
- package/dist/bundle/chunks/{session-picker-E4KUJDLO.js → session-picker-BP33NRSL.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +199 -190
- package/dist/bundle/chunks/{typesafe-system-one-P75WJ767.js → typesafe-system-one-GNNZ5O5U.js} +1 -1
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +2 -2
- 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/host-command-args.d.ts +1 -0
- package/dist/cli/host-command-args.js +5 -0
- package/dist/cli/host-command.js +7 -1
- package/dist/cli.js +21 -3
- package/dist/core/extensions/builtin/mcp/catalog-cache.d.ts +2 -1
- package/dist/core/extensions/builtin/mcp/catalog-cache.js +19 -6
- package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -2
- package/dist/core/extensions/builtin/mcp/catalog.js +20 -3
- package/dist/core/extensions/builtin/mcp/expose/call.d.ts +2 -1
- package/dist/core/extensions/builtin/mcp/expose/call.js +2 -1
- package/dist/core/extensions/builtin/mcp/expose/register.js +1 -1
- package/dist/core/extensions/builtin/mcp/expose/schema-compat.d.ts +2 -2
- package/dist/core/extensions/builtin/mcp/expose/schema-compat.js +11 -1
- package/dist/core/extensions/builtin/mcp/invocation.js +16 -2
- package/dist/core/extensions/builtin/mcp/modern/request.d.ts +1 -1
- package/dist/core/extensions/builtin/mcp/modern/request.js +1 -1
- package/dist/core/extensions/builtin/mcp/modern/sdk-bridge.js +18 -3
- package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +23 -0
- package/dist/core/extensions/builtin/mcp/modern/subscriptions.d.ts +14 -0
- package/dist/core/extensions/builtin/mcp/modern/subscriptions.js +226 -0
- package/dist/core/extensions/builtin/mcp/modern/tool-schema.d.ts +13 -0
- package/dist/core/extensions/builtin/mcp/modern/tool-schema.js +75 -0
- package/dist/core/extensions/builtin/mcp/modern/tools.d.ts +107 -0
- package/dist/core/extensions/builtin/mcp/modern/tools.js +53 -0
- package/dist/core/extensions/builtin/mcp/resources.js +4 -0
- package/dist/core/extensions/builtin/mcp/tool-client.d.ts +13 -0
- package/dist/core/extensions/builtin/mcp/tool-client.js +17 -0
- package/dist/core/extensions/builtin/mcp/tool-contracts.d.ts +19 -0
- package/dist/core/extensions/builtin/mcp/tool-contracts.js +2 -0
- package/dist/core/extensions/builtin/mcp/wrap.js +2 -2
- package/dist/core/extensions/builtin/permission-system/bash-command-segments.d.ts +37 -0
- package/dist/core/extensions/builtin/permission-system/bash-command-segments.js +899 -0
- package/dist/core/extensions/builtin/permission-system/parsers.d.ts +2 -0
- package/dist/core/extensions/builtin/permission-system/parsers.js +44 -10
- package/dist/modes/interactive/session-control-commands.js +21 -4
- package/dist/modes/interactive/session-control-rpc-commands.d.ts +1 -1
- package/dist/modes/interactive/session-control-rpc-commands.js +19 -0
- package/dist/modes/rpc/compact-content-store.d.ts +96 -0
- package/dist/modes/rpc/compact-content-store.js +269 -0
- package/dist/modes/rpc/compact-session-state.d.ts +111 -0
- package/dist/modes/rpc/compact-session-state.js +206 -0
- package/dist/modes/rpc/compact-spool-dir.d.ts +5 -0
- package/dist/modes/rpc/compact-spool-dir.js +61 -0
- package/dist/modes/rpc/compact-storage-io.d.ts +27 -0
- package/dist/modes/rpc/compact-storage-io.js +19 -0
- package/dist/modes/rpc/connection-handler.d.ts +2 -0
- package/dist/modes/rpc/connection-handler.js +39 -7
- package/dist/modes/rpc/connection-question-bridge.d.ts +2 -1
- package/dist/modes/rpc/connection-question-bridge.js +6 -2
- package/dist/modes/rpc/extension-ui-response.d.ts +7 -2
- package/dist/modes/rpc/extension-ui-response.js +27 -7
- package/dist/modes/rpc/get-snapshot.d.ts +18 -0
- package/dist/modes/rpc/get-snapshot.js +43 -0
- package/dist/modes/rpc/host-capabilities.js +3 -0
- package/dist/modes/rpc/host-ensure-start.js +1 -0
- 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-request.d.ts +1 -1
- package/dist/modes/rpc/host-handover-request.js +4 -3
- package/dist/modes/rpc/host-handover-wire.js +6 -0
- package/dist/modes/rpc/host-idle-handover.d.ts +2 -0
- package/dist/modes/rpc/host-idle-handover.js +3 -1
- package/dist/modes/rpc/host-observe-request.js +1 -0
- package/dist/modes/rpc/host-runner.d.ts +1 -0
- package/dist/modes/rpc/host-runner.js +9 -7
- package/dist/modes/rpc/host-successor.js +4 -0
- package/dist/modes/rpc/json-normal-value.d.ts +6 -0
- package/dist/modes/rpc/json-normal-value.js +100 -0
- package/dist/modes/rpc/mcp-presenter-bootstrap.js +5 -0
- package/dist/modes/rpc/media-placeholders.d.ts +9 -3
- package/dist/modes/rpc/media-placeholders.js +12 -4
- package/dist/modes/rpc/rpc-host-command-types.d.ts +2 -0
- package/dist/modes/rpc/rpc-question-types.d.ts +1 -0
- package/dist/modes/rpc/rpc-read-response-types.d.ts +7 -0
- package/dist/modes/rpc/rpc-session-command-types.d.ts +6 -0
- package/dist/modes/rpc/rpc-session-state-types.d.ts +3 -0
- package/dist/modes/rpc/rpc-session-state.js +2 -0
- package/dist/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/modes/rpc/session-attachment-gate.js +1 -0
- package/dist/modes/rpc/session-binding.js +21 -18
- package/dist/modes/rpc/session-command-activity.js +1 -0
- package/dist/modes/rpc/session-event-fanout.d.ts +1 -1
- package/dist/modes/rpc/session-event-fanout.js +17 -9
- package/dist/modes/rpc/session-event-writer.js +22 -5
- package/dist/modes/rpc/session-extension-ui-requests.d.ts +2 -1
- package/dist/modes/rpc/session-extension-ui-requests.js +15 -10
- package/dist/modes/rpc/session-held.js +1 -0
- package/dist/modes/rpc/session-status-stream.d.ts +2 -1
- package/dist/modes/rpc/session-status-stream.js +8 -2
- package/dist/modes/rpc/session-status-subscriptions.d.ts +2 -1
- package/dist/modes/rpc/session-status-subscriptions.js +4 -0
- package/dist/modes/rpc/session-worker.js +5 -2
- package/dist/modes/rpc/snapshot-types.d.ts +29 -0
- package/dist/modes/rpc/snapshot-types.js +2 -0
- package/dist/modes/rpc/ui-answer-ledger.d.ts +13 -0
- package/dist/modes/rpc/ui-answer-ledger.js +34 -0
- package/dist/senpi +21 -3
- package/docs/mcp.md +20 -0
- package/docs/rpc.md +44 -0
- package/package.json +7 -6
- package/dist/bundle/chunks/azure-openai-responses-7I45DI72.js +0 -2
- package/dist/bundle/chunks/chunk-DMI55M2N.js +0 -2
- package/dist/bundle/chunks/chunk-DODH3N6U.js +0 -410
- package/dist/bundle/chunks/chunk-J37GRED2.js +0 -5
- package/dist/bundle/chunks/chunk-TBU6VYHN.js +0 -13
- package/dist/bundle/chunks/cli-main-YYH3IBC6.js +0 -3
- package/dist/bundle/chunks/dispatch-MXG5YWLV.js +0 -9
- package/dist/bundle/chunks/google-generative-ai-UM5Y7T6B.js +0 -2
- package/dist/bundle/chunks/google-vertex-OKU2VQHL.js +0 -2
- package/dist/bundle/chunks/host-command-TDLLHXVI.js +0 -2
- package/dist/bundle/chunks/interactive-mode-OEPULLZK.js +0 -2
- package/dist/bundle/chunks/openai-responses-TNHQ6SJS.js +0 -3
- package/dist/bundle/chunks/rpc-mode-ETXQWEB2.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-4LEIDIVI.js +0 -3
|
@@ -6,6 +6,9 @@ import { redactResolvedReferenceData } from "../../../config-reference-redaction
|
|
|
6
6
|
import { lockWithLossHandling } from "../../../file-lock.js";
|
|
7
7
|
import { isEphemeralIdentity } from "./config-identity-key.js";
|
|
8
8
|
import { collectAllPages } from "./expose/pagination.js";
|
|
9
|
+
import { listMcpTools } from "./tool-client.js";
|
|
10
|
+
import { isModernMcpClient } from "./modern/request.js";
|
|
11
|
+
import { hasSupportedModernToolSchemas } from "./modern/tool-schema.js";
|
|
9
12
|
const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
10
13
|
const EMPTY_CACHE = { version: 1, servers: {} };
|
|
11
14
|
export function getMcpCatalogCachePath(agentDir = getAgentDir()) {
|
|
@@ -36,7 +39,7 @@ export function cachedCatalogNeedsRefresh(cached, now = Date.now()) {
|
|
|
36
39
|
return now - cached.fetchedAt > CACHE_TTL_MS;
|
|
37
40
|
}
|
|
38
41
|
export async function collectServerCatalogForCache(connection, config, configHash, credentialIdentity) {
|
|
39
|
-
const tools = await collectAllPages((cursor) => connection.client
|
|
42
|
+
const tools = await collectAllPages((cursor) => listMcpTools(connection.client, cursor === undefined ? {} : { cursor }, {
|
|
40
43
|
timeout: config.requestTimeoutMs,
|
|
41
44
|
}));
|
|
42
45
|
const resources = await collectOptionalPages((cursor) => connection.client.listResources(cursor === undefined ? {} : { cursor }, {
|
|
@@ -50,6 +53,7 @@ export async function collectServerCatalogForCache(connection, config, configHas
|
|
|
50
53
|
}));
|
|
51
54
|
return {
|
|
52
55
|
configHash,
|
|
56
|
+
...(isModernMcpClient(connection.client) ? { modern: true } : {}),
|
|
53
57
|
credentialIdentity,
|
|
54
58
|
fetchedAt: Date.now(),
|
|
55
59
|
instructions: connection.client.getInstructions(),
|
|
@@ -128,7 +132,7 @@ function normalizeCachedServer(value) {
|
|
|
128
132
|
if (!isRecord(value) || typeof value.configHash !== "string" || typeof value.fetchedAt !== "number") {
|
|
129
133
|
return undefined;
|
|
130
134
|
}
|
|
131
|
-
const tools = normalizeTools(value.tools);
|
|
135
|
+
const tools = normalizeTools(value.tools, value.modern === true);
|
|
132
136
|
if (tools === undefined)
|
|
133
137
|
return undefined;
|
|
134
138
|
const resources = Array.isArray(value.resources) ? value.resources : [];
|
|
@@ -139,6 +143,7 @@ function normalizeCachedServer(value) {
|
|
|
139
143
|
const instructions = typeof value.instructions === "string" ? value.instructions : undefined;
|
|
140
144
|
return {
|
|
141
145
|
configHash: value.configHash,
|
|
146
|
+
...(value.modern === true ? { modern: true } : {}),
|
|
142
147
|
credentialIdentity: typeof value.credentialIdentity === "string" ? value.credentialIdentity : undefined,
|
|
143
148
|
fetchedAt: value.fetchedAt,
|
|
144
149
|
instructions,
|
|
@@ -148,19 +153,27 @@ function normalizeCachedServer(value) {
|
|
|
148
153
|
tools,
|
|
149
154
|
};
|
|
150
155
|
}
|
|
151
|
-
function normalizeTools(value) {
|
|
156
|
+
function normalizeTools(value, modern) {
|
|
152
157
|
if (!Array.isArray(value))
|
|
153
158
|
return undefined;
|
|
154
159
|
const tools = [];
|
|
155
160
|
for (const item of value) {
|
|
156
|
-
if (!isRecord(item) || typeof item.name !== "string"
|
|
161
|
+
if (!isRecord(item) || typeof item.name !== "string")
|
|
157
162
|
return undefined;
|
|
158
|
-
|
|
163
|
+
if (!isRecord(item.inputSchema) && !(modern && typeof item.inputSchema === "boolean"))
|
|
164
|
+
return undefined;
|
|
165
|
+
const tool = {
|
|
166
|
+
...(modern ? item : {}),
|
|
159
167
|
annotations: isRecord(item.annotations) ? item.annotations : undefined,
|
|
160
168
|
description: typeof item.description === "string" ? item.description : undefined,
|
|
161
169
|
inputSchema: item.inputSchema,
|
|
170
|
+
...(modern && (isRecord(item.outputSchema) || typeof item.outputSchema === "boolean")
|
|
171
|
+
? { outputSchema: item.outputSchema }
|
|
172
|
+
: {}),
|
|
162
173
|
name: item.name,
|
|
163
|
-
}
|
|
174
|
+
};
|
|
175
|
+
if (!modern || hasSupportedModernToolSchemas(tool))
|
|
176
|
+
tools.push(tool);
|
|
164
177
|
}
|
|
165
178
|
return tools;
|
|
166
179
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { McpListedTool as ListedTool } from "./tool-contracts.ts";
|
|
2
2
|
import type { McpCachedServerCatalog } from "./catalog-cache.ts";
|
|
3
3
|
import type { McpServerDeclaredConfig, McpSettings } from "./config-schema.ts";
|
|
4
4
|
import type { ServerConnection } from "./connection.ts";
|
|
@@ -6,12 +6,13 @@ import type { McpOutputArtifacts } from "./guard/output-guard.ts";
|
|
|
6
6
|
import type { McpEnsureFreshAuth } from "./health.ts";
|
|
7
7
|
import type { McpInvocationResolver } from "./invocation.ts";
|
|
8
8
|
import type { McpRequestOrigin } from "./request-origin.ts";
|
|
9
|
-
type ListedTool = Awaited<ReturnType<Client["listTools"]>>["tools"][number];
|
|
10
9
|
export interface McpToolCatalogEntry {
|
|
11
10
|
requestOrigin?: () => McpRequestOrigin | undefined;
|
|
12
11
|
server: string;
|
|
13
12
|
tool: string;
|
|
14
13
|
schema: ListedTool["inputSchema"];
|
|
14
|
+
outputSchema?: ListedTool["outputSchema"];
|
|
15
|
+
modern?: boolean;
|
|
15
16
|
description?: string;
|
|
16
17
|
annotations?: ListedTool["annotations"];
|
|
17
18
|
requestTimeoutMs: number;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { listMcpTools } from "./tool-client.js";
|
|
2
|
+
import { isModernMcpClient } from "./modern/request.js";
|
|
3
|
+
import { hasSupportedModernToolSchemas } from "./modern/tool-schema.js";
|
|
1
4
|
import { collectAllPages } from "./expose/pagination.js";
|
|
2
5
|
export async function collectToolCatalog(server, connection, config, options = {}) {
|
|
3
|
-
const result = await collectAllPages((cursor) => connection.client
|
|
6
|
+
const result = await collectAllPages((cursor) => listMcpTools(connection.client, cursor === undefined ? {} : { cursor }, { timeout: config.requestTimeoutMs }));
|
|
4
7
|
return result.items.map((tool) => ({
|
|
5
8
|
requestOrigin: options.requestOrigin,
|
|
6
9
|
annotations: tool.annotations,
|
|
@@ -13,6 +16,8 @@ export async function collectToolCatalog(server, connection, config, options = {
|
|
|
13
16
|
outputGuard: options.outputGuard,
|
|
14
17
|
requestTimeoutMs: config.requestTimeoutMs,
|
|
15
18
|
schema: tool.inputSchema,
|
|
19
|
+
outputSchema: tool.outputSchema,
|
|
20
|
+
modern: isModernMcpClient(connection.client),
|
|
16
21
|
server,
|
|
17
22
|
tool: tool.name,
|
|
18
23
|
}));
|
|
@@ -21,13 +26,23 @@ export async function collectToolCatalog(server, connection, config, options = {
|
|
|
21
26
|
* equal identities register identical definitions. */
|
|
22
27
|
export function mcpRegistrationIdentity(tools, catalog) {
|
|
23
28
|
return JSON.stringify([
|
|
24
|
-
tools.map((entry) => [
|
|
29
|
+
tools.map((entry) => [
|
|
30
|
+
entry.tool,
|
|
31
|
+
entry.description,
|
|
32
|
+
entry.schema,
|
|
33
|
+
entry.outputSchema,
|
|
34
|
+
entry.modern,
|
|
35
|
+
entry.annotations,
|
|
36
|
+
]),
|
|
25
37
|
catalog?.resources ?? [],
|
|
26
38
|
catalog?.prompts ?? [],
|
|
27
39
|
]);
|
|
28
40
|
}
|
|
29
41
|
export function cachedToolsToCatalogEntries(server, catalog, connection, requestTimeoutMs, ensureConnected, options = {}) {
|
|
30
|
-
|
|
42
|
+
const modern = connection.state === "connected" ? isModernMcpClient(connection.client) || undefined : catalog.modern;
|
|
43
|
+
return catalog.tools
|
|
44
|
+
.filter((tool) => !modern || hasSupportedModernToolSchemas(tool))
|
|
45
|
+
.map((tool) => ({
|
|
31
46
|
requestOrigin: options.requestOrigin,
|
|
32
47
|
annotations: tool.annotations,
|
|
33
48
|
agentDir: options.agentDir,
|
|
@@ -40,6 +55,8 @@ export function cachedToolsToCatalogEntries(server, catalog, connection, request
|
|
|
40
55
|
outputGuard: options.outputGuard,
|
|
41
56
|
requestTimeoutMs,
|
|
42
57
|
schema: tool.inputSchema,
|
|
58
|
+
outputSchema: tool.outputSchema,
|
|
59
|
+
modern,
|
|
43
60
|
server,
|
|
44
61
|
tool: tool.name,
|
|
45
62
|
}));
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { McpToolCatalogEntry } from "../catalog.ts";
|
|
2
2
|
import type { McpInvocation, McpInvocationError } from "../invocation.ts";
|
|
3
3
|
import type { McpToolDefinition } from "./register.ts";
|
|
4
|
+
import type { McpToolResult } from "../tool-contracts.ts";
|
|
4
5
|
export declare function callMcpTool(entry: McpToolCatalogEntry, args: Record<string, unknown>, signal: AbortSignal | undefined, onUpdate: Parameters<McpToolDefinition["execute"]>[3], label: string, invocation: McpInvocation | undefined): Promise<{
|
|
5
6
|
kind: "called";
|
|
6
7
|
entry: McpToolCatalogEntry;
|
|
7
|
-
result:
|
|
8
|
+
result: McpToolResult;
|
|
8
9
|
} | {
|
|
9
10
|
kind: "refused";
|
|
10
11
|
error: McpInvocationError;
|
|
@@ -4,6 +4,7 @@ import { ensureMcpToolCallConnection, withMcpRetriableFailedSendRetry, withMcpSe
|
|
|
4
4
|
import { runMcpConnectionLifecycleCall } from "../idle.js";
|
|
5
5
|
import { mcpRequestOptions } from "../request-origin.js";
|
|
6
6
|
import { McpMrtrError } from "../modern/errors.js";
|
|
7
|
+
import { callMcpToolRequest } from "../tool-client.js";
|
|
7
8
|
export async function callMcpTool(entry, args, signal, onUpdate, label, invocation) {
|
|
8
9
|
try {
|
|
9
10
|
return await runMcpConnectionLifecycleCall(entry.connection, () => withMcpSessionExpiryRetry(entry.connection, async () => {
|
|
@@ -31,7 +32,7 @@ export async function callMcpTool(entry, args, signal, onUpdate, label, invocati
|
|
|
31
32
|
if (prepared !== undefined && !prepared.isCurrent())
|
|
32
33
|
continue;
|
|
33
34
|
const current = prepared?.entry ?? entry;
|
|
34
|
-
const result = await current.connection.client
|
|
35
|
+
const result = await callMcpToolRequest(current.connection.client, { name: current.tool, arguments: args }, current.outputSchema, mcpRequestOptions(current.requestOrigin?.(), {
|
|
35
36
|
onprogress: (progress) => {
|
|
36
37
|
onUpdate?.(redactResolvedReferenceData({
|
|
37
38
|
content: [{ type: "text", text: formatProgress(label, progress) }],
|
|
@@ -18,7 +18,7 @@ export function buildMcpToolDefinitions(entries, warn) {
|
|
|
18
18
|
return mapMcpCatalogNames(entries, warn).map(({ entry, name }) => createMcpToolDefinition(entry, name));
|
|
19
19
|
}
|
|
20
20
|
function createMcpToolDefinition(entry, name) {
|
|
21
|
-
const converted = convertJsonSchemaToTypeBox(entry.schema);
|
|
21
|
+
const converted = convertJsonSchemaToTypeBox(entry.schema, entry.modern);
|
|
22
22
|
const label = `${entry.server}/${entry.tool}`;
|
|
23
23
|
registerDispatchIdentity(converted.schema, () => entry.invocation?.identity(entry));
|
|
24
24
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ImageContent, type TextContent } from "@earendil-works/pi-ai";
|
|
2
2
|
import { type TSchema } from "typebox";
|
|
3
3
|
import { buildMcpToolNames, type McpToolNameEntry } from "./naming.ts";
|
|
4
4
|
import { collectAllPages, type McpListPage, type McpPaginationResult } from "./pagination.ts";
|
|
@@ -62,7 +62,7 @@ export type McpMappedToolResult = {
|
|
|
62
62
|
content: McpMappedContentBlock[];
|
|
63
63
|
};
|
|
64
64
|
};
|
|
65
|
-
export declare function convertJsonSchemaToTypeBox(schema: unknown): SchemaConversionResult;
|
|
65
|
+
export declare function convertJsonSchemaToTypeBox(schema: unknown, modern?: boolean): SchemaConversionResult;
|
|
66
66
|
export declare function prepareOutputSchemaRetry(schema: unknown): OutputSchemaRetryResult;
|
|
67
67
|
export declare function mapMcpToolResult(result: McpToolResultLike): McpMappedToolResult;
|
|
68
68
|
//# sourceMappingURL=schema-compat.d.ts.map
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
import { registerToolArgumentValidator } from "@earendil-works/pi-ai";
|
|
1
2
|
import { Type } from "typebox";
|
|
3
|
+
import { compileModernSchema } from "../modern/tool-schema.js";
|
|
2
4
|
import { buildMcpToolNames } from "./naming.js";
|
|
3
5
|
import { collectAllPages } from "./pagination.js";
|
|
4
6
|
export { buildMcpToolNames, collectAllPages };
|
|
5
7
|
const PERMISSIVE_OBJECT_SCHEMA = { type: "object", properties: {} };
|
|
6
|
-
export function convertJsonSchemaToTypeBox(schema) {
|
|
8
|
+
export function convertJsonSchemaToTypeBox(schema, modern = false) {
|
|
9
|
+
if (modern && (typeof schema === "boolean" || isRecord(schema))) {
|
|
10
|
+
const validate = compileModernSchema(schema);
|
|
11
|
+
// TypeBox/tool APIs require an object container. allOf preserves the exact
|
|
12
|
+
// boolean constraint; provider-specific object adaptation happens in pi-ai.
|
|
13
|
+
const parameters = Type.Unsafe(typeof schema === "boolean" ? { allOf: [schema] } : structuredClone(schema));
|
|
14
|
+
registerToolArgumentValidator(parameters, validate);
|
|
15
|
+
return { schema: parameters, warnings: [] };
|
|
16
|
+
}
|
|
7
17
|
const warnings = [];
|
|
8
18
|
const resolved = resolveRefs(stripTopLevelSchemaKeys(schema), schema, new Set(), warnings);
|
|
9
19
|
if (warnings.length > 0) {
|
|
@@ -3,6 +3,7 @@ import { Check } from "typebox/value";
|
|
|
3
3
|
import { authorizeToolDispatch, hasDispatchAuthorizer } from "../permission-system/dispatch.js";
|
|
4
4
|
import { getDispatchIdentity } from "../permission-system/dispatch-metadata.js";
|
|
5
5
|
import { TimeoutError } from "./errors.js";
|
|
6
|
+
import { compileModernSchema, McpToolSchemaError } from "./modern/tool-schema.js";
|
|
6
7
|
import { safeTimer } from "./wrap.js";
|
|
7
8
|
export function createMcpInvocationResolver(scope) {
|
|
8
9
|
const identity = (entry) => ({
|
|
@@ -13,6 +14,8 @@ export function createMcpInvocationResolver(scope) {
|
|
|
13
14
|
entry.server,
|
|
14
15
|
entry.tool,
|
|
15
16
|
entry.schema,
|
|
17
|
+
entry.outputSchema,
|
|
18
|
+
entry.modern,
|
|
16
19
|
entry.description,
|
|
17
20
|
entry.annotations,
|
|
18
21
|
]),
|
|
@@ -66,7 +69,18 @@ export function createMcpInvocationResolver(scope) {
|
|
|
66
69
|
if (current === undefined)
|
|
67
70
|
return refuse("unavailable", "MCP tool is no longer available.");
|
|
68
71
|
// Validate the current raw schema, never the coercing preflight schema.
|
|
69
|
-
|
|
72
|
+
let validate;
|
|
73
|
+
try {
|
|
74
|
+
validate = current.modern
|
|
75
|
+
? compileModernSchema(current.schema)
|
|
76
|
+
: (value) => Check(Type.Unsafe(current.schema), value);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (!(error instanceof McpToolSchemaError))
|
|
80
|
+
throw error;
|
|
81
|
+
return refuse("schema_changed", `${error.code}: ${error.message}`, current);
|
|
82
|
+
}
|
|
83
|
+
if (!validate(args)) {
|
|
70
84
|
return refuse("schema_changed", "Arguments do not match the current MCP tool schema.", current);
|
|
71
85
|
}
|
|
72
86
|
const preparedIdentity = identity(current);
|
|
@@ -107,7 +121,7 @@ export function createMcpInvocationResolver(scope) {
|
|
|
107
121
|
const latest = scope.current(offered.tool);
|
|
108
122
|
return (latest !== undefined &&
|
|
109
123
|
identity(latest).metadata === preparedIdentity.metadata &&
|
|
110
|
-
|
|
124
|
+
validate(args) &&
|
|
111
125
|
(operationFence === undefined || operationFence()) &&
|
|
112
126
|
(permissionFence === undefined || permissionFence()));
|
|
113
127
|
},
|
|
@@ -15,6 +15,6 @@ export type MrtrClock = McpClock;
|
|
|
15
15
|
export declare const isModernMcpClient: (client: Client) => boolean;
|
|
16
16
|
export declare const legacyMcpFormsAdvertised: (client: Client) => boolean | undefined;
|
|
17
17
|
export declare function getModernDispatchGuard(params: unknown): ((credential?: string) => void) | undefined;
|
|
18
|
-
/** One instance-local adapter. Captured SDK requests retain ID allocation
|
|
18
|
+
/** One instance-local adapter. Captured SDK requests retain ID allocation; the caller supplies its era's result contract. */
|
|
19
19
|
export declare function installModernRequestAdapter(client: Client, protocol: ModernProtocolState, clock?: MrtrClock, readCredential?: () => string | undefined): void;
|
|
20
20
|
//# sourceMappingURL=request.d.ts.map
|
|
@@ -22,7 +22,7 @@ export function getModernDispatchGuard(params) {
|
|
|
22
22
|
? params[modernDispatchGuard]
|
|
23
23
|
: undefined;
|
|
24
24
|
}
|
|
25
|
-
/** One instance-local adapter. Captured SDK requests retain ID allocation
|
|
25
|
+
/** One instance-local adapter. Captured SDK requests retain ID allocation; the caller supplies its era's result contract. */
|
|
26
26
|
export function installModernRequestAdapter(client, protocol, clock = mcpElapsedClock, readCredential = () => undefined) {
|
|
27
27
|
const sdkRequest = client.request.bind(client);
|
|
28
28
|
states.set(client, protocol);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InitializeResultSchema, LATEST_PROTOCOL_VERSION, } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
import { ModernTransportError } from "./transport.js";
|
|
3
|
+
import { ModernSubscriptions } from "./subscriptions.js";
|
|
3
4
|
/** The SDK's local `initialize` answer, built from validated discovery; discovery must support the real wire version. */
|
|
4
5
|
export function modernInitializeResult(discovery, wireVersion) {
|
|
5
6
|
if (!discovery.supportedVersions.includes(wireVersion)) {
|
|
@@ -22,16 +23,22 @@ export function normalizeModernResult(message) {
|
|
|
22
23
|
export class ModernSdkBridge {
|
|
23
24
|
#transport;
|
|
24
25
|
#initializeResult;
|
|
26
|
+
#subscriptions;
|
|
25
27
|
#initialized = false;
|
|
26
28
|
constructor(transport, discovery, wireVersion) {
|
|
27
29
|
this.#initializeResult = modernInitializeResult(discovery, wireVersion);
|
|
28
30
|
this.#transport = transport;
|
|
31
|
+
this.#subscriptions = new ModernSubscriptions(transport.send.bind(transport), discovery.capabilities, true);
|
|
29
32
|
}
|
|
30
33
|
async start() {
|
|
31
34
|
this.#transport.onmessage = (message, extra) => {
|
|
32
|
-
this.
|
|
35
|
+
if (this.#subscriptions.incoming(message))
|
|
36
|
+
this.onmessage?.(normalizeModernResult(message), extra);
|
|
37
|
+
};
|
|
38
|
+
this.#transport.onclose = () => {
|
|
39
|
+
this.#subscriptions.dispose();
|
|
40
|
+
this.onclose?.();
|
|
33
41
|
};
|
|
34
|
-
this.#transport.onclose = () => this.onclose?.();
|
|
35
42
|
this.#transport.onerror = (error) => this.onerror?.(error);
|
|
36
43
|
await this.#transport.start();
|
|
37
44
|
}
|
|
@@ -46,9 +53,17 @@ export class ModernSdkBridge {
|
|
|
46
53
|
}
|
|
47
54
|
if ("method" in message && message.method === "notifications/initialized" && !("id" in message))
|
|
48
55
|
return;
|
|
49
|
-
|
|
56
|
+
this.#subscriptions.outgoing(message);
|
|
57
|
+
try {
|
|
58
|
+
await this.#transport.send(message, options);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
this.#subscriptions.failedSend(message);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
50
64
|
}
|
|
51
65
|
close() {
|
|
66
|
+
this.#subscriptions.dispose();
|
|
52
67
|
return this.#transport.close();
|
|
53
68
|
}
|
|
54
69
|
}
|
|
@@ -3,6 +3,7 @@ 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 { ModernSubscriptions } from "./subscriptions.js";
|
|
6
7
|
import { sanitizeModernMessage } from "./diagnostics.js";
|
|
7
8
|
import { McpMrtrError } from "./errors.js";
|
|
8
9
|
import { getModernDispatchGuard } from "./request.js";
|
|
@@ -186,6 +187,17 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
|
|
|
186
187
|
const initializeResult = modernInitializeResult(discovery, version);
|
|
187
188
|
const deliver = transport.onmessage;
|
|
188
189
|
const live = new Set();
|
|
190
|
+
const subscriptions = new ModernSubscriptions((message, options) => transport.send(message, options), discovery.capabilities);
|
|
191
|
+
const end = transport.close.bind(transport);
|
|
192
|
+
transport.close = async () => {
|
|
193
|
+
subscriptions.dispose();
|
|
194
|
+
await end();
|
|
195
|
+
};
|
|
196
|
+
const onclose = transport.onclose;
|
|
197
|
+
transport.onclose = () => {
|
|
198
|
+
subscriptions.dispose();
|
|
199
|
+
onclose?.();
|
|
200
|
+
};
|
|
189
201
|
let initialized = false;
|
|
190
202
|
transport.onerror = () => {
|
|
191
203
|
// A parser error may quote a wire body; never forward it.
|
|
@@ -204,6 +216,14 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
|
|
|
204
216
|
}
|
|
205
217
|
if ("id" in message && message.id !== undefined && !live.delete(message.id))
|
|
206
218
|
return;
|
|
219
|
+
if (!subscriptions.incoming(message)) {
|
|
220
|
+
const id = "method" in message && message.method === "notifications/cancelled"
|
|
221
|
+
? message.params?.requestId
|
|
222
|
+
: undefined;
|
|
223
|
+
if (typeof id === "string" || typeof id === "number")
|
|
224
|
+
live.delete(id);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
207
227
|
if ("method" in message && message.method === "notifications/progress") {
|
|
208
228
|
const token = message.params?.progressToken;
|
|
209
229
|
if ((typeof token !== "string" && typeof token !== "number") || !live.has(token))
|
|
@@ -227,12 +247,14 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
|
|
|
227
247
|
if (message.method === "notifications/initialized" && !("id" in message))
|
|
228
248
|
return;
|
|
229
249
|
if (message.method === "notifications/cancelled" && !("id" in message)) {
|
|
250
|
+
subscriptions.outgoing(message);
|
|
230
251
|
const id = message.params?.requestId;
|
|
231
252
|
if ((typeof id !== "string" && typeof id !== "number") || !live.delete(id))
|
|
232
253
|
return;
|
|
233
254
|
return send({ jsonrpc: "2.0", method: message.method, params: { requestId: id, reason: "MCP request canceled" } }, sendOptions);
|
|
234
255
|
}
|
|
235
256
|
getModernDispatchGuard(message.params)?.();
|
|
257
|
+
subscriptions.outgoing(message);
|
|
236
258
|
const request = modernizeRequest("id" in message ? message.id : 0, message.method, options.clientInfo, options.clientCapabilities, version, message.params);
|
|
237
259
|
if ("id" in message) {
|
|
238
260
|
live.add(message.id);
|
|
@@ -241,6 +263,7 @@ export function probeStdioTransport(transport, options, logger, timeoutMs, serve
|
|
|
241
263
|
}
|
|
242
264
|
catch {
|
|
243
265
|
live.delete(message.id);
|
|
266
|
+
subscriptions.failedSend(message);
|
|
244
267
|
throw new McpMrtrError("MCP_MRTR_TRANSPORT_FAILED");
|
|
245
268
|
}
|
|
246
269
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
2
|
+
import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import type { DiscoveryResult } from "./era.ts";
|
|
4
|
+
/** Connection-owned streams bypass the SDK's finite request timeout and MRTR consent lifecycle. */
|
|
5
|
+
export declare class ModernSubscriptions {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(send: Transport["send"], capabilities: DiscoveryResult["capabilities"], sendCompletesStream?: boolean);
|
|
8
|
+
outgoing(message: JSONRPCMessage): void;
|
|
9
|
+
/** Returns true only for application messages that may reach the existing SDK notification handlers. */
|
|
10
|
+
incoming(message: JSONRPCMessage): boolean;
|
|
11
|
+
failedSend(message: JSONRPCMessage): void;
|
|
12
|
+
dispose(): void;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=subscriptions.d.ts.map
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mcpElapsedClock } from "../wrap.js";
|
|
3
|
+
import { isRecord } from "./form-schema.js";
|
|
4
|
+
const subscriptionKey = "io.modelcontextprotocol/subscriptionId";
|
|
5
|
+
const listFilters = {
|
|
6
|
+
"notifications/tools/list_changed": "toolsListChanged",
|
|
7
|
+
"notifications/prompts/list_changed": "promptsListChanged",
|
|
8
|
+
"notifications/resources/list_changed": "resourcesListChanged",
|
|
9
|
+
};
|
|
10
|
+
/** Connection-owned streams bypass the SDK's finite request timeout and MRTR consent lifecycle. */
|
|
11
|
+
export class ModernSubscriptions {
|
|
12
|
+
#send;
|
|
13
|
+
#capabilities;
|
|
14
|
+
#sendCompletesStream;
|
|
15
|
+
#filter = {};
|
|
16
|
+
#resourceLists = new Map();
|
|
17
|
+
#resourceCursors = new Map();
|
|
18
|
+
#latestSweep;
|
|
19
|
+
#active;
|
|
20
|
+
#pending;
|
|
21
|
+
#timer;
|
|
22
|
+
#retry = 0;
|
|
23
|
+
#closed = false;
|
|
24
|
+
#started = false;
|
|
25
|
+
constructor(send, capabilities, sendCompletesStream = false) {
|
|
26
|
+
this.#send = send;
|
|
27
|
+
this.#capabilities = capabilities;
|
|
28
|
+
this.#sendCompletesStream = sendCompletesStream;
|
|
29
|
+
}
|
|
30
|
+
outgoing(message) {
|
|
31
|
+
if (!("method" in message))
|
|
32
|
+
return;
|
|
33
|
+
if (message.method === "notifications/cancelled") {
|
|
34
|
+
const id = message.params?.requestId;
|
|
35
|
+
if (typeof id === "string" || typeof id === "number")
|
|
36
|
+
this.#resourceLists.delete(id);
|
|
37
|
+
}
|
|
38
|
+
if (!("id" in message))
|
|
39
|
+
return;
|
|
40
|
+
if (message.method === "resources/list" && this.#supports("resources", "subscribe")) {
|
|
41
|
+
const cursor = message.params?.cursor;
|
|
42
|
+
if (cursor === undefined) {
|
|
43
|
+
const sweep = { uris: [] };
|
|
44
|
+
this.#latestSweep = sweep;
|
|
45
|
+
this.#resourceLists.set(message.id, sweep);
|
|
46
|
+
}
|
|
47
|
+
else if (typeof cursor === "string") {
|
|
48
|
+
const sweep = this.#resourceCursors.get(cursor);
|
|
49
|
+
this.#resourceCursors.delete(cursor);
|
|
50
|
+
if (sweep)
|
|
51
|
+
this.#resourceLists.set(message.id, sweep);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (this.#started || !["tools/list", "prompts/list", "resources/list"].includes(message.method))
|
|
55
|
+
return;
|
|
56
|
+
this.#started = true;
|
|
57
|
+
// The builtin catalog uses all three lists. Never opt in to an undeclared feature.
|
|
58
|
+
if (this.#supports("tools", "listChanged"))
|
|
59
|
+
this.#filter.toolsListChanged = true;
|
|
60
|
+
if (this.#supports("prompts", "listChanged"))
|
|
61
|
+
this.#filter.promptsListChanged = true;
|
|
62
|
+
if (this.#supports("resources", "listChanged"))
|
|
63
|
+
this.#filter.resourcesListChanged = true;
|
|
64
|
+
this.#open();
|
|
65
|
+
}
|
|
66
|
+
/** Returns true only for application messages that may reach the existing SDK notification handlers. */
|
|
67
|
+
incoming(message) {
|
|
68
|
+
const sweep = "id" in message && message.id !== undefined ? this.#resourceLists.get(message.id) : undefined;
|
|
69
|
+
if ("id" in message && message.id !== undefined)
|
|
70
|
+
this.#resourceLists.delete(message.id);
|
|
71
|
+
if (sweep && "result" in message) {
|
|
72
|
+
const resources = message.result.resources;
|
|
73
|
+
if (Array.isArray(resources)) {
|
|
74
|
+
for (const resource of resources)
|
|
75
|
+
if (isRecord(resource) && typeof resource.uri === "string")
|
|
76
|
+
sweep.uris.push(resource.uri);
|
|
77
|
+
if (typeof message.result.nextCursor === "string" && message.result.nextCursor) {
|
|
78
|
+
this.#resourceCursors.set(message.result.nextCursor, sweep);
|
|
79
|
+
}
|
|
80
|
+
else if (sweep === this.#latestSweep) {
|
|
81
|
+
const uris = [...new Set(sweep.uris)].sort();
|
|
82
|
+
if (JSON.stringify(uris) !== JSON.stringify(this.#filter.resourceSubscriptions ?? [])) {
|
|
83
|
+
this.#filter.resourceSubscriptions = uris;
|
|
84
|
+
this.#open();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if ("id" in message) {
|
|
90
|
+
const stream = [this.#active, this.#pending].find((entry) => entry?.id === message.id);
|
|
91
|
+
if (!stream)
|
|
92
|
+
return true;
|
|
93
|
+
// A response ends the long-lived request; graceful server shutdown is not a retry trigger.
|
|
94
|
+
this.#retire(stream);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (!("method" in message))
|
|
98
|
+
return true;
|
|
99
|
+
if (message.method === "notifications/cancelled") {
|
|
100
|
+
const stream = [this.#active, this.#pending].find((entry) => entry?.id === message.params?.requestId);
|
|
101
|
+
if (stream) {
|
|
102
|
+
this.#retire(stream);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const meta = message.params?._meta;
|
|
107
|
+
const id = isRecord(meta) ? meta[subscriptionKey] : undefined;
|
|
108
|
+
const acknowledged = message.method === "notifications/subscriptions/acknowledged";
|
|
109
|
+
const list = Object.hasOwn(listFilters, message.method);
|
|
110
|
+
const resource = message.method === "notifications/resources/updated";
|
|
111
|
+
if (!acknowledged && !list && !resource)
|
|
112
|
+
return true;
|
|
113
|
+
const active = [this.#active, this.#pending].find((entry) => entry?.id === id);
|
|
114
|
+
if (!active)
|
|
115
|
+
return false;
|
|
116
|
+
if (acknowledged) {
|
|
117
|
+
if (active.accepted)
|
|
118
|
+
return false;
|
|
119
|
+
if (!isRecord(message.params?.notifications)) {
|
|
120
|
+
this.#failed(active);
|
|
121
|
+
this.#cancel(active);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
const offered = message.params.notifications;
|
|
125
|
+
const accepted = {};
|
|
126
|
+
for (const key of Object.values(listFilters))
|
|
127
|
+
if (active.requested[key] && offered[key] === true)
|
|
128
|
+
accepted[key] = true;
|
|
129
|
+
const uris = offered.resourceSubscriptions;
|
|
130
|
+
if (Array.isArray(uris))
|
|
131
|
+
accepted.resourceSubscriptions = active.requested.resourceSubscriptions?.filter((uri) => uris.includes(uri));
|
|
132
|
+
active.accepted = accepted;
|
|
133
|
+
const previous = this.#active;
|
|
134
|
+
this.#pending = undefined;
|
|
135
|
+
this.#active = active;
|
|
136
|
+
if (previous && previous !== active)
|
|
137
|
+
this.#cancel(previous);
|
|
138
|
+
if (!Object.values(accepted).some((value) => value === true) && !accepted.resourceSubscriptions?.length)
|
|
139
|
+
this.#cancel(active);
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
if (active !== this.#active || !active.accepted)
|
|
143
|
+
return false;
|
|
144
|
+
const deliver = resource
|
|
145
|
+
? typeof message.params?.uri === "string" &&
|
|
146
|
+
active.accepted.resourceSubscriptions?.includes(message.params.uri) === true
|
|
147
|
+
: active.accepted[listFilters[message.method]] === true;
|
|
148
|
+
if (deliver)
|
|
149
|
+
this.#retry = 0;
|
|
150
|
+
return deliver;
|
|
151
|
+
}
|
|
152
|
+
failedSend(message) {
|
|
153
|
+
if ("id" in message && message.id !== undefined)
|
|
154
|
+
this.#resourceLists.delete(message.id);
|
|
155
|
+
}
|
|
156
|
+
dispose() {
|
|
157
|
+
this.#closed = true;
|
|
158
|
+
if (this.#timer)
|
|
159
|
+
mcpElapsedClock.cancel(this.#timer);
|
|
160
|
+
this.#timer = undefined;
|
|
161
|
+
if (this.#pending)
|
|
162
|
+
this.#cancel(this.#pending);
|
|
163
|
+
if (this.#active)
|
|
164
|
+
this.#cancel(this.#active);
|
|
165
|
+
this.#resourceLists.clear();
|
|
166
|
+
this.#resourceCursors.clear();
|
|
167
|
+
this.#latestSweep = undefined;
|
|
168
|
+
}
|
|
169
|
+
#supports(feature, flag) {
|
|
170
|
+
const capability = this.#capabilities[feature];
|
|
171
|
+
return isRecord(capability) && capability[flag] === true;
|
|
172
|
+
}
|
|
173
|
+
#retire(active) {
|
|
174
|
+
if (this.#active === active)
|
|
175
|
+
this.#active = undefined;
|
|
176
|
+
if (this.#pending === active)
|
|
177
|
+
this.#pending = undefined;
|
|
178
|
+
}
|
|
179
|
+
#cancel(active) {
|
|
180
|
+
this.#retire(active);
|
|
181
|
+
void this.#send({
|
|
182
|
+
jsonrpc: "2.0",
|
|
183
|
+
method: "notifications/cancelled",
|
|
184
|
+
params: { requestId: active.id },
|
|
185
|
+
}).catch(() => { });
|
|
186
|
+
}
|
|
187
|
+
#open() {
|
|
188
|
+
if (this.#closed || JSON.stringify((this.#pending ?? this.#active)?.requested) === JSON.stringify(this.#filter))
|
|
189
|
+
return;
|
|
190
|
+
if (this.#timer)
|
|
191
|
+
mcpElapsedClock.cancel(this.#timer);
|
|
192
|
+
this.#timer = undefined;
|
|
193
|
+
if (this.#pending)
|
|
194
|
+
this.#cancel(this.#pending);
|
|
195
|
+
if (!Object.values(this.#filter).some((value) => value === true) && !this.#filter.resourceSubscriptions?.length) {
|
|
196
|
+
if (this.#active)
|
|
197
|
+
this.#cancel(this.#active);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const active = { id: `senpi-subscription-${randomUUID()}`, requested: structuredClone(this.#filter) };
|
|
201
|
+
this.#pending = active;
|
|
202
|
+
// HTTP send remains pending for the entire SSE lifetime. No caller awaits it.
|
|
203
|
+
void this.#send({
|
|
204
|
+
jsonrpc: "2.0",
|
|
205
|
+
id: active.id,
|
|
206
|
+
method: "subscriptions/listen",
|
|
207
|
+
params: { notifications: active.requested },
|
|
208
|
+
}).then(() => {
|
|
209
|
+
if (this.#sendCompletesStream)
|
|
210
|
+
this.#failed(active);
|
|
211
|
+
}, () => this.#failed(active));
|
|
212
|
+
}
|
|
213
|
+
#failed(active) {
|
|
214
|
+
if (this.#closed || (this.#active !== active && this.#pending !== active))
|
|
215
|
+
return;
|
|
216
|
+
this.#retire(active);
|
|
217
|
+
if (this.#pending || this.#timer)
|
|
218
|
+
return;
|
|
219
|
+
this.#timer = mcpElapsedClock.schedule(() => {
|
|
220
|
+
this.#timer = undefined;
|
|
221
|
+
this.#open();
|
|
222
|
+
}, Math.min(30_000, 1000 * 2 ** Math.min(this.#retry++, 5)));
|
|
223
|
+
this.#timer.unref?.();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=subscriptions.js.map
|