@openrouter/agent 0.9.0 → 0.11.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/README.md +267 -2
- package/THIRD_PARTY_NOTICES.md +25 -0
- package/esm/index.d.ts +4 -3
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -1
- package/esm/index.js.map +1 -1
- package/esm/inner-loop/call-model.d.ts.map +1 -1
- package/esm/inner-loop/call-model.js +26 -7
- package/esm/inner-loop/call-model.js.map +1 -1
- package/esm/lib/agent-tool.d.ts +1 -1
- package/esm/lib/agent-tool.d.ts.map +1 -1
- package/esm/lib/agent-tool.js.map +1 -1
- package/esm/lib/async-params.d.ts +23 -1
- package/esm/lib/async-params.d.ts.map +1 -1
- package/esm/lib/async-params.js +26 -1
- package/esm/lib/async-params.js.map +1 -1
- package/esm/lib/conversation-state.d.ts +1 -1
- package/esm/lib/conversation-state.d.ts.map +1 -1
- package/esm/lib/conversation-state.js +48 -13
- package/esm/lib/conversation-state.js.map +1 -1
- package/esm/lib/hooks-schemas.d.ts +3 -7
- package/esm/lib/hooks-schemas.d.ts.map +1 -1
- package/esm/lib/hooks-schemas.js.map +1 -1
- package/esm/lib/model-result.d.ts +32 -4
- package/esm/lib/model-result.d.ts.map +1 -1
- package/esm/lib/model-result.js +444 -128
- package/esm/lib/model-result.js.map +1 -1
- package/esm/lib/next-turn-params.d.ts.map +1 -1
- package/esm/lib/next-turn-params.js +3 -1
- package/esm/lib/next-turn-params.js.map +1 -1
- package/esm/lib/next-turn-params.test.js +72 -0
- package/esm/lib/next-turn-params.test.js.map +1 -1
- package/esm/lib/reusable-stream.d.ts +30 -3
- package/esm/lib/reusable-stream.d.ts.map +1 -1
- package/esm/lib/reusable-stream.js +127 -32
- package/esm/lib/reusable-stream.js.map +1 -1
- package/esm/lib/stream-transformers.d.ts +1 -1
- package/esm/lib/stream-transformers.d.ts.map +1 -1
- package/esm/lib/stream-transformers.js.map +1 -1
- package/esm/lib/tool-event-broadcaster.d.ts +18 -5
- package/esm/lib/tool-event-broadcaster.d.ts.map +1 -1
- package/esm/lib/tool-event-broadcaster.js +90 -19
- package/esm/lib/tool-event-broadcaster.js.map +1 -1
- package/esm/lib/tool-set-types.d.ts +298 -0
- package/esm/lib/tool-set-types.d.ts.map +1 -0
- package/esm/lib/tool-set-types.js +2 -0
- package/esm/lib/tool-set-types.js.map +1 -0
- package/esm/lib/tool-set.d.ts +157 -0
- package/esm/lib/tool-set.d.ts.map +1 -0
- package/esm/lib/tool-set.js +473 -0
- package/esm/lib/tool-set.js.map +1 -0
- package/esm/lib/tool-types.d.ts +203 -29
- package/esm/lib/tool-types.d.ts.map +1 -1
- package/esm/lib/tool-types.js.map +1 -1
- package/esm/lib/tool.d.ts +47 -21
- package/esm/lib/tool.d.ts.map +1 -1
- package/esm/lib/tool.js +13 -3
- package/esm/lib/tool.js.map +1 -1
- package/esm/lib/turn-context.d.ts +1 -1
- package/esm/lib/turn-context.d.ts.map +1 -1
- package/esm/lib/turn-context.js +1 -2
- package/esm/lib/turn-context.js.map +1 -1
- package/esm/mcp/auth/auth-resolver.d.ts +14 -0
- package/esm/mcp/auth/auth-resolver.d.ts.map +1 -0
- package/esm/mcp/auth/auth-resolver.js +27 -0
- package/esm/mcp/auth/auth-resolver.js.map +1 -0
- package/esm/mcp/auth/auth-types.d.ts +33 -0
- package/esm/mcp/auth/auth-types.d.ts.map +1 -0
- package/esm/mcp/auth/auth-types.js +4 -0
- package/esm/mcp/auth/auth-types.js.map +1 -0
- package/esm/mcp/build-tools.d.ts +29 -0
- package/esm/mcp/build-tools.d.ts.map +1 -0
- package/esm/mcp/build-tools.js +80 -0
- package/esm/mcp/build-tools.js.map +1 -0
- package/esm/mcp/cache/cache-store.d.ts +24 -0
- package/esm/mcp/cache/cache-store.d.ts.map +1 -0
- package/esm/mcp/cache/cache-store.js +20 -0
- package/esm/mcp/cache/cache-store.js.map +1 -0
- package/esm/mcp/cache/cache-types.d.ts +71 -0
- package/esm/mcp/cache/cache-types.d.ts.map +1 -0
- package/esm/mcp/cache/cache-types.js +49 -0
- package/esm/mcp/cache/cache-types.js.map +1 -0
- package/esm/mcp/cache/serialize.d.ts +25 -0
- package/esm/mcp/cache/serialize.d.ts.map +1 -0
- package/esm/mcp/cache/serialize.js +96 -0
- package/esm/mcp/cache/serialize.js.map +1 -0
- package/esm/mcp/close-quietly.d.ts +24 -0
- package/esm/mcp/close-quietly.d.ts.map +1 -0
- package/esm/mcp/close-quietly.js +30 -0
- package/esm/mcp/close-quietly.js.map +1 -0
- package/esm/mcp/create-mcp-tools.d.ts +13 -0
- package/esm/mcp/create-mcp-tools.d.ts.map +1 -0
- package/esm/mcp/create-mcp-tools.js +87 -0
- package/esm/mcp/create-mcp-tools.js.map +1 -0
- package/esm/mcp/elicitation.d.ts +11 -0
- package/esm/mcp/elicitation.d.ts.map +1 -0
- package/esm/mcp/elicitation.js +51 -0
- package/esm/mcp/elicitation.js.map +1 -0
- package/esm/mcp/errors.d.ts +91 -0
- package/esm/mcp/errors.d.ts.map +1 -0
- package/esm/mcp/errors.js +91 -0
- package/esm/mcp/errors.js.map +1 -0
- package/esm/mcp/handle.d.ts +66 -0
- package/esm/mcp/handle.d.ts.map +1 -0
- package/esm/mcp/handle.js +323 -0
- package/esm/mcp/handle.js.map +1 -0
- package/esm/mcp/index.d.ts +13 -0
- package/esm/mcp/index.d.ts.map +1 -0
- package/esm/mcp/index.js +11 -0
- package/esm/mcp/index.js.map +1 -0
- package/esm/mcp/mcp-connection.d.ts +150 -0
- package/esm/mcp/mcp-connection.d.ts.map +1 -0
- package/esm/mcp/mcp-connection.js +478 -0
- package/esm/mcp/mcp-connection.js.map +1 -0
- package/esm/mcp/mcp-sdk.d.ts +10 -0
- package/esm/mcp/mcp-sdk.d.ts.map +1 -0
- package/esm/mcp/mcp-sdk.js +37 -0
- package/esm/mcp/mcp-sdk.js.map +1 -0
- package/esm/mcp/rehydrate.d.ts +76 -0
- package/esm/mcp/rehydrate.d.ts.map +1 -0
- package/esm/mcp/rehydrate.js +505 -0
- package/esm/mcp/rehydrate.js.map +1 -0
- package/esm/mcp/resource-tools.d.ts +17 -0
- package/esm/mcp/resource-tools.d.ts.map +1 -0
- package/esm/mcp/resource-tools.js +155 -0
- package/esm/mcp/resource-tools.js.map +1 -0
- package/esm/mcp/result-mapper.d.ts +19 -0
- package/esm/mcp/result-mapper.d.ts.map +1 -0
- package/esm/mcp/result-mapper.js +40 -0
- package/esm/mcp/result-mapper.js.map +1 -0
- package/esm/mcp/schema/json-schema-guards.d.ts +14 -0
- package/esm/mcp/schema/json-schema-guards.d.ts.map +1 -0
- package/esm/mcp/schema/json-schema-guards.js +19 -0
- package/esm/mcp/schema/json-schema-guards.js.map +1 -0
- package/esm/mcp/schema/json-schema-to-zod.d.ts +20 -0
- package/esm/mcp/schema/json-schema-to-zod.d.ts.map +1 -0
- package/esm/mcp/schema/json-schema-to-zod.js +72 -0
- package/esm/mcp/schema/json-schema-to-zod.js.map +1 -0
- package/esm/mcp/tool-wrapper.d.ts +41 -0
- package/esm/mcp/tool-wrapper.d.ts.map +1 -0
- package/esm/mcp/tool-wrapper.js +131 -0
- package/esm/mcp/tool-wrapper.js.map +1 -0
- package/esm/mcp/transport-types.d.ts +45 -0
- package/esm/mcp/transport-types.d.ts.map +1 -0
- package/esm/mcp/transport-types.js +2 -0
- package/esm/mcp/transport-types.js.map +1 -0
- package/esm/mcp/types.d.ts +119 -0
- package/esm/mcp/types.d.ts.map +1 -0
- package/esm/mcp/types.js +2 -0
- package/esm/mcp/types.js.map +1 -0
- package/esm/mcp/version.d.ts +3 -0
- package/esm/mcp/version.d.ts.map +1 -0
- package/esm/mcp/version.js +5 -0
- package/esm/mcp/version.js.map +1 -0
- package/package.json +48 -7
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { Client } from '@modelcontextprotocol/client';
|
|
2
|
+
import type { MCPAuth } from './auth/auth-types.js';
|
|
3
|
+
import type { MCPSdk } from './mcp-sdk.js';
|
|
4
|
+
import type { MCPProtocolNegotiation, MCPTransportKind } from './transport-types.js';
|
|
5
|
+
import type { ElicitationHandler } from './types.js';
|
|
6
|
+
export interface ConnectOptions {
|
|
7
|
+
url: URL;
|
|
8
|
+
transport?: MCPTransportKind;
|
|
9
|
+
auth?: MCPAuth;
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
clientInfo?: {
|
|
12
|
+
name: string;
|
|
13
|
+
version: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Streamable HTTP session id to resume.
|
|
17
|
+
*
|
|
18
|
+
* @deprecated Ignored by 2026-07-28 servers — protocol-level sessions are
|
|
19
|
+
* removed (SEP-2567). Still honoured on 2025-era connections.
|
|
20
|
+
*/
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
onElicitation?: ElicitationHandler;
|
|
23
|
+
protocolNegotiation?: MCPProtocolNegotiation;
|
|
24
|
+
/**
|
|
25
|
+
* Aborts the connect — every transport attempt, the negotiation probe, and
|
|
26
|
+
* the implicit legacy retry. Without this a caller with its own deadline has
|
|
27
|
+
* no way to bound the ladder, whose worst case is minutes (see
|
|
28
|
+
* {@link DEFAULT_PROBE_TIMEOUT_MS}).
|
|
29
|
+
*/
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
/**
|
|
32
|
+
* Ceiling on the `server/discover` probe, in ms. Defaults to
|
|
33
|
+
* `DEFAULT_PROBE_TIMEOUT_MS` (30000).
|
|
34
|
+
*
|
|
35
|
+
* Raise it for a server that is slow to answer its first request; the SDK's own
|
|
36
|
+
* default is the full request timeout, which makes a hanging gateway far slower
|
|
37
|
+
* to fail than it needs to be.
|
|
38
|
+
*/
|
|
39
|
+
probeTimeoutMs?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface MCPConnection {
|
|
42
|
+
client: Client;
|
|
43
|
+
transport: MCPTransportKind;
|
|
44
|
+
/**
|
|
45
|
+
* Session id reported by the transport, when there is one.
|
|
46
|
+
*
|
|
47
|
+
* @deprecated `undefined` on 2026-07-28 connections (SEP-2567) and on SSE,
|
|
48
|
+
* which never had a protocol-level session.
|
|
49
|
+
*/
|
|
50
|
+
sessionId?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Register a callback for `tools/list_changed`. Settable after connect so the
|
|
53
|
+
* handle can wire it to its own `refresh()`. Replaces any prior handler.
|
|
54
|
+
*/
|
|
55
|
+
setToolListChangedHandler(handler: () => void): void;
|
|
56
|
+
close(): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build the configured `Client` — negotiation mode, elicitation handler, and the
|
|
60
|
+
* `tools/list_changed` subscription — without connecting it.
|
|
61
|
+
*
|
|
62
|
+
* Exported for tests only, and deliberately not re-exported from the package
|
|
63
|
+
* entrypoint. It is the seam that lets a test drive the real handler wiring over
|
|
64
|
+
* `InMemoryTransport`: the notification method name is compile-checked (it is a
|
|
65
|
+
* `NotificationMethod` literal union, so a typo fails `tsc`), but the *dispatch*
|
|
66
|
+
* — that an inbound notification actually reaches `setToolListChangedHandler`'s
|
|
67
|
+
* callback — is only observable end to end. Without this seam, default-on
|
|
68
|
+
* `autoRefreshOnListChanged` could be silently dead with every test green.
|
|
69
|
+
*
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
export declare function makeClientForTest(options: ConnectOptions, onListChanged: () => void): Promise<Client>;
|
|
73
|
+
/**
|
|
74
|
+
* Did any attempt behind this failure end in the server rejecting our
|
|
75
|
+
* credentials?
|
|
76
|
+
*
|
|
77
|
+
* Searches the whole tree rather than the `cause` spine, because a single
|
|
78
|
+
* `connect()` can fail more than once: `connectWithNegotiation` wraps transport
|
|
79
|
+
* errors in `MCPConnectionError`, and on the two-transport path it records both
|
|
80
|
+
* attempts in `errors`. Checking only `cause` would miss an `UnauthorizedError`
|
|
81
|
+
* from the Streamable HTTP attempt whenever the SSE fallback then failed for an
|
|
82
|
+
* unrelated reason — a URL that isn't an SSE endpoint answering 404 never
|
|
83
|
+
* reaches the auth path — and the retry would re-drive the OAuth flow anyway.
|
|
84
|
+
*
|
|
85
|
+
* Depth-capped: a cyclic `cause` would otherwise hang, and real chains here are
|
|
86
|
+
* two or three links.
|
|
87
|
+
*
|
|
88
|
+
* Exported for `rehydrateMCPTools`, whose fallback-to-`freshConnect` is a third
|
|
89
|
+
* reconnect layer with the same duplicated-OAuth hazard; deliberately not
|
|
90
|
+
* re-exported from the package entrypoint.
|
|
91
|
+
*/
|
|
92
|
+
interface AuthFailureArgs {
|
|
93
|
+
err: unknown;
|
|
94
|
+
auth: MCPAuth | undefined;
|
|
95
|
+
UnauthorizedErrorType: MCPSdk['UnauthorizedError'] | undefined;
|
|
96
|
+
depth?: number;
|
|
97
|
+
}
|
|
98
|
+
export declare function isAuthFailure({ err, auth, UnauthorizedErrorType, depth, }: AuthFailureArgs): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Connect a `Client` to the MCP server, degrading to the 2025-era handshake if
|
|
101
|
+
* protocol negotiation gets in the way.
|
|
102
|
+
*
|
|
103
|
+
* We default `protocolNegotiation` to `'auto'` where the SDK defaults to
|
|
104
|
+
* `'legacy'`, so every connection's first request is a `server/discover` probe.
|
|
105
|
+
* On its own that would be a connectivity regression: a proxy, WAF, or strict
|
|
106
|
+
* gateway that hangs or 5xx's on an unknown method takes a server from working
|
|
107
|
+
* to failing, and the SSE fallback re-probes and fails identically — so the
|
|
108
|
+
* two-transport fallback, which looks like defense in depth, collapses to a
|
|
109
|
+
* single point of failure against exactly the infrastructure it should rescue.
|
|
110
|
+
*
|
|
111
|
+
* So when the caller left `protocolNegotiation` unset, a failed connect is
|
|
112
|
+
* retried once with `'legacy'`. That makes `'auto'` strictly additive: modern
|
|
113
|
+
* servers get the new revision, everything else lands exactly where it did
|
|
114
|
+
* before this package started probing. The cost is one extra attempt on a path
|
|
115
|
+
* that is already failing, which is where latency matters least.
|
|
116
|
+
*
|
|
117
|
+
* The retry fires on **any** failure rather than only on ones that look like
|
|
118
|
+
* probe timeouts. Inspecting the cause would avoid a wasted attempt against a
|
|
119
|
+
* genuinely dead server, but it would couple this to SDK error codes — and a
|
|
120
|
+
* reshaped error would silently disable the degradation, which is the failure
|
|
121
|
+
* mode this package keeps getting bitten by.
|
|
122
|
+
*
|
|
123
|
+
* Two bounds keep the retry from making a bad situation worse:
|
|
124
|
+
*
|
|
125
|
+
* 1. **It is skipped on an auth failure.** `UnauthorizedError` means the
|
|
126
|
+
* transport reached the server and the credentials were rejected — nothing a
|
|
127
|
+
* different protocol revision changes. Retrying would re-drive the OAuth
|
|
128
|
+
* provider's authorization flow: a second `redirectToAuthorization`, a second
|
|
129
|
+
* saved PKCE verifier overwriting the first. Replaying a failure that had
|
|
130
|
+
* side effects is worse than not retrying.
|
|
131
|
+
* 2. **It re-walks the same transport ladder the first pass did.** An earlier
|
|
132
|
+
* revision pinned Streamable HTTP to cap the attempt count, which broke the
|
|
133
|
+
* case this whole mechanism exists for: a legacy server reachable *only* over
|
|
134
|
+
* SSE, behind infrastructure that chokes on the probe, would fail under
|
|
135
|
+
* `'auto'` and then never be offered SSE again. Correctness wins over the
|
|
136
|
+
* attempt count here — a server that used to connect must still connect.
|
|
137
|
+
*
|
|
138
|
+
* So the worst case for a genuinely unreachable server is four `connect()`
|
|
139
|
+
* calls, two per negotiation mode. That is the honest cost of guaranteeing no
|
|
140
|
+
* working setup regresses, and it is paid only on a path that was already going
|
|
141
|
+
* to fail. The bound that matters is that it is a fixed multiple rather than a
|
|
142
|
+
* retry loop.
|
|
143
|
+
*
|
|
144
|
+
* An **explicit** `protocolNegotiation` is honoured exactly, including
|
|
145
|
+
* `'auto'`: asking for a mode means asking for its failures too, and silently
|
|
146
|
+
* ignoring a caller's `{ pin }` would defeat the point of pinning.
|
|
147
|
+
*/
|
|
148
|
+
export declare function connect(options: ConnectOptions): Promise<MCPConnection>;
|
|
149
|
+
export {};
|
|
150
|
+
//# sourceMappingURL=mcp-connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-connection.d.ts","sourceRoot":"","sources":["../../src/mcp/mcp-connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EAGP,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAKpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACrF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA0CrD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,GAAG,CAAC;IACT,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,UAAU,CAAC,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,mBAAmB,CAAC,EAAE,sBAAsB,CAAC;IAC7C;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,gBAAgB,CAAC;IAC5B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,yBAAyB,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACrD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAoDD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,MAAM,IAAI,GACxB,OAAO,CAAC,MAAM,CAAC,CAKjB;AAsOD;;;;;;;;;;;;;;;;;;GAkBG;AACH,UAAU,eAAe;IACvB,GAAG,EAAE,OAAO,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC,mBAAmB,CAAC,GAAG,SAAS,CAAC;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,aAAa,CAAC,EAC5B,GAAG,EACH,IAAI,EACJ,qBAAqB,EACrB,KAAS,GACV,EAAE,eAAe,GAAG,OAAO,CAsD3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAiD7E"}
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { resolveAuth } from './auth/auth-resolver.js';
|
|
2
|
+
import { isOAuthAuth } from './auth/auth-types.js';
|
|
3
|
+
import { closeQuietly } from './close-quietly.js';
|
|
4
|
+
import { makeElicitationRequestHandler } from './elicitation.js';
|
|
5
|
+
import { MCPConnectionError } from './errors.js';
|
|
6
|
+
import { loadMcpSdk } from './mcp-sdk.js';
|
|
7
|
+
import { PACKAGE_VERSION } from './version.js';
|
|
8
|
+
/**
|
|
9
|
+
* Ceiling on the `server/discover` probe, in ms.
|
|
10
|
+
*
|
|
11
|
+
* The SDK falls back to the full request timeout (60s) when this is unset, which
|
|
12
|
+
* is a long time to spend on a liveness question: under the `'auto'` default the
|
|
13
|
+
* probe is the first request of every connection, and a gateway that black-holes
|
|
14
|
+
* it burns the whole budget per attempt — up to four attempts once the legacy
|
|
15
|
+
* retry re-walks the transport ladder, so minutes before `createMCPTools()`
|
|
16
|
+
* rejects on the no-configuration path.
|
|
17
|
+
*
|
|
18
|
+
* **But a probe timeout is not recoverable, so this cannot be tight.** On HTTP
|
|
19
|
+
* the SDK classifies a timed-out probe as an outage and rejects (only stdio
|
|
20
|
+
* treats it as a legacy verdict), and the legacy retry that follows sends
|
|
21
|
+
* `initialize` — which revision 2026-07-28 removed (SEP-2575). So a
|
|
22
|
+
* modern-only server slower than this ceiling fails *both* passes and does not
|
|
23
|
+
* connect at all, where the SDK's own default would have waited and succeeded.
|
|
24
|
+
* Serverless cold starts routinely exceed a few seconds, which makes an
|
|
25
|
+
* aggressive value a correctness bug rather than a latency tradeoff.
|
|
26
|
+
*
|
|
27
|
+
* 30s splits the difference: comfortably past a cold start, while keeping the
|
|
28
|
+
* probe from consuming the SDK's full 60s request budget per attempt. Note the
|
|
29
|
+
* ceiling bounds only the `'auto'` probes — the implicit legacy retry's
|
|
30
|
+
* `initialize` attempts still run under the SDK's own timeout, so the
|
|
31
|
+
* black-holed-gateway worst case on the default path is roughly
|
|
32
|
+
* 30s + 30s + 60s + 60s ≈ 3 minutes across the four attempts (vs ~2 minutes
|
|
33
|
+
* for the two attempts before this package probed at all). A caller with a
|
|
34
|
+
* harder deadline bounds the whole ladder with `signal`. Raise this with
|
|
35
|
+
* `probeTimeoutMs` for a server known to be slower; lower it when you control
|
|
36
|
+
* the server and want to fail fast.
|
|
37
|
+
*/
|
|
38
|
+
const DEFAULT_PROBE_TIMEOUT_MS = 30000;
|
|
39
|
+
// Self-reported to every MCP server we connect to as `clientInfo`. Both the
|
|
40
|
+
// implementation and generated version belong to the agent package.
|
|
41
|
+
const DEFAULT_CLIENT_INFO = {
|
|
42
|
+
name: '@openrouter/agent',
|
|
43
|
+
version: PACKAGE_VERSION,
|
|
44
|
+
};
|
|
45
|
+
function buildStreamableHttp(sdk, options) {
|
|
46
|
+
const { headers, authProvider } = resolveAuth(options.auth);
|
|
47
|
+
return new sdk.StreamableHTTPClientTransport(options.url, {
|
|
48
|
+
requestInit: {
|
|
49
|
+
headers,
|
|
50
|
+
},
|
|
51
|
+
...(authProvider !== undefined && {
|
|
52
|
+
authProvider,
|
|
53
|
+
}),
|
|
54
|
+
...(options.fetch !== undefined && {
|
|
55
|
+
fetch: options.fetch,
|
|
56
|
+
}),
|
|
57
|
+
...(options.sessionId !== undefined && {
|
|
58
|
+
sessionId: options.sessionId,
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function buildSse(sdk, options) {
|
|
63
|
+
const { headers, authProvider } = resolveAuth(options.auth);
|
|
64
|
+
return new sdk.SSEClientTransport(options.url, {
|
|
65
|
+
requestInit: {
|
|
66
|
+
headers,
|
|
67
|
+
},
|
|
68
|
+
...(authProvider !== undefined && {
|
|
69
|
+
authProvider,
|
|
70
|
+
}),
|
|
71
|
+
...(options.fetch !== undefined && {
|
|
72
|
+
fetch: options.fetch,
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/** Per-call request options threaded into the SDK's `connect()`. */
|
|
77
|
+
function connectRequestOptions(options) {
|
|
78
|
+
return options.signal !== undefined
|
|
79
|
+
? {
|
|
80
|
+
signal: options.signal,
|
|
81
|
+
}
|
|
82
|
+
: undefined;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build the configured `Client` — negotiation mode, elicitation handler, and the
|
|
86
|
+
* `tools/list_changed` subscription — without connecting it.
|
|
87
|
+
*
|
|
88
|
+
* Exported for tests only, and deliberately not re-exported from the package
|
|
89
|
+
* entrypoint. It is the seam that lets a test drive the real handler wiring over
|
|
90
|
+
* `InMemoryTransport`: the notification method name is compile-checked (it is a
|
|
91
|
+
* `NotificationMethod` literal union, so a typo fails `tsc`), but the *dispatch*
|
|
92
|
+
* — that an inbound notification actually reaches `setToolListChangedHandler`'s
|
|
93
|
+
* callback — is only observable end to end. Without this seam, default-on
|
|
94
|
+
* `autoRefreshOnListChanged` could be silently dead with every test green.
|
|
95
|
+
*
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
export async function makeClientForTest(options, onListChanged) {
|
|
99
|
+
const sdk = await loadMcpSdk();
|
|
100
|
+
return makeClient(sdk, options, {
|
|
101
|
+
handler: onListChanged,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function makeClient(sdk, options, listChanged) {
|
|
105
|
+
const client = new sdk.Client(options.clientInfo ?? DEFAULT_CLIENT_INFO, {
|
|
106
|
+
capabilities: {
|
|
107
|
+
elicitation: {},
|
|
108
|
+
},
|
|
109
|
+
// The SDK defaults to 'legacy'; we default to 'auto' so callers reach both
|
|
110
|
+
// 2025-era and 2026-07-28 servers without configuring anything.
|
|
111
|
+
//
|
|
112
|
+
// `inputRequired` is deliberately left unset: its defaults (auto-fulfil on,
|
|
113
|
+
// 10 rounds) are what we want, and pinning them here would freeze values
|
|
114
|
+
// the SDK may tune.
|
|
115
|
+
versionNegotiation: {
|
|
116
|
+
mode: options.protocolNegotiation ?? 'auto',
|
|
117
|
+
probe: {
|
|
118
|
+
timeoutMs: options.probeTimeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
// Method-name-first in SDK v2; spec methods supply their own schema. This one
|
|
123
|
+
// handler serves both protocol eras: on 2025-era connections the server sends
|
|
124
|
+
// `elicitation/create` directly, and on 2026-07-28 the multi-round-trip driver
|
|
125
|
+
// dispatches `input_required` through this same handler, then retries the
|
|
126
|
+
// original call with the collected `inputResponses`.
|
|
127
|
+
client.setRequestHandler('elicitation/create', makeElicitationRequestHandler(options.onElicitation));
|
|
128
|
+
// Kept as a notification handler rather than `ClientOptions.listChanged`: the
|
|
129
|
+
// latter re-lists tools itself, duplicating the handle's own `refresh()` and
|
|
130
|
+
// its cache write. Notification handlers are era-transparent.
|
|
131
|
+
client.setNotificationHandler('notifications/tools/list_changed', () => {
|
|
132
|
+
listChanged.handler?.();
|
|
133
|
+
});
|
|
134
|
+
return client;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Connect a `Client` to the MCP server.
|
|
138
|
+
*
|
|
139
|
+
* Defaults to Streamable HTTP and falls back to SSE on connection failure
|
|
140
|
+
* (legacy servers), unless a transport is pinned explicitly. Auth, the
|
|
141
|
+
* elicitation handler, and the list_changed subscription are wired into the
|
|
142
|
+
* single connected client so they apply to discovery and every tool call.
|
|
143
|
+
*
|
|
144
|
+
* When the caller does not set `protocolNegotiation`, a failed connect is
|
|
145
|
+
* retried once with `'legacy'` — see {@link connect} below, which owns that
|
|
146
|
+
* policy; this function performs exactly one negotiation mode.
|
|
147
|
+
*/
|
|
148
|
+
async function connectWithNegotiation(sdk, options) {
|
|
149
|
+
const preferred = options.transport ?? 'streamableHttp';
|
|
150
|
+
const listChanged = {
|
|
151
|
+
handler: undefined,
|
|
152
|
+
};
|
|
153
|
+
if (preferred === 'sse') {
|
|
154
|
+
const client = makeClient(sdk, options, listChanged);
|
|
155
|
+
try {
|
|
156
|
+
await client.connect(buildSse(sdk, options), connectRequestOptions(options));
|
|
157
|
+
}
|
|
158
|
+
catch (sseErr) {
|
|
159
|
+
// Same transport-release reason as the Streamable HTTP path below.
|
|
160
|
+
await closeQuietly(client);
|
|
161
|
+
// Wrapped like every other path. Rethrowing raw here made the error type a
|
|
162
|
+
// caller sees depend on whether `protocolNegotiation` happened to be set —
|
|
163
|
+
// unset, the outer retry aggregated it into an `MCPConnectionError`; set, the
|
|
164
|
+
// transport error escaped. Same server, same failure, different `catch`.
|
|
165
|
+
throw new MCPConnectionError('Failed to connect over SSE', {
|
|
166
|
+
cause: sseErr,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return wrap({
|
|
170
|
+
client,
|
|
171
|
+
transport: 'sse',
|
|
172
|
+
listChanged,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
// Streamable HTTP, with SSE fallback when the transport wasn't pinned.
|
|
176
|
+
const client = makeClient(sdk, options, listChanged);
|
|
177
|
+
try {
|
|
178
|
+
const http = buildStreamableHttp(sdk, options);
|
|
179
|
+
await client.connect(http, connectRequestOptions(options));
|
|
180
|
+
return wrap({
|
|
181
|
+
client,
|
|
182
|
+
transport: 'streamableHttp',
|
|
183
|
+
listChanged,
|
|
184
|
+
...(http.sessionId !== undefined && {
|
|
185
|
+
sessionId: http.sessionId,
|
|
186
|
+
}),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
catch (httpErr) {
|
|
190
|
+
// Release the failed client's transport. The SDK cleans up after a failed
|
|
191
|
+
// probe or `initialize` handshake, but not when `transport.start()` itself
|
|
192
|
+
// throws: the base connect stores the transport before starting it and
|
|
193
|
+
// propagates without teardown, leaving a keep-alive socket open. Releasing
|
|
194
|
+
// unconditionally is safe on the paths the SDK already handles — see
|
|
195
|
+
// `closeQuietly`, which tolerates any close outcome rather than depending on
|
|
196
|
+
// what the SDK does internally.
|
|
197
|
+
await closeQuietly(client);
|
|
198
|
+
if (options.transport === 'streamableHttp' ||
|
|
199
|
+
isAuthFailure({
|
|
200
|
+
err: httpErr,
|
|
201
|
+
auth: options.auth,
|
|
202
|
+
UnauthorizedErrorType: sdk.UnauthorizedError,
|
|
203
|
+
})) {
|
|
204
|
+
// Auth failures stop here rather than falling through. The SSE attempt
|
|
205
|
+
// would carry the same `authProvider` into the SDK's auth path — a second
|
|
206
|
+
// `redirectToAuthorization`, a second `saveCodeVerifier` overwriting the
|
|
207
|
+
// first — which is the duplicated side effect `connect()`'s retry guard
|
|
208
|
+
// exists to prevent. Guarding one and not the other left the hazard inside
|
|
209
|
+
// a single pass, where it is arguably worse: the caller did not even opt
|
|
210
|
+
// into a second negotiation mode.
|
|
211
|
+
throw new MCPConnectionError('Failed to connect over Streamable HTTP', {
|
|
212
|
+
cause: httpErr,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
// An aborted caller gets no fallback: the same signal rides into the SSE
|
|
216
|
+
// attempt, but whether it interrupts `transport.start()` promptly is the
|
|
217
|
+
// SDK's business — checking here makes "abort means stop dialling"
|
|
218
|
+
// deterministic instead of dependent on where the SDK polls the signal.
|
|
219
|
+
if (options.signal?.aborted === true) {
|
|
220
|
+
throw new MCPConnectionError('Failed to connect over Streamable HTTP', {
|
|
221
|
+
cause: httpErr,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
// Fall back to SSE on a fresh client (the failed one may be half-initialized).
|
|
225
|
+
// Under `'auto'` this re-probes, so a probe-hostile server fails here too —
|
|
226
|
+
// which is why `connect()` retries the whole thing under `'legacy'` when the
|
|
227
|
+
// caller did not pin a negotiation mode.
|
|
228
|
+
const sseClient = makeClient(sdk, options, listChanged);
|
|
229
|
+
try {
|
|
230
|
+
await sseClient.connect(buildSse(sdk, options), connectRequestOptions(options));
|
|
231
|
+
return wrap({
|
|
232
|
+
client: sseClient,
|
|
233
|
+
transport: 'sse',
|
|
234
|
+
listChanged,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
catch (sseErr) {
|
|
238
|
+
await closeQuietly(sseClient);
|
|
239
|
+
// Both failures are kept. `sseErr` is the `cause` because it is the last
|
|
240
|
+
// thing tried, but discarding `httpErr` loses information the caller needs:
|
|
241
|
+
// an `UnauthorizedError` from the HTTP attempt is what tells `connect()` not
|
|
242
|
+
// to retry, and a 404/405 from an endpoint that simply isn't an SSE URL
|
|
243
|
+
// would otherwise hide it. `errors` matches `AggregateError`'s shape so the
|
|
244
|
+
// pair is discoverable without a bespoke field.
|
|
245
|
+
throw new MCPConnectionError('Failed to connect over Streamable HTTP and SSE', {
|
|
246
|
+
cause: sseErr,
|
|
247
|
+
errors: [
|
|
248
|
+
httpErr,
|
|
249
|
+
sseErr,
|
|
250
|
+
],
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Reduce one pass's failure to the list of attempts behind it.
|
|
257
|
+
*
|
|
258
|
+
* A pass that tried both transports is already an `MCPConnectionError` carrying
|
|
259
|
+
* them in `errors`; unwrapping keeps `errors` a flat list of real attempts rather
|
|
260
|
+
* than a tree of wrappers, so a caller can iterate it without recursing. A pass
|
|
261
|
+
* that tried one transport contributes itself.
|
|
262
|
+
*/
|
|
263
|
+
function flattenAttempts(err) {
|
|
264
|
+
if (err instanceof MCPConnectionError) {
|
|
265
|
+
if (err.errors.length > 0) {
|
|
266
|
+
return err.errors;
|
|
267
|
+
}
|
|
268
|
+
// A single-transport pass wraps its one failure with only `cause` set, so
|
|
269
|
+
// returning it as-is would put an opaque wrapper in a list documented as
|
|
270
|
+
// holding real attempts — and a caller scanning for a rejected token would
|
|
271
|
+
// have to know to dig through `cause` on some entries but not others.
|
|
272
|
+
if (err.cause !== undefined) {
|
|
273
|
+
return [
|
|
274
|
+
err.cause,
|
|
275
|
+
];
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return [
|
|
279
|
+
err,
|
|
280
|
+
];
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Does this error carry an HTTP status that means "your credentials were the
|
|
284
|
+
* problem"?
|
|
285
|
+
*
|
|
286
|
+
* 401 only, deliberately. The suppression this feeds exists to avoid re-driving
|
|
287
|
+
* OAuth side effects, and in SDK v2 those side effects (`saveCodeVerifier`,
|
|
288
|
+
* `redirectToAuthorization`) live exclusively behind the transport's
|
|
289
|
+
* `status === 401 && this._authProvider` branch — a 403 never enters the OAuth
|
|
290
|
+
* flow at all. Meanwhile proxies and WAFs commonly answer an unknown method
|
|
291
|
+
* like `server/discover` with 403 without consulting credentials, so treating
|
|
292
|
+
* 403 as a credential rejection made exactly the deployments the legacy retry
|
|
293
|
+
* exists to rescue permanently unreachable, for OAuth users only. A 403 retry
|
|
294
|
+
* re-drives nothing; a 401 retry re-drives the flow. Match the side effect,
|
|
295
|
+
* not the status class.
|
|
296
|
+
*
|
|
297
|
+
* Read via a duck-typed `status` rather than `instanceof SdkHttpError` plus an
|
|
298
|
+
* `SdkErrorCode` comparison: the numeric status is the stable half of that
|
|
299
|
+
* contract, and it also catches a gateway surfacing 401 in some other error
|
|
300
|
+
* shape.
|
|
301
|
+
*
|
|
302
|
+
* Only consulted when the caller configured OAuth — see {@link isAuthFailure}.
|
|
303
|
+
*/
|
|
304
|
+
function isAuthStatus(err) {
|
|
305
|
+
// Errors only. A plain object carrying a `status` is far more likely to be a
|
|
306
|
+
// response, a log record, or some other payload that happens to travel in a
|
|
307
|
+
// `cause` than an authoritative rejection — the SDK itself builds log entries
|
|
308
|
+
// with `status: 0`, and treating one of those as a credential failure would
|
|
309
|
+
// silently suppress the retry and make a probe-hostile server unreachable.
|
|
310
|
+
if (!(err instanceof Error) || !('status' in err)) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
const { status } = err;
|
|
314
|
+
return status === 401;
|
|
315
|
+
}
|
|
316
|
+
export function isAuthFailure({ err, auth, UnauthorizedErrorType, depth = 0, }) {
|
|
317
|
+
if (depth >= 8) {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
// `UnauthorizedError` is unconditional: the SDK only throws it from the
|
|
321
|
+
// provider-wrapped fetch and the authorization flow itself, so it inherently
|
|
322
|
+
// means an OAuth provider is in play and its flow would be re-driven.
|
|
323
|
+
if (UnauthorizedErrorType !== undefined && err instanceof UnauthorizedErrorType) {
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
// A bare 401 status counts only when the caller configured OAuth — the one
|
|
327
|
+
// auth kind where a 401 retry has side effects (a second
|
|
328
|
+
// `redirectToAuthorization`, an overwritten PKCE verifier). 403 never
|
|
329
|
+
// suppresses, even under OAuth: the SDK's PKCE writes live exclusively behind
|
|
330
|
+
// its 401 branch, so a 403 retry re-drives nothing — while proxies and WAFs
|
|
331
|
+
// commonly answer an unknown method like `server/discover` with 403, exactly
|
|
332
|
+
// the probe-hostile infrastructure the retry exists to rescue. With bearer,
|
|
333
|
+
// headers, or no auth, no status suppresses.
|
|
334
|
+
if (isOAuthAuth(auth) && isAuthStatus(err)) {
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
if (!(err instanceof Error)) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
// Covers our own `errors` and `AggregateError`'s alike — Node's
|
|
341
|
+
// happy-eyeballs path and some fetch implementations report a 401 as an
|
|
342
|
+
// `AggregateError` member rather than as a `cause`, which a spine-only walk
|
|
343
|
+
// would miss.
|
|
344
|
+
const { errors } = err;
|
|
345
|
+
if (Array.isArray(errors)) {
|
|
346
|
+
for (const nested of errors) {
|
|
347
|
+
if (isAuthFailure({
|
|
348
|
+
err: nested,
|
|
349
|
+
auth,
|
|
350
|
+
UnauthorizedErrorType,
|
|
351
|
+
depth: depth + 1,
|
|
352
|
+
})) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return (err.cause !== undefined &&
|
|
358
|
+
isAuthFailure({
|
|
359
|
+
err: err.cause,
|
|
360
|
+
auth,
|
|
361
|
+
UnauthorizedErrorType,
|
|
362
|
+
depth: depth + 1,
|
|
363
|
+
}));
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Connect a `Client` to the MCP server, degrading to the 2025-era handshake if
|
|
367
|
+
* protocol negotiation gets in the way.
|
|
368
|
+
*
|
|
369
|
+
* We default `protocolNegotiation` to `'auto'` where the SDK defaults to
|
|
370
|
+
* `'legacy'`, so every connection's first request is a `server/discover` probe.
|
|
371
|
+
* On its own that would be a connectivity regression: a proxy, WAF, or strict
|
|
372
|
+
* gateway that hangs or 5xx's on an unknown method takes a server from working
|
|
373
|
+
* to failing, and the SSE fallback re-probes and fails identically — so the
|
|
374
|
+
* two-transport fallback, which looks like defense in depth, collapses to a
|
|
375
|
+
* single point of failure against exactly the infrastructure it should rescue.
|
|
376
|
+
*
|
|
377
|
+
* So when the caller left `protocolNegotiation` unset, a failed connect is
|
|
378
|
+
* retried once with `'legacy'`. That makes `'auto'` strictly additive: modern
|
|
379
|
+
* servers get the new revision, everything else lands exactly where it did
|
|
380
|
+
* before this package started probing. The cost is one extra attempt on a path
|
|
381
|
+
* that is already failing, which is where latency matters least.
|
|
382
|
+
*
|
|
383
|
+
* The retry fires on **any** failure rather than only on ones that look like
|
|
384
|
+
* probe timeouts. Inspecting the cause would avoid a wasted attempt against a
|
|
385
|
+
* genuinely dead server, but it would couple this to SDK error codes — and a
|
|
386
|
+
* reshaped error would silently disable the degradation, which is the failure
|
|
387
|
+
* mode this package keeps getting bitten by.
|
|
388
|
+
*
|
|
389
|
+
* Two bounds keep the retry from making a bad situation worse:
|
|
390
|
+
*
|
|
391
|
+
* 1. **It is skipped on an auth failure.** `UnauthorizedError` means the
|
|
392
|
+
* transport reached the server and the credentials were rejected — nothing a
|
|
393
|
+
* different protocol revision changes. Retrying would re-drive the OAuth
|
|
394
|
+
* provider's authorization flow: a second `redirectToAuthorization`, a second
|
|
395
|
+
* saved PKCE verifier overwriting the first. Replaying a failure that had
|
|
396
|
+
* side effects is worse than not retrying.
|
|
397
|
+
* 2. **It re-walks the same transport ladder the first pass did.** An earlier
|
|
398
|
+
* revision pinned Streamable HTTP to cap the attempt count, which broke the
|
|
399
|
+
* case this whole mechanism exists for: a legacy server reachable *only* over
|
|
400
|
+
* SSE, behind infrastructure that chokes on the probe, would fail under
|
|
401
|
+
* `'auto'` and then never be offered SSE again. Correctness wins over the
|
|
402
|
+
* attempt count here — a server that used to connect must still connect.
|
|
403
|
+
*
|
|
404
|
+
* So the worst case for a genuinely unreachable server is four `connect()`
|
|
405
|
+
* calls, two per negotiation mode. That is the honest cost of guaranteeing no
|
|
406
|
+
* working setup regresses, and it is paid only on a path that was already going
|
|
407
|
+
* to fail. The bound that matters is that it is a fixed multiple rather than a
|
|
408
|
+
* retry loop.
|
|
409
|
+
*
|
|
410
|
+
* An **explicit** `protocolNegotiation` is honoured exactly, including
|
|
411
|
+
* `'auto'`: asking for a mode means asking for its failures too, and silently
|
|
412
|
+
* ignoring a caller's `{ pin }` would defeat the point of pinning.
|
|
413
|
+
*/
|
|
414
|
+
export async function connect(options) {
|
|
415
|
+
const sdk = await loadMcpSdk();
|
|
416
|
+
if (options.protocolNegotiation !== undefined) {
|
|
417
|
+
return connectWithNegotiation(sdk, options);
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
return await connectWithNegotiation(sdk, options);
|
|
421
|
+
}
|
|
422
|
+
catch (autoErr) {
|
|
423
|
+
// A caller-initiated abort is not a server problem: retrying under
|
|
424
|
+
// `'legacy'` would immediately re-abort (or worse, outlive the caller's
|
|
425
|
+
// deadline). Surface the abort as-is.
|
|
426
|
+
if (options.signal?.aborted === true) {
|
|
427
|
+
throw autoErr;
|
|
428
|
+
}
|
|
429
|
+
if (isAuthFailure({
|
|
430
|
+
err: autoErr,
|
|
431
|
+
auth: options.auth,
|
|
432
|
+
UnauthorizedErrorType: sdk.UnauthorizedError,
|
|
433
|
+
})) {
|
|
434
|
+
throw autoErr;
|
|
435
|
+
}
|
|
436
|
+
// Not inspecting the error for *whether* the probe was at fault: that would
|
|
437
|
+
// couple us to SDK error codes, and a reshaped error would silently disable
|
|
438
|
+
// the whole degradation. `UnauthorizedError` above is the one exception,
|
|
439
|
+
// because there the cost of retrying is a duplicated side effect rather than
|
|
440
|
+
// a wasted dial — and if that check ever stops matching we merely retry,
|
|
441
|
+
// which is the pre-existing behavior rather than a silent loss of function.
|
|
442
|
+
try {
|
|
443
|
+
return await connectWithNegotiation(sdk, {
|
|
444
|
+
...options,
|
|
445
|
+
protocolNegotiation: 'legacy',
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
catch (legacyErr) {
|
|
449
|
+
// Carry both passes. Letting `legacyErr` propagate untouched would drop
|
|
450
|
+
// everything the `'auto'` pass learned — up to half the attempts, and any
|
|
451
|
+
// auth-shaped rejection that `isAuthFailure` did not match — which would
|
|
452
|
+
// also make `MCPConnectionError.errors` a lie about being every failure in
|
|
453
|
+
// attempt order.
|
|
454
|
+
throw new MCPConnectionError('Failed to connect over any transport or protocol revision', {
|
|
455
|
+
cause: legacyErr,
|
|
456
|
+
errors: [
|
|
457
|
+
...flattenAttempts(autoErr),
|
|
458
|
+
...flattenAttempts(legacyErr),
|
|
459
|
+
],
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function wrap(args) {
|
|
465
|
+
const { client, transport, listChanged, sessionId } = args;
|
|
466
|
+
return {
|
|
467
|
+
client,
|
|
468
|
+
transport,
|
|
469
|
+
...(sessionId !== undefined && {
|
|
470
|
+
sessionId,
|
|
471
|
+
}),
|
|
472
|
+
setToolListChangedHandler: (handler) => {
|
|
473
|
+
listChanged.handler = handler;
|
|
474
|
+
},
|
|
475
|
+
close: () => client.close(),
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
//# sourceMappingURL=mcp-connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-connection.js","sourceRoot":"","sources":["../../src/mcp/mcp-connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Client, SSEClientTransport, StreamableHTTPClientTransport, UnauthorizedError } from '@modelcontextprotocol/client';
|
|
2
|
+
export interface MCPSdk {
|
|
3
|
+
Client: typeof Client;
|
|
4
|
+
SSEClientTransport: typeof SSEClientTransport;
|
|
5
|
+
StreamableHTTPClientTransport: typeof StreamableHTTPClientTransport;
|
|
6
|
+
UnauthorizedError: typeof UnauthorizedError;
|
|
7
|
+
}
|
|
8
|
+
/** Load the optional MCP client only when a connection is actually requested. */
|
|
9
|
+
export declare function loadMcpSdk(): Promise<MCPSdk>;
|
|
10
|
+
//# sourceMappingURL=mcp-sdk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-sdk.d.ts","sourceRoot":"","sources":["../../src/mcp/mcp-sdk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,kBAAkB,EAClB,6BAA6B,EAC7B,iBAAiB,EAClB,MAAM,8BAA8B,CAAC;AAGtC,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,OAAO,MAAM,CAAC;IACtB,kBAAkB,EAAE,OAAO,kBAAkB,CAAC;IAC9C,6BAA6B,EAAE,OAAO,6BAA6B,CAAC;IACpE,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;CAC7C;AAqBD,iFAAiF;AACjF,wBAAgB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAkB5C"}
|