@sema-agent/core 7.5.2 โ 7.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/dist/agents/agent-transcript-tool.d.ts +2 -2
- package/dist/agents/cascade.d.ts +4 -5
- package/dist/agents/cascade.js +12 -10
- package/dist/agents/repair-loop.d.ts +7 -5
- package/dist/agents/repair-loop.js +13 -15
- package/dist/agents/retain-ledger.d.ts +2 -3
- package/dist/agents/send-message-tool.d.ts +2 -2
- package/dist/agents/session-util.d.ts +2 -2
- package/dist/agents/subagent.d.ts +27 -46
- package/dist/agents/subagent.js +119 -105
- package/dist/agents/suspend-guard.d.ts +31 -19
- package/dist/agents/suspend-guard.js +14 -8
- package/dist/agents/teacher.d.ts +2 -2
- package/dist/agents/teacher.js +9 -9
- package/dist/agents/team.d.ts +6 -5
- package/dist/agents/team.js +10 -8
- package/dist/agents/verify.d.ts +8 -9
- package/dist/agents/verify.js +17 -17
- package/dist/core/a2a.js +2 -1
- package/dist/core/agent-definition.d.ts +172 -0
- package/dist/core/agent-definition.js +1 -0
- package/dist/core/ask-origin.d.ts +60 -7
- package/dist/core/ask-origin.js +26 -1
- package/dist/core/checkpoint-store.d.ts +78 -76
- package/dist/core/checkpoint-store.js +17 -1
- package/dist/core/delegation-frames.d.ts +298 -0
- package/dist/core/delegation-frames.js +21 -0
- package/dist/core/engine-notice.d.ts +555 -0
- package/dist/core/engine-notice.js +55 -0
- package/dist/core/gate-fold.d.ts +12 -0
- package/dist/core/gate-fold.js +158 -0
- package/dist/core/gate-lanes.d.ts +93 -0
- package/dist/core/gate-lanes.js +626 -0
- package/dist/core/gate-outcome.d.ts +189 -0
- package/dist/core/gate-outcome.js +70 -0
- package/dist/core/hands-band.d.ts +134 -0
- package/dist/core/hands-band.js +1 -0
- package/dist/core/hooks.d.ts +22 -177
- package/dist/core/hooks.js +53 -851
- package/dist/core/mcp-failure.d.ts +142 -0
- package/dist/core/mcp-failure.js +145 -0
- package/dist/core/mcp-server-spec.d.ts +217 -0
- package/dist/core/mcp-server-spec.js +1 -0
- package/dist/core/mcp.d.ts +21 -77
- package/dist/core/mcp.js +76 -150
- package/dist/core/model-seat.d.ts +99 -0
- package/dist/core/model-seat.js +1 -0
- package/dist/core/pause-registry.d.ts +131 -0
- package/dist/core/pause-registry.js +27 -0
- package/dist/core/reminder-mint.d.ts +10 -0
- package/dist/core/reminder-mint.js +3 -0
- package/dist/core/runner/assemble-result.d.ts +32 -41
- package/dist/core/runner/assemble-result.js +55 -74
- package/dist/core/runner/contracts.d.ts +427 -69
- package/dist/core/runner/denial-limit-arms.d.ts +1 -1
- package/dist/core/runner/denial-limit-arms.js +3 -3
- package/dist/core/runner/gate-exit.d.ts +242 -0
- package/dist/core/runner/gate-exit.js +124 -0
- package/dist/core/runner/park-commit.d.ts +17 -23
- package/dist/core/runner/park-commit.js +14 -15
- package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
- package/dist/core/runner/prepare-ask-lane.js +3 -5
- package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
- package/dist/core/runner/prepare-boundary-parks.js +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
- package/dist/core/runner/prepare-gate-stations.js +29 -54
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-memory.d.ts +44 -26
- package/dist/core/runner/prepare-park-ask.d.ts +2 -4
- package/dist/core/runner/prepare-park-ask.js +5 -5
- package/dist/core/runner/prepare-task.d.ts +2 -2
- package/dist/core/runner/prepare-task.js +8 -9
- package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
- package/dist/core/runner/prepare-wiring-manifest.js +9 -10
- package/dist/core/runner/runtask.d.ts +20 -102
- package/dist/core/runner/runtask.js +119 -121
- package/dist/core/runner/terminal-projection.d.ts +22 -0
- package/dist/core/runner/terminal-projection.js +28 -0
- package/dist/core/runner-deps.d.ts +1416 -0
- package/dist/core/runner-deps.js +1 -0
- package/dist/core/runtime-caps.d.ts +164 -0
- package/dist/core/runtime-caps.js +1 -0
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
- package/dist/core/task-event.d.ts +910 -0
- package/dist/core/task-event.js +1 -0
- package/dist/core/task-limits.d.ts +110 -0
- package/dist/core/task-limits.js +1 -0
- package/dist/core/task-result.d.ts +809 -0
- package/dist/core/task-result.js +1 -0
- package/dist/core/task-spec.d.ts +1370 -0
- package/dist/core/task-spec.js +1 -0
- package/dist/core/task-stream.d.ts +382 -0
- package/dist/core/task-stream.js +1 -0
- package/dist/core/terminal-cause.d.ts +137 -0
- package/dist/core/terminal-cause.js +9 -0
- package/dist/core/tool-policy.d.ts +43 -139
- package/dist/core/tool-policy.js +79 -112
- package/dist/core/tool-spec.d.ts +1174 -0
- package/dist/core/tool-spec.js +1 -0
- package/dist/core/types.d.ts +27 -7789
- package/dist/core/types.js +2 -76
- package/dist/core/warm-resume.d.ts +2 -2
- package/dist/core/wiring-manifest.d.ts +6 -3
- package/dist/core/workflow-journal-store.js +3 -4
- package/dist/engine/harness/agent-harness.d.ts +1 -1
- package/dist/index.d.ts +12 -8
- package/dist/index.js +9 -6
- package/dist/orchestration/builtin-workflows.d.ts +2 -2
- package/dist/orchestration/builtin-workflows.js +1 -1
- package/dist/orchestration/goal.d.ts +2 -2
- package/dist/orchestration/goal.js +8 -7
- package/dist/orchestration/run-spec.d.ts +2 -2
- package/dist/orchestration/run-spec.js +5 -3
- package/dist/orchestration/run-workflow-tool.d.ts +4 -4
- package/dist/orchestration/run-workflow-tool.js +4 -4
- package/dist/orchestration/workflow-governance.d.ts +4 -4
- package/dist/orchestration/workflow-governance.js +4 -2
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-primitives.js +1 -1
- package/dist/orchestration/workflow.d.ts +15 -4
- package/dist/orchestration/workflow.js +64 -39
- package/dist/prompts/supervisor.d.ts +1 -1
- package/dist/prompts/supervisor.js +3 -3
- package/dist/scenarios/scenario-registry.d.ts +3 -3
- package/dist/scenarios/scenario-registry.js +1 -1
- package/dist/scenarios/teacher-quickstart.d.ts +2 -2
- package/dist/server/http.d.ts +2 -2
- package/dist/stores/file/fs-atomic.d.ts +88 -12
- package/dist/stores/file/fs-atomic.js +184 -55
- package/dist/stores/file/index.d.ts +1 -0
- package/dist/stores/file/index.js +1 -0
- package/package.json +3 -1
- package/test/export-surface.snapshot.json +82 -22
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { AssertAllKeysHandled } from "./ask-origin.js";
|
|
2
|
+
/**
|
|
3
|
+
* The closed set of failure classes ({@link McpFailure.kind}):
|
|
4
|
+
* ยท `connect_refused` โ a connect-phase errno (ECONNREFUSED / ENOTFOUND / EAI_AGAIN / EHOSTUNREACH /
|
|
5
|
+
* ENETUNREACH / UND_ERR_CONNECT_TIMEOUT): the request provably never left;
|
|
6
|
+
* ยท `connection_failed` โ any other errno / undici code on the cause chain (a reset or a timeout
|
|
7
|
+
* mid-exchange): the request may have run;
|
|
8
|
+
* ยท `connection_closed` โ the SDK reported the transport closed (`McpError` `ConnectionClosed`): in
|
|
9
|
+
* flight the fate is unknowable, and for a server already known dead the
|
|
10
|
+
* request was never attempted โ the two are told apart by `delivered`;
|
|
11
|
+
* ยท `http_status` โ the HTTP endpoint answered with a status instead of an MCP response
|
|
12
|
+
* (`httpStatus` carries the number: 401/403 = re-authorize, 5xx = down);
|
|
13
|
+
* ยท `not_mcp_response` โ the HTTP endpoint answered something that is not an MCP payload and no status;
|
|
14
|
+
* ยท `spawn_failed` โ a stdio server process could not be started (a missing/unexecutable command);
|
|
15
|
+
* ยท `timeout` โ the request timed out (the SDK's per-call timer, the total ceiling, or the
|
|
16
|
+
* idle watchdog โ `details` names which);
|
|
17
|
+
* ยท `protocol` โ the server ANSWERED with an MCP/JSON-RPC error (a version mismatch, a rejected
|
|
18
|
+
* capability, an ordinary `-32603`): the exchange happened;
|
|
19
|
+
* ยท `invalid_config` โ the declaration itself could not be dialed (a malformed `url`): a fault to
|
|
20
|
+
* fix in the spec, never an outage to wait out;
|
|
21
|
+
* ยท `unknown` โ the thrown value carried no structure this table reads.
|
|
22
|
+
*/
|
|
23
|
+
export declare const MCP_FAILURE_KINDS: readonly ["connect_refused", "connection_failed", "connection_closed", "http_status", "not_mcp_response", "spawn_failed", "timeout", "protocol", "invalid_config", "unknown"];
|
|
24
|
+
export type McpFailureKind = (typeof MCP_FAILURE_KINDS)[number];
|
|
25
|
+
/** The closed set of delivery verdicts ({@link McpFailure.delivered}). */
|
|
26
|
+
export declare const MCP_DELIVERY_VERDICTS: readonly ["yes", "no", "unknown"];
|
|
27
|
+
export type McpDelivered = (typeof MCP_DELIVERY_VERDICTS)[number];
|
|
28
|
+
/**
|
|
29
|
+
* The delivery verdict each KIND carries when the failure happened on an established connection
|
|
30
|
+
* (`phase: "request"`) โ the disposition table over {@link MCP_FAILURE_KINDS}. The classifier's rows
|
|
31
|
+
* read the thrown value's STRUCTURE and mint a class; what that class means for delivery is decided
|
|
32
|
+
* here, once, so no two rows can disagree about the same word and a new kind cannot arrive without
|
|
33
|
+
* saying what it means for delivery (the fence below reds `tsc` with the missing word spelled out).
|
|
34
|
+
*/
|
|
35
|
+
export declare const DELIVERED_BY_KIND: {
|
|
36
|
+
readonly connect_refused: "no";
|
|
37
|
+
readonly connection_failed: "unknown";
|
|
38
|
+
readonly connection_closed: "unknown";
|
|
39
|
+
readonly http_status: "unknown";
|
|
40
|
+
readonly not_mcp_response: "unknown";
|
|
41
|
+
readonly spawn_failed: "no";
|
|
42
|
+
readonly timeout: "unknown";
|
|
43
|
+
readonly protocol: "yes";
|
|
44
|
+
readonly invalid_config: "no";
|
|
45
|
+
readonly unknown: "unknown";
|
|
46
|
+
};
|
|
47
|
+
/** The fence over the delivery table: `never` while every kind has a verdict. */
|
|
48
|
+
export type DeliveryTableCoversEveryMcpFailureKind = AssertAllKeysHandled<Exclude<McpFailureKind, keyof typeof DELIVERED_BY_KIND>>;
|
|
49
|
+
/** Where the failure was judged: at the dial (connect + initial listing), where an errno on a stdio
|
|
50
|
+
* transport is the child process failing to spawn, or on a request over an established connection. */
|
|
51
|
+
export type McpFailureSite = {
|
|
52
|
+
phase: "connect";
|
|
53
|
+
transport: "stdio" | "http" | undefined;
|
|
54
|
+
} | {
|
|
55
|
+
phase: "request";
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The one MCP failure record. Both fields are always present; `httpStatus` rides only with
|
|
59
|
+
* `kind === "http_status"`.
|
|
60
|
+
*/
|
|
61
|
+
export interface McpFailure {
|
|
62
|
+
/**
|
|
63
|
+
* @contract mcp.failure.kind โ the failure's CLASS, one of {@link MCP_FAILURE_KINDS}, decided by
|
|
64
|
+
* {@link classifyMcpFailure} from the thrown value's structure alone (never from its message). It is
|
|
65
|
+
* the SAME word on every face: the per-server status's and the wiring manifest's `errorCode`, the
|
|
66
|
+
* thrown request error's `details.code`, and the `tool_end` frame's `errorCode`. Closed: a consumer
|
|
67
|
+
* switching on it is complete; a new class is a new word for consumers, never a re-meaning of one.
|
|
68
|
+
*/
|
|
69
|
+
kind: McpFailureKind;
|
|
70
|
+
/**
|
|
71
|
+
* @contract mcp.failure.delivered โ whether the request REACHED the server, as a first-class fact
|
|
72
|
+
* beside the class: `"yes"` = the server answered (a `protocol` rejection proves the exchange
|
|
73
|
+
* happened); `"no"` = provably never sent (a connect-phase errno, a spawn failure, a malformed
|
|
74
|
+
* declaration, a server already known dead โ safe to retry or abandon, nothing executed); `"unknown"`
|
|
75
|
+
* = the client stopped waiting or lost the pipe mid-exchange (a timeout, an in-flight close, a reset,
|
|
76
|
+
* an HTTP status from a gateway in front of the server) โ the request MAY have executed, so a
|
|
77
|
+
* write-capable tool's side effects must be verified before a retry. Two failures with the same
|
|
78
|
+
* `kind` can differ here (`connection_closed` in flight vs. against a dead server), which is why the
|
|
79
|
+
* verdict is its own field and not folded into the class. It is a function of the class and the SITE,
|
|
80
|
+
* never of the row that matched: {@link DELIVERED_BY_KIND} says what a class means on an established
|
|
81
|
+
* connection, and at the dial the verdict is `"no"` for every class but the two minted from an answer
|
|
82
|
+
* ({@link CONNECT_KEEPS_THE_TABLE_VERDICT}) โ a connect failure is a server whose tools never mounted,
|
|
83
|
+
* so no TOOL CALL of the caller's was dispatched, let alone executed. Read
|
|
84
|
+
* {@link CONNECT_KEEPS_THE_TABLE_VERDICT} for what a dial's `"no"` does not promise (an elicitation
|
|
85
|
+
* answered during the dial has already crossed the connection) and for the facts that would let the
|
|
86
|
+
* rule be narrower.
|
|
87
|
+
*/
|
|
88
|
+
delivered: McpDelivered;
|
|
89
|
+
/** The HTTP status the endpoint answered with; present iff `kind === "http_status"`. */
|
|
90
|
+
httpStatus?: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The first errno-shaped / undici-shaped `code` on an error's cause chain (`E[A-Z_]+`, `UND_ERR_*`,
|
|
94
|
+
* Node's `ERR_*`). `fetch` reports every network failure as a bare `TypeError: fetch failed` with the
|
|
95
|
+
* real cause nested underneath (and an AggregateError when a host resolved to several addresses), so
|
|
96
|
+
* the code is never on the thrown error itself. The E-prefixed alternative allows `_`: Node's
|
|
97
|
+
* DNS-temporary-failure errno is literally `EAI_AGAIN`.
|
|
98
|
+
*/
|
|
99
|
+
export declare function networkErrorCode(err: unknown, depth?: number): string | undefined;
|
|
100
|
+
/** What a row reads OFF the thrown value: the failure's class, and the status the `http_status` class
|
|
101
|
+
* carries. A row does NOT write the delivery verdict โ that follows from the kind and the SITE, in
|
|
102
|
+
* one place ({@link DELIVERED_BY_KIND} + {@link classifyMcpFailure}), so a row cannot spell it wrong. */
|
|
103
|
+
export type McpFailureClass = {
|
|
104
|
+
kind: McpFailureKind;
|
|
105
|
+
httpStatus?: number;
|
|
106
|
+
};
|
|
107
|
+
/** One row of the classifier: `when` reads structure only; `reads` names the class it saw. */
|
|
108
|
+
export interface McpFailureRule {
|
|
109
|
+
/** The row's name, for the discrimination pins (one pin per row) and for a reader of a verdict. */
|
|
110
|
+
readonly name: string;
|
|
111
|
+
readonly when: (err: unknown, at: McpFailureSite) => boolean;
|
|
112
|
+
readonly reads: (err: unknown) => McpFailureClass;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The ORDERED classifier. First match wins; the final row matches everything. The order is part of
|
|
116
|
+
* the contract โ the SDK's typed errors go first because they are an answer FROM the exchange (an
|
|
117
|
+
* McpError is never a transport failure, whatever its text says), then the HTTP transport's typed
|
|
118
|
+
* error, then the cause-chain codes from most specific (a configuration fault, a spawn) to least
|
|
119
|
+
* (a connect-phase errno, any other network code).
|
|
120
|
+
*/
|
|
121
|
+
export declare const FAILURE_CLASSIFIER: ReadonlyArray<McpFailureRule>;
|
|
122
|
+
/**
|
|
123
|
+
* Classify one MCP failure into {@link McpFailure}: the CLASS comes from the first matching row of
|
|
124
|
+
* {@link FAILURE_CLASSIFIER} (structure only), the VERDICT from {@link DELIVERED_BY_KIND} and the site
|
|
125
|
+
* โ one predicate for the dial ({@link CONNECT_KEEPS_THE_TABLE_VERDICT}), never a per-row spelling.
|
|
126
|
+
* Total: every input maps to a record.
|
|
127
|
+
*/
|
|
128
|
+
export declare function classifyMcpFailure(err: unknown, at: McpFailureSite): McpFailure;
|
|
129
|
+
/**
|
|
130
|
+
* What a transport-level HTTP failure can be said to be, in words for a model-facing line: the
|
|
131
|
+
* condition to name, and the delivery verdict. `undefined` for every failure that is NOT of the HTTP
|
|
132
|
+
* transport family (an McpError is an answer FROM the server; a bare error with no structure is
|
|
133
|
+
* `unknown` and gets no transport story). Shared with the A2A client leg, which faces the SAME `fetch`
|
|
134
|
+
* layer and owes the same outcome contract โ one classifier, so the two protocols cannot drift on what
|
|
135
|
+
* "the request never reached them" means.
|
|
136
|
+
*/
|
|
137
|
+
export interface HttpTransportFailure {
|
|
138
|
+
condition: string;
|
|
139
|
+
delivered: McpDelivered;
|
|
140
|
+
httpStatus?: number;
|
|
141
|
+
}
|
|
142
|
+
export declare function describeHttpTransportFailure(err: unknown): HttpTransportFailure | undefined;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
|
+
export const MCP_FAILURE_KINDS = [
|
|
4
|
+
"connect_refused",
|
|
5
|
+
"connection_failed",
|
|
6
|
+
"connection_closed",
|
|
7
|
+
"http_status",
|
|
8
|
+
"not_mcp_response",
|
|
9
|
+
"spawn_failed",
|
|
10
|
+
"timeout",
|
|
11
|
+
"protocol",
|
|
12
|
+
"invalid_config",
|
|
13
|
+
"unknown",
|
|
14
|
+
];
|
|
15
|
+
export const MCP_DELIVERY_VERDICTS = ["yes", "no", "unknown"];
|
|
16
|
+
export const DELIVERED_BY_KIND = {
|
|
17
|
+
connect_refused: "no",
|
|
18
|
+
connection_failed: "unknown",
|
|
19
|
+
connection_closed: "unknown",
|
|
20
|
+
http_status: "unknown",
|
|
21
|
+
not_mcp_response: "unknown",
|
|
22
|
+
spawn_failed: "no",
|
|
23
|
+
timeout: "unknown",
|
|
24
|
+
protocol: "yes",
|
|
25
|
+
invalid_config: "no",
|
|
26
|
+
unknown: "unknown",
|
|
27
|
+
};
|
|
28
|
+
const CONNECT_KEEPS_THE_TABLE_VERDICT = new Set(["protocol", "http_status"]);
|
|
29
|
+
const NETWORK_CODES_NEVER_DELIVERED = new Set([
|
|
30
|
+
"ECONNREFUSED",
|
|
31
|
+
"ENOTFOUND",
|
|
32
|
+
"EAI_AGAIN",
|
|
33
|
+
"EHOSTUNREACH",
|
|
34
|
+
"ENETUNREACH",
|
|
35
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
36
|
+
]);
|
|
37
|
+
export function networkErrorCode(err, depth = 0) {
|
|
38
|
+
if (depth > 5 || !(err instanceof Error))
|
|
39
|
+
return undefined;
|
|
40
|
+
const code = err.code;
|
|
41
|
+
if (typeof code === "string" && /^(?:E[A-Z_]+|UND_ERR_[A-Z_]+)$/.test(code))
|
|
42
|
+
return code;
|
|
43
|
+
if (err instanceof AggregateError) {
|
|
44
|
+
for (const inner of err.errors) {
|
|
45
|
+
const found = networkErrorCode(inner, depth + 1);
|
|
46
|
+
if (found !== undefined)
|
|
47
|
+
return found;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return networkErrorCode(err.cause, depth + 1);
|
|
51
|
+
}
|
|
52
|
+
const isNodeErrCode = (code) => code.startsWith("ERR_");
|
|
53
|
+
export const FAILURE_CLASSIFIER = [
|
|
54
|
+
{
|
|
55
|
+
name: "sdk connection closed",
|
|
56
|
+
when: (err) => err instanceof McpError && err.code === ErrorCode.ConnectionClosed,
|
|
57
|
+
reads: () => ({ kind: "connection_closed" }),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "sdk request timeout",
|
|
61
|
+
when: (err) => err instanceof McpError && err.code === ErrorCode.RequestTimeout,
|
|
62
|
+
reads: () => ({ kind: "timeout" }),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "server answered with a protocol error",
|
|
66
|
+
when: (err) => err instanceof McpError,
|
|
67
|
+
reads: () => ({ kind: "protocol" }),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "http endpoint answered a status",
|
|
71
|
+
when: (err) => err instanceof StreamableHTTPError && typeof err.code === "number" && err.code > 0,
|
|
72
|
+
reads: (err) => ({ kind: "http_status", httpStatus: err.code }),
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "http endpoint answered a non-mcp payload",
|
|
76
|
+
when: (err) => err instanceof StreamableHTTPError,
|
|
77
|
+
reads: () => ({ kind: "not_mcp_response" }),
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "malformed declaration",
|
|
81
|
+
when: (err, at) => at.phase === "connect" && err instanceof Error && err.code === "ERR_INVALID_URL",
|
|
82
|
+
reads: () => ({ kind: "invalid_config" }),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "node error code, not a network errno",
|
|
86
|
+
when: (err) => {
|
|
87
|
+
const code = networkErrorCode(err);
|
|
88
|
+
return code !== undefined && isNodeErrCode(code);
|
|
89
|
+
},
|
|
90
|
+
reads: () => ({ kind: "unknown" }),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "stdio spawn failed",
|
|
94
|
+
when: (err, at) => at.phase === "connect" && at.transport === "stdio" && networkErrorCode(err) !== undefined,
|
|
95
|
+
reads: () => ({ kind: "spawn_failed" }),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "connect-phase errno",
|
|
99
|
+
when: (err) => {
|
|
100
|
+
const code = networkErrorCode(err);
|
|
101
|
+
return code !== undefined && NETWORK_CODES_NEVER_DELIVERED.has(code);
|
|
102
|
+
},
|
|
103
|
+
reads: () => ({ kind: "connect_refused" }),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "network errno past the connect phase",
|
|
107
|
+
when: (err) => networkErrorCode(err) !== undefined,
|
|
108
|
+
reads: () => ({ kind: "connection_failed" }),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "no structure",
|
|
112
|
+
when: () => true,
|
|
113
|
+
reads: () => ({ kind: "unknown" }),
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
export function classifyMcpFailure(err, at) {
|
|
117
|
+
for (const rule of FAILURE_CLASSIFIER) {
|
|
118
|
+
if (!rule.when(err, at))
|
|
119
|
+
continue;
|
|
120
|
+
const read = rule.reads(err);
|
|
121
|
+
const dialled = at.phase === "connect" && !CONNECT_KEEPS_THE_TABLE_VERDICT.has(read.kind);
|
|
122
|
+
return { ...read, delivered: dialled ? "no" : DELIVERED_BY_KIND[read.kind] };
|
|
123
|
+
}
|
|
124
|
+
return { kind: "unknown", delivered: "unknown" };
|
|
125
|
+
}
|
|
126
|
+
export function describeHttpTransportFailure(err) {
|
|
127
|
+
const f = classifyMcpFailure(err, { phase: "request" });
|
|
128
|
+
switch (f.kind) {
|
|
129
|
+
case "http_status":
|
|
130
|
+
return { condition: `its HTTP endpoint answered ${f.httpStatus} instead of an MCP response`, delivered: f.delivered, httpStatus: f.httpStatus };
|
|
131
|
+
case "not_mcp_response":
|
|
132
|
+
return { condition: "its HTTP endpoint answered something that is not an MCP response", delivered: f.delivered };
|
|
133
|
+
case "connect_refused":
|
|
134
|
+
return { condition: `its HTTP endpoint could not be reached (${networkErrorCode(err)})`, delivered: f.delivered };
|
|
135
|
+
case "connection_failed":
|
|
136
|
+
return { condition: `the connection to its HTTP endpoint failed (${networkErrorCode(err)})`, delivered: f.delivered };
|
|
137
|
+
case "connection_closed":
|
|
138
|
+
case "spawn_failed":
|
|
139
|
+
case "timeout":
|
|
140
|
+
case "protocol":
|
|
141
|
+
case "invalid_config":
|
|
142
|
+
case "unknown":
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a deployment DECLARES an external tool server and what an elicitation round-trip looks like:
|
|
3
|
+
* `McpServerSpec` (stdio / HTTP / in-process MCP), `A2aServerSpec` (the agent-to-agent peer) and the
|
|
4
|
+
* elicit pair (`McpElicitRequest` / `McpElicitResponse` / `OnElicit`). Named `mcp-server-spec.ts`, not
|
|
5
|
+
* `mcp.ts`: `src/core/mcp.ts` is the CLIENT, and two files a letter apart would be a worse hazard than
|
|
6
|
+
* a longer name. Layer 0 vocabulary; `types.ts` re-exports every name below.
|
|
7
|
+
*/
|
|
8
|
+
import type { ToolContentOrigin, ToolEffect } from "./tool-spec.js";
|
|
9
|
+
/** Definition of an MCP server to connect to for the duration of one task, then discard. */
|
|
10
|
+
export interface McpServerSpec {
|
|
11
|
+
/** Stable name; tool names are namespaced as `<name>__<tool>` to avoid collisions. */
|
|
12
|
+
name: string;
|
|
13
|
+
/**
|
|
14
|
+
* Where this declaration CAME FROM, in the declaring side's own words (a shell's `plugin` / `user` /
|
|
15
|
+
* `project` / `local` config tiers, or anything else the deployment finds useful). Core reads
|
|
16
|
+
* nothing off it: it is echoed verbatim (single-line, length-bounded) onto the wiring manifest's
|
|
17
|
+
* per-server read face (`WiringManifest.mcp[].source`) so a consumer can group servers by their
|
|
18
|
+
* declaring tier without keeping a side table keyed on names. Optional; absent stays absent.
|
|
19
|
+
*/
|
|
20
|
+
source?: string;
|
|
21
|
+
transport: {
|
|
22
|
+
kind: "stdio";
|
|
23
|
+
command: string;
|
|
24
|
+
args?: string[];
|
|
25
|
+
env?: Record<string, string>;
|
|
26
|
+
} | {
|
|
27
|
+
kind: "http";
|
|
28
|
+
url: string;
|
|
29
|
+
/** Static headers sent on every request (e.g. an auth bearer for the MCP server itself). */
|
|
30
|
+
headers?: Record<string, string>;
|
|
31
|
+
/**
|
|
32
|
+
* Per-task **end-user principal** header (design/62). When set, the Runner injects the running task's
|
|
33
|
+
* authenticated {@link TaskSpec.principal} as this header (e.g. `"X-Agent-Principal"`) on every call to
|
|
34
|
+
* this server's tools, so the MCP server can enforce per-user RBAC. Absent {@link TaskSpec.principal}
|
|
35
|
+
* โ the header is **not** sent (the MCP server must default to deny/public, never admin). Overrides any
|
|
36
|
+
* same-named static `headers` entry. The principal is Runner-held and the model/worker can neither read
|
|
37
|
+
* nor set it (it is not a tool argument) โ see {@link TaskSpec.principal}.
|
|
38
|
+
*/
|
|
39
|
+
principalHeader?: string;
|
|
40
|
+
};
|
|
41
|
+
/** Optional allowlist of tool names to expose (others are dropped). ORTHOGONAL to
|
|
42
|
+
* {@link contentOrigin} and usefully paired with it (design/378 ยง2): the class declaration follows
|
|
43
|
+
* the server's roster, so a high-assurance deployment that wants a CLOSED tool set writes both โ
|
|
44
|
+
* a tool a later refresh adds then lands outside this list and is simply not mounted. Existing
|
|
45
|
+
* semantics, no new mechanism; the refresh receipt still names every added tool either way. */
|
|
46
|
+
allowTools?: string[];
|
|
47
|
+
/**
|
|
48
|
+
* design/99 ยงE23 โ opt in to INBOUND elicitation for THIS server: when `true` AND a {@link RunnerDeps.onElicit}
|
|
49
|
+
* seam is wired, core advertises the `elicitation` capability to this server and registers a handler, so the
|
|
50
|
+
* server may send an `elicitation/create` request to ask the END USER for input mid-tool-call. **Default OFF
|
|
51
|
+
* (fail-closed)**: never advertise unless explicitly wired, so a untrusted/over-eager server cannot pull a human
|
|
52
|
+
* into the loop. v1 is LIVE-ONLY โ `onElicit` answers synchronously on the open connection (no durable
|
|
53
|
+
* suspend; a durable/cross-replica elicitation needs the deferred E22 longer-lived connection).
|
|
54
|
+
*/
|
|
55
|
+
elicitation?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Caller-side per-tool safety-axis overrides, keyed by the server's REMOTE (un-namespaced) tool name. The
|
|
58
|
+
* CALLER is the trust root: unlike a server's self-declared annotations (which may only TIGHTEN), a caller
|
|
59
|
+
* override is AUTHORITATIVE and may LOWER an effect (vouch a tool is `read`/`idempotent`) as well as raise it
|
|
60
|
+
* (`egress` / irreversible). This is the ONLY trusted way to drop an MCP tool below the fail-closed `write`
|
|
61
|
+
* default (design F). Folds over the server hints in prepare-task (caller > server hint > fail-closed write).
|
|
62
|
+
*
|
|
63
|
+
* A DIFFERENT AXIS from {@link contentOrigin}: this one is about what a call DOES (repeat-safety,
|
|
64
|
+
* blast radius, reversibility โ it feeds the approval gate); the content class is about what a call
|
|
65
|
+
* BRINGS BACK (memory-write governance, no gate/policy/roster effect). Neither implies the other โ
|
|
66
|
+
* a read-only tool can return third-party text, and a deployment's own writer brings back nothing
|
|
67
|
+
* external โ so vouching on one axis never quietly vouches on the other.
|
|
68
|
+
*/
|
|
69
|
+
toolAxes?: Record<string, {
|
|
70
|
+
effect?: ToolEffect;
|
|
71
|
+
egress?: boolean;
|
|
72
|
+
irreversibility?: "always" | "never";
|
|
73
|
+
}>;
|
|
74
|
+
/**
|
|
75
|
+
* design/378 โ declare {@link ToolSpec.contentOrigin} on behalf of THIS SERVER'S ENTIRE TOOL SET
|
|
76
|
+
* (tools a mid-task refresh adds included), with the same authority and the same responsibility a
|
|
77
|
+
* directly-mounted host tool's own declaration carries.
|
|
78
|
+
*
|
|
79
|
+
* WHY THE SEAT EXISTS. Without it the class keys on the MOUNT SHAPE rather than on lineage: a tool
|
|
80
|
+
* the deployment wrote and runs itself is structurally `"external"` the moment it arrives over the
|
|
81
|
+
* MCP protocol namespace, so every call marks the session's memory externally exposed. The
|
|
82
|
+
* per-name channels cannot express the fact either โ {@link import("./memory.js").MemorySpecInput.trustedTools}
|
|
83
|
+
* is a per-REQUEST allowlist keyed on the MINTED name (the host would have to predict the charset
|
|
84
|
+
* normalization) whose own definition is "the exception channel for UNDECLARED tools", and it says
|
|
85
|
+
* nothing about tools the server adds later.
|
|
86
|
+
*
|
|
87
|
+
* THIS IS A TRUST DECLARATION, not a routing hint. Use it only for servers inside the deployment's
|
|
88
|
+
* trust boundary โ a process, socket or service the deployment itself runs. Declaring a THIRD-PARTY
|
|
89
|
+
* server means treating its output as content the deployment wrote: the class names are
|
|
90
|
+
* BOUNDARY-relative, never topological, so neither the transport kind nor the address is evidence
|
|
91
|
+
* of lineage (a stdio child can be an untrusted package; a loopback URL can be your own service) and
|
|
92
|
+
* core deliberately does not gate on either. The declaration covers THE PEER THIS ENTRY CONNECTS TO
|
|
93
|
+
* โ authenticating that peer (socket permissions, credentials) is the host's mounting duty.
|
|
94
|
+
*
|
|
95
|
+
* SEMANTICS. `"local"` โ invocations no longer mark this session's memory; `"execution"` โ the same,
|
|
96
|
+
* except that {@link import("./memory.js").MemorySpecInput.execIsExternalContent} can still upgrade
|
|
97
|
+
* the class for a strict deployment (which is why this seat takes the three-value vocabulary and not
|
|
98
|
+
* a single "mine" flag โ an execution-shaped tool mounted over MCP must stay inside that knob's
|
|
99
|
+
* reach); `"external"` is an explicit PIN, and pins are not no-ops โ a declaration beats the
|
|
100
|
+
* `trustedTools` allowlist, so writing it forecloses the per-name exemption for this server's tools.
|
|
101
|
+
* ABSENT โ the pre-378 behavior byte for byte: the protocol namespace classifies the tools
|
|
102
|
+
* `"external"` (fail-closed). A value outside the vocabulary is refused at the preparation door
|
|
103
|
+
* (`config.mcp_content_class`), never folded to a class.
|
|
104
|
+
*
|
|
105
|
+
* COVERAGE, stated honestly: the class rides this server's own mounted tools. The cross-server
|
|
106
|
+
* resource faces (ListMcpResourcesTool / ReadMcpResourceTool / ReadMcpResourceDirTool) aggregate
|
|
107
|
+
* over every connected server in one call, so they stay `"external"` and still mark โ over-marking,
|
|
108
|
+
* the safe direction. A delegated child's pool is a separate static declaration surface
|
|
109
|
+
* ({@link ToolSpec.agentToolPool} entries carry their own `contentOrigin`): a deployment handing
|
|
110
|
+
* this server's tools to children mirrors the value there, and not mirroring it over-marks.
|
|
111
|
+
*
|
|
112
|
+
* TRUST SOURCE โ a DEPLOYMENT-plane key. It redefines where the trust boundary runs, which puts it
|
|
113
|
+
* on the same authority plane as {@link RunnerDeps} wiring, not on the request plane. Core sees one
|
|
114
|
+
* `TaskSpec` and cannot tell a deployment-baseline entry from one a request supplied, so any
|
|
115
|
+
* assembly layer that accepts REQUEST-side MCP entries must reject or strip this key from them:
|
|
116
|
+
* "allowed to mount a server" is not "allowed to redefine the deployment's trust boundary", and a
|
|
117
|
+
* caller-supplied `"local"` would otherwise be self-authorization around the session mark. A
|
|
118
|
+
* single-tenant superuser surface (a host reading its own `--mcp-config` file) IS the deployment
|
|
119
|
+
* plane and needs no such gate.
|
|
120
|
+
*/
|
|
121
|
+
contentOrigin?: ToolContentOrigin;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Definition of one A2A (agent-to-agent protocol) PEER to talk to for the duration of one task.
|
|
125
|
+
*
|
|
126
|
+
* The peer is a remote AGENT, not a data source: it acts on its own side. That single fact sets the
|
|
127
|
+
* defaults โ every skill this peer advertises mounts with `egress: true` and `effect: "write"` (ruled
|
|
128
|
+
* 2026-08-03), so it registers the approval gate and is never auto-allowed on a no-policy deployment,
|
|
129
|
+
* and its replies reach the model inside an untrusted-data fence. {@link toolAxes} is the only trusted
|
|
130
|
+
* way down from those defaults (design F: the caller is the trust root).
|
|
131
|
+
*
|
|
132
|
+
* Skills mount as `a2a__<peer>__<skill>` (the protocol table's second namespace).
|
|
133
|
+
*/
|
|
134
|
+
export interface A2aServerSpec {
|
|
135
|
+
/** Stable local name for this peer; it becomes the namespace segment of every mounted tool name. */
|
|
136
|
+
name: string;
|
|
137
|
+
/** The peer's service URL. Doubles as the origin the well-known agent-card paths resolve against, and
|
|
138
|
+
* as the JSON-RPC endpoint when the card does not name a different one. */
|
|
139
|
+
url: string;
|
|
140
|
+
/** Explicit agent-card location, when the peer does not publish at a well-known path. Set โ it is used
|
|
141
|
+
* ALONE (no well-known probing behind the operator's back). */
|
|
142
|
+
cardUrl?: string;
|
|
143
|
+
/** Static headers sent on every request to this peer (e.g. an auth bearer). */
|
|
144
|
+
headers?: Record<string, string>;
|
|
145
|
+
/**
|
|
146
|
+
* Per-task **end-user principal** header (design/62), same contract as the MCP side: when set, the
|
|
147
|
+
* Runner injects the running task's authenticated {@link TaskSpec.principal} under this header name on
|
|
148
|
+
* every call to this peer, so the peer can enforce per-user authorization. Absent principal โ the
|
|
149
|
+
* header is not sent (the peer must then default to deny/public, never admin). Overrides any same-named
|
|
150
|
+
* static entry in {@link headers}. The principal is Runner-held: the model can neither read nor set it.
|
|
151
|
+
*/
|
|
152
|
+
principalHeader?: string;
|
|
153
|
+
/** Optional allowlist of skill ids to expose (others are not mounted). */
|
|
154
|
+
allowSkills?: string[];
|
|
155
|
+
/**
|
|
156
|
+
* Caller-side per-skill safety-axis overrides, keyed by the peer's REMOTE skill id. The CALLER is the
|
|
157
|
+
* trust root, so this may LOWER the fail-closed default (`egress:true` + `effect:"write"`) as well as
|
|
158
|
+
* raise it โ e.g. `{ egress: false, effect: "read" }` vouches that a skill only reads. Note that
|
|
159
|
+
* lowering `effect` WITHOUT clearing `egress` is a contradiction prepare-task refuses by design (an
|
|
160
|
+
* external write must be repeat-unsafe).
|
|
161
|
+
*/
|
|
162
|
+
toolAxes?: Record<string, {
|
|
163
|
+
effect?: ToolEffect;
|
|
164
|
+
egress?: boolean;
|
|
165
|
+
irreversibility?: "always" | "never";
|
|
166
|
+
}>;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* design/99 ยงE23 โ an INBOUND MCP elicitation request (a connected server asking the END USER for input
|
|
170
|
+
* mid-tool-call). Handed to the deployment's {@link RunnerDeps.onElicit} seam. **UNTRUSTED**: `message` /
|
|
171
|
+
* `requestedSchema` / `url` are SERVER-controlled โ core never interprets `requestedSchema`; a consumer that
|
|
172
|
+
* shows `message` to a human MUST fence it (`delimitUntrusted`).
|
|
173
|
+
*/
|
|
174
|
+
export interface McpElicitRequest {
|
|
175
|
+
/** The {@link McpServerSpec.name} of the server that issued the request. */
|
|
176
|
+
server: string;
|
|
177
|
+
/** The server's human-facing prompt (UNTRUSTED โ fence before display). */
|
|
178
|
+
message: string;
|
|
179
|
+
/** Form mode: the server's requested input schema (a flat object of scalars). OPAQUE to core. */
|
|
180
|
+
requestedSchema?: unknown;
|
|
181
|
+
/**
|
|
182
|
+
* URL mode: a server-controlled URL the user is asked to visit (UNTRUSTED โ a phishing/social-engineering
|
|
183
|
+
* surface; a consumer MUST scrutinize/fence it before showing it). **Inactive**: core advertises FORM mode
|
|
184
|
+
* only, so the SDK rejects url-mode requests before this seam โ the field is never populated today.
|
|
185
|
+
*
|
|
186
|
+
* Where url mode is heading (MCP revision `2026-07-28`), because the earlier note here ("reserved for a
|
|
187
|
+
* future url-mode opt-in") pointed at machinery that no longer exists: the revision REMOVES the
|
|
188
|
+
* `2025-11-25` url-mode correlator (`elicitationId`) and the `notifications/elicitation/complete`
|
|
189
|
+
* notification that went with it. A url interaction is instead carried by the revision's multi-round-trip
|
|
190
|
+
* request flow โ the server answers with an input-required result, the client RETRIES the original
|
|
191
|
+
* request, and the server correlates across retries through its OWN opaque request state rather than a
|
|
192
|
+
* client-visible id. The one thing both shapes agree on is the server-supplied URL itself, which is what
|
|
193
|
+
* this field holds. So the field stays; anything that would have hung off the removed correlator must not
|
|
194
|
+
* be added here without re-reading the protocol revision core actually speaks.
|
|
195
|
+
*/
|
|
196
|
+
url?: string;
|
|
197
|
+
/** The running task's authenticated end-user (design/62), if any โ for routing the prompt to the right human. */
|
|
198
|
+
principal?: string;
|
|
199
|
+
}
|
|
200
|
+
/** design/99 ยงE23 โ the deployment's answer to an {@link McpElicitRequest} (returned to the MCP server). */
|
|
201
|
+
export interface McpElicitResponse {
|
|
202
|
+
action: "accept" | "decline" | "cancel";
|
|
203
|
+
/** Submitted values (accept only); core passes them through opaquely, never validated against the schema. */
|
|
204
|
+
content?: Record<string, string | number | boolean | string[]>;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* design/99 ยงE23 โ the LIVE-ONLY inbound-elicitation seam. A deployment provides it (e.g. the sema shell
|
|
208
|
+
* prompts the user at the terminal) and returns the answer SYNCHRONOUSLY; core replies to the server on the
|
|
209
|
+
* still-open connection. Absent โ no server is advertised the elicitation capability (fail-closed). Not a
|
|
210
|
+
* durable suspend โ a crash/replica-change loses an in-flight elicitation (acceptable for an interactive shell).
|
|
211
|
+
*
|
|
212
|
+
* ๐ด THROTTLING IS THE DEPLOYMENT'S JOB: once a server is opted in, it may issue elicitations at any frequency
|
|
213
|
+
* (one per tool call, or many) โ core imposes NO rate/count limit (an elicitation is not a tool call and does
|
|
214
|
+
* not ride the policy gate). `onElicit` is the choke point: throttle, aggregate, or `decline`/`cancel` an
|
|
215
|
+
* over-eager or untrusted server here (the request's `AbortSignal` is forwarded so a deadline can cancel it).
|
|
216
|
+
*/
|
|
217
|
+
export type OnElicit = (req: McpElicitRequest, signal?: AbortSignal) => Promise<McpElicitResponse>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/core/mcp.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ProtocolId } from "./protocol-table.js";
|
|
2
|
+
import { type McpDelivered, type McpFailureKind } from "./mcp-failure.js";
|
|
2
3
|
import type { AgentTool } from "../internal/harness-types.js";
|
|
3
4
|
import type { ImageContent, TextContent } from "../internal/llm.js";
|
|
4
5
|
import { type McpImageResizer } from "./image-downsample.js";
|
|
@@ -258,11 +259,13 @@ export interface McpServerStatus {
|
|
|
258
259
|
};
|
|
259
260
|
toolNames?: string[];
|
|
260
261
|
error?: string;
|
|
261
|
-
/** Present iff `status === "failed"` โ the failure
|
|
262
|
-
*
|
|
263
|
-
* `
|
|
264
|
-
*
|
|
265
|
-
errorCode?:
|
|
262
|
+
/** Present iff `status === "failed"` โ the connect-time failure record, spread flat: {@link McpFailure.kind}
|
|
263
|
+
* as `errorCode`, {@link McpFailure.delivered} as `delivered`, and `httpStatus` when the kind is
|
|
264
|
+
* `http_status`. A consumer keys an action off THESE (a `http_status` 401 is a re-authorization door,
|
|
265
|
+
* a `spawn_failed` is a missing binary) instead of parsing the neutralized `error` text. */
|
|
266
|
+
errorCode?: McpFailureKind;
|
|
267
|
+
delivered?: McpDelivered;
|
|
268
|
+
httpStatus?: number;
|
|
266
269
|
/**
|
|
267
270
|
* RB-437-a โ set to `true` once core OBSERVES this server's transport close during the task; absent
|
|
268
271
|
* otherwise (never `false`: absence means "no close was observed", which is not the same as "alive").
|
|
@@ -380,78 +383,6 @@ export declare function describeMcpSpecErrorCode(code: unknown): string | undefi
|
|
|
380
383
|
* ours, and is left alone). Exported for unit testing; not part of the package index.
|
|
381
384
|
*/
|
|
382
385
|
export declare function collapseMcpErrorPrefix(message: string): string;
|
|
383
|
-
/** The first errno-shaped `code` on an error's cause chain. `fetch` reports every network failure as a
|
|
384
|
-
* bare `TypeError: fetch failed` with the real cause nested underneath (and an AggregateError when a
|
|
385
|
-
* host resolved to several addresses), so the code is never on the thrown error itself.
|
|
386
|
-
* Exported for unit testing (RB-449 mcp-1); not part of the package index (src/index.ts re-exports are
|
|
387
|
-
* explicit and do not name it โ see the sibling `collapseMcpErrorPrefix`/`classifyDirReadInvalidParams`
|
|
388
|
-
* comments for the same "exported for tests only" convention). */
|
|
389
|
-
export declare function networkErrorCode(err: unknown, depth?: number): string | undefined;
|
|
390
|
-
/** What {@link describeHttpTransportFailure} could establish about a transport-level HTTP failure:
|
|
391
|
-
* the condition to name in the model-facing copy, whether the request can be said NOT to have reached
|
|
392
|
-
* the remote, and the HTTP status when the endpoint answered with one. */
|
|
393
|
-
export interface HttpTransportFailure {
|
|
394
|
-
condition: string;
|
|
395
|
-
/** `"no"` = provably never delivered (a connect-phase errno); `"unknown"` = the request may have run. */
|
|
396
|
-
delivered: "no" | "unknown";
|
|
397
|
-
httpStatus?: number;
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* RB-437-b โ a streamable-HTTP failure that never became an MCP exchange at all: the POST failed at the
|
|
401
|
-
* network layer (endpoint gone / unresolvable), or the endpoint answered with something that is not an
|
|
402
|
-
* MCP response. The SDK surfaces the first as `fetch`'s bare `TypeError: fetch failed` and the second as
|
|
403
|
-
* a `StreamableHTTPError`; NEITHER carries the server name, the MCP context or an outcome โ while the
|
|
404
|
-
* stdio path for the same event (the server went away) has all three. Returns the condition to name and
|
|
405
|
-
* whether the request can be said not to have reached the server, or `undefined` when the failure is not
|
|
406
|
-
* of this class (an McpError is an answer FROM the server: the exchange happened, so it is never one).
|
|
407
|
-
*
|
|
408
|
-
* Exported (not on the package index) for the A2A client leg, which faces the SAME `fetch` layer and owes
|
|
409
|
-
* the same outcome contract โ one classifier, so the two protocols cannot drift on what "the request never
|
|
410
|
-
* reached them" means.
|
|
411
|
-
*/
|
|
412
|
-
export declare function describeHttpTransportFailure(err: unknown): HttpTransportFailure | undefined;
|
|
413
|
-
/**
|
|
414
|
-
* The closed vocabulary of MCP connect-failure classes a per-server status record may carry
|
|
415
|
-
* ({@link McpServerStatus.errorCode}, relayed onto the wiring manifest's `mcp[]` read face). The
|
|
416
|
-
* transport family is named by the failure's SHAPE wherever the SDK exposes one (error class, JSON-RPC
|
|
417
|
-
* code, errno on the cause chain, HTTP status), so a consumer can map a class to an action without
|
|
418
|
-
* parsing the neutralized error string. Two words are the exception and are text-derived, because
|
|
419
|
-
* undici and the SDK surface those two conditions as bare messages with no code: `network` (a
|
|
420
|
-
* `TypeError` whose message matches `fetch failed|terminated|network` and carries no errno) and the
|
|
421
|
-
* non-`McpError` arm of `connection_closed` (a message matching `not connected|connection closed`).
|
|
422
|
-
* A wording change in those libraries moves such a failure to `unknown`, never to a wrong word; the
|
|
423
|
-
* classifier table pins both regexes. An HTTP endpoint that answered with a status is
|
|
424
|
-
* reported in the `http_<status>` form (`http_401` = the endpoint wants (re-)authorization, `http_5xx` =
|
|
425
|
-
* the endpoint is down) โ a template member of the type beside these fixed words. Additive-only: a new
|
|
426
|
-
* word here is a new arm for consumers, never a re-meaning of an existing one.
|
|
427
|
-
* ยท `connect_refused` โ a connect-phase errno (ECONNREFUSED / ENOTFOUND / โฆ): the request provably never left;
|
|
428
|
-
* ยท `connection_failed` โ a network errno past the connect phase (reset / timed out mid-exchange);
|
|
429
|
-
* ยท `network` โ the HTTP request failed at the network layer with no errno on the cause chain;
|
|
430
|
-
* ยท `not_mcp_response` โ the HTTP endpoint answered something that is not an MCP payload (no status to name);
|
|
431
|
-
* ยท `spawn_failed` โ a stdio server process could not be started (a missing/unexecutable command);
|
|
432
|
-
* ยท `connection_closed` โ the transport closed during the handshake (the server process exited, the stream ended);
|
|
433
|
-
* ยท `timeout` โ the handshake's request timed out;
|
|
434
|
-
* ยท `protocol` โ the server answered the handshake with an MCP/JSON-RPC error (a version mismatch, a
|
|
435
|
-
* rejected capability, โฆ) โ `error` carries the named condition;
|
|
436
|
-
* ยท `invalid_config` โ the declaration itself could not be dialed (a malformed `url`): a configuration
|
|
437
|
-
* fault to fix in the spec, never an outage to wait out;
|
|
438
|
-
* ยท `unknown` โ none of the above could be established from the thrown value.
|
|
439
|
-
*/
|
|
440
|
-
export declare const MCP_FAILURE_CODES: readonly ["connect_refused", "connection_failed", "network", "not_mcp_response", "spawn_failed", "connection_closed", "timeout", "protocol", "invalid_config", "unknown"];
|
|
441
|
-
export type McpFailureCode = (typeof MCP_FAILURE_CODES)[number] | `http_${number}`;
|
|
442
|
-
/**
|
|
443
|
-
* Classify one connect-time failure into {@link McpFailureCode}. Reads the thrown value's SHAPE
|
|
444
|
-
* (error class, JSON-RPC code, errno on the cause chain, HTTP status) first; only the two
|
|
445
|
-
* message-shaped conditions named on {@link MCP_FAILURE_CODES} (`network`, the non-`McpError`
|
|
446
|
-
* `connection_closed`) are decided by a fixed regex over the message, and no other word can be chosen
|
|
447
|
-
* by message text โ a remote's own wording can at most leave a failure at `unknown`.
|
|
448
|
-
* `transport` disambiguates the errno family: on a stdio server an errno is the child
|
|
449
|
-
* process failing to spawn, on an HTTP server it is the socket; a caller that could not establish the
|
|
450
|
-
* kind passes `undefined` and the errno family reads as the transport-neutral `connection_failed`.
|
|
451
|
-
* Total: every input maps to a word, and `unknown` is the honest word for a value this table cannot
|
|
452
|
-
* read (a caller must not receive a guess).
|
|
453
|
-
*/
|
|
454
|
-
export declare function mcpFailureCodeOf(err: unknown, transport: "stdio" | "http" | undefined): McpFailureCode;
|
|
455
386
|
export { normalizeNameSegment as normalizeMcpName, clampNameSegment } from "./protocol-naming.js";
|
|
456
387
|
export * from "./image-downsample.js";
|
|
457
388
|
/**
|
|
@@ -483,6 +414,19 @@ export interface ProtocolHttpAuth {
|
|
|
483
414
|
* turns it into a skipped peer instead of a failed task.
|
|
484
415
|
*/
|
|
485
416
|
export declare function resolveProtocolHttpHeaders(protocol: ProtocolId, t: ProtocolHttpAuth, principal?: string): Record<string, string> | undefined;
|
|
417
|
+
/** `kind` is the dial's ONE read of `spec.transport.kind` (taken by the caller beside the dial and threaded
|
|
418
|
+
* here, to the idle bucket and to the failure classifier alike): a nested `transport` object is the
|
|
419
|
+
* caller's, and a getter that answered differently to two readers would otherwise connect over one
|
|
420
|
+
* transport and be judged under another. An unknown kind takes the non-stdio branch, as it always did. */
|
|
421
|
+
/**
|
|
422
|
+
* The MCP HTTP transport's `fetch`: every request the transport makes โ the POST, the SSE GET (initial and
|
|
423
|
+
* resumed), the DELETE, the auth flows โ goes to the endpoint the declaration NAMED and follows no redirect.
|
|
424
|
+
* `fetch`'s default follows a 3xx silently and then reports only the last hop, which is the one shape under
|
|
425
|
+
* which the delivery verdict would lie (a connect refusal on the redirect target reading "never sent" while
|
|
426
|
+
* the declared endpoint had received the POST); a 3xx now surfaces as the SDK's HTTP-status error, and a
|
|
427
|
+
* body or a credential never travels to a host the declaration did not name. Exported for its pin only.
|
|
428
|
+
*/
|
|
429
|
+
export declare const declaredEndpointFetch: typeof fetch;
|
|
486
430
|
interface McpContentItem {
|
|
487
431
|
type: string;
|
|
488
432
|
text?: string;
|