@graphorin/mcp 0.5.0
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 +25 -0
- package/LICENSE +21 -0
- package/README.md +296 -0
- package/dist/client/adapt-result.d.ts +25 -0
- package/dist/client/adapt-result.d.ts.map +1 -0
- package/dist/client/adapt-result.js +174 -0
- package/dist/client/adapt-result.js.map +1 -0
- package/dist/client/client-handlers.js +134 -0
- package/dist/client/client-handlers.js.map +1 -0
- package/dist/client/client.d.ts +21 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +358 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/defer-loading.d.ts +7 -0
- package/dist/client/defer-loading.d.ts.map +1 -0
- package/dist/client/defer-loading.js +81 -0
- package/dist/client/defer-loading.js.map +1 -0
- package/dist/client/inbound-filters.js +65 -0
- package/dist/client/inbound-filters.js.map +1 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/index.js +7 -0
- package/dist/client/mcp-resource-reader.d.ts +22 -0
- package/dist/client/mcp-resource-reader.d.ts.map +1 -0
- package/dist/client/mcp-resource-reader.js +113 -0
- package/dist/client/mcp-resource-reader.js.map +1 -0
- package/dist/client/pinning.js +41 -0
- package/dist/client/pinning.js.map +1 -0
- package/dist/client/to-tools.d.ts +41 -0
- package/dist/client/to-tools.d.ts.map +1 -0
- package/dist/client/to-tools.js +125 -0
- package/dist/client/to-tools.js.map +1 -0
- package/dist/client/transport-factory.js +86 -0
- package/dist/client/transport-factory.js.map +1 -0
- package/dist/client/types.d.ts +353 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/errors/index.d.ts +120 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +88 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/helpers/identity.d.ts +22 -0
- package/dist/helpers/identity.d.ts.map +1 -0
- package/dist/helpers/identity.js +85 -0
- package/dist/helpers/identity.js.map +1 -0
- package/dist/helpers/index.d.ts +3 -0
- package/dist/helpers/index.js +4 -0
- package/dist/helpers/validate-config.d.ts +16 -0
- package/dist/helpers/validate-config.d.ts.map +1 -0
- package/dist/helpers/validate-config.js +61 -0
- package/dist/helpers/validate-config.js.map +1 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth/bridge.d.ts +59 -0
- package/dist/oauth/bridge.d.ts.map +1 -0
- package/dist/oauth/bridge.js +75 -0
- package/dist/oauth/bridge.js.map +1 -0
- package/dist/oauth/index.d.ts +3 -0
- package/dist/oauth/index.js +4 -0
- package/dist/oauth/library.d.ts +23 -0
- package/dist/oauth/library.d.ts.map +1 -0
- package/dist/oauth/library.js +27 -0
- package/dist/oauth/library.js.map +1 -0
- package/dist/registry/json-schema.js +215 -0
- package/dist/registry/json-schema.js.map +1 -0
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.js +4 -0
- package/dist/transport/types.d.ts +93 -0
- package/dist/transport/types.d.ts.map +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { incrementCounter } from "@graphorin/tools/audit";
|
|
2
|
+
|
|
3
|
+
//#region src/client/defer-loading.ts
|
|
4
|
+
/**
|
|
5
|
+
* Deferred-loading resolution for the `toTools()` adapter (extracted
|
|
6
|
+
* from `to-tools.ts` per F-MCP-001).
|
|
7
|
+
*
|
|
8
|
+
* Computes the per-server effective `defer_loading` flag: when the
|
|
9
|
+
* operator does not pass an explicit `defer_loading`, the auto-default
|
|
10
|
+
* fires once when `listTools().length > deferLoadingThreshold` (default
|
|
11
|
+
* `10`) and flips deferral on for every tool from the server. Emits the
|
|
12
|
+
* retrieval counters and the once-per-server INFO log.
|
|
13
|
+
*
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
*/
|
|
16
|
+
/** Default auto-deferral threshold per the operator-facing convention. */
|
|
17
|
+
const DEFAULT_DEFER_LOADING_THRESHOLD = 10;
|
|
18
|
+
/**
|
|
19
|
+
* Process-scoped dedup keys for the auto-default INFO-log. Each
|
|
20
|
+
* `(serverIdentity, threshold)` pair triggers the log once across all
|
|
21
|
+
* `MCPClient.toTools(...)` invocations in the process so re-running
|
|
22
|
+
* `toTools()` on the same client does not double-log.
|
|
23
|
+
*/
|
|
24
|
+
const autoDeferralInfoSeen = /* @__PURE__ */ new Set();
|
|
25
|
+
/**
|
|
26
|
+
* Process-scoped dedup keys for the explicit `defer_loading: true`
|
|
27
|
+
* INFO-log. Mirrors the auto-default discipline.
|
|
28
|
+
*/
|
|
29
|
+
const explicitDeferralInfoSeen = /* @__PURE__ */ new Set();
|
|
30
|
+
/**
|
|
31
|
+
* Reset the deferral dedup sets. Used by
|
|
32
|
+
* {@link import('./to-tools.js')._resetMcpAdapterDedupForTesting}.
|
|
33
|
+
*
|
|
34
|
+
* @experimental
|
|
35
|
+
*/
|
|
36
|
+
function _resetDeferLoadingDedupForTesting() {
|
|
37
|
+
autoDeferralInfoSeen.clear();
|
|
38
|
+
explicitDeferralInfoSeen.clear();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the effective `defer_loading` flag for a server's tool
|
|
42
|
+
* catalogue and emit the retrieval counters + once-per-server INFO log.
|
|
43
|
+
*/
|
|
44
|
+
function resolveDeferLoading(args) {
|
|
45
|
+
const total = args.toolNames.length;
|
|
46
|
+
const autoDeferralFired = args.explicitDefer === void 0 && total > args.threshold;
|
|
47
|
+
const resolvedDeferLoading = args.explicitDefer ?? autoDeferralFired;
|
|
48
|
+
if (autoDeferralFired) {
|
|
49
|
+
for (let i = 0; i < total; i++) incrementCounter("tool.retrieval.deferred.total", { source: "mcp-server-default" });
|
|
50
|
+
const dedupKey = `${args.serverIdentity.id}:${args.threshold}`;
|
|
51
|
+
if (!autoDeferralInfoSeen.has(dedupKey) && args.logger !== void 0) {
|
|
52
|
+
autoDeferralInfoSeen.add(dedupKey);
|
|
53
|
+
args.logger("info", "mcp.tools.defer_loading.auto-default fired", {
|
|
54
|
+
server: args.serverIdentity.id,
|
|
55
|
+
thresholdValue: args.threshold,
|
|
56
|
+
toolCount: total,
|
|
57
|
+
toolNames: [...args.toolNames],
|
|
58
|
+
source: "mcp-server-default"
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
} else if (args.explicitDefer === true) {
|
|
62
|
+
for (let i = 0; i < total; i++) incrementCounter("tool.retrieval.deferred.total", { source: "explicit" });
|
|
63
|
+
const dedupKey = `${args.serverIdentity.id}:explicit`;
|
|
64
|
+
if (!explicitDeferralInfoSeen.has(dedupKey) && args.logger !== void 0) {
|
|
65
|
+
explicitDeferralInfoSeen.add(dedupKey);
|
|
66
|
+
args.logger("info", "mcp.tools.defer_loading.explicit fired", {
|
|
67
|
+
server: args.serverIdentity.id,
|
|
68
|
+
toolCount: total,
|
|
69
|
+
source: "explicit"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
} else if (args.explicitDefer === false) for (let i = 0; i < total; i++) incrementCounter("tool.retrieval.eager.total", { source: "mcp-explicit-eager" });
|
|
73
|
+
return {
|
|
74
|
+
autoDeferralFired,
|
|
75
|
+
resolvedDeferLoading
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
export { DEFAULT_DEFER_LOADING_THRESHOLD, _resetDeferLoadingDedupForTesting, resolveDeferLoading };
|
|
81
|
+
//# sourceMappingURL=defer-loading.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defer-loading.js","names":[],"sources":["../../src/client/defer-loading.ts"],"sourcesContent":["/**\n * Deferred-loading resolution for the `toTools()` adapter (extracted\n * from `to-tools.ts` per F-MCP-001).\n *\n * Computes the per-server effective `defer_loading` flag: when the\n * operator does not pass an explicit `defer_loading`, the auto-default\n * fires once when `listTools().length > deferLoadingThreshold` (default\n * `10`) and flips deferral on for every tool from the server. Emits the\n * retrieval counters and the once-per-server INFO log.\n *\n * @packageDocumentation\n */\n\nimport { incrementCounter } from '@graphorin/tools/audit';\nimport type { ServerIdentity } from '../transport/types.js';\n\n/** Default auto-deferral threshold per the operator-facing convention. */\nexport const DEFAULT_DEFER_LOADING_THRESHOLD = 10;\n\n/** Operator-supplied structured logger (mirrors the client logger shape). */\ntype AdapterLogger = (\n level: 'debug' | 'info' | 'warn' | 'error',\n message: string,\n fields?: Record<string, unknown>,\n) => void;\n\n/**\n * Process-scoped dedup keys for the auto-default INFO-log. Each\n * `(serverIdentity, threshold)` pair triggers the log once across all\n * `MCPClient.toTools(...)` invocations in the process so re-running\n * `toTools()` on the same client does not double-log.\n */\nconst autoDeferralInfoSeen = new Set<string>();\n\n/**\n * Process-scoped dedup keys for the explicit `defer_loading: true`\n * INFO-log. Mirrors the auto-default discipline.\n */\nconst explicitDeferralInfoSeen = new Set<string>();\n\n/**\n * Reset the deferral dedup sets. Used by\n * {@link import('./to-tools.js')._resetMcpAdapterDedupForTesting}.\n *\n * @experimental\n */\nexport function _resetDeferLoadingDedupForTesting(): void {\n autoDeferralInfoSeen.clear();\n explicitDeferralInfoSeen.clear();\n}\n\n/** Outcome of {@link resolveDeferLoading}. */\nexport interface DeferralResolution {\n readonly autoDeferralFired: boolean;\n readonly resolvedDeferLoading: boolean;\n}\n\n/**\n * Resolve the effective `defer_loading` flag for a server's tool\n * catalogue and emit the retrieval counters + once-per-server INFO log.\n */\nexport function resolveDeferLoading(args: {\n readonly serverIdentity: ServerIdentity;\n readonly toolNames: ReadonlyArray<string>;\n readonly explicitDefer: boolean | undefined;\n readonly threshold: number;\n readonly logger?: AdapterLogger;\n}): DeferralResolution {\n const total = args.toolNames.length;\n const autoDeferralFired = args.explicitDefer === undefined && total > args.threshold;\n const resolvedDeferLoading = args.explicitDefer ?? autoDeferralFired;\n\n if (autoDeferralFired) {\n for (let i = 0; i < total; i++) {\n incrementCounter('tool.retrieval.deferred.total', { source: 'mcp-server-default' });\n }\n const dedupKey = `${args.serverIdentity.id}:${args.threshold}`;\n if (!autoDeferralInfoSeen.has(dedupKey) && args.logger !== undefined) {\n autoDeferralInfoSeen.add(dedupKey);\n args.logger('info', 'mcp.tools.defer_loading.auto-default fired', {\n server: args.serverIdentity.id,\n thresholdValue: args.threshold,\n toolCount: total,\n toolNames: [...args.toolNames],\n source: 'mcp-server-default',\n });\n }\n } else if (args.explicitDefer === true) {\n for (let i = 0; i < total; i++) {\n incrementCounter('tool.retrieval.deferred.total', { source: 'explicit' });\n }\n const dedupKey = `${args.serverIdentity.id}:explicit`;\n if (!explicitDeferralInfoSeen.has(dedupKey) && args.logger !== undefined) {\n explicitDeferralInfoSeen.add(dedupKey);\n args.logger('info', 'mcp.tools.defer_loading.explicit fired', {\n server: args.serverIdentity.id,\n toolCount: total,\n source: 'explicit',\n });\n }\n } else if (args.explicitDefer === false) {\n for (let i = 0; i < total; i++) {\n incrementCounter('tool.retrieval.eager.total', { source: 'mcp-explicit-eager' });\n }\n }\n\n return { autoDeferralFired, resolvedDeferLoading };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,MAAa,kCAAkC;;;;;;;AAe/C,MAAM,uCAAuB,IAAI,KAAa;;;;;AAM9C,MAAM,2CAA2B,IAAI,KAAa;;;;;;;AAQlD,SAAgB,oCAA0C;AACxD,sBAAqB,OAAO;AAC5B,0BAAyB,OAAO;;;;;;AAalC,SAAgB,oBAAoB,MAMb;CACrB,MAAM,QAAQ,KAAK,UAAU;CAC7B,MAAM,oBAAoB,KAAK,kBAAkB,UAAa,QAAQ,KAAK;CAC3E,MAAM,uBAAuB,KAAK,iBAAiB;AAEnD,KAAI,mBAAmB;AACrB,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,IACzB,kBAAiB,iCAAiC,EAAE,QAAQ,sBAAsB,CAAC;EAErF,MAAM,WAAW,GAAG,KAAK,eAAe,GAAG,GAAG,KAAK;AACnD,MAAI,CAAC,qBAAqB,IAAI,SAAS,IAAI,KAAK,WAAW,QAAW;AACpE,wBAAqB,IAAI,SAAS;AAClC,QAAK,OAAO,QAAQ,8CAA8C;IAChE,QAAQ,KAAK,eAAe;IAC5B,gBAAgB,KAAK;IACrB,WAAW;IACX,WAAW,CAAC,GAAG,KAAK,UAAU;IAC9B,QAAQ;IACT,CAAC;;YAEK,KAAK,kBAAkB,MAAM;AACtC,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,IACzB,kBAAiB,iCAAiC,EAAE,QAAQ,YAAY,CAAC;EAE3E,MAAM,WAAW,GAAG,KAAK,eAAe,GAAG;AAC3C,MAAI,CAAC,yBAAyB,IAAI,SAAS,IAAI,KAAK,WAAW,QAAW;AACxE,4BAAyB,IAAI,SAAS;AACtC,QAAK,OAAO,QAAQ,0CAA0C;IAC5D,QAAQ,KAAK,eAAe;IAC5B,WAAW;IACX,QAAQ;IACT,CAAC;;YAEK,KAAK,kBAAkB,MAChC,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,IACzB,kBAAiB,8BAA8B,EAAE,QAAQ,sBAAsB,CAAC;AAIpF,QAAO;EAAE;EAAmB;EAAsB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { incrementCounter } from "@graphorin/tools/audit";
|
|
2
|
+
import { applyInboundSanitization } from "@graphorin/tools/inbound";
|
|
3
|
+
|
|
4
|
+
//#region src/client/inbound-filters.ts
|
|
5
|
+
/**
|
|
6
|
+
* Process-scoped dedup keys for the `pass-through` override WARN. The
|
|
7
|
+
* spec mandates exactly-one WARN per server identity per process — the
|
|
8
|
+
* Set retains the keys for the lifetime of the process. Tests reset via
|
|
9
|
+
* {@link import('./to-tools.js')._resetMcpAdapterDedupForTesting}.
|
|
10
|
+
*/
|
|
11
|
+
const passthroughWarnSeen = /* @__PURE__ */ new Set();
|
|
12
|
+
/**
|
|
13
|
+
* Reset the inbound-filter dedup set. Used by
|
|
14
|
+
* {@link import('./to-tools.js')._resetMcpAdapterDedupForTesting}.
|
|
15
|
+
*
|
|
16
|
+
* @experimental
|
|
17
|
+
*/
|
|
18
|
+
function _resetInboundFiltersDedupForTesting() {
|
|
19
|
+
passthroughWarnSeen.clear();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the effective per-server inbound-sanitization policy. MCP
|
|
23
|
+
* tools default to the strictest body-level policy.
|
|
24
|
+
*/
|
|
25
|
+
function resolveInboundPolicy(override) {
|
|
26
|
+
return override ?? "detect-and-strip-and-wrap";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* WARN-once per server when the operator opts out of body-level
|
|
30
|
+
* sanitization. The trust class stays `'mcp-derived'` regardless so the
|
|
31
|
+
* per-step preamble in the agent runtime still fires; the WARN exists so
|
|
32
|
+
* the audit log records the operator's deliberate choice.
|
|
33
|
+
*/
|
|
34
|
+
function warnOnPassthroughOverride(args) {
|
|
35
|
+
if (args.resolvedInbound !== "pass-through") return;
|
|
36
|
+
if (passthroughWarnSeen.has(args.serverIdentity.id)) return;
|
|
37
|
+
passthroughWarnSeen.add(args.serverIdentity.id);
|
|
38
|
+
incrementCounter("mcp.inbound.sanitization.passthrough-override.warn.total", { server: args.serverIdentity.id });
|
|
39
|
+
if (args.logger !== void 0) args.logger("warn", "mcp.inbound.sanitization.passthrough-override", {
|
|
40
|
+
server: args.serverIdentity.id,
|
|
41
|
+
policy: "pass-through",
|
|
42
|
+
note: "Body-level prompt-injection sanitization is disabled for this MCP server; the trust class remains 'mcp-derived' so the per-step preamble still fires. The WARN cannot be silenced (deliberate operator-friction)."
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Strip imperative payloads from a tool description before it enters the
|
|
47
|
+
* per-step tool catalogue. The description is never wrapped in the
|
|
48
|
+
* `<<<untrusted_content>>>` envelope (the wrap is reserved for tool
|
|
49
|
+
* result bodies emitted into the conversation history).
|
|
50
|
+
*/
|
|
51
|
+
function sanitizeDescription(args) {
|
|
52
|
+
const stripPolicy = args.inboundSanitization === "pass-through" ? "pass-through" : "detect-and-strip";
|
|
53
|
+
return applyInboundSanitization({
|
|
54
|
+
body: args.description,
|
|
55
|
+
policy: stripPolicy,
|
|
56
|
+
trustClass: "mcp-derived",
|
|
57
|
+
toolName: args.toolName,
|
|
58
|
+
contentOrigin: `mcp:tool-description:${args.serverIdentity.id}`,
|
|
59
|
+
failClosed: false
|
|
60
|
+
}).body;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
export { _resetInboundFiltersDedupForTesting, resolveInboundPolicy, sanitizeDescription, warnOnPassthroughOverride };
|
|
65
|
+
//# sourceMappingURL=inbound-filters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound-filters.js","names":["stripPolicy: InboundSanitizationPolicy"],"sources":["../../src/client/inbound-filters.ts"],"sourcesContent":["/**\n * Inbound prompt-injection filtering for the `toTools()` adapter\n * (extracted from `to-tools.ts` per F-MCP-001).\n *\n * Owns: the per-server inbound-sanitization default, the once-per-server\n * `pass-through` override WARN, and the tool-description strip applied at\n * registration time. The trust class is pinned to `'mcp-derived'` for\n * every produced tool so the agent runtime's per-step preamble fires\n * regardless of the body-level policy chosen here.\n *\n * @packageDocumentation\n */\n\nimport type { InboundSanitizationPolicy } from '@graphorin/core';\nimport { incrementCounter } from '@graphorin/tools/audit';\nimport { applyInboundSanitization } from '@graphorin/tools/inbound';\nimport type { ServerIdentity } from '../transport/types.js';\n\n/** Operator-supplied structured logger (mirrors the client logger shape). */\ntype AdapterLogger = (\n level: 'debug' | 'info' | 'warn' | 'error',\n message: string,\n fields?: Record<string, unknown>,\n) => void;\n\n/**\n * Process-scoped dedup keys for the `pass-through` override WARN. The\n * spec mandates exactly-one WARN per server identity per process — the\n * Set retains the keys for the lifetime of the process. Tests reset via\n * {@link import('./to-tools.js')._resetMcpAdapterDedupForTesting}.\n */\nconst passthroughWarnSeen = new Set<string>();\n\n/**\n * Reset the inbound-filter dedup set. Used by\n * {@link import('./to-tools.js')._resetMcpAdapterDedupForTesting}.\n *\n * @experimental\n */\nexport function _resetInboundFiltersDedupForTesting(): void {\n passthroughWarnSeen.clear();\n}\n\n/**\n * Resolve the effective per-server inbound-sanitization policy. MCP\n * tools default to the strictest body-level policy.\n */\nexport function resolveInboundPolicy(\n override: InboundSanitizationPolicy | undefined,\n): InboundSanitizationPolicy {\n return override ?? 'detect-and-strip-and-wrap';\n}\n\n/**\n * WARN-once per server when the operator opts out of body-level\n * sanitization. The trust class stays `'mcp-derived'` regardless so the\n * per-step preamble in the agent runtime still fires; the WARN exists so\n * the audit log records the operator's deliberate choice.\n */\nexport function warnOnPassthroughOverride(args: {\n readonly resolvedInbound: InboundSanitizationPolicy;\n readonly serverIdentity: ServerIdentity;\n readonly logger?: AdapterLogger;\n}): void {\n if (args.resolvedInbound !== 'pass-through') return;\n if (passthroughWarnSeen.has(args.serverIdentity.id)) return;\n passthroughWarnSeen.add(args.serverIdentity.id);\n incrementCounter('mcp.inbound.sanitization.passthrough-override.warn.total', {\n server: args.serverIdentity.id,\n });\n if (args.logger !== undefined) {\n args.logger('warn', 'mcp.inbound.sanitization.passthrough-override', {\n server: args.serverIdentity.id,\n policy: 'pass-through',\n note: \"Body-level prompt-injection sanitization is disabled for this MCP server; the trust class remains 'mcp-derived' so the per-step preamble still fires. The WARN cannot be silenced (deliberate operator-friction).\",\n });\n }\n}\n\n/**\n * Strip imperative payloads from a tool description before it enters the\n * per-step tool catalogue. The description is never wrapped in the\n * `<<<untrusted_content>>>` envelope (the wrap is reserved for tool\n * result bodies emitted into the conversation history).\n */\nexport function sanitizeDescription(args: {\n readonly description: string;\n readonly inboundSanitization: InboundSanitizationPolicy;\n readonly toolName: string;\n readonly serverIdentity: ServerIdentity;\n}): string {\n const stripPolicy: InboundSanitizationPolicy =\n args.inboundSanitization === 'pass-through' ? 'pass-through' : 'detect-and-strip';\n const outcome = applyInboundSanitization({\n body: args.description,\n policy: stripPolicy,\n trustClass: 'mcp-derived',\n toolName: args.toolName,\n contentOrigin: `mcp:tool-description:${args.serverIdentity.id}`,\n failClosed: false,\n });\n return outcome.body;\n}\n"],"mappings":";;;;;;;;;;AA+BA,MAAM,sCAAsB,IAAI,KAAa;;;;;;;AAQ7C,SAAgB,sCAA4C;AAC1D,qBAAoB,OAAO;;;;;;AAO7B,SAAgB,qBACd,UAC2B;AAC3B,QAAO,YAAY;;;;;;;;AASrB,SAAgB,0BAA0B,MAIjC;AACP,KAAI,KAAK,oBAAoB,eAAgB;AAC7C,KAAI,oBAAoB,IAAI,KAAK,eAAe,GAAG,CAAE;AACrD,qBAAoB,IAAI,KAAK,eAAe,GAAG;AAC/C,kBAAiB,4DAA4D,EAC3E,QAAQ,KAAK,eAAe,IAC7B,CAAC;AACF,KAAI,KAAK,WAAW,OAClB,MAAK,OAAO,QAAQ,iDAAiD;EACnE,QAAQ,KAAK,eAAe;EAC5B,QAAQ;EACR,MAAM;EACP,CAAC;;;;;;;;AAUN,SAAgB,oBAAoB,MAKzB;CACT,MAAMA,cACJ,KAAK,wBAAwB,iBAAiB,iBAAiB;AASjE,QARgB,yBAAyB;EACvC,MAAM,KAAK;EACX,QAAQ;EACR,YAAY;EACZ,UAAU,KAAK;EACf,eAAe,wBAAwB,KAAK,eAAe;EAC3D,YAAY;EACb,CAAC,CACa"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CreateMCPClientOptions, MCPCallToolResult, MCPClient, MCPContentPart, MCPElicitationHandler, MCPElicitationRequest, MCPElicitationResult, MCPPromptDefinition, MCPPromptMessage, MCPResourceContent, MCPResourceDefinition, MCPSamplingContent, MCPSamplingHandler, MCPSamplingMessage, MCPSamplingRequest, MCPSamplingResult, MCPToToolsOptions, MCPToolDefinition } from "./types.js";
|
|
2
|
+
import { _resetSseWarnDedupForTesting, createMCPClient } from "./client.js";
|
|
3
|
+
import { McpResourceReaderOptions, createMcpResourceReader } from "./mcp-resource-reader.js";
|
|
4
|
+
import { adaptCallResult } from "./adapt-result.js";
|
|
5
|
+
import { DEFAULT_DEFER_LOADING_THRESHOLD } from "./defer-loading.js";
|
|
6
|
+
import { _resetMcpAdapterDedupForTesting, adaptMCPTools } from "./to-tools.js";
|
|
7
|
+
export { type CreateMCPClientOptions, DEFAULT_DEFER_LOADING_THRESHOLD, type MCPCallToolResult, type MCPClient, type MCPContentPart, type MCPElicitationHandler, type MCPElicitationRequest, type MCPElicitationResult, type MCPPromptDefinition, type MCPPromptMessage, type MCPResourceContent, type MCPResourceDefinition, type MCPSamplingContent, type MCPSamplingHandler, type MCPSamplingMessage, type MCPSamplingRequest, type MCPSamplingResult, type MCPToToolsOptions, type MCPToolDefinition, type McpResourceReaderOptions, _resetMcpAdapterDedupForTesting, _resetSseWarnDedupForTesting, adaptCallResult, adaptMCPTools, createMCPClient, createMcpResourceReader };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { adaptCallResult } from "./adapt-result.js";
|
|
2
|
+
import { DEFAULT_DEFER_LOADING_THRESHOLD } from "./defer-loading.js";
|
|
3
|
+
import { _resetMcpAdapterDedupForTesting, adaptMCPTools } from "./to-tools.js";
|
|
4
|
+
import { _resetSseWarnDedupForTesting, createMCPClient } from "./client.js";
|
|
5
|
+
import { createMcpResourceReader } from "./mcp-resource-reader.js";
|
|
6
|
+
|
|
7
|
+
export { DEFAULT_DEFER_LOADING_THRESHOLD, _resetMcpAdapterDedupForTesting, _resetSseWarnDedupForTesting, adaptCallResult, adaptMCPTools, createMCPClient, createMcpResourceReader };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { MCPClient } from "./types.js";
|
|
2
|
+
import { ResultReader } from "@graphorin/tools/result";
|
|
3
|
+
|
|
4
|
+
//#region src/client/mcp-resource-reader.d.ts
|
|
5
|
+
|
|
6
|
+
/** Configuration for {@link createMcpResourceReader}. */
|
|
7
|
+
interface McpResourceReaderOptions {
|
|
8
|
+
/** Clients consulted (in order) to resolve a resource URI. */
|
|
9
|
+
readonly clients: ReadonlyArray<MCPClient>;
|
|
10
|
+
/** Default `maxBytes` when `read(...)` is called without one. Default `65536`. */
|
|
11
|
+
readonly defaultMaxBytes?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Build a {@link ResultReader} that resolves MCP resource URIs through
|
|
15
|
+
* one or more connected {@link MCPClient}s.
|
|
16
|
+
*
|
|
17
|
+
* @stable
|
|
18
|
+
*/
|
|
19
|
+
declare function createMcpResourceReader(opts: McpResourceReaderOptions): ResultReader;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { McpResourceReaderOptions, createMcpResourceReader };
|
|
22
|
+
//# sourceMappingURL=mcp-resource-reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-resource-reader.d.ts","names":[],"sources":["../../src/client/mcp-resource-reader.ts"],"sourcesContent":[],"mappings":";;;;;;UA4BiB,wBAAA;;oBAEG,cAAc;;;;;;;;;;iBAWlB,uBAAA,OAA8B,2BAA2B"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { incrementCounter } from "@graphorin/tools/audit";
|
|
2
|
+
|
|
3
|
+
//#region src/client/mcp-resource-reader.ts
|
|
4
|
+
/**
|
|
5
|
+
* {@link createMcpResourceReader} — resolve MCP `resource_link` handles
|
|
6
|
+
* on demand (WI-13 / P2-2, ties to WI-10 result handles).
|
|
7
|
+
*
|
|
8
|
+
* The `toTools()` adapter surfaces an MCP `resource_link` as a preview +
|
|
9
|
+
* the resource `uri` (the {@link import('@graphorin/tools/result').ResultReader}
|
|
10
|
+
* handle) instead of inlining the body. This reader resolves that handle
|
|
11
|
+
* via {@link MCPClient.readResource} when the model later calls the
|
|
12
|
+
* built-in `read_result` tool — so a large MCP resource never inflates
|
|
13
|
+
* context until the model actually asks for it.
|
|
14
|
+
*
|
|
15
|
+
* Compose it with the agent's spill-file reader (the agent tries each
|
|
16
|
+
* configured reader in order): the spill reader claims
|
|
17
|
+
* `graphorin-spill:` handles, this reader resolves the rest. With more
|
|
18
|
+
* than one MCP client, the reader tries each until one server resolves
|
|
19
|
+
* the URI.
|
|
20
|
+
*
|
|
21
|
+
* Network note (R4): this reader performs no I/O until `read(...)` is
|
|
22
|
+
* called, and then only over a connection the operator already opened.
|
|
23
|
+
*
|
|
24
|
+
* @packageDocumentation
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Build a {@link ResultReader} that resolves MCP resource URIs through
|
|
28
|
+
* one or more connected {@link MCPClient}s.
|
|
29
|
+
*
|
|
30
|
+
* @stable
|
|
31
|
+
*/
|
|
32
|
+
function createMcpResourceReader(opts) {
|
|
33
|
+
const clients = [...opts.clients];
|
|
34
|
+
const defaultMaxBytes = opts.defaultMaxBytes ?? 65536;
|
|
35
|
+
return { async read(uri, range) {
|
|
36
|
+
if (clients.length === 0) throw new Error("createMcpResourceReader: no MCP clients configured to resolve resource handles.");
|
|
37
|
+
let lastError;
|
|
38
|
+
for (const client of clients) {
|
|
39
|
+
let content;
|
|
40
|
+
try {
|
|
41
|
+
content = await client.readResource(uri);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
lastError = err;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
incrementCounter("mcp.resource-link.resolved.total", { server: client.serverIdentity.id });
|
|
47
|
+
return sliceResource(content, range, defaultMaxBytes);
|
|
48
|
+
}
|
|
49
|
+
const suffix = lastError instanceof Error ? ` Last error: ${lastError.message}` : "";
|
|
50
|
+
throw new Error(`createMcpResourceReader: no configured MCP server resolved resource ${JSON.stringify(uri)}.${suffix}`);
|
|
51
|
+
} };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Raw resource bytes (MC-10): text resources as UTF-8, blob resources
|
|
55
|
+
* DECODED from base64 — slicing/totalBytes operate on real payload
|
|
56
|
+
* bytes, never on the ~33%-inflated base64 string (whose arbitrary
|
|
57
|
+
* cuts also break base64 quads).
|
|
58
|
+
*/
|
|
59
|
+
function resourceBytes(content) {
|
|
60
|
+
if (content.text !== void 0) return Buffer.from(content.text, "utf8");
|
|
61
|
+
if (content.blob !== void 0) return Buffer.from(content.blob, "base64");
|
|
62
|
+
return Buffer.alloc(0);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Apply a {@link ResultReadRange} to the fully-fetched resource body,
|
|
66
|
+
* mirroring the spill-file reader's slicing semantics (line mode wins;
|
|
67
|
+
* `maxBytes` caps the returned slice) so `read_result` pages uniformly.
|
|
68
|
+
*/
|
|
69
|
+
function sliceResource(content, range, defaultMaxBytes) {
|
|
70
|
+
const buf = resourceBytes(content);
|
|
71
|
+
const full = buf.toString("utf8");
|
|
72
|
+
const totalBytes = buf.byteLength;
|
|
73
|
+
const cap = Math.max(0, range?.maxBytes ?? defaultMaxBytes);
|
|
74
|
+
if (range?.startLine !== void 0 || range?.endLine !== void 0) {
|
|
75
|
+
const lines = full.split("\n");
|
|
76
|
+
const start = Math.max(1, range.startLine ?? 1);
|
|
77
|
+
const end$1 = Math.min(lines.length, range.endLine ?? lines.length);
|
|
78
|
+
const selected = start <= end$1 ? lines.slice(start - 1, end$1).join("\n") : "";
|
|
79
|
+
const capped = Buffer.byteLength(selected, "utf8") > cap;
|
|
80
|
+
const out = capBytes(selected, cap);
|
|
81
|
+
return Object.freeze({
|
|
82
|
+
producerTrustClass: "mcp-derived",
|
|
83
|
+
content: out,
|
|
84
|
+
bytes: Buffer.byteLength(out, "utf8"),
|
|
85
|
+
totalBytes,
|
|
86
|
+
eof: end$1 >= lines.length && !capped
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const offset = clamp(range?.offset ?? 0, 0, totalBytes);
|
|
90
|
+
const requested = range?.length ?? totalBytes - offset;
|
|
91
|
+
const rawEnd = clamp(offset + Math.max(0, requested), offset, totalBytes);
|
|
92
|
+
const end = Math.min(rawEnd, offset + cap);
|
|
93
|
+
const slice = buf.subarray(offset, end);
|
|
94
|
+
return Object.freeze({
|
|
95
|
+
producerTrustClass: "mcp-derived",
|
|
96
|
+
content: slice.toString("utf8"),
|
|
97
|
+
bytes: slice.byteLength,
|
|
98
|
+
totalBytes,
|
|
99
|
+
eof: end >= totalBytes
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function clamp(value, lo, hi) {
|
|
103
|
+
return Math.min(hi, Math.max(lo, value));
|
|
104
|
+
}
|
|
105
|
+
/** Truncate `s` to at most `maxBytes` UTF-8 bytes (tolerating a split trailing char). */
|
|
106
|
+
function capBytes(s, maxBytes) {
|
|
107
|
+
if (Buffer.byteLength(s, "utf8") <= maxBytes) return s;
|
|
108
|
+
return Buffer.from(s, "utf8").subarray(0, maxBytes).toString("utf8");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
export { createMcpResourceReader };
|
|
113
|
+
//# sourceMappingURL=mcp-resource-reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-resource-reader.js","names":["lastError: unknown","content: MCPResourceContent","end"],"sources":["../../src/client/mcp-resource-reader.ts"],"sourcesContent":["/**\n * {@link createMcpResourceReader} — resolve MCP `resource_link` handles\n * on demand (WI-13 / P2-2, ties to WI-10 result handles).\n *\n * The `toTools()` adapter surfaces an MCP `resource_link` as a preview +\n * the resource `uri` (the {@link import('@graphorin/tools/result').ResultReader}\n * handle) instead of inlining the body. This reader resolves that handle\n * via {@link MCPClient.readResource} when the model later calls the\n * built-in `read_result` tool — so a large MCP resource never inflates\n * context until the model actually asks for it.\n *\n * Compose it with the agent's spill-file reader (the agent tries each\n * configured reader in order): the spill reader claims\n * `graphorin-spill:` handles, this reader resolves the rest. With more\n * than one MCP client, the reader tries each until one server resolves\n * the URI.\n *\n * Network note (R4): this reader performs no I/O until `read(...)` is\n * called, and then only over a connection the operator already opened.\n *\n * @packageDocumentation\n */\n\nimport { incrementCounter } from '@graphorin/tools/audit';\nimport type { ResultReader, ResultReadOutcome, ResultReadRange } from '@graphorin/tools/result';\nimport type { MCPClient, MCPResourceContent } from './types.js';\n\n/** Configuration for {@link createMcpResourceReader}. */\nexport interface McpResourceReaderOptions {\n /** Clients consulted (in order) to resolve a resource URI. */\n readonly clients: ReadonlyArray<MCPClient>;\n /** Default `maxBytes` when `read(...)` is called without one. Default `65536`. */\n readonly defaultMaxBytes?: number;\n}\n\n/**\n * Build a {@link ResultReader} that resolves MCP resource URIs through\n * one or more connected {@link MCPClient}s.\n *\n * @stable\n */\nexport function createMcpResourceReader(opts: McpResourceReaderOptions): ResultReader {\n const clients = [...opts.clients];\n const defaultMaxBytes = opts.defaultMaxBytes ?? 65_536;\n return {\n async read(uri, range): Promise<ResultReadOutcome> {\n if (clients.length === 0) {\n throw new Error(\n 'createMcpResourceReader: no MCP clients configured to resolve resource handles.',\n );\n }\n let lastError: unknown;\n for (const client of clients) {\n let content: MCPResourceContent;\n try {\n content = await client.readResource(uri);\n } catch (err) {\n lastError = err;\n continue;\n }\n incrementCounter('mcp.resource-link.resolved.total', {\n server: client.serverIdentity.id,\n });\n return sliceResource(content, range, defaultMaxBytes);\n }\n const suffix = lastError instanceof Error ? ` Last error: ${lastError.message}` : '';\n throw new Error(\n `createMcpResourceReader: no configured MCP server resolved resource ${JSON.stringify(uri)}.${suffix}`,\n );\n },\n };\n}\n\n/**\n * Raw resource bytes (MC-10): text resources as UTF-8, blob resources\n * DECODED from base64 — slicing/totalBytes operate on real payload\n * bytes, never on the ~33%-inflated base64 string (whose arbitrary\n * cuts also break base64 quads).\n */\nfunction resourceBytes(content: MCPResourceContent): Buffer {\n if (content.text !== undefined) return Buffer.from(content.text, 'utf8');\n if (content.blob !== undefined) return Buffer.from(content.blob, 'base64');\n return Buffer.alloc(0);\n}\n\n/**\n * Apply a {@link ResultReadRange} to the fully-fetched resource body,\n * mirroring the spill-file reader's slicing semantics (line mode wins;\n * `maxBytes` caps the returned slice) so `read_result` pages uniformly.\n */\nfunction sliceResource(\n content: MCPResourceContent,\n range: ResultReadRange | undefined,\n defaultMaxBytes: number,\n): ResultReadOutcome {\n const buf = resourceBytes(content);\n const full = buf.toString('utf8');\n const totalBytes = buf.byteLength;\n const cap = Math.max(0, range?.maxBytes ?? defaultMaxBytes);\n\n if (range?.startLine !== undefined || range?.endLine !== undefined) {\n const lines = full.split('\\n');\n const start = Math.max(1, range.startLine ?? 1);\n const end = Math.min(lines.length, range.endLine ?? lines.length);\n const selected = start <= end ? lines.slice(start - 1, end).join('\\n') : '';\n const capped = Buffer.byteLength(selected, 'utf8') > cap;\n const out = capBytes(selected, cap);\n return Object.freeze({\n // TL-6: MCP resource content is mcp-derived by definition — the\n // executor re-applies inbound sanitization + dataflow provenance\n // by this class when read_result relays it.\n producerTrustClass: 'mcp-derived' as const,\n content: out,\n bytes: Buffer.byteLength(out, 'utf8'),\n totalBytes,\n eof: end >= lines.length && !capped,\n });\n }\n\n const offset = clamp(range?.offset ?? 0, 0, totalBytes);\n const requested = range?.length ?? totalBytes - offset;\n const rawEnd = clamp(offset + Math.max(0, requested), offset, totalBytes);\n const end = Math.min(rawEnd, offset + cap);\n const slice = buf.subarray(offset, end);\n return Object.freeze({\n // TL-6: MCP resource content is mcp-derived by definition.\n producerTrustClass: 'mcp-derived' as const,\n content: slice.toString('utf8'),\n bytes: slice.byteLength,\n totalBytes,\n eof: end >= totalBytes,\n });\n}\n\nfunction clamp(value: number, lo: number, hi: number): number {\n return Math.min(hi, Math.max(lo, value));\n}\n\n/** Truncate `s` to at most `maxBytes` UTF-8 bytes (tolerating a split trailing char). */\nfunction capBytes(s: string, maxBytes: number): string {\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n return Buffer.from(s, 'utf8').subarray(0, maxBytes).toString('utf8');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,wBAAwB,MAA8C;CACpF,MAAM,UAAU,CAAC,GAAG,KAAK,QAAQ;CACjC,MAAM,kBAAkB,KAAK,mBAAmB;AAChD,QAAO,EACL,MAAM,KAAK,KAAK,OAAmC;AACjD,MAAI,QAAQ,WAAW,EACrB,OAAM,IAAI,MACR,kFACD;EAEH,IAAIA;AACJ,OAAK,MAAM,UAAU,SAAS;GAC5B,IAAIC;AACJ,OAAI;AACF,cAAU,MAAM,OAAO,aAAa,IAAI;YACjC,KAAK;AACZ,gBAAY;AACZ;;AAEF,oBAAiB,oCAAoC,EACnD,QAAQ,OAAO,eAAe,IAC/B,CAAC;AACF,UAAO,cAAc,SAAS,OAAO,gBAAgB;;EAEvD,MAAM,SAAS,qBAAqB,QAAQ,gBAAgB,UAAU,YAAY;AAClF,QAAM,IAAI,MACR,uEAAuE,KAAK,UAAU,IAAI,CAAC,GAAG,SAC/F;IAEJ;;;;;;;;AASH,SAAS,cAAc,SAAqC;AAC1D,KAAI,QAAQ,SAAS,OAAW,QAAO,OAAO,KAAK,QAAQ,MAAM,OAAO;AACxE,KAAI,QAAQ,SAAS,OAAW,QAAO,OAAO,KAAK,QAAQ,MAAM,SAAS;AAC1E,QAAO,OAAO,MAAM,EAAE;;;;;;;AAQxB,SAAS,cACP,SACA,OACA,iBACmB;CACnB,MAAM,MAAM,cAAc,QAAQ;CAClC,MAAM,OAAO,IAAI,SAAS,OAAO;CACjC,MAAM,aAAa,IAAI;CACvB,MAAM,MAAM,KAAK,IAAI,GAAG,OAAO,YAAY,gBAAgB;AAE3D,KAAI,OAAO,cAAc,UAAa,OAAO,YAAY,QAAW;EAClE,MAAM,QAAQ,KAAK,MAAM,KAAK;EAC9B,MAAM,QAAQ,KAAK,IAAI,GAAG,MAAM,aAAa,EAAE;EAC/C,MAAMC,QAAM,KAAK,IAAI,MAAM,QAAQ,MAAM,WAAW,MAAM,OAAO;EACjE,MAAM,WAAW,SAASA,QAAM,MAAM,MAAM,QAAQ,GAAGA,MAAI,CAAC,KAAK,KAAK,GAAG;EACzE,MAAM,SAAS,OAAO,WAAW,UAAU,OAAO,GAAG;EACrD,MAAM,MAAM,SAAS,UAAU,IAAI;AACnC,SAAO,OAAO,OAAO;GAInB,oBAAoB;GACpB,SAAS;GACT,OAAO,OAAO,WAAW,KAAK,OAAO;GACrC;GACA,KAAKA,SAAO,MAAM,UAAU,CAAC;GAC9B,CAAC;;CAGJ,MAAM,SAAS,MAAM,OAAO,UAAU,GAAG,GAAG,WAAW;CACvD,MAAM,YAAY,OAAO,UAAU,aAAa;CAChD,MAAM,SAAS,MAAM,SAAS,KAAK,IAAI,GAAG,UAAU,EAAE,QAAQ,WAAW;CACzE,MAAM,MAAM,KAAK,IAAI,QAAQ,SAAS,IAAI;CAC1C,MAAM,QAAQ,IAAI,SAAS,QAAQ,IAAI;AACvC,QAAO,OAAO,OAAO;EAEnB,oBAAoB;EACpB,SAAS,MAAM,SAAS,OAAO;EAC/B,OAAO,MAAM;EACb;EACA,KAAK,OAAO;EACb,CAAC;;AAGJ,SAAS,MAAM,OAAe,IAAY,IAAoB;AAC5D,QAAO,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,MAAM,CAAC;;;AAI1C,SAAS,SAAS,GAAW,UAA0B;AACrD,KAAI,OAAO,WAAW,GAAG,OAAO,IAAI,SAAU,QAAO;AACrD,QAAO,OAAO,KAAK,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,SAAS,OAAO"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
//#region src/client/pinning.ts
|
|
4
|
+
/**
|
|
5
|
+
* Tool-definition pinning (MC-6): stable fingerprints over MCP tool
|
|
6
|
+
* definitions so approve-then-swap rug-pulls — a server changing a
|
|
7
|
+
* tool's description/schema behind an already-approved name — are
|
|
8
|
+
* detectable across snapshots and process restarts.
|
|
9
|
+
*
|
|
10
|
+
* The fingerprint is a sha256 over a key-sorted canonical render of
|
|
11
|
+
* `{ name, description, inputSchema, outputSchema, title }`. Operators
|
|
12
|
+
* persist the stamped `__definitionHash` (or read it from the audit
|
|
13
|
+
* trail) and pass it back via `toTools({ pinnedFingerprints })`.
|
|
14
|
+
*
|
|
15
|
+
* @packageDocumentation
|
|
16
|
+
*/
|
|
17
|
+
/** Deterministic JSON render: object keys sorted recursively. */
|
|
18
|
+
function stableStringify(value) {
|
|
19
|
+
if (Array.isArray(value)) return `[${value.map((v) => stableStringify(v)).join(",")}]`;
|
|
20
|
+
if (typeof value === "object" && value !== null) return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
|
|
21
|
+
return JSON.stringify(value) ?? "null";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Stable sha256 fingerprint of one MCP tool definition (MC-6).
|
|
25
|
+
*
|
|
26
|
+
* @stable
|
|
27
|
+
*/
|
|
28
|
+
function computeToolDefinitionHash(def) {
|
|
29
|
+
const canonical = stableStringify({
|
|
30
|
+
name: def.name,
|
|
31
|
+
description: def.description,
|
|
32
|
+
inputSchema: def.inputSchema,
|
|
33
|
+
...def.outputSchema !== void 0 ? { outputSchema: def.outputSchema } : {},
|
|
34
|
+
...def.title !== void 0 ? { title: def.title } : {}
|
|
35
|
+
});
|
|
36
|
+
return createHash("sha256").update(canonical).digest("hex");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
export { computeToolDefinitionHash };
|
|
41
|
+
//# sourceMappingURL=pinning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pinning.js","names":[],"sources":["../../src/client/pinning.ts"],"sourcesContent":["/**\n * Tool-definition pinning (MC-6): stable fingerprints over MCP tool\n * definitions so approve-then-swap rug-pulls — a server changing a\n * tool's description/schema behind an already-approved name — are\n * detectable across snapshots and process restarts.\n *\n * The fingerprint is a sha256 over a key-sorted canonical render of\n * `{ name, description, inputSchema, outputSchema, title }`. Operators\n * persist the stamped `__definitionHash` (or read it from the audit\n * trail) and pass it back via `toTools({ pinnedFingerprints })`.\n *\n * @packageDocumentation\n */\n\nimport { createHash } from 'node:crypto';\n\nimport type { MCPToolDefinition } from './types.js';\n\n/** Deterministic JSON render: object keys sorted recursively. */\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) {\n return `[${value.map((v) => stableStringify(v)).join(',')}]`;\n }\n if (typeof value === 'object' && value !== null) {\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`);\n return `{${entries.join(',')}}`;\n }\n return JSON.stringify(value) ?? 'null';\n}\n\n/**\n * Stable sha256 fingerprint of one MCP tool definition (MC-6).\n *\n * @stable\n */\nexport function computeToolDefinitionHash(def: MCPToolDefinition): string {\n const canonical = stableStringify({\n name: def.name,\n description: def.description,\n inputSchema: def.inputSchema,\n ...(def.outputSchema !== undefined ? { outputSchema: def.outputSchema } : {}),\n ...(def.title !== undefined ? { title: def.title } : {}),\n });\n return createHash('sha256').update(canonical).digest('hex');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAmBA,SAAS,gBAAgB,OAAwB;AAC/C,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,IAAI,MAAM,KAAK,MAAM,gBAAgB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;AAE5D,KAAI,OAAO,UAAU,YAAY,UAAU,KAKzC,QAAO,IAJS,OAAO,QAAQ,MAAiC,CAC7D,QAAQ,GAAG,OAAO,MAAM,OAAU,CAClC,MAAM,CAAC,IAAI,CAAC,OAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,EAAG,CAChD,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,EAAE,CAAC,GAAG,gBAAgB,EAAE,GAAG,CAC7C,KAAK,IAAI,CAAC;AAE/B,QAAO,KAAK,UAAU,MAAM,IAAI;;;;;;;AAQlC,SAAgB,0BAA0B,KAAgC;CACxE,MAAM,YAAY,gBAAgB;EAChC,MAAM,IAAI;EACV,aAAa,IAAI;EACjB,aAAa,IAAI;EACjB,GAAI,IAAI,iBAAiB,SAAY,EAAE,cAAc,IAAI,cAAc,GAAG,EAAE;EAC5E,GAAI,IAAI,UAAU,SAAY,EAAE,OAAO,IAAI,OAAO,GAAG,EAAE;EACxD,CAAC;AACF,QAAO,WAAW,SAAS,CAAC,OAAO,UAAU,CAAC,OAAO,MAAM"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ServerIdentity } from "../transport/types.js";
|
|
2
|
+
import { MCPClient, MCPToToolsOptions, MCPToolDefinition } from "./types.js";
|
|
3
|
+
import { adaptCallResult } from "./adapt-result.js";
|
|
4
|
+
import { DEFAULT_DEFER_LOADING_THRESHOLD } from "./defer-loading.js";
|
|
5
|
+
import { InboundSanitizationPolicy, Tool } from "@graphorin/core";
|
|
6
|
+
|
|
7
|
+
//#region src/client/to-tools.d.ts
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Reset every process-scoped dedup set owned by the adapter modules.
|
|
11
|
+
* Used by tests.
|
|
12
|
+
*
|
|
13
|
+
* @experimental
|
|
14
|
+
*/
|
|
15
|
+
declare function _resetMcpAdapterDedupForTesting(): void;
|
|
16
|
+
/** Result returned by {@link adaptMCPTools}. */
|
|
17
|
+
interface AdaptedToolsResult {
|
|
18
|
+
readonly tools: ReadonlyArray<Tool>;
|
|
19
|
+
/** MC-6: sha256 definition fingerprint per MCP tool name. */
|
|
20
|
+
readonly fingerprints: ReadonlyMap<string, string>;
|
|
21
|
+
readonly autoDeferralFired: boolean;
|
|
22
|
+
readonly resolvedDeferLoading: boolean;
|
|
23
|
+
readonly resolvedInboundSanitization: InboundSanitizationPolicy;
|
|
24
|
+
readonly toolCount: number;
|
|
25
|
+
readonly deferralThreshold: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build the {@link Tool} array for the supplied MCP tool catalogue.
|
|
29
|
+
*
|
|
30
|
+
* @stable
|
|
31
|
+
*/
|
|
32
|
+
declare function adaptMCPTools(args: {
|
|
33
|
+
readonly client: MCPClient;
|
|
34
|
+
readonly serverIdentity: ServerIdentity;
|
|
35
|
+
readonly catalogue: ReadonlyArray<MCPToolDefinition>;
|
|
36
|
+
readonly options?: MCPToToolsOptions;
|
|
37
|
+
readonly logger?: (level: 'debug' | 'info' | 'warn' | 'error', message: string, fields?: Record<string, unknown>) => void;
|
|
38
|
+
}): AdaptedToolsResult;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { _resetMcpAdapterDedupForTesting, adaptMCPTools };
|
|
41
|
+
//# sourceMappingURL=to-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"to-tools.d.ts","names":[],"sources":["../../src/client/to-tools.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;iBAmDgB,+BAAA,CAAA;;UAMC,kBAAA;kBACC,cAAc;;yBAEP;;;wCAGe;;;;;;;;;iBAUxB,aAAA;mBACG;2BACQ;sBACL,cAAc;qBACf;2FAIR;IAET"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { buildJsonSchemaValidator } from "../registry/json-schema.js";
|
|
2
|
+
import { adaptCallResult } from "./adapt-result.js";
|
|
3
|
+
import { DEFAULT_DEFER_LOADING_THRESHOLD, _resetDeferLoadingDedupForTesting, resolveDeferLoading } from "./defer-loading.js";
|
|
4
|
+
import { _resetInboundFiltersDedupForTesting, resolveInboundPolicy, sanitizeDescription, warnOnPassthroughOverride } from "./inbound-filters.js";
|
|
5
|
+
import { computeToolDefinitionHash } from "./pinning.js";
|
|
6
|
+
|
|
7
|
+
//#region src/client/to-tools.ts
|
|
8
|
+
/**
|
|
9
|
+
* Reset every process-scoped dedup set owned by the adapter modules.
|
|
10
|
+
* Used by tests.
|
|
11
|
+
*
|
|
12
|
+
* @experimental
|
|
13
|
+
*/
|
|
14
|
+
function _resetMcpAdapterDedupForTesting() {
|
|
15
|
+
_resetDeferLoadingDedupForTesting();
|
|
16
|
+
_resetInboundFiltersDedupForTesting();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build the {@link Tool} array for the supplied MCP tool catalogue.
|
|
20
|
+
*
|
|
21
|
+
* @stable
|
|
22
|
+
*/
|
|
23
|
+
function adaptMCPTools(args) {
|
|
24
|
+
const opts = args.options ?? {};
|
|
25
|
+
const fingerprints = /* @__PURE__ */ new Map();
|
|
26
|
+
const filter = opts.filter;
|
|
27
|
+
const namespace = (opts.namespace ?? "").trim();
|
|
28
|
+
const filtered = filter === void 0 ? args.catalogue : args.catalogue.filter((t) => filter(t));
|
|
29
|
+
const total = filtered.length;
|
|
30
|
+
const threshold = opts.deferLoadingThreshold ?? DEFAULT_DEFER_LOADING_THRESHOLD;
|
|
31
|
+
const { autoDeferralFired, resolvedDeferLoading } = resolveDeferLoading({
|
|
32
|
+
serverIdentity: args.serverIdentity,
|
|
33
|
+
toolNames: filtered.map((t) => t.name),
|
|
34
|
+
explicitDefer: opts.defer_loading,
|
|
35
|
+
threshold,
|
|
36
|
+
...args.logger === void 0 ? {} : { logger: args.logger }
|
|
37
|
+
});
|
|
38
|
+
const resolvedInbound = resolveInboundPolicy(opts.inboundSanitization);
|
|
39
|
+
warnOnPassthroughOverride({
|
|
40
|
+
resolvedInbound,
|
|
41
|
+
serverIdentity: args.serverIdentity,
|
|
42
|
+
...args.logger === void 0 ? {} : { logger: args.logger }
|
|
43
|
+
});
|
|
44
|
+
const tools = [];
|
|
45
|
+
for (const definition of filtered) {
|
|
46
|
+
const namespacedName = namespace.length === 0 ? definition.name : `${namespace}.${definition.name}`;
|
|
47
|
+
const sideEffectClass = opts.sideEffectClassByTool?.[namespacedName] ?? "external-stateful";
|
|
48
|
+
const preferredModel = opts.preferredModelByTool?.[namespacedName];
|
|
49
|
+
const inputValidator = buildJsonSchemaValidator(definition.inputSchema);
|
|
50
|
+
const outputValidator = definition.outputSchema === void 0 ? void 0 : buildJsonSchemaValidator(definition.outputSchema);
|
|
51
|
+
const definitionHash = computeToolDefinitionHash(definition);
|
|
52
|
+
fingerprints.set(definition.name, definitionHash);
|
|
53
|
+
tools.push(buildAdaptedTool({
|
|
54
|
+
client: args.client,
|
|
55
|
+
serverIdentity: args.serverIdentity,
|
|
56
|
+
definitionHash,
|
|
57
|
+
...args.logger !== void 0 ? { logger: args.logger } : {},
|
|
58
|
+
mcpToolName: definition.name,
|
|
59
|
+
graphorinToolName: namespacedName,
|
|
60
|
+
description: definition.description.length === 0 ? `${definition.name} (MCP)` : definition.description,
|
|
61
|
+
inputSchema: inputValidator,
|
|
62
|
+
outputSchema: outputValidator,
|
|
63
|
+
defer_loading: resolvedDeferLoading,
|
|
64
|
+
inboundSanitization: resolvedInbound,
|
|
65
|
+
sideEffectClass,
|
|
66
|
+
...opts.maxResultTokens === void 0 ? {} : { maxResultTokens: opts.maxResultTokens },
|
|
67
|
+
...opts.truncationStrategy === void 0 ? {} : { truncationStrategy: opts.truncationStrategy },
|
|
68
|
+
...opts.callTimeoutMs === void 0 ? {} : { callTimeoutMs: opts.callTimeoutMs },
|
|
69
|
+
...preferredModel === void 0 ? {} : { preferredModel }
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
return Object.freeze({
|
|
73
|
+
tools: Object.freeze(tools),
|
|
74
|
+
fingerprints,
|
|
75
|
+
autoDeferralFired,
|
|
76
|
+
resolvedDeferLoading,
|
|
77
|
+
resolvedInboundSanitization: resolvedInbound,
|
|
78
|
+
toolCount: total,
|
|
79
|
+
deferralThreshold: threshold
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function buildAdaptedTool(args) {
|
|
83
|
+
const sanitizedDescription = sanitizeDescription({
|
|
84
|
+
description: args.description,
|
|
85
|
+
inboundSanitization: args.inboundSanitization,
|
|
86
|
+
toolName: args.graphorinToolName,
|
|
87
|
+
serverIdentity: args.serverIdentity
|
|
88
|
+
});
|
|
89
|
+
const tool = {
|
|
90
|
+
name: args.graphorinToolName,
|
|
91
|
+
description: sanitizedDescription,
|
|
92
|
+
inputSchema: args.inputSchema,
|
|
93
|
+
...args.outputSchema === void 0 ? {} : { outputSchema: args.outputSchema },
|
|
94
|
+
defer_loading: args.defer_loading,
|
|
95
|
+
inboundSanitization: args.inboundSanitization,
|
|
96
|
+
sideEffectClass: args.sideEffectClass,
|
|
97
|
+
sandboxPolicy: "sandboxed",
|
|
98
|
+
...args.maxResultTokens === void 0 ? {} : { maxResultTokens: args.maxResultTokens },
|
|
99
|
+
...args.truncationStrategy === void 0 ? {} : { truncationStrategy: args.truncationStrategy },
|
|
100
|
+
...args.preferredModel === void 0 ? {} : { preferredModel: args.preferredModel },
|
|
101
|
+
async execute(input, ctx) {
|
|
102
|
+
return adaptCallResult({
|
|
103
|
+
result: await args.client.callTool(args.mcpToolName, input, {
|
|
104
|
+
...ctx?.signal !== void 0 ? { signal: ctx.signal } : {},
|
|
105
|
+
...args.callTimeoutMs !== void 0 ? { timeoutMs: args.callTimeoutMs } : {}
|
|
106
|
+
}),
|
|
107
|
+
outputSchema: args.outputSchema,
|
|
108
|
+
serverIdentity: args.serverIdentity,
|
|
109
|
+
toolName: args.graphorinToolName,
|
|
110
|
+
...args.logger !== void 0 ? { logger: args.logger } : {}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
return Object.assign(tool, {
|
|
115
|
+
__source: {
|
|
116
|
+
kind: "mcp",
|
|
117
|
+
serverIdentity: args.serverIdentity.id
|
|
118
|
+
},
|
|
119
|
+
__definitionHash: args.definitionHash
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
//#endregion
|
|
124
|
+
export { _resetMcpAdapterDedupForTesting, adaptMCPTools };
|
|
125
|
+
//# sourceMappingURL=to-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"to-tools.js","names":["tools: Tool[]"],"sources":["../../src/client/to-tools.ts"],"sourcesContent":["/**\n * `toTools()` adapter — bridges MCP tool descriptors into the\n * Graphorin tool registry.\n *\n * The adapter orchestrates three extracted concerns (F-MCP-001):\n *\n * 1. Filters / namespaces the `listTools()` output, then resolves the\n * per-server effective `defer_loading` flag — see\n * {@link import('./defer-loading.js').resolveDeferLoading}.\n * 2. Resolves the per-server inbound prompt-injection policy and strips\n * imperative payloads from each description — see\n * {@link import('./inbound-filters.js')}.\n * 3. Converts each MCP tool into a strongly-typed Graphorin `Tool` whose\n * `execute(...)` calls back into {@link MCPClient.callTool} and adapts\n * the result — see {@link import('./adapt-result.js').adaptCallResult}.\n *\n * The trust class is pinned to `'mcp-derived'` so the agent runtime's\n * per-step preamble fires regardless of the body-level policy.\n *\n * @packageDocumentation\n */\n\nimport type { ContentChunk, InboundSanitizationPolicy, Tool, ToolReturn } from '@graphorin/core';\nimport { buildJsonSchemaValidator, type JsonSchemaLike } from '../registry/json-schema.js';\nimport type { ServerIdentity } from '../transport/types.js';\nimport { adaptCallResult } from './adapt-result.js';\nimport {\n _resetDeferLoadingDedupForTesting,\n DEFAULT_DEFER_LOADING_THRESHOLD,\n resolveDeferLoading,\n} from './defer-loading.js';\nimport {\n _resetInboundFiltersDedupForTesting,\n resolveInboundPolicy,\n sanitizeDescription,\n warnOnPassthroughOverride,\n} from './inbound-filters.js';\nimport { computeToolDefinitionHash } from './pinning.js';\nimport type { MCPClient, MCPToolDefinition, MCPToToolsOptions } from './types.js';\n\n// Re-exported for backward compatibility: callers (and tests) import\n// these symbols directly from `./to-tools.js` and via the client barrel.\nexport { adaptCallResult } from './adapt-result.js';\nexport { DEFAULT_DEFER_LOADING_THRESHOLD } from './defer-loading.js';\n\n/**\n * Reset every process-scoped dedup set owned by the adapter modules.\n * Used by tests.\n *\n * @experimental\n */\nexport function _resetMcpAdapterDedupForTesting(): void {\n _resetDeferLoadingDedupForTesting();\n _resetInboundFiltersDedupForTesting();\n}\n\n/** Result returned by {@link adaptMCPTools}. */\nexport interface AdaptedToolsResult {\n readonly tools: ReadonlyArray<Tool>;\n /** MC-6: sha256 definition fingerprint per MCP tool name. */\n readonly fingerprints: ReadonlyMap<string, string>;\n readonly autoDeferralFired: boolean;\n readonly resolvedDeferLoading: boolean;\n readonly resolvedInboundSanitization: InboundSanitizationPolicy;\n readonly toolCount: number;\n readonly deferralThreshold: number;\n}\n\n/**\n * Build the {@link Tool} array for the supplied MCP tool catalogue.\n *\n * @stable\n */\nexport function adaptMCPTools(args: {\n readonly client: MCPClient;\n readonly serverIdentity: ServerIdentity;\n readonly catalogue: ReadonlyArray<MCPToolDefinition>;\n readonly options?: MCPToToolsOptions;\n readonly logger?: (\n level: 'debug' | 'info' | 'warn' | 'error',\n message: string,\n fields?: Record<string, unknown>,\n ) => void;\n}): AdaptedToolsResult {\n const opts = args.options ?? {};\n const fingerprints = new Map<string, string>();\n const filter = opts.filter;\n const namespace = (opts.namespace ?? '').trim();\n const filtered = filter === undefined ? args.catalogue : args.catalogue.filter((t) => filter(t));\n const total = filtered.length;\n const threshold = opts.deferLoadingThreshold ?? DEFAULT_DEFER_LOADING_THRESHOLD;\n\n const { autoDeferralFired, resolvedDeferLoading } = resolveDeferLoading({\n serverIdentity: args.serverIdentity,\n toolNames: filtered.map((t) => t.name),\n explicitDefer: opts.defer_loading,\n threshold,\n ...(args.logger === undefined ? {} : { logger: args.logger }),\n });\n\n const resolvedInbound = resolveInboundPolicy(opts.inboundSanitization);\n warnOnPassthroughOverride({\n resolvedInbound,\n serverIdentity: args.serverIdentity,\n ...(args.logger === undefined ? {} : { logger: args.logger }),\n });\n\n const tools: Tool[] = [];\n for (const definition of filtered) {\n const namespacedName =\n namespace.length === 0 ? definition.name : `${namespace}.${definition.name}`;\n const sideEffectClass = opts.sideEffectClassByTool?.[namespacedName] ?? 'external-stateful';\n const preferredModel = opts.preferredModelByTool?.[namespacedName];\n const inputValidator = buildJsonSchemaValidator(definition.inputSchema as JsonSchemaLike);\n const outputValidator =\n definition.outputSchema === undefined\n ? undefined\n : buildJsonSchemaValidator(definition.outputSchema as JsonSchemaLike);\n const definitionHash = computeToolDefinitionHash(definition);\n fingerprints.set(definition.name, definitionHash);\n tools.push(\n buildAdaptedTool({\n client: args.client,\n serverIdentity: args.serverIdentity,\n definitionHash,\n ...(args.logger !== undefined ? { logger: args.logger } : {}),\n mcpToolName: definition.name,\n graphorinToolName: namespacedName,\n description:\n definition.description.length === 0 ? `${definition.name} (MCP)` : definition.description,\n inputSchema: inputValidator,\n outputSchema: outputValidator,\n defer_loading: resolvedDeferLoading,\n inboundSanitization: resolvedInbound,\n sideEffectClass,\n ...(opts.maxResultTokens === undefined ? {} : { maxResultTokens: opts.maxResultTokens }),\n ...(opts.truncationStrategy === undefined\n ? {}\n : { truncationStrategy: opts.truncationStrategy }),\n ...(opts.callTimeoutMs === undefined ? {} : { callTimeoutMs: opts.callTimeoutMs }),\n ...(preferredModel === undefined ? {} : { preferredModel }),\n }),\n );\n }\n\n return Object.freeze({\n tools: Object.freeze(tools),\n fingerprints,\n autoDeferralFired,\n resolvedDeferLoading,\n resolvedInboundSanitization: resolvedInbound,\n toolCount: total,\n deferralThreshold: threshold,\n });\n}\n\ninterface BuildAdaptedToolArgs {\n readonly client: MCPClient;\n readonly serverIdentity: ServerIdentity;\n readonly mcpToolName: string;\n readonly graphorinToolName: string;\n readonly description: string;\n readonly inputSchema: import('@graphorin/core').ZodLikeSchema<unknown>;\n readonly outputSchema?: import('@graphorin/core').ZodLikeSchema<unknown> | undefined;\n readonly defer_loading: boolean;\n readonly inboundSanitization: InboundSanitizationPolicy;\n readonly sideEffectClass: import('@graphorin/core').SideEffectClass;\n readonly maxResultTokens?: number;\n readonly truncationStrategy?: import('@graphorin/core').TruncationStrategy;\n /** Per-call timeout forwarded to `client.callTool` (MC-3/MC-5). */\n readonly callTimeoutMs?: number;\n /** MC-6: sha256 fingerprint of the producing MCP definition. */\n readonly definitionHash: string;\n readonly logger?: (\n level: 'debug' | 'info' | 'warn' | 'error',\n message: string,\n fields?: Record<string, unknown>,\n ) => void;\n readonly preferredModel?:\n | import('@graphorin/core').ModelHint\n | import('@graphorin/core').ModelSpec;\n}\n\nfunction buildAdaptedTool(args: BuildAdaptedToolArgs): Tool<unknown, unknown, unknown> {\n const sanitizedDescription = sanitizeDescription({\n description: args.description,\n inboundSanitization: args.inboundSanitization,\n toolName: args.graphorinToolName,\n serverIdentity: args.serverIdentity,\n });\n\n const tool = {\n name: args.graphorinToolName,\n description: sanitizedDescription,\n inputSchema: args.inputSchema,\n ...(args.outputSchema === undefined ? {} : { outputSchema: args.outputSchema }),\n defer_loading: args.defer_loading,\n inboundSanitization: args.inboundSanitization,\n sideEffectClass: args.sideEffectClass,\n sandboxPolicy: 'sandboxed' as const,\n ...(args.maxResultTokens === undefined ? {} : { maxResultTokens: args.maxResultTokens }),\n ...(args.truncationStrategy === undefined\n ? {}\n : { truncationStrategy: args.truncationStrategy }),\n ...(args.preferredModel === undefined ? {} : { preferredModel: args.preferredModel }),\n async execute(\n input: unknown,\n ctx?: import('@graphorin/core').ToolExecutionContext<unknown>,\n ): Promise<ToolReturn<unknown>> {\n // MC-5: the agent's per-call AbortSignal reaches the wire — an\n // aborted run sends `notifications/cancelled` instead of orphaning\n // the JSON-RPC request on the server. MC-3: the per-server call\n // timeout rides along.\n const result = await args.client.callTool(args.mcpToolName, input, {\n ...(ctx?.signal !== undefined ? { signal: ctx.signal } : {}),\n ...(args.callTimeoutMs !== undefined ? { timeoutMs: args.callTimeoutMs } : {}),\n });\n return adaptCallResult({\n result,\n outputSchema: args.outputSchema,\n serverIdentity: args.serverIdentity,\n toolName: args.graphorinToolName,\n ...(args.logger !== undefined ? { logger: args.logger } : {}),\n });\n },\n } satisfies Tool<unknown, unknown, unknown>;\n // MC-7: pre-stamp the provenance so the agent's inferToolSource —\n // which honours an existing stamp — classifies tools passed via\n // `config.tools` as 'mcp-derived' (untrusted for the WI-12 dataflow\n // policy) instead of first-party. Zero operator boilerplate.\n return Object.assign(tool, {\n __source: { kind: 'mcp', serverIdentity: args.serverIdentity.id } as const,\n // MC-6: operators persist this fingerprint to pin the approved\n // definition (`toTools({ pinnedFingerprints })`).\n __definitionHash: args.definitionHash,\n });\n}\n\n/** Unused export kept for ergonomic test access. */\nexport type AdaptedToolChunkBuffer = ReadonlyArray<ContentChunk>;\n"],"mappings":";;;;;;;;;;;;;AAmDA,SAAgB,kCAAwC;AACtD,oCAAmC;AACnC,sCAAqC;;;;;;;AAoBvC,SAAgB,cAAc,MAUP;CACrB,MAAM,OAAO,KAAK,WAAW,EAAE;CAC/B,MAAM,+BAAe,IAAI,KAAqB;CAC9C,MAAM,SAAS,KAAK;CACpB,MAAM,aAAa,KAAK,aAAa,IAAI,MAAM;CAC/C,MAAM,WAAW,WAAW,SAAY,KAAK,YAAY,KAAK,UAAU,QAAQ,MAAM,OAAO,EAAE,CAAC;CAChG,MAAM,QAAQ,SAAS;CACvB,MAAM,YAAY,KAAK,yBAAyB;CAEhD,MAAM,EAAE,mBAAmB,yBAAyB,oBAAoB;EACtE,gBAAgB,KAAK;EACrB,WAAW,SAAS,KAAK,MAAM,EAAE,KAAK;EACtC,eAAe,KAAK;EACpB;EACA,GAAI,KAAK,WAAW,SAAY,EAAE,GAAG,EAAE,QAAQ,KAAK,QAAQ;EAC7D,CAAC;CAEF,MAAM,kBAAkB,qBAAqB,KAAK,oBAAoB;AACtE,2BAA0B;EACxB;EACA,gBAAgB,KAAK;EACrB,GAAI,KAAK,WAAW,SAAY,EAAE,GAAG,EAAE,QAAQ,KAAK,QAAQ;EAC7D,CAAC;CAEF,MAAMA,QAAgB,EAAE;AACxB,MAAK,MAAM,cAAc,UAAU;EACjC,MAAM,iBACJ,UAAU,WAAW,IAAI,WAAW,OAAO,GAAG,UAAU,GAAG,WAAW;EACxE,MAAM,kBAAkB,KAAK,wBAAwB,mBAAmB;EACxE,MAAM,iBAAiB,KAAK,uBAAuB;EACnD,MAAM,iBAAiB,yBAAyB,WAAW,YAA8B;EACzF,MAAM,kBACJ,WAAW,iBAAiB,SACxB,SACA,yBAAyB,WAAW,aAA+B;EACzE,MAAM,iBAAiB,0BAA0B,WAAW;AAC5D,eAAa,IAAI,WAAW,MAAM,eAAe;AACjD,QAAM,KACJ,iBAAiB;GACf,QAAQ,KAAK;GACb,gBAAgB,KAAK;GACrB;GACA,GAAI,KAAK,WAAW,SAAY,EAAE,QAAQ,KAAK,QAAQ,GAAG,EAAE;GAC5D,aAAa,WAAW;GACxB,mBAAmB;GACnB,aACE,WAAW,YAAY,WAAW,IAAI,GAAG,WAAW,KAAK,UAAU,WAAW;GAChF,aAAa;GACb,cAAc;GACd,eAAe;GACf,qBAAqB;GACrB;GACA,GAAI,KAAK,oBAAoB,SAAY,EAAE,GAAG,EAAE,iBAAiB,KAAK,iBAAiB;GACvF,GAAI,KAAK,uBAAuB,SAC5B,EAAE,GACF,EAAE,oBAAoB,KAAK,oBAAoB;GACnD,GAAI,KAAK,kBAAkB,SAAY,EAAE,GAAG,EAAE,eAAe,KAAK,eAAe;GACjF,GAAI,mBAAmB,SAAY,EAAE,GAAG,EAAE,gBAAgB;GAC3D,CAAC,CACH;;AAGH,QAAO,OAAO,OAAO;EACnB,OAAO,OAAO,OAAO,MAAM;EAC3B;EACA;EACA;EACA,6BAA6B;EAC7B,WAAW;EACX,mBAAmB;EACpB,CAAC;;AA8BJ,SAAS,iBAAiB,MAA6D;CACrF,MAAM,uBAAuB,oBAAoB;EAC/C,aAAa,KAAK;EAClB,qBAAqB,KAAK;EAC1B,UAAU,KAAK;EACf,gBAAgB,KAAK;EACtB,CAAC;CAEF,MAAM,OAAO;EACX,MAAM,KAAK;EACX,aAAa;EACb,aAAa,KAAK;EAClB,GAAI,KAAK,iBAAiB,SAAY,EAAE,GAAG,EAAE,cAAc,KAAK,cAAc;EAC9E,eAAe,KAAK;EACpB,qBAAqB,KAAK;EAC1B,iBAAiB,KAAK;EACtB,eAAe;EACf,GAAI,KAAK,oBAAoB,SAAY,EAAE,GAAG,EAAE,iBAAiB,KAAK,iBAAiB;EACvF,GAAI,KAAK,uBAAuB,SAC5B,EAAE,GACF,EAAE,oBAAoB,KAAK,oBAAoB;EACnD,GAAI,KAAK,mBAAmB,SAAY,EAAE,GAAG,EAAE,gBAAgB,KAAK,gBAAgB;EACpF,MAAM,QACJ,OACA,KAC8B;AAS9B,UAAO,gBAAgB;IACrB,QALa,MAAM,KAAK,OAAO,SAAS,KAAK,aAAa,OAAO;KACjE,GAAI,KAAK,WAAW,SAAY,EAAE,QAAQ,IAAI,QAAQ,GAAG,EAAE;KAC3D,GAAI,KAAK,kBAAkB,SAAY,EAAE,WAAW,KAAK,eAAe,GAAG,EAAE;KAC9E,CAAC;IAGA,cAAc,KAAK;IACnB,gBAAgB,KAAK;IACrB,UAAU,KAAK;IACf,GAAI,KAAK,WAAW,SAAY,EAAE,QAAQ,KAAK,QAAQ,GAAG,EAAE;IAC7D,CAAC;;EAEL;AAKD,QAAO,OAAO,OAAO,MAAM;EACzB,UAAU;GAAE,MAAM;GAAO,gBAAgB,KAAK,eAAe;GAAI;EAGjE,kBAAkB,KAAK;EACxB,CAAC"}
|