@adonis-agora/agent 0.30.2 → 0.33.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 +866 -0
- package/dist/assets/spa/assets/{index-Cfi3g3b2.js → index-3Q2m5_Ax.js} +1 -1
- package/dist/assets/spa/index.html +1 -1
- package/dist/providers/agent_provider.d.ts.map +1 -1
- package/dist/providers/agent_provider.js +157 -9
- package/dist/providers/agent_provider.js.map +1 -1
- package/dist/src/agent-deps-factory.d.ts +16 -2
- package/dist/src/agent-deps-factory.d.ts.map +1 -1
- package/dist/src/agent-deps-factory.js +8 -0
- package/dist/src/agent-deps-factory.js.map +1 -1
- package/dist/src/agent-deps.d.ts +21 -1
- package/dist/src/agent-deps.d.ts.map +1 -1
- package/dist/src/agent-deps.js.map +1 -1
- package/dist/src/agent-loop.d.ts +226 -9
- package/dist/src/agent-loop.d.ts.map +1 -1
- package/dist/src/agent-loop.js +1311 -208
- package/dist/src/agent-loop.js.map +1 -1
- package/dist/src/agent-service.d.ts +27 -0
- package/dist/src/agent-service.d.ts.map +1 -1
- package/dist/src/agent-service.js +29 -0
- package/dist/src/agent-service.js.map +1 -1
- package/dist/src/ai-sdk/ai-sdk-model.d.ts.map +1 -1
- package/dist/src/ai-sdk/ai-sdk-model.js +21 -1
- package/dist/src/ai-sdk/ai-sdk-model.js.map +1 -1
- package/dist/src/client/sse.d.ts +32 -2
- package/dist/src/client/sse.d.ts.map +1 -1
- package/dist/src/client/sse.js +45 -2
- package/dist/src/client/sse.js.map +1 -1
- package/dist/src/define_config.d.ts +41 -7
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/durable/agent-run-workflow.d.ts +10 -11
- package/dist/src/durable/agent-run-workflow.d.ts.map +1 -1
- package/dist/src/durable/agent-run-workflow.js +49 -21
- package/dist/src/durable/agent-run-workflow.js.map +1 -1
- package/dist/src/durable/durable-agent-runner.d.ts +3 -2
- package/dist/src/durable/durable-agent-runner.d.ts.map +1 -1
- package/dist/src/durable/durable-agent-runner.js +2 -2
- package/dist/src/durable/durable-agent-runner.js.map +1 -1
- package/dist/src/elicitation.d.ts +207 -0
- package/dist/src/elicitation.d.ts.map +1 -0
- package/dist/src/elicitation.js +315 -0
- package/dist/src/elicitation.js.map +1 -0
- package/dist/src/evals/approval-prior.d.ts +58 -0
- package/dist/src/evals/approval-prior.d.ts.map +1 -0
- package/dist/src/evals/approval-prior.js +82 -0
- package/dist/src/evals/approval-prior.js.map +1 -0
- package/dist/src/evals/evaluate.d.ts +51 -0
- package/dist/src/evals/evaluate.d.ts.map +1 -0
- package/dist/src/evals/evaluate.js +76 -0
- package/dist/src/evals/evaluate.js.map +1 -0
- package/dist/src/evals/governance-sample-source.d.ts +23 -0
- package/dist/src/evals/governance-sample-source.d.ts.map +1 -0
- package/dist/src/evals/governance-sample-source.js +93 -0
- package/dist/src/evals/governance-sample-source.js.map +1 -0
- package/dist/src/evals/in-memory-score-store.d.ts +16 -0
- package/dist/src/evals/in-memory-score-store.d.ts.map +1 -0
- package/dist/src/evals/in-memory-score-store.js +57 -0
- package/dist/src/evals/in-memory-score-store.js.map +1 -0
- package/dist/src/evals/index.d.ts +15 -0
- package/dist/src/evals/index.d.ts.map +1 -0
- package/dist/src/evals/index.js +14 -0
- package/dist/src/evals/index.js.map +1 -0
- package/dist/src/evals/judge.d.ts +30 -0
- package/dist/src/evals/judge.d.ts.map +1 -0
- package/dist/src/evals/judge.js +53 -0
- package/dist/src/evals/judge.js.map +1 -0
- package/dist/src/evals/live-scoring.d.ts +42 -0
- package/dist/src/evals/live-scoring.d.ts.map +1 -0
- package/dist/src/evals/live-scoring.js +103 -0
- package/dist/src/evals/live-scoring.js.map +1 -0
- package/dist/src/evals/sample-source.d.ts +40 -0
- package/dist/src/evals/sample-source.d.ts.map +1 -0
- package/dist/src/evals/sample-source.js +33 -0
- package/dist/src/evals/sample-source.js.map +1 -0
- package/dist/src/evals/score-store.d.ts +54 -0
- package/dist/src/evals/score-store.d.ts.map +1 -0
- package/dist/src/evals/score-store.js +2 -0
- package/dist/src/evals/score-store.js.map +1 -0
- package/dist/src/evals/scorers/answer-relevancy.scorer.d.ts +28 -0
- package/dist/src/evals/scorers/answer-relevancy.scorer.d.ts.map +1 -0
- package/dist/src/evals/scorers/answer-relevancy.scorer.js +42 -0
- package/dist/src/evals/scorers/answer-relevancy.scorer.js.map +1 -0
- package/dist/src/evals/scorers/approval-outcome.scorer.d.ts +22 -0
- package/dist/src/evals/scorers/approval-outcome.scorer.d.ts.map +1 -0
- package/dist/src/evals/scorers/approval-outcome.scorer.js +60 -0
- package/dist/src/evals/scorers/approval-outcome.scorer.js.map +1 -0
- package/dist/src/evals/scorers/approval-risk.scorer.d.ts +28 -0
- package/dist/src/evals/scorers/approval-risk.scorer.d.ts.map +1 -0
- package/dist/src/evals/scorers/approval-risk.scorer.js +54 -0
- package/dist/src/evals/scorers/approval-risk.scorer.js.map +1 -0
- package/dist/src/evals/scorers/run-completion.scorer.d.ts +18 -0
- package/dist/src/evals/scorers/run-completion.scorer.d.ts.map +1 -0
- package/dist/src/evals/scorers/run-completion.scorer.js +55 -0
- package/dist/src/evals/scorers/run-completion.scorer.js.map +1 -0
- package/dist/src/evals/summarize.d.ts +44 -0
- package/dist/src/evals/summarize.d.ts.map +1 -0
- package/dist/src/evals/summarize.js +87 -0
- package/dist/src/evals/summarize.js.map +1 -0
- package/dist/src/evals/types.d.ts +81 -0
- package/dist/src/evals/types.d.ts.map +1 -0
- package/dist/src/evals/types.js +15 -0
- package/dist/src/evals/types.js.map +1 -0
- package/dist/src/history-window.d.ts +50 -11
- package/dist/src/history-window.d.ts.map +1 -1
- package/dist/src/history-window.js +84 -12
- package/dist/src/history-window.js.map +1 -1
- package/dist/src/index.d.ts +11 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +10 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/server.d.ts +8 -0
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +27 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/mcp-client/importer.d.ts +75 -0
- package/dist/src/mcp-client/importer.d.ts.map +1 -0
- package/dist/src/mcp-client/importer.js +161 -0
- package/dist/src/mcp-client/importer.js.map +1 -0
- package/dist/src/mcp-client/index.d.ts +9 -0
- package/dist/src/mcp-client/index.d.ts.map +1 -0
- package/dist/src/mcp-client/index.js +8 -0
- package/dist/src/mcp-client/index.js.map +1 -0
- package/dist/src/mcp-client/options.d.ts +99 -0
- package/dist/src/mcp-client/options.d.ts.map +1 -0
- package/dist/src/mcp-client/options.js +2 -0
- package/dist/src/mcp-client/options.js.map +1 -0
- package/dist/src/mcp-client/tool-input.d.ts +39 -0
- package/dist/src/mcp-client/tool-input.d.ts.map +1 -0
- package/dist/src/mcp-client/tool-input.js +61 -0
- package/dist/src/mcp-client/tool-input.js.map +1 -0
- package/dist/src/mcp-client/tool-kind.d.ts +37 -0
- package/dist/src/mcp-client/tool-kind.d.ts.map +1 -0
- package/dist/src/mcp-client/tool-kind.js +20 -0
- package/dist/src/mcp-client/tool-kind.js.map +1 -0
- package/dist/src/mcp-client/tool-name.d.ts +22 -0
- package/dist/src/mcp-client/tool-name.d.ts.map +1 -0
- package/dist/src/mcp-client/tool-name.js +35 -0
- package/dist/src/mcp-client/tool-name.js.map +1 -0
- package/dist/src/mcp-client/tool-source.d.ts +75 -0
- package/dist/src/mcp-client/tool-source.d.ts.map +1 -0
- package/dist/src/mcp-client/tool-source.js +244 -0
- package/dist/src/mcp-client/tool-source.js.map +1 -0
- package/dist/src/mcp-client/transient.d.ts +28 -0
- package/dist/src/mcp-client/transient.d.ts.map +1 -0
- package/dist/src/mcp-client/transient.js +126 -0
- package/dist/src/mcp-client/transient.js.map +1 -0
- package/dist/src/mcp-client/unsafe-regex.d.ts +23 -0
- package/dist/src/mcp-client/unsafe-regex.d.ts.map +1 -0
- package/dist/src/mcp-client/unsafe-regex.js +194 -0
- package/dist/src/mcp-client/unsafe-regex.js.map +1 -0
- package/dist/src/memory.d.ts +448 -0
- package/dist/src/memory.d.ts.map +1 -0
- package/dist/src/memory.js +330 -0
- package/dist/src/memory.js.map +1 -0
- package/dist/src/pagination.d.ts +63 -0
- package/dist/src/pagination.d.ts.map +1 -0
- package/dist/src/pagination.js +16 -0
- package/dist/src/pagination.js.map +1 -0
- package/dist/src/processors.d.ts +134 -0
- package/dist/src/processors.d.ts.map +1 -0
- package/dist/src/processors.js +215 -0
- package/dist/src/processors.js.map +1 -0
- package/dist/src/rag/qdrant-store.d.ts +48 -6
- package/dist/src/rag/qdrant-store.d.ts.map +1 -1
- package/dist/src/rag/qdrant-store.js +77 -24
- package/dist/src/rag/qdrant-store.js.map +1 -1
- package/dist/src/replay-integrity.d.ts +17 -0
- package/dist/src/replay-integrity.d.ts.map +1 -0
- package/dist/src/replay-integrity.js +19 -0
- package/dist/src/replay-integrity.js.map +1 -0
- package/dist/src/runners/inline-agent-runner.d.ts +33 -7
- package/dist/src/runners/inline-agent-runner.d.ts.map +1 -1
- package/dist/src/runners/inline-agent-runner.js +113 -27
- package/dist/src/runners/inline-agent-runner.js.map +1 -1
- package/dist/src/skills.d.ts +291 -0
- package/dist/src/skills.d.ts.map +1 -0
- package/dist/src/skills.js +0 -0
- package/dist/src/skills.js.map +1 -0
- package/dist/src/spi/agent-runner.d.ts +20 -6
- package/dist/src/spi/agent-runner.d.ts.map +1 -1
- package/dist/src/spi/agent-store.d.ts +53 -0
- package/dist/src/spi/agent-store.d.ts.map +1 -1
- package/dist/src/spi/governance-queries.d.ts +33 -13
- package/dist/src/spi/governance-queries.d.ts.map +1 -1
- package/dist/src/spi/history-window.d.ts +64 -10
- package/dist/src/spi/history-window.d.ts.map +1 -1
- package/dist/src/spi/model-provider.d.ts +18 -0
- package/dist/src/spi/model-provider.d.ts.map +1 -1
- package/dist/src/spi/processors.d.ts +161 -0
- package/dist/src/spi/processors.d.ts.map +1 -0
- package/dist/src/spi/processors.js +57 -0
- package/dist/src/spi/processors.js.map +1 -0
- package/dist/src/spi/token-stream-sink.d.ts +43 -0
- package/dist/src/spi/token-stream-sink.d.ts.map +1 -1
- package/dist/src/spi/token-stream-sink.js +17 -1
- package/dist/src/spi/token-stream-sink.js.map +1 -1
- package/dist/src/sse.d.ts +5 -2
- package/dist/src/sse.d.ts.map +1 -1
- package/dist/src/sse.js +11 -2
- package/dist/src/sse.js.map +1 -1
- package/dist/src/stores/lucid-governance-queries.d.ts +5 -2
- package/dist/src/stores/lucid-governance-queries.d.ts.map +1 -1
- package/dist/src/stores/lucid-governance-queries.js +14 -5
- package/dist/src/stores/lucid-governance-queries.js.map +1 -1
- package/dist/src/stores/lucid-schema.d.ts.map +1 -1
- package/dist/src/stores/lucid-schema.js +15 -0
- package/dist/src/stores/lucid-schema.js.map +1 -1
- package/dist/src/stores/lucid.d.ts +11 -3
- package/dist/src/stores/lucid.d.ts.map +1 -1
- package/dist/src/stores/lucid.js +65 -0
- package/dist/src/stores/lucid.js.map +1 -1
- package/dist/src/structured-output.d.ts +57 -0
- package/dist/src/structured-output.d.ts.map +1 -0
- package/dist/src/structured-output.js +80 -0
- package/dist/src/structured-output.js.map +1 -0
- package/dist/src/telescope/agent-governance-providers.js +2 -2
- package/dist/src/telescope/agent-governance-providers.js.map +1 -1
- package/dist/src/testing/fake-model-provider.d.ts +8 -4
- package/dist/src/testing/fake-model-provider.d.ts.map +1 -1
- package/dist/src/testing/fake-model-provider.js +6 -9
- package/dist/src/testing/fake-model-provider.js.map +1 -1
- package/dist/src/testing/in-memory-governance-queries.d.ts.map +1 -1
- package/dist/src/testing/in-memory-governance-queries.js +8 -3
- package/dist/src/testing/in-memory-governance-queries.js.map +1 -1
- package/dist/src/testing/in-memory-store.d.ts +4 -3
- package/dist/src/testing/in-memory-store.d.ts.map +1 -1
- package/dist/src/testing/in-memory-store.js +13 -7
- package/dist/src/testing/in-memory-store.js.map +1 -1
- package/dist/src/testing/index.d.ts +1 -1
- package/dist/src/testing/index.d.ts.map +1 -1
- package/dist/src/testing/index.js.map +1 -1
- package/dist/src/tool-registry.d.ts +9 -0
- package/dist/src/tool-registry.d.ts.map +1 -1
- package/dist/src/tool-registry.js +11 -0
- package/dist/src/tool-registry.js.map +1 -1
- package/dist/src/types.d.ts +55 -2
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +20 -10
- package/skills/agent-personas-agents/SKILL.md +16 -12
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-source.d.ts","sourceRoot":"","sources":["../../../src/mcp-client/tool-source.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAU/D,gGAAgG;AAChG,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,WAAW,CAAC;IACrB,gGAAgG;IAChG,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,yGAAyG;AACzG,qBAAa,gBAAiB,SAAQ,KAAK;aAEvB,UAAU,EAAE,MAAM;aAClB,QAAQ,EAAE,MAAM;IAFlC,YACkB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChC,OAAO,EAAE,MAAM,EAIhB;CACF;AAoBD;;;;;;;;;;;GAWG;AACH,qBAAa,aAAa;IAKtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAL1B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,UAAU,CAA8B;IAEhD,YACmB,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,SAAS,YAAA,EACjC;IAEJ,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,mGAAmG;IAC7F,MAAM,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAczC;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAK3B;IAED,OAAO,CAAC,UAAU;IAQlB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAqCtB,OAAO,CAAC,QAAQ;YAcF,QAAQ;IAgBtB,oGAAoG;YACtF,OAAO;IAgBrB,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,YAAY;YAYN,OAAO;IAerB,OAAO,CAAC,eAAe;IAuBvB,OAAO,CAAC,UAAU;IAMlB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,gBAAgB;CAGzB"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
|
+
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { invokeWithTransientRetry } from '../tool-retry.js';
|
|
6
|
+
import { mcpInputSchema } from './tool-input.js';
|
|
7
|
+
import { resolveMcpToolKind } from './tool-kind.js';
|
|
8
|
+
import { localToolName } from './tool-name.js';
|
|
9
|
+
import { isPreExecutionMcpError, isTransientMcpError } from './transient.js';
|
|
10
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;
|
|
11
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
12
|
+
const CLIENT_INFO = { name: 'adonis-agora-agent-mcp', version: '1.0.0' };
|
|
13
|
+
/** A remote tool that answered with `isError` — a business failure of the tool, not of the transport. */
|
|
14
|
+
export class McpToolCallError extends Error {
|
|
15
|
+
serverName;
|
|
16
|
+
toolName;
|
|
17
|
+
constructor(serverName, toolName, message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.serverName = serverName;
|
|
20
|
+
this.toolName = toolName;
|
|
21
|
+
this.name = 'McpToolCallError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function isRecord(value) {
|
|
25
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* `content` is optional in the compatibility result shape the SDK still accepts, so it is narrowed
|
|
29
|
+
* rather than trusted: a server that answers without it yields no blocks instead of a crash.
|
|
30
|
+
*/
|
|
31
|
+
function toContentBlocks(content) {
|
|
32
|
+
return Array.isArray(content) ? content : [];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* One MCP server, as a source of tools.
|
|
36
|
+
*
|
|
37
|
+
* Holds a single client and reconnects on demand. The connection is deliberately not treated as
|
|
38
|
+
* durable state: a remote server may restart, be redeployed, or drop an idle connection, and the
|
|
39
|
+
* first symptom is a failed tool call. So a call that fails transiently drops the client, and the
|
|
40
|
+
* retry — core's `invokeWithTransientRetry`, the same one the agent loop wraps every tool with —
|
|
41
|
+
* reconnects on its next attempt.
|
|
42
|
+
*
|
|
43
|
+
* Every timeout here is the SDK's own per-request timeout rather than a race of our own, so a
|
|
44
|
+
* timed-out request is also CANCELLED on the wire instead of being abandoned.
|
|
45
|
+
*/
|
|
46
|
+
export class McpToolSource {
|
|
47
|
+
config;
|
|
48
|
+
logger;
|
|
49
|
+
client;
|
|
50
|
+
connecting;
|
|
51
|
+
constructor(config, logger) {
|
|
52
|
+
this.config = config;
|
|
53
|
+
this.logger = logger;
|
|
54
|
+
}
|
|
55
|
+
get name() {
|
|
56
|
+
return this.config.name;
|
|
57
|
+
}
|
|
58
|
+
/** Connect if needed, list the server's tools, and map the selected ones onto specs + handlers. */
|
|
59
|
+
async import() {
|
|
60
|
+
const client = await this.ensureClient();
|
|
61
|
+
const { tools } = await client.listTools(undefined, { timeout: this.requestTimeoutMs() });
|
|
62
|
+
const imported = [];
|
|
63
|
+
for (const tool of tools) {
|
|
64
|
+
if (!this.isSelected(tool.name)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const mapped = this.toImportedTool(tool);
|
|
68
|
+
if (mapped !== undefined) {
|
|
69
|
+
imported.push(mapped);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return imported;
|
|
73
|
+
}
|
|
74
|
+
async close() {
|
|
75
|
+
const client = this.client ?? (await this.connecting?.catch(() => undefined));
|
|
76
|
+
this.client = undefined;
|
|
77
|
+
this.connecting = undefined;
|
|
78
|
+
await client?.close();
|
|
79
|
+
}
|
|
80
|
+
isSelected(remoteName) {
|
|
81
|
+
const { include, exclude } = this.config;
|
|
82
|
+
if (include !== undefined && !include.includes(remoteName)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return exclude === undefined || !exclude.includes(remoteName);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A tool whose schema won't compile — or whose `pattern` can backtrack exponentially — is
|
|
89
|
+
* dropped, not imported with a permissive stand-in: the model would then be free to send that
|
|
90
|
+
* remote tool any arguments at all, and the call would still look validated from this side.
|
|
91
|
+
*/
|
|
92
|
+
toImportedTool(tool) {
|
|
93
|
+
const name = localToolName(this.config.name, tool.name, this.config.namespace);
|
|
94
|
+
let inputSchema;
|
|
95
|
+
try {
|
|
96
|
+
inputSchema = mcpInputSchema(tool.inputSchema, {
|
|
97
|
+
...(this.config.validator !== undefined ? { validator: this.config.validator } : {}),
|
|
98
|
+
...(this.config.rejectUnsafePatterns !== undefined
|
|
99
|
+
? { rejectUnsafePatterns: this.config.rejectUnsafePatterns }
|
|
100
|
+
: {}),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
this.logger?.warn(`MCP server "${this.config.name}": tool "${tool.name}" has an input schema this process will not validate against — skipped (${error instanceof Error ? error.message : String(error)})`);
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
const spec = {
|
|
108
|
+
name,
|
|
109
|
+
kind: resolveMcpToolKind({
|
|
110
|
+
name: tool.name,
|
|
111
|
+
...(tool.description !== undefined ? { description: tool.description } : {}),
|
|
112
|
+
...(tool.annotations !== undefined ? { annotations: tool.annotations } : {}),
|
|
113
|
+
}, this.config.kind),
|
|
114
|
+
description: tool.description ?? `The "${tool.name}" tool on MCP server ${this.config.name}.`,
|
|
115
|
+
inputSchema,
|
|
116
|
+
...(this.config.roles !== undefined ? { roles: this.config.roles } : {}),
|
|
117
|
+
...(this.config.ability !== undefined ? { ability: this.config.ability } : {}),
|
|
118
|
+
};
|
|
119
|
+
const handler = {
|
|
120
|
+
execute: (input) => this.callTool(tool.name, input, spec.kind),
|
|
121
|
+
};
|
|
122
|
+
return { spec, handler, remoteName: tool.name, serverName: this.config.name };
|
|
123
|
+
}
|
|
124
|
+
callTool(remoteName, input, kind) {
|
|
125
|
+
return invokeWithTransientRetry(() => this.callOnce(remoteName, input), this.retrySetting(kind), {
|
|
126
|
+
onRetry: (attempt, error) => {
|
|
127
|
+
this.logger?.warn(`MCP server "${this.config.name}": tool "${remoteName}" failed transiently on attempt ${attempt}, reconnecting (${error instanceof Error ? error.message : String(error)})`);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async callOnce(remoteName, input) {
|
|
132
|
+
const result = await this.request(remoteName, input);
|
|
133
|
+
const content = toContentBlocks(result.content);
|
|
134
|
+
if (result.isError === true) {
|
|
135
|
+
throw new McpToolCallError(this.config.name, remoteName, this.errorText(content));
|
|
136
|
+
}
|
|
137
|
+
if (result.structuredContent !== undefined) {
|
|
138
|
+
return result.structuredContent;
|
|
139
|
+
}
|
|
140
|
+
// The overwhelmingly common shape is one text block; handing the model the bare string keeps it
|
|
141
|
+
// from having to read past an envelope that carries nothing. Anything richer (images, embedded
|
|
142
|
+
// resources, several blocks) is passed through as MCP modelled it.
|
|
143
|
+
const only = content.length === 1 ? content[0] : undefined;
|
|
144
|
+
return only?.type === 'text' ? only.text : { content };
|
|
145
|
+
}
|
|
146
|
+
/** The wire call alone, so only a TRANSPORT failure can recycle the client — never a tool's own. */
|
|
147
|
+
async request(remoteName, input) {
|
|
148
|
+
const client = await this.ensureClient();
|
|
149
|
+
try {
|
|
150
|
+
return await client.callTool({ name: remoteName, ...(isRecord(input) ? { arguments: input } : {}) }, CallToolResultSchema, { timeout: this.requestTimeoutMs() });
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (isTransientMcpError(error)) {
|
|
154
|
+
this.dropClient();
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
errorText(content) {
|
|
160
|
+
const text = content
|
|
161
|
+
.filter((part) => part.type === 'text')
|
|
162
|
+
.map((part) => part.text ?? '')
|
|
163
|
+
.join('\n')
|
|
164
|
+
.trim();
|
|
165
|
+
return text === '' ? `MCP tool "${this.config.name}" reported an error` : text;
|
|
166
|
+
}
|
|
167
|
+
ensureClient() {
|
|
168
|
+
if (this.client !== undefined) {
|
|
169
|
+
return Promise.resolve(this.client);
|
|
170
|
+
}
|
|
171
|
+
// One in-flight connect shared by every caller: a turn can call several of a server's tools at
|
|
172
|
+
// once, and each opening its own connection would leave all but one orphaned.
|
|
173
|
+
this.connecting ??= this.connect().finally(() => {
|
|
174
|
+
this.connecting = undefined;
|
|
175
|
+
});
|
|
176
|
+
return this.connecting;
|
|
177
|
+
}
|
|
178
|
+
async connect() {
|
|
179
|
+
const client = new Client(this.config.clientInfo ?? CLIENT_INFO);
|
|
180
|
+
const transport = await this.createTransport();
|
|
181
|
+
await client.connect(transport, {
|
|
182
|
+
timeout: this.config.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,
|
|
183
|
+
});
|
|
184
|
+
client.onclose = () => {
|
|
185
|
+
if (this.client === client) {
|
|
186
|
+
this.client = undefined;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
this.client = client;
|
|
190
|
+
return client;
|
|
191
|
+
}
|
|
192
|
+
createTransport() {
|
|
193
|
+
const transport = this.config.transport;
|
|
194
|
+
switch (transport.type) {
|
|
195
|
+
case 'stdio':
|
|
196
|
+
return new StdioClientTransport({
|
|
197
|
+
command: transport.command,
|
|
198
|
+
...(transport.args !== undefined ? { args: transport.args } : {}),
|
|
199
|
+
...(transport.env !== undefined ? { env: transport.env } : {}),
|
|
200
|
+
...(transport.cwd !== undefined ? { cwd: transport.cwd } : {}),
|
|
201
|
+
});
|
|
202
|
+
case 'http':
|
|
203
|
+
// The SDK's own transports expose `sessionId` as `string | undefined` where the `Transport`
|
|
204
|
+
// interface declares the property optional, which `exactOptionalPropertyTypes` reads as a
|
|
205
|
+
// mismatch — the class does implement the interface it is declared against.
|
|
206
|
+
return new StreamableHTTPClientTransport(new URL(transport.url), transport.headers !== undefined ? { requestInit: { headers: transport.headers } } : {});
|
|
207
|
+
case 'custom':
|
|
208
|
+
return transport.create();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
dropClient() {
|
|
212
|
+
const client = this.client;
|
|
213
|
+
this.client = undefined;
|
|
214
|
+
void client?.close().catch(() => undefined);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The configured policy with a classifier filled in. The library's default classifier recognizes
|
|
218
|
+
* local database lock contention, which is not how a remote server fails — leaving it in place
|
|
219
|
+
* would mean the policy is on and never matches anything.
|
|
220
|
+
*
|
|
221
|
+
* WHICH CLASSIFIER, AND WHY IT DEPENDS ON THE KIND. An `action` is a tool whose effects happen on
|
|
222
|
+
* somebody else's machine and are invisible from here, which is why one waits for a human at all.
|
|
223
|
+
* Most of what {@link isTransientMcpError} accepts cannot tell "the request never arrived" from
|
|
224
|
+
* "it ran and the answer was lost" — a timeout, a reset, a 503 — so retrying on it would spend one
|
|
225
|
+
* approval on two remote effects. An `action` therefore retries only
|
|
226
|
+
* {@link isPreExecutionMcpError}: failures that prove nothing ran. A `read` keeps the broader
|
|
227
|
+
* policy, where a second identical call costs a round trip.
|
|
228
|
+
*
|
|
229
|
+
* A host that supplied its own `classify` has taken that judgement over, and it is honoured for
|
|
230
|
+
* every kind.
|
|
231
|
+
*/
|
|
232
|
+
retrySetting(kind) {
|
|
233
|
+
const setting = this.config.transientRetry;
|
|
234
|
+
if (setting === false) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
const fallback = kind === 'action' ? isPreExecutionMcpError : isTransientMcpError;
|
|
238
|
+
return { ...setting, classify: setting?.classify ?? fallback };
|
|
239
|
+
}
|
|
240
|
+
requestTimeoutMs() {
|
|
241
|
+
return this.config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=tool-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-source.js","sourceRoot":"","sources":["../../../src/mcp-client/tool-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAEnG,OAAO,EAAE,oBAAoB,EAAa,MAAM,oCAAoC,CAAC;AAErF,OAAO,EAAE,wBAAwB,EAAkC,MAAM,kBAAkB,CAAC;AAG5F,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE7E,MAAM,0BAA0B,GAAG,MAAM,CAAC;AAC1C,MAAM,0BAA0B,GAAG,MAAM,CAAC;AAC1C,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAWzE,yGAAyG;AACzG,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAEvB,UAAU;IACV,QAAQ;IAF1B,YACkB,UAAkB,EAClB,QAAgB,EAChC,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;0BAJC,UAAU;wBACV,QAAQ;QAIxB,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAQD;;;GAGG;AACH,SAAS,eAAe,CAAC,OAAgB;IACvC,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAE,OAA0B,CAAC,CAAC,CAAC,EAAE,CAAC;AACnE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,aAAa;IAKL,MAAM;IACN,MAAM;IALjB,MAAM,CAAqB;IAC3B,UAAU,CAA8B;IAEhD,YACmB,MAAuB,EACvB,MAAkB;sBADlB,MAAM;sBACN,MAAM;IACtB,CAAC;IAEJ,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;IAC1B,CAAC;IAED,mGAAmG;IACnG,KAAK,CAAC,MAAM;QACV,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QACzC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;QAC1F,MAAM,QAAQ,GAAsB,EAAE,CAAC;QACvC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChC,SAAS;YACX,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;YACzC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACxB,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;QAC9E,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC;IACxB,CAAC;IAEO,UAAU,CAAC,UAAkB;QACnC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QACzC,IAAI,OAAO,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3D,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,OAAO,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAChE,CAAC;IAED;;;;OAIG;IACK,cAAc,CAAC,IAAU;QAC/B,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC/E,IAAI,WAAoC,CAAC;QACzC,IAAI,CAAC;YACH,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE;gBAC7C,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpF,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,oBAAoB,KAAK,SAAS;oBAChD,CAAC,CAAC,EAAE,oBAAoB,EAAE,IAAI,CAAC,MAAM,CAAC,oBAAoB,EAAE;oBAC5D,CAAC,CAAC,EAAE,CAAC;aACR,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,EAAE,IAAI,CACf,eAAe,IAAI,CAAC,MAAM,CAAC,IAAI,YAAY,IAAI,CAAC,IAAI,2EAA2E,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CACzL,CAAC;YACF,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,IAAI,GAAa;YACrB,IAAI;YACJ,IAAI,EAAE,kBAAkB,CACtB;gBACE,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC5E,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC7E,EACD,IAAI,CAAC,MAAM,CAAC,IAAI,CACjB;YACD,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,QAAQ,IAAI,CAAC,IAAI,wBAAwB,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG;YAC7F,WAAW;YACX,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/E,CAAC;QACF,MAAM,OAAO,GAAgB;YAC3B,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC;SAC/D,CAAC;QACF,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAChF,CAAC;IAEO,QAAQ,CAAC,UAAkB,EAAE,KAAc,EAAE,IAAc;QACjE,OAAO,wBAAwB,CAC7B,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,EACtC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EACvB;YACE,OAAO,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE;gBAC1B,IAAI,CAAC,MAAM,EAAE,IAAI,CACf,eAAe,IAAI,CAAC,MAAM,CAAC,IAAI,YAAY,UAAU,mCAAmC,OAAO,mBAAmB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAC5K,CAAC;YACJ,CAAC;SACF,CACF,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,QAAQ,CAAC,UAAkB,EAAE,KAAc;QACvD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAChD,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC5B,MAAM,IAAI,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QACpF,CAAC;QACD,IAAI,MAAM,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;YAC3C,OAAO,MAAM,CAAC,iBAAiB,CAAC;QAClC,CAAC;QACD,gGAAgG;QAChG,+FAA+F;QAC/F,mEAAmE;QACnE,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3D,OAAO,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;IACzD,CAAC;IAED,oGAAoG;IAC5F,KAAK,CAAC,OAAO,CAAC,UAAkB,EAAE,KAAc;QACtD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QACzC,IAAI,CAAC;YACH,OAAO,MAAM,MAAM,CAAC,QAAQ,CAC1B,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EACtE,oBAAoB,EACpB,EAAE,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,EAAE,CACrC,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC/B,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAEO,SAAS,CAAC,OAAuB;QACvC,MAAM,IAAI,GAAG,OAAO;aACjB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC;aACtC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;aAC9B,IAAI,CAAC,IAAI,CAAC;aACV,IAAI,EAAE,CAAC;QACV,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,MAAM,CAAC,IAAI,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC;IACjF,CAAC;IAEO,YAAY;QAClB,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC9B,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtC,CAAC;QACD,+FAA+F;QAC/F,8EAA8E;QAC9E,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;YAC9C,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC9B,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,WAAW,CAAC,CAAC;QACjE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;QAC/C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE;YAC9B,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB,IAAI,0BAA0B;SACpE,CAAC,CAAC;QACH,MAAM,CAAC,OAAO,GAAG,GAAG,EAAE;YACpB,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;gBAC3B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;YAC1B,CAAC;QACH,CAAC,CAAC;QACF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,eAAe;QACrB,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;QACxC,QAAQ,SAAS,CAAC,IAAI,EAAE,CAAC;YACvB,KAAK,OAAO;gBACV,OAAO,IAAI,oBAAoB,CAAC;oBAC9B,OAAO,EAAE,SAAS,CAAC,OAAO;oBAC1B,GAAG,CAAC,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACjE,GAAG,CAAC,SAAS,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC9D,GAAG,CAAC,SAAS,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC/D,CAAC,CAAC;YACL,KAAK,MAAM;gBACT,4FAA4F;gBAC5F,0FAA0F;gBAC1F,4EAA4E;gBAC5E,OAAO,IAAI,6BAA6B,CACtC,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,EACtB,SAAS,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAC1E,CAAC;YACjB,KAAK,QAAQ;gBACX,OAAO,SAAS,CAAC,MAAM,EAAE,CAAC;QAC9B,CAAC;IACH,CAAC;IAEO,UAAU;QAChB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,KAAK,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC9C,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACK,YAAY,CAAC,IAAc;QACjC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC;QAC3C,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;YACtB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,mBAAmB,CAAC;QAClF,OAAO,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,QAAQ,EAAE,CAAC;IACjE,CAAC;IAEO,gBAAgB;QACtB,OAAO,IAAI,CAAC,MAAM,CAAC,gBAAgB,IAAI,0BAA0B,CAAC;IACpE,CAAC;CACF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transient-error classification for an MCP server, in the shape `toolTransientRetry` already speaks
|
|
3
|
+
* (`classify: (error) => boolean`). {@link import('../tool-retry.js').isTransientToolError}
|
|
4
|
+
* recognizes local database lock contention; a remote MCP server fails in a different vocabulary —
|
|
5
|
+
* JSON-RPC error codes, HTTP statuses, socket errors — which is what this adds.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* True for a remote-MCP failure that the same call, made again, may well survive: a dropped
|
|
9
|
+
* connection, a request timeout, a retryable HTTP status, a socket error. Checked on the error and
|
|
10
|
+
* one level of `cause` — `fetch` reports a socket failure as a bare `TypeError` wrapping the real
|
|
11
|
+
* one, the same wrapping core's classifier already unwraps for database drivers.
|
|
12
|
+
*
|
|
13
|
+
* A protocol-level refusal (`InvalidParams`, `MethodNotFound`) and a server-side `InternalError`
|
|
14
|
+
* are deliberately NOT transient: the identical request would be refused identically, so retrying
|
|
15
|
+
* only spends the turn's time budget.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isTransientMcpError(error: unknown): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* True only for a failure that PROVES the remote tool did not run: the connection was refused, the
|
|
20
|
+
* host was never reached, or the SDK had no transport to send on. Checked on the error and one level
|
|
21
|
+
* of `cause`, so `fetch`'s bare `TypeError` wrapper is read through to the socket error underneath.
|
|
22
|
+
*
|
|
23
|
+
* Everything {@link isTransientMcpError} also accepts — a request timeout, a dropped connection, a
|
|
24
|
+
* 500/502/503, a reset mid-flight — is deliberately excluded: in each of those the request may have
|
|
25
|
+
* arrived and executed, and only the answer was lost. Retrying them would run the tool twice.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isPreExecutionMcpError(error: unknown): boolean;
|
|
28
|
+
//# sourceMappingURL=transient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transient.d.ts","sourceRoot":"","sources":["../../../src/mcp-client/transient.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAmDH;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAW3D;AAqCD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAW9D"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transient-error classification for an MCP server, in the shape `toolTransientRetry` already speaks
|
|
3
|
+
* (`classify: (error) => boolean`). {@link import('../tool-retry.js').isTransientToolError}
|
|
4
|
+
* recognizes local database lock contention; a remote MCP server fails in a different vocabulary —
|
|
5
|
+
* JSON-RPC error codes, HTTP statuses, socket errors — which is what this adds.
|
|
6
|
+
*/
|
|
7
|
+
/** JSON-RPC codes whose retry is safe: the request never reached the tool, or its answer was lost. */
|
|
8
|
+
const TRANSIENT_JSONRPC_CODES = new Set([-32000, -32001]); // ConnectionClosed, RequestTimeout
|
|
9
|
+
/** HTTP statuses the streamable-HTTP transport surfaces for "try again", not "you asked wrong". */
|
|
10
|
+
const TRANSIENT_HTTP_STATUSES = new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
11
|
+
/** Socket/DNS failures from `fetch` or a spawned stdio server that a reconnect can clear. */
|
|
12
|
+
const TRANSIENT_SYSCALL_CODES = new Set([
|
|
13
|
+
'ECONNRESET',
|
|
14
|
+
'ECONNREFUSED',
|
|
15
|
+
'ECONNABORTED',
|
|
16
|
+
'EPIPE',
|
|
17
|
+
'ETIMEDOUT',
|
|
18
|
+
'EAI_AGAIN',
|
|
19
|
+
'EHOSTUNREACH',
|
|
20
|
+
'ENETUNREACH',
|
|
21
|
+
]);
|
|
22
|
+
/**
|
|
23
|
+
* The SDK reports "you called while there was no transport" as a plain `Error` with no code, so the
|
|
24
|
+
* message is the only marker available. Kept narrow on purpose — a message test that matched more
|
|
25
|
+
* broadly would start retrying a remote tool's ordinary business failures, which are one-shot.
|
|
26
|
+
*/
|
|
27
|
+
const TRANSIENT_MESSAGES = /^not connected$|connection closed|socket hang up|fetch failed/i;
|
|
28
|
+
function hasTransientMcpShape(error) {
|
|
29
|
+
if (typeof error !== 'object' || error === null) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
// A tool that answered `isError` failed on its own merits, and its message is text the remote
|
|
33
|
+
// server wrote — matching it against the markers below would let a tool's prose decide whether
|
|
34
|
+
// this side retries. Matched by name rather than `instanceof`, which a second copy of this
|
|
35
|
+
// package (pnpm peer multiplexing, dual ESM/CJS) would not survive.
|
|
36
|
+
if ('name' in error && error.name === 'McpToolCallError') {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const code = 'code' in error ? error.code : undefined;
|
|
40
|
+
if (typeof code === 'number') {
|
|
41
|
+
// `McpError` and the streamable-HTTP transport both surface a `code`, and the two vocabularies
|
|
42
|
+
// never overlap: JSON-RPC codes are negative, HTTP statuses are positive.
|
|
43
|
+
return code < 0 ? TRANSIENT_JSONRPC_CODES.has(code) : TRANSIENT_HTTP_STATUSES.has(code);
|
|
44
|
+
}
|
|
45
|
+
if (typeof code === 'string' && TRANSIENT_SYSCALL_CODES.has(code)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
const message = 'message' in error ? error.message : undefined;
|
|
49
|
+
return typeof message === 'string' && TRANSIENT_MESSAGES.test(message);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* True for a remote-MCP failure that the same call, made again, may well survive: a dropped
|
|
53
|
+
* connection, a request timeout, a retryable HTTP status, a socket error. Checked on the error and
|
|
54
|
+
* one level of `cause` — `fetch` reports a socket failure as a bare `TypeError` wrapping the real
|
|
55
|
+
* one, the same wrapping core's classifier already unwraps for database drivers.
|
|
56
|
+
*
|
|
57
|
+
* A protocol-level refusal (`InvalidParams`, `MethodNotFound`) and a server-side `InternalError`
|
|
58
|
+
* are deliberately NOT transient: the identical request would be refused identically, so retrying
|
|
59
|
+
* only spends the turn's time budget.
|
|
60
|
+
*/
|
|
61
|
+
export function isTransientMcpError(error) {
|
|
62
|
+
if (hasTransientMcpShape(error)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (typeof error === 'object' && error !== null && 'cause' in error) {
|
|
66
|
+
const cause = error.cause;
|
|
67
|
+
if (cause !== undefined && cause !== error && hasTransientMcpShape(cause)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Syscall codes that mean the request was never delivered: the connection was refused, or the host
|
|
75
|
+
* was never resolved or reached. A reset, a broken pipe or a timeout does NOT belong here — each of
|
|
76
|
+
* those can equally well be a reply that was lost after the tool ran.
|
|
77
|
+
*/
|
|
78
|
+
const PRE_EXECUTION_SYSCALL_CODES = new Set([
|
|
79
|
+
'ECONNREFUSED',
|
|
80
|
+
'EAI_AGAIN',
|
|
81
|
+
'EHOSTUNREACH',
|
|
82
|
+
'ENETUNREACH',
|
|
83
|
+
]);
|
|
84
|
+
/** The SDK refusing to send because there is no transport — nothing left this process. */
|
|
85
|
+
const PRE_EXECUTION_MESSAGES = /^not connected$/i;
|
|
86
|
+
function hasPreExecutionShape(error) {
|
|
87
|
+
if (typeof error !== 'object' || error === null) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
if ('name' in error && error.name === 'McpToolCallError') {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const code = 'code' in error ? error.code : undefined;
|
|
94
|
+
if (typeof code === 'string') {
|
|
95
|
+
return PRE_EXECUTION_SYSCALL_CODES.has(code);
|
|
96
|
+
}
|
|
97
|
+
if (code !== undefined) {
|
|
98
|
+
// A JSON-RPC code or an HTTP status means the wire carried the request far enough to be
|
|
99
|
+
// answered, so neither can establish that the tool did not run.
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
const message = 'message' in error ? error.message : undefined;
|
|
103
|
+
return typeof message === 'string' && PRE_EXECUTION_MESSAGES.test(message);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* True only for a failure that PROVES the remote tool did not run: the connection was refused, the
|
|
107
|
+
* host was never reached, or the SDK had no transport to send on. Checked on the error and one level
|
|
108
|
+
* of `cause`, so `fetch`'s bare `TypeError` wrapper is read through to the socket error underneath.
|
|
109
|
+
*
|
|
110
|
+
* Everything {@link isTransientMcpError} also accepts — a request timeout, a dropped connection, a
|
|
111
|
+
* 500/502/503, a reset mid-flight — is deliberately excluded: in each of those the request may have
|
|
112
|
+
* arrived and executed, and only the answer was lost. Retrying them would run the tool twice.
|
|
113
|
+
*/
|
|
114
|
+
export function isPreExecutionMcpError(error) {
|
|
115
|
+
if (hasPreExecutionShape(error)) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (typeof error === 'object' && error !== null && 'cause' in error) {
|
|
119
|
+
const cause = error.cause;
|
|
120
|
+
if (cause !== undefined && cause !== error && hasPreExecutionShape(cause)) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=transient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transient.js","sourceRoot":"","sources":["../../../src/mcp-client/transient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A screen for the one thing a remote JSON Schema can do to the process that validates against it.
|
|
3
|
+
*
|
|
4
|
+
* Every validator in the MCP SDK compiles a schema's `pattern` to a native `RegExp`, and JS regex
|
|
5
|
+
* matching backtracks: `(a+)+$` against 27 a's and a non-matching tail takes ~1s, 30 a's ~8s, 40 a's
|
|
6
|
+
* longer than anyone will wait — synchronously, on the only thread the process has. The pattern is
|
|
7
|
+
* written by the remote server and the string is written by the model, which the same server's tool
|
|
8
|
+
* description is free to steer, so the whole thing fits inside one tool definition.
|
|
9
|
+
*
|
|
10
|
+
* What is detected is the shape that makes backtracking EXPONENTIAL: an unbounded repetition whose
|
|
11
|
+
* body can match one piece of text in more than one way, so a failed match has to try every split.
|
|
12
|
+
* It is a structural screen and not a decision procedure — `(ab|abc)+` is ambiguous across
|
|
13
|
+
* alternatives and is not flagged here. A host that needs a guarantee rather than a screen gives
|
|
14
|
+
* `validator` an engine that does not backtrack (AJV's `code.regExp` option takes an RE2 binding).
|
|
15
|
+
*/
|
|
16
|
+
/** Whether this pattern can be made to backtrack exponentially. See the file comment for the limits. */
|
|
17
|
+
export declare function isUnsafeRegex(pattern: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* The first pattern anywhere in a JSON Schema that {@link isUnsafeRegex} flags — `pattern` at any
|
|
20
|
+
* depth, and the keys of `patternProperties`, which are regexes too.
|
|
21
|
+
*/
|
|
22
|
+
export declare function findUnsafePattern(schema: unknown): string | undefined;
|
|
23
|
+
//# sourceMappingURL=unsafe-regex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unsafe-regex.d.ts","sourceRoot":"","sources":["../../../src/mcp-client/unsafe-regex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAwKH,wGAAwG;AACxG,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAgCrE"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A screen for the one thing a remote JSON Schema can do to the process that validates against it.
|
|
3
|
+
*
|
|
4
|
+
* Every validator in the MCP SDK compiles a schema's `pattern` to a native `RegExp`, and JS regex
|
|
5
|
+
* matching backtracks: `(a+)+$` against 27 a's and a non-matching tail takes ~1s, 30 a's ~8s, 40 a's
|
|
6
|
+
* longer than anyone will wait — synchronously, on the only thread the process has. The pattern is
|
|
7
|
+
* written by the remote server and the string is written by the model, which the same server's tool
|
|
8
|
+
* description is free to steer, so the whole thing fits inside one tool definition.
|
|
9
|
+
*
|
|
10
|
+
* What is detected is the shape that makes backtracking EXPONENTIAL: an unbounded repetition whose
|
|
11
|
+
* body can match one piece of text in more than one way, so a failed match has to try every split.
|
|
12
|
+
* It is a structural screen and not a decision procedure — `(ab|abc)+` is ambiguous across
|
|
13
|
+
* alternatives and is not flagged here. A host that needs a guarantee rather than a screen gives
|
|
14
|
+
* `validator` an engine that does not backtrack (AJV's `code.regExp` option takes an RE2 binding).
|
|
15
|
+
*/
|
|
16
|
+
const QUANTIFIER = /^(?:([*+?])|\{(\d+)(,(\d*))?\})/;
|
|
17
|
+
/**
|
|
18
|
+
* Enough of a regex parser to see the nesting: alternatives, groups, quantifiers, and one opaque
|
|
19
|
+
* atom for everything else. Anything it does not recognize is a literal, which makes the parse
|
|
20
|
+
* total — a pattern this cannot read comes out as a flat list of tight atoms and is not flagged,
|
|
21
|
+
* rather than throwing where the caller expects a verdict.
|
|
22
|
+
*/
|
|
23
|
+
class PatternParser {
|
|
24
|
+
pattern;
|
|
25
|
+
index = 0;
|
|
26
|
+
constructor(pattern) {
|
|
27
|
+
this.pattern = pattern;
|
|
28
|
+
}
|
|
29
|
+
parse() {
|
|
30
|
+
const branches = this.parseAlternation();
|
|
31
|
+
// A `)` with no `(` — the pattern is not valid, and the compiler is about to say so.
|
|
32
|
+
this.index = this.pattern.length;
|
|
33
|
+
return branches;
|
|
34
|
+
}
|
|
35
|
+
parseAlternation() {
|
|
36
|
+
const branches = [];
|
|
37
|
+
let branch = [];
|
|
38
|
+
while (this.index < this.pattern.length) {
|
|
39
|
+
const char = this.pattern[this.index];
|
|
40
|
+
if (char === ')') {
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
if (char === '|') {
|
|
44
|
+
this.index += 1;
|
|
45
|
+
branches.push(branch);
|
|
46
|
+
branch = [];
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
branch.push(this.parseAtom());
|
|
50
|
+
}
|
|
51
|
+
branches.push(branch);
|
|
52
|
+
return branches;
|
|
53
|
+
}
|
|
54
|
+
parseAtom() {
|
|
55
|
+
const char = this.pattern[this.index];
|
|
56
|
+
if (char === '(') {
|
|
57
|
+
this.index += 1;
|
|
58
|
+
this.skipGroupPrefix();
|
|
59
|
+
const body = this.parseAlternation();
|
|
60
|
+
if (this.pattern[this.index] === ')') {
|
|
61
|
+
this.index += 1;
|
|
62
|
+
}
|
|
63
|
+
return this.withQuantifier({ body });
|
|
64
|
+
}
|
|
65
|
+
if (char === '[') {
|
|
66
|
+
this.skipCharacterClass();
|
|
67
|
+
return this.withQuantifier({});
|
|
68
|
+
}
|
|
69
|
+
// An escape is two characters, so `\(` never opens a group and `\\` never escapes the next one.
|
|
70
|
+
this.index += char === '\\' ? 2 : 1;
|
|
71
|
+
return this.withQuantifier({});
|
|
72
|
+
}
|
|
73
|
+
skipGroupPrefix() {
|
|
74
|
+
if (this.pattern[this.index] !== '?') {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const closing = this.pattern.indexOf('>', this.index);
|
|
78
|
+
// `(?<name>` — the only prefix whose length is not fixed. Every other one (`?:`, `?=`, `?!`,
|
|
79
|
+
// `?<=`, `?<!`) ends before the first character of the body.
|
|
80
|
+
if (this.pattern[this.index + 1] === '<' &&
|
|
81
|
+
!'=!'.includes(this.pattern[this.index + 2] ?? '')) {
|
|
82
|
+
this.index = closing === -1 ? this.pattern.length : closing + 1;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
this.index += this.pattern[this.index + 1] === '<' ? 3 : 2;
|
|
86
|
+
}
|
|
87
|
+
skipCharacterClass() {
|
|
88
|
+
this.index += 1;
|
|
89
|
+
while (this.index < this.pattern.length && this.pattern[this.index] !== ']') {
|
|
90
|
+
this.index += this.pattern[this.index] === '\\' ? 2 : 1;
|
|
91
|
+
}
|
|
92
|
+
this.index += 1;
|
|
93
|
+
}
|
|
94
|
+
withQuantifier(atom) {
|
|
95
|
+
const match = QUANTIFIER.exec(this.pattern.slice(this.index));
|
|
96
|
+
if (match === null) {
|
|
97
|
+
return atom;
|
|
98
|
+
}
|
|
99
|
+
this.index += match[0].length;
|
|
100
|
+
// A trailing `?` makes the repetition lazy, which changes the order the splits are tried and
|
|
101
|
+
// not how many there are.
|
|
102
|
+
if (this.pattern[this.index] === '?') {
|
|
103
|
+
this.index += 1;
|
|
104
|
+
}
|
|
105
|
+
return { ...atom, quantifier: toQuantifier(match) };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function toQuantifier(match) {
|
|
109
|
+
const symbol = match[1];
|
|
110
|
+
if (symbol !== undefined) {
|
|
111
|
+
return { variable: true, unbounded: symbol !== '?' };
|
|
112
|
+
}
|
|
113
|
+
const min = Number(match[2]);
|
|
114
|
+
const comma = match[3] !== undefined;
|
|
115
|
+
const max = match[4] === undefined || match[4] === '' ? Number.POSITIVE_INFINITY : Number(match[4]);
|
|
116
|
+
return {
|
|
117
|
+
variable: comma ? min < max : false,
|
|
118
|
+
unbounded: comma && max === Number.POSITIVE_INFINITY,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** Whether this atom can match more than one length — on its own, or through what it contains. */
|
|
122
|
+
function isVariable(atom) {
|
|
123
|
+
if (atom.quantifier?.variable === true) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return (atom.body ?? []).some((branch) => branch.some(isVariable));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Whether repeating these alternatives is ambiguous: one alternative made entirely of parts that
|
|
130
|
+
* can each stretch, so the boundary between two repetitions can fall in more than one place and a
|
|
131
|
+
* failed match has to try all of them. `(a+)` qualifies and `(a+b)` does not — the `b` pins every
|
|
132
|
+
* repetition to one position.
|
|
133
|
+
*/
|
|
134
|
+
function isAmbiguous(branches) {
|
|
135
|
+
return branches.some((branch) => branch.length > 0 && branch.every(isVariable));
|
|
136
|
+
}
|
|
137
|
+
function containsUnsafeRepeat(branches) {
|
|
138
|
+
for (const branch of branches) {
|
|
139
|
+
for (const atom of branch) {
|
|
140
|
+
if (atom.body === undefined) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (atom.quantifier?.unbounded === true && isAmbiguous(atom.body)) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
if (containsUnsafeRepeat(atom.body)) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
/** Whether this pattern can be made to backtrack exponentially. See the file comment for the limits. */
|
|
154
|
+
export function isUnsafeRegex(pattern) {
|
|
155
|
+
return containsUnsafeRepeat(new PatternParser(pattern).parse());
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The first pattern anywhere in a JSON Schema that {@link isUnsafeRegex} flags — `pattern` at any
|
|
159
|
+
* depth, and the keys of `patternProperties`, which are regexes too.
|
|
160
|
+
*/
|
|
161
|
+
export function findUnsafePattern(schema) {
|
|
162
|
+
if (Array.isArray(schema)) {
|
|
163
|
+
for (const item of schema) {
|
|
164
|
+
const found = findUnsafePattern(item);
|
|
165
|
+
if (found !== undefined) {
|
|
166
|
+
return found;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
if (typeof schema !== 'object' || schema === null) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
175
|
+
if (key === 'pattern' && typeof value === 'string') {
|
|
176
|
+
if (isUnsafeRegex(value)) {
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (key === 'patternProperties' && typeof value === 'object' && value !== null) {
|
|
182
|
+
const unsafeKey = Object.keys(value).find(isUnsafeRegex);
|
|
183
|
+
if (unsafeKey !== undefined) {
|
|
184
|
+
return unsafeKey;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const found = findUnsafePattern(value);
|
|
188
|
+
if (found !== undefined) {
|
|
189
|
+
return found;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=unsafe-regex.js.map
|