@code-yeongyu/senpi 2026.10.25-4 → 2026.10.26-2
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 +47 -0
- package/dist/bundle/chunks/{anthropic-messages-WBASBTIW.js → anthropic-messages-ODSFJCXR.js} +3 -3
- package/dist/bundle/chunks/{args-B444OQBK.js → args-SWKJAYQD.js} +1 -1
- package/dist/bundle/chunks/azure-openai-responses-REGXS4XO.js +2 -0
- package/dist/bundle/chunks/bedrock-converse-stream.js +3 -3
- package/dist/bundle/chunks/chunk-2XF6W6F6.js +410 -0
- package/dist/bundle/chunks/{chunk-WDPQ6CZJ.js → chunk-4QVORFQ2.js} +1 -1
- package/dist/bundle/chunks/{chunk-6AOVUTUR.js → chunk-5W3DTM2J.js} +2 -2
- package/dist/bundle/chunks/{chunk-E6WYH3ZL.js → chunk-6GAMRAVM.js} +9 -7
- package/dist/bundle/chunks/{chunk-N7IN46Q5.js → chunk-7IZH2KQX.js} +9 -7
- package/dist/bundle/chunks/{chunk-7GZUFOM3.js → chunk-ARSENVDY.js} +1 -1
- package/dist/bundle/chunks/{chunk-STMXIBXF.js → chunk-CNDPCYNT.js} +1 -1
- package/dist/bundle/chunks/{chunk-XF3LJC3O.js → chunk-CYOHFREV.js} +1 -1
- package/dist/bundle/chunks/{chunk-E6D4RIT7.js → chunk-DNDZPKIQ.js} +2 -2
- package/dist/bundle/chunks/{chunk-E4LBKOZG.js → chunk-EQNUL2WB.js} +4 -4
- package/dist/bundle/chunks/{chunk-2RB5EL4X.js → chunk-ESNSRUW4.js} +1 -1
- package/dist/bundle/chunks/{chunk-4AOMNOGY.js → chunk-FQDARFHT.js} +1 -1
- package/dist/bundle/chunks/{chunk-BWA4FVFW.js → chunk-GPPOPK76.js} +3 -3
- 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-I2WFLKZ2.js +5 -0
- package/dist/bundle/chunks/{chunk-WD6WBYC5.js → chunk-IHKHCWWG.js} +1 -1
- package/dist/bundle/chunks/{chunk-T36LJ6MW.js → chunk-IY4G44MN.js} +1 -1
- package/dist/bundle/chunks/{chunk-BZ7726QO.js → chunk-J2YAYTB2.js} +1 -1
- package/dist/bundle/chunks/{chunk-UFJ2K7Q7.js → chunk-JH7MVRNJ.js} +1 -1
- package/dist/bundle/chunks/chunk-KBSFTXAT.js +2 -0
- package/dist/bundle/chunks/{chunk-OLCDIITS.js → chunk-KRO63T4Q.js} +1 -1
- package/dist/bundle/chunks/{chunk-TTU2RNVX.js → chunk-KTDIIF2F.js} +1 -1
- package/dist/bundle/chunks/{chunk-KNVNKDER.js → chunk-L2O6K3SJ.js} +1 -1
- package/dist/bundle/chunks/{chunk-3BTN337S.js → chunk-NDNIMC5W.js} +1 -1
- package/dist/bundle/chunks/{chunk-ITGYN22E.js → chunk-ODRGPC5S.js} +1 -1
- package/dist/bundle/chunks/{chunk-4MOZXOJX.js → chunk-PDG4XWO2.js} +1 -1
- package/dist/bundle/chunks/{chunk-3QCYBOKN.js → chunk-PPECSSWX.js} +1 -1
- package/dist/bundle/chunks/{chunk-4P7WARQZ.js → chunk-PY4ZQHTN.js} +1 -1
- package/dist/bundle/chunks/{chunk-C2MQ4QXN.js → chunk-RPW3XQW3.js} +48 -42
- package/dist/bundle/chunks/{chunk-JMOUU75H.js → chunk-RQ5JTXUC.js} +1 -1
- package/dist/bundle/chunks/chunk-TQ67PB7U.js +13 -0
- package/dist/bundle/chunks/{chunk-5JJDR6XI.js → chunk-UJPG4ROT.js} +1 -1
- package/dist/bundle/chunks/{chunk-FHXR4ZAC.js → chunk-XM36IFP3.js} +1 -1
- package/dist/bundle/chunks/{chunk-PT6JJUN2.js → chunk-XMNPK472.js} +1 -1
- package/dist/bundle/chunks/{client-WTYEICLH.js → chunk-Y3TMJV3J.js} +3 -3
- package/dist/bundle/chunks/{chunk-GSSD3R5X.js → chunk-Z6BREXV3.js} +1 -1
- 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-7CNOCJCN.js → chunk-ZP6JJDYV.js} +1 -1
- package/dist/bundle/chunks/cli-main-Q2GA53XM.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-WDHT7BBV.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-5R3TYAYB.js +9 -0
- package/dist/bundle/chunks/google-generative-ai-FHYFXKLA.js +2 -0
- package/dist/bundle/chunks/google-vertex-5DWE5U5K.js +2 -0
- package/dist/bundle/chunks/{help-fast-path-3Z26DQSV.js → help-fast-path-FETLIF3W.js} +1 -1
- package/dist/bundle/chunks/host-command-XEB7SHT3.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-FFN52LXJ.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-5DEKY3YW.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-I2PI7DDF.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-Z2CW4SDM.js → multi-session-host-OPR5VBFO.js} +6 -7
- package/dist/bundle/chunks/{openai-codex-responses-SERAFKAS.js → openai-codex-responses-WSEX6SH7.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-BZB5O45A.js → openai-completions-YCBHTBSS.js} +1 -1
- package/dist/bundle/chunks/openai-responses-GXNSQF4A.js +3 -0
- package/dist/bundle/chunks/{package-manager-cli-N55U6PEO.js → package-manager-cli-W55E3LHP.js} +1 -1
- package/dist/bundle/chunks/{pi-messages-VXATX45B.js → pi-messages-O6GISIRW.js} +2 -2
- package/dist/bundle/chunks/{role-JVSBNKGE.js → role-SHIZQKK4.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-CBGCBYHV.js → rotation-stream-2PCZWNKF.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-IDUJTSYS.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-B7RRSANU.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-LMZH5JLH.js +3 -0
- package/dist/bundle/chunks/{session-picker-E4KUJDLO.js → session-picker-DTGBOXDR.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +214 -205
- package/dist/bundle/chunks/{typesafe-system-one-P75WJ767.js → typesafe-system-one-XGYJJVSE.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/agent-session.d.ts +11 -1
- package/dist/core/agent-session.js +19 -2
- package/dist/core/extensions/builtin/compaction/index.js +7 -1
- package/dist/core/extensions/builtin/herdr/index.js +9 -5
- package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.d.ts +6 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.js +25 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +3 -3
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +50 -19
- package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +4 -3
- package/dist/core/extensions/builtin/mcp/auth/oauth.js +19 -12
- 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/dispatch.js +1 -0
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +33 -35
- package/dist/core/extensions/builtin/mcp/modern/request.d.ts +1 -1
- package/dist/core/extensions/builtin/mcp/modern/request.js +4 -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/modern/transport.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/modern/transport.js +4 -2
- 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/core/extensions/builtin/terminal/extension-state.js +2 -1
- package/dist/core/extensions/builtin/vertex-model-garden/auth.d.ts +9 -0
- package/dist/core/extensions/builtin/vertex-model-garden/auth.js +17 -1
- package/dist/core/extensions/builtin/websearch/websearch/providers/google.d.ts +6 -0
- package/dist/core/extensions/builtin/websearch/websearch/providers/google.js +6 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/lifecycle-children.d.ts +3 -1
- package/dist/core/lifecycle-children.js +79 -15
- package/dist/core/lifecycle-record.js +1 -24
- package/dist/core/lifecycle-session-binding.js +2 -1
- package/dist/core/lifecycle-wait-items.d.ts +17 -0
- package/dist/core/lifecycle-wait-items.js +72 -0
- package/dist/core/model-runtime.js +6 -1
- package/dist/core/provider-api-key-auth.d.ts +6 -1
- package/dist/core/provider-api-key-auth.js +11 -4
- package/dist/core/provider-composer.d.ts +8 -1
- package/dist/core/provider-composer.js +7 -3
- package/dist/core/sdk.js +4 -1
- package/dist/core/session-activity.js +2 -0
- package/dist/core/session-lifecycle-types.d.ts +20 -4
- package/dist/core/session-lifecycle.js +35 -4
- package/dist/core/structured-session-lane.d.ts +33 -0
- package/dist/core/structured-session-lane.js +62 -0
- package/dist/core/work-custody-record.d.ts +2 -1
- package/dist/core/work-custody-record.js +2 -0
- package/dist/core/work-obligations.d.ts +1 -1
- package/dist/core/work-obligations.js +8 -2
- 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/json-event.d.ts +79 -1
- package/dist/modes/json-event.js +79 -1
- package/dist/modes/rpc/compact-content-index.d.ts +85 -0
- package/dist/modes/rpc/compact-content-index.js +254 -0
- package/dist/modes/rpc/compact-content-store.d.ts +59 -0
- package/dist/modes/rpc/compact-content-store.js +134 -0
- package/dist/modes/rpc/compact-index-pages.d.ts +12 -0
- package/dist/modes/rpc/compact-index-pages.js +31 -0
- package/dist/modes/rpc/compact-session-state.d.ts +83 -0
- package/dist/modes/rpc/compact-session-state.js +197 -0
- package/dist/modes/rpc/compact-spool-dir.d.ts +8 -0
- package/dist/modes/rpc/compact-spool-dir.js +94 -0
- package/dist/modes/rpc/compact-spool-storage.d.ts +53 -0
- package/dist/modes/rpc/compact-spool-storage.js +162 -0
- package/dist/modes/rpc/compact-storage-io.d.ts +28 -0
- package/dist/modes/rpc/compact-storage-io.js +25 -0
- package/dist/modes/rpc/compact-transition.d.ts +41 -0
- package/dist/modes/rpc/compact-transition.js +12 -0
- package/dist/modes/rpc/connection-handler.d.ts +4 -0
- package/dist/modes/rpc/connection-handler.js +51 -13
- 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 +6 -3
- 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/security-presentations.js +2 -1
- package/dist/modes/rpc/session-attachment-gate.js +1 -0
- package/dist/modes/rpc/session-binding.js +30 -17
- 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.d.ts +2 -0
- package/dist/modes/rpc/session-event-writer.js +24 -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-record-stream.d.ts +3 -3
- package/dist/modes/rpc/session-record-stream.js +3 -3
- 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-structured-lanes.d.ts +12 -0
- package/dist/modes/rpc/session-structured-lanes.js +24 -0
- package/dist/modes/rpc/session-worker-client.js +6 -5
- 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/structured-session-records.d.ts +12 -0
- package/dist/modes/rpc/structured-session-records.js +88 -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/extensions.md +7 -1
- package/docs/mcp.md +20 -0
- package/docs/rpc.md +61 -2
- 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
|
@@ -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
|
},
|
|
@@ -39,6 +39,7 @@ export async function materializeNegotiatedTransport(options, serverName, timeou
|
|
|
39
39
|
clientInfo,
|
|
40
40
|
clientCapabilities: {},
|
|
41
41
|
protocolVersion: outcome.version,
|
|
42
|
+
logger,
|
|
42
43
|
}), outcome.discovery, outcome.version);
|
|
43
44
|
const version = outcome.version;
|
|
44
45
|
logVersion(transport, () => version);
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
export declare function encodeMcpHeaderValue(value: string): string;
|
|
3
3
|
/** Configured headers must never supply parameter metadata absent from the request body. */
|
|
4
4
|
export declare function clearMcpParameterHeaders(headers: Headers): void;
|
|
5
|
+
/** A local argument error, not a failed transport or an untrusted server diagnostic. */
|
|
6
|
+
export declare class ParameterHeaderValidationError extends Error {
|
|
7
|
+
}
|
|
5
8
|
/** Connection-local catalog: never shared with another server or transport generation. */
|
|
6
9
|
export declare class ModernParameterHeaders {
|
|
7
10
|
#private;
|
|
8
|
-
filterTools(tools: readonly unknown[], firstPage: boolean): unknown[];
|
|
11
|
+
filterTools(tools: readonly unknown[], firstPage: boolean, warn: (message: string) => void): unknown[];
|
|
9
12
|
append(headers: Headers, name: unknown, args: unknown): void;
|
|
10
13
|
}
|
|
11
14
|
//# sourceMappingURL=parameter-headers.d.ts.map
|
|
@@ -18,22 +18,31 @@ export function clearMcpParameterHeaders(headers) {
|
|
|
18
18
|
function parseParameterHeaders(schema) {
|
|
19
19
|
const headers = [];
|
|
20
20
|
const names = new Set();
|
|
21
|
+
let reason = "";
|
|
21
22
|
const visit = (node, path) => {
|
|
23
|
+
if (Array.isArray(node))
|
|
24
|
+
return node.every((child) => visit(child, undefined));
|
|
22
25
|
if (!isRecord(node))
|
|
23
26
|
return true;
|
|
24
27
|
if (Object.hasOwn(node, "x-mcp-header")) {
|
|
25
28
|
const name = node["x-mcp-header"];
|
|
26
29
|
const type = node.type;
|
|
27
|
-
if (!path?.length
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
if (!path?.length)
|
|
31
|
+
reason = "x-mcp-header must be on a properties-only path";
|
|
32
|
+
else if (typeof name !== "string" || !/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(name))
|
|
33
|
+
reason = "x-mcp-header must be a nonempty HTTP field-name token";
|
|
34
|
+
else if (names.has(name.toLowerCase()))
|
|
35
|
+
reason = "x-mcp-header names must be case-insensitively unique";
|
|
36
|
+
else if (type !== "string" && type !== "integer" && type !== "boolean")
|
|
37
|
+
reason = "x-mcp-header type must be string, integer, or boolean";
|
|
38
|
+
else {
|
|
39
|
+
names.add(name.toLowerCase());
|
|
40
|
+
headers.push({ name, path, type });
|
|
41
|
+
}
|
|
42
|
+
if (reason)
|
|
32
43
|
return false;
|
|
33
|
-
names.add(name.toLowerCase());
|
|
34
|
-
headers.push({ name, path, type });
|
|
35
44
|
}
|
|
36
|
-
//
|
|
45
|
+
// "Anywhere else" includes unknown keywords; instance-data keywords are not schemas.
|
|
37
46
|
for (const [keyword, value] of Object.entries(node)) {
|
|
38
47
|
if (["properties", "patternProperties", "$defs", "definitions", "dependentSchemas", "dependencies"].includes(keyword)) {
|
|
39
48
|
if (!isRecord(value))
|
|
@@ -44,46 +53,31 @@ function parseParameterHeaders(schema) {
|
|
|
44
53
|
return false;
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
|
-
else if ([
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"allOf",
|
|
51
|
-
"anyOf",
|
|
52
|
-
"oneOf",
|
|
53
|
-
"additionalItems",
|
|
54
|
-
"additionalProperties",
|
|
55
|
-
"unevaluatedItems",
|
|
56
|
-
"unevaluatedProperties",
|
|
57
|
-
"contains",
|
|
58
|
-
"propertyNames",
|
|
59
|
-
"not",
|
|
60
|
-
"if",
|
|
61
|
-
"then",
|
|
62
|
-
"else",
|
|
63
|
-
"contentSchema",
|
|
64
|
-
].includes(keyword)) {
|
|
65
|
-
for (const child of Array.isArray(value) ? value : [value]) {
|
|
66
|
-
if (!visit(child, undefined))
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
56
|
+
else if (!["examples", "default", "const", "enum"].includes(keyword)) {
|
|
57
|
+
if (!visit(value, undefined))
|
|
58
|
+
return false;
|
|
69
59
|
}
|
|
70
60
|
}
|
|
71
61
|
return true;
|
|
72
62
|
};
|
|
73
|
-
return visit(schema, []) ? headers :
|
|
63
|
+
return visit(schema, []) ? headers : reason;
|
|
64
|
+
}
|
|
65
|
+
/** A local argument error, not a failed transport or an untrusted server diagnostic. */
|
|
66
|
+
export class ParameterHeaderValidationError extends Error {
|
|
74
67
|
}
|
|
75
68
|
/** Connection-local catalog: never shared with another server or transport generation. */
|
|
76
69
|
export class ModernParameterHeaders {
|
|
77
70
|
#tools = new Map();
|
|
78
|
-
filterTools(tools, firstPage) {
|
|
71
|
+
filterTools(tools, firstPage, warn) {
|
|
79
72
|
if (firstPage)
|
|
80
73
|
this.#tools.clear();
|
|
81
74
|
return tools.filter((tool) => {
|
|
82
75
|
if (!isRecord(tool) || typeof tool.name !== "string")
|
|
83
76
|
return true;
|
|
84
77
|
const headers = parseParameterHeaders(tool.inputSchema);
|
|
85
|
-
if (headers ===
|
|
78
|
+
if (typeof headers === "string") {
|
|
86
79
|
this.#tools.delete(tool.name);
|
|
80
|
+
warn(`Rejected MCP tool ${JSON.stringify(tool.name)}: ${headers}`);
|
|
87
81
|
return false;
|
|
88
82
|
}
|
|
89
83
|
this.#tools.set(tool.name, headers);
|
|
@@ -98,11 +92,15 @@ export class ModernParameterHeaders {
|
|
|
98
92
|
for (const key of parameter.path) {
|
|
99
93
|
value = isRecord(value) && Object.hasOwn(value, key) ? value[key] : undefined;
|
|
100
94
|
}
|
|
95
|
+
if (value === undefined)
|
|
96
|
+
continue;
|
|
101
97
|
const valid = (parameter.type === "string" && typeof value === "string") ||
|
|
102
98
|
(parameter.type === "boolean" && typeof value === "boolean") ||
|
|
103
99
|
(parameter.type === "integer" && typeof value === "number" && Number.isSafeInteger(value));
|
|
104
|
-
if (valid)
|
|
105
|
-
|
|
100
|
+
if (!valid) {
|
|
101
|
+
throw new ParameterHeaderValidationError(`MCP tool ${JSON.stringify(name)} argument ${JSON.stringify(parameter.path)} must be ${parameter.type === "integer" ? "a safe integer" : `a ${parameter.type}`}`);
|
|
102
|
+
}
|
|
103
|
+
headers.set(`Mcp-Param-${parameter.name}`, encodeMcpHeaderValue(String(value)));
|
|
106
104
|
}
|
|
107
105
|
}
|
|
108
106
|
}
|
|
@@ -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
|
|
@@ -7,6 +7,7 @@ import { isRecord, validateMrtrForm } from "./form-schema.js";
|
|
|
7
7
|
import { McpMrtrError } from "./errors.js";
|
|
8
8
|
import { queuedForm } from "./consent-queue.js";
|
|
9
9
|
import { parseInputs } from "./inputs.js";
|
|
10
|
+
import { ParameterHeaderValidationError } from "./parameter-headers.js";
|
|
10
11
|
export const MAX_MCP_MRTR_ROUNDS = 8;
|
|
11
12
|
const capableMethods = new Set(["tools/call", "resources/read", "prompts/get"]);
|
|
12
13
|
const capabilitiesKey = "io.modelcontextprotocol/clientCapabilities";
|
|
@@ -22,7 +23,7 @@ export function getModernDispatchGuard(params) {
|
|
|
22
23
|
? params[modernDispatchGuard]
|
|
23
24
|
: undefined;
|
|
24
25
|
}
|
|
25
|
-
/** One instance-local adapter. Captured SDK requests retain ID allocation
|
|
26
|
+
/** One instance-local adapter. Captured SDK requests retain ID allocation; the caller supplies its era's result contract. */
|
|
26
27
|
export function installModernRequestAdapter(client, protocol, clock = mcpElapsedClock, readCredential = () => undefined) {
|
|
27
28
|
const sdkRequest = client.request.bind(client);
|
|
28
29
|
states.set(client, protocol);
|
|
@@ -259,6 +260,8 @@ export function installModernRequestAdapter(client, protocol, clock = mcpElapsed
|
|
|
259
260
|
throw error;
|
|
260
261
|
if (error instanceof AuthError)
|
|
261
262
|
throw error;
|
|
263
|
+
if (error instanceof ParameterHeaderValidationError)
|
|
264
|
+
throw error;
|
|
262
265
|
const remoteCode = isRecord(error) && typeof error.code === "number" ? error.code : undefined;
|
|
263
266
|
throw new McpMrtrError("MCP_MRTR_TRANSPORT_FAILED", remoteCode);
|
|
264
267
|
}
|
|
@@ -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
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ValidateFunction } from "ajv";
|
|
2
|
+
import type { McpJsonSchema, McpListedTool } from "../tool-contracts.ts";
|
|
3
|
+
type SchemaErrorCode = "MCP_SCHEMA_UNSUPPORTED_DIALECT" | "MCP_SCHEMA_INVALID" | "MCP_SCHEMA_LIMIT" | "MCP_OUTPUT_SCHEMA_MISMATCH";
|
|
4
|
+
export declare class McpToolSchemaError extends Error {
|
|
5
|
+
readonly code: SchemaErrorCode;
|
|
6
|
+
constructor(code: SchemaErrorCode, message: string);
|
|
7
|
+
}
|
|
8
|
+
/** No coercion/default insertion/removal, no network resolver, no shared $id namespace. */
|
|
9
|
+
export declare function compileModernSchema(schema: McpJsonSchema): ValidateFunction;
|
|
10
|
+
/** Reject one unusable definition, never its valid siblings; diagnostics contain no schema or arguments. */
|
|
11
|
+
export declare function hasSupportedModernToolSchemas(tool: Pick<McpListedTool, "name" | "inputSchema" | "outputSchema">): boolean;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=tool-schema.d.ts.map
|