@openrouter/mcp 0.0.1 → 1.0.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.
Files changed (64) hide show
  1. package/README.md +156 -6
  2. package/esm/auth/auth-resolver.d.ts +1 -1
  3. package/esm/auth/auth-resolver.d.ts.map +1 -1
  4. package/esm/auth/auth-types.d.ts +9 -1
  5. package/esm/auth/auth-types.d.ts.map +1 -1
  6. package/esm/auth/auth-types.js.map +1 -1
  7. package/esm/build-tools.d.ts +4 -2
  8. package/esm/build-tools.d.ts.map +1 -1
  9. package/esm/build-tools.js +3 -0
  10. package/esm/build-tools.js.map +1 -1
  11. package/esm/cache/cache-types.d.ts +17 -0
  12. package/esm/cache/cache-types.d.ts.map +1 -1
  13. package/esm/cache/cache-types.js +11 -3
  14. package/esm/cache/cache-types.js.map +1 -1
  15. package/esm/cache/serialize.d.ts +4 -4
  16. package/esm/cache/serialize.d.ts.map +1 -1
  17. package/esm/cache/serialize.js +17 -6
  18. package/esm/cache/serialize.js.map +1 -1
  19. package/esm/close-quietly.d.ts +24 -0
  20. package/esm/close-quietly.d.ts.map +1 -0
  21. package/esm/close-quietly.js +30 -0
  22. package/esm/close-quietly.js.map +1 -0
  23. package/esm/create-mcp-tools.d.ts.map +1 -1
  24. package/esm/create-mcp-tools.js +23 -2
  25. package/esm/create-mcp-tools.js.map +1 -1
  26. package/esm/elicitation.d.ts +1 -1
  27. package/esm/elicitation.d.ts.map +1 -1
  28. package/esm/errors.d.ts +47 -0
  29. package/esm/errors.d.ts.map +1 -1
  30. package/esm/errors.js +41 -0
  31. package/esm/errors.js.map +1 -1
  32. package/esm/handle.d.ts +25 -0
  33. package/esm/handle.d.ts.map +1 -1
  34. package/esm/handle.js +116 -14
  35. package/esm/handle.js.map +1 -1
  36. package/esm/index.d.ts +3 -3
  37. package/esm/index.d.ts.map +1 -1
  38. package/esm/index.js +1 -1
  39. package/esm/index.js.map +1 -1
  40. package/esm/mcp-connection.d.ts +114 -7
  41. package/esm/mcp-connection.d.ts.map +1 -1
  42. package/esm/mcp-connection.js +364 -34
  43. package/esm/mcp-connection.js.map +1 -1
  44. package/esm/rehydrate.d.ts +40 -5
  45. package/esm/rehydrate.d.ts.map +1 -1
  46. package/esm/rehydrate.js +381 -55
  47. package/esm/rehydrate.js.map +1 -1
  48. package/esm/resource-tools.d.ts +1 -1
  49. package/esm/resource-tools.d.ts.map +1 -1
  50. package/esm/resource-tools.js +24 -2
  51. package/esm/resource-tools.js.map +1 -1
  52. package/esm/tool-wrapper.d.ts +17 -2
  53. package/esm/tool-wrapper.d.ts.map +1 -1
  54. package/esm/tool-wrapper.js +16 -3
  55. package/esm/tool-wrapper.js.map +1 -1
  56. package/esm/transport-types.d.ts +43 -1
  57. package/esm/transport-types.d.ts.map +1 -1
  58. package/esm/types.d.ts +56 -7
  59. package/esm/types.d.ts.map +1 -1
  60. package/esm/version.d.ts +3 -0
  61. package/esm/version.d.ts.map +1 -0
  62. package/esm/version.js +5 -0
  63. package/esm/version.js.map +1 -0
  64. package/package.json +13 -9
@@ -1,15 +1,48 @@
1
- import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
- // SSEClientTransport is deprecated upstream but intentionally supported here for
3
- // legacy MCP servers that haven't migrated to Streamable HTTP.
4
- import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
5
- import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
6
- import { ElicitRequestSchema, ToolListChangedNotificationSchema, } from '@modelcontextprotocol/sdk/types.js';
1
+ // SSEClientTransport is deprecated upstream (SEP-2596) but intentionally
2
+ // supported here for legacy MCP servers that haven't migrated to Streamable HTTP.
3
+ import { Client, SSEClientTransport, StreamableHTTPClientTransport, UnauthorizedError, } from '@modelcontextprotocol/client';
7
4
  import { resolveAuth } from './auth/auth-resolver.js';
5
+ import { isOAuthAuth } from './auth/auth-types.js';
6
+ import { closeQuietly } from './close-quietly.js';
8
7
  import { makeElicitationRequestHandler } from './elicitation.js';
9
8
  import { MCPConnectionError } from './errors.js';
9
+ import { PACKAGE_VERSION } from './version.js';
10
+ /**
11
+ * Ceiling on the `server/discover` probe, in ms.
12
+ *
13
+ * The SDK falls back to the full request timeout (60s) when this is unset, which
14
+ * is a long time to spend on a liveness question: under the `'auto'` default the
15
+ * probe is the first request of every connection, and a gateway that black-holes
16
+ * it burns the whole budget per attempt — up to four attempts once the legacy
17
+ * retry re-walks the transport ladder, so minutes before `createMCPTools()`
18
+ * rejects on the no-configuration path.
19
+ *
20
+ * **But a probe timeout is not recoverable, so this cannot be tight.** On HTTP
21
+ * the SDK classifies a timed-out probe as an outage and rejects (only stdio
22
+ * treats it as a legacy verdict), and the legacy retry that follows sends
23
+ * `initialize` — which revision 2026-07-28 removed (SEP-2575). So a
24
+ * modern-only server slower than this ceiling fails *both* passes and does not
25
+ * connect at all, where the SDK's own default would have waited and succeeded.
26
+ * Serverless cold starts routinely exceed a few seconds, which makes an
27
+ * aggressive value a correctness bug rather than a latency tradeoff.
28
+ *
29
+ * 30s splits the difference: comfortably past a cold start, while keeping the
30
+ * probe from consuming the SDK's full 60s request budget per attempt. Note the
31
+ * ceiling bounds only the `'auto'` probes — the implicit legacy retry's
32
+ * `initialize` attempts still run under the SDK's own timeout, so the
33
+ * black-holed-gateway worst case on the default path is roughly
34
+ * 30s + 30s + 60s + 60s ≈ 3 minutes across the four attempts (vs ~2 minutes
35
+ * for the two attempts before this package probed at all). A caller with a
36
+ * harder deadline bounds the whole ladder with `signal`. Raise this with
37
+ * `probeTimeoutMs` for a server known to be slower; lower it when you control
38
+ * the server and want to fail fast.
39
+ */
40
+ const DEFAULT_PROBE_TIMEOUT_MS = 30000;
41
+ // Self-reported to every MCP server we connect to as `clientInfo`. The version
42
+ // is generated from package.json (scripts/gen-version.mjs) so it cannot drift.
10
43
  const DEFAULT_CLIENT_INFO = {
11
44
  name: '@openrouter/mcp',
12
- version: '0.1.0',
45
+ version: PACKAGE_VERSION,
13
46
  };
14
47
  function buildStreamableHttp(options) {
15
48
  const { headers, authProvider } = resolveAuth(options.auth);
@@ -42,51 +75,98 @@ function buildSse(options) {
42
75
  }),
43
76
  });
44
77
  }
78
+ /** Per-call request options threaded into the SDK's `connect()`. */
79
+ function connectRequestOptions(options) {
80
+ return options.signal !== undefined
81
+ ? {
82
+ signal: options.signal,
83
+ }
84
+ : undefined;
85
+ }
45
86
  /**
46
- * Runtime narrowing to `Transport`. The SDK's transport classes implement the
47
- * interface, but their `.d.ts` types `sessionId` as `string | undefined` rather
48
- * than `sessionId?: string`, which `exactOptionalPropertyTypes` rejects at the
49
- * `connect()` call site. We confirm the structural contract at runtime instead
50
- * of asserting past the variance with `as`.
87
+ * Build the configured `Client` — negotiation mode, elicitation handler, and the
88
+ * `tools/list_changed` subscription — without connecting it.
89
+ *
90
+ * Exported for tests only, and deliberately not re-exported from the package
91
+ * entrypoint. It is the seam that lets a test drive the real handler wiring over
92
+ * `InMemoryTransport`: the notification method name is compile-checked (it is a
93
+ * `NotificationMethod` literal union, so a typo fails `tsc`), but the *dispatch*
94
+ * — that an inbound notification actually reaches `setToolListChangedHandler`'s
95
+ * callback — is only observable end to end. Without this seam, default-on
96
+ * `autoRefreshOnListChanged` could be silently dead with every test green.
97
+ *
98
+ * @internal
51
99
  */
52
- function isTransport(value) {
53
- return (typeof value.start === 'function' &&
54
- typeof value.send === 'function' &&
55
- typeof value.close === 'function');
100
+ export function makeClientForTest(options, onListChanged) {
101
+ return makeClient(options, {
102
+ handler: onListChanged,
103
+ });
56
104
  }
57
105
  function makeClient(options, listChanged) {
58
106
  const client = new Client(options.clientInfo ?? DEFAULT_CLIENT_INFO, {
59
107
  capabilities: {
60
108
  elicitation: {},
61
109
  },
110
+ // The SDK defaults to 'legacy'; we default to 'auto' so callers reach both
111
+ // 2025-era and 2026-07-28 servers without configuring anything.
112
+ //
113
+ // `inputRequired` is deliberately left unset: its defaults (auto-fulfil on,
114
+ // 10 rounds) are what we want, and pinning them here would freeze values
115
+ // the SDK may tune.
116
+ versionNegotiation: {
117
+ mode: options.protocolNegotiation ?? 'auto',
118
+ probe: {
119
+ timeoutMs: options.probeTimeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS,
120
+ },
121
+ },
62
122
  });
63
- client.setRequestHandler(ElicitRequestSchema, makeElicitationRequestHandler(options.onElicitation));
64
- client.setNotificationHandler(ToolListChangedNotificationSchema, () => {
123
+ // Method-name-first in SDK v2; spec methods supply their own schema. This one
124
+ // handler serves both protocol eras: on 2025-era connections the server sends
125
+ // `elicitation/create` directly, and on 2026-07-28 the multi-round-trip driver
126
+ // dispatches `input_required` through this same handler, then retries the
127
+ // original call with the collected `inputResponses`.
128
+ client.setRequestHandler('elicitation/create', makeElicitationRequestHandler(options.onElicitation));
129
+ // Kept as a notification handler rather than `ClientOptions.listChanged`: the
130
+ // latter re-lists tools itself, duplicating the handle's own `refresh()` and
131
+ // its cache write. Notification handlers are era-transparent.
132
+ client.setNotificationHandler('notifications/tools/list_changed', () => {
65
133
  listChanged.handler?.();
66
134
  });
67
135
  return client;
68
136
  }
69
- async function connectWith(client, transport) {
70
- if (!isTransport(transport)) {
71
- throw new MCPConnectionError('MCP transport does not implement the Transport contract');
72
- }
73
- await client.connect(transport);
74
- }
75
137
  /**
76
- * Connect a `Client` to the MCP server. Defaults to Streamable HTTP and falls
77
- * back to SSE on connection failure (legacy servers), unless a transport is
78
- * pinned explicitly. Auth, the elicitation handler, and the list_changed
79
- * subscription are wired into the single connected client so they apply to
80
- * discovery and every tool call.
138
+ * Connect a `Client` to the MCP server.
139
+ *
140
+ * Defaults to Streamable HTTP and falls back to SSE on connection failure
141
+ * (legacy servers), unless a transport is pinned explicitly. Auth, the
142
+ * elicitation handler, and the list_changed subscription are wired into the
143
+ * single connected client so they apply to discovery and every tool call.
144
+ *
145
+ * When the caller does not set `protocolNegotiation`, a failed connect is
146
+ * retried once with `'legacy'` — see {@link connect} below, which owns that
147
+ * policy; this function performs exactly one negotiation mode.
81
148
  */
82
- export async function connect(options) {
149
+ async function connectWithNegotiation(options) {
83
150
  const preferred = options.transport ?? 'streamableHttp';
84
151
  const listChanged = {
85
152
  handler: undefined,
86
153
  };
87
154
  if (preferred === 'sse') {
88
155
  const client = makeClient(options, listChanged);
89
- await connectWith(client, buildSse(options));
156
+ try {
157
+ await client.connect(buildSse(options), connectRequestOptions(options));
158
+ }
159
+ catch (sseErr) {
160
+ // Same transport-release reason as the Streamable HTTP path below.
161
+ await closeQuietly(client);
162
+ // Wrapped like every other path. Rethrowing raw here made the error type a
163
+ // caller sees depend on whether `protocolNegotiation` happened to be set —
164
+ // unset, the outer retry aggregated it into an `MCPConnectionError`; set, the
165
+ // transport error escaped. Same server, same failure, different `catch`.
166
+ throw new MCPConnectionError('Failed to connect over SSE', {
167
+ cause: sseErr,
168
+ });
169
+ }
90
170
  return wrap({
91
171
  client,
92
172
  transport: 'sse',
@@ -97,7 +177,7 @@ export async function connect(options) {
97
177
  const client = makeClient(options, listChanged);
98
178
  try {
99
179
  const http = buildStreamableHttp(options);
100
- await connectWith(client, http);
180
+ await client.connect(http, connectRequestOptions(options));
101
181
  return wrap({
102
182
  client,
103
183
  transport: 'streamableHttp',
@@ -108,15 +188,42 @@ export async function connect(options) {
108
188
  });
109
189
  }
110
190
  catch (httpErr) {
111
- if (options.transport === 'streamableHttp') {
191
+ // Release the failed client's transport. The SDK cleans up after a failed
192
+ // probe or `initialize` handshake, but not when `transport.start()` itself
193
+ // throws: the base connect stores the transport before starting it and
194
+ // propagates without teardown, leaving a keep-alive socket open. Releasing
195
+ // unconditionally is safe on the paths the SDK already handles — see
196
+ // `closeQuietly`, which tolerates any close outcome rather than depending on
197
+ // what the SDK does internally.
198
+ await closeQuietly(client);
199
+ if (options.transport === 'streamableHttp' || isAuthFailure(httpErr, options.auth)) {
200
+ // Auth failures stop here rather than falling through. The SSE attempt
201
+ // would carry the same `authProvider` into the SDK's auth path — a second
202
+ // `redirectToAuthorization`, a second `saveCodeVerifier` overwriting the
203
+ // first — which is the duplicated side effect `connect()`'s retry guard
204
+ // exists to prevent. Guarding one and not the other left the hazard inside
205
+ // a single pass, where it is arguably worse: the caller did not even opt
206
+ // into a second negotiation mode.
207
+ throw new MCPConnectionError('Failed to connect over Streamable HTTP', {
208
+ cause: httpErr,
209
+ });
210
+ }
211
+ // An aborted caller gets no fallback: the same signal rides into the SSE
212
+ // attempt, but whether it interrupts `transport.start()` promptly is the
213
+ // SDK's business — checking here makes "abort means stop dialling"
214
+ // deterministic instead of dependent on where the SDK polls the signal.
215
+ if (options.signal?.aborted === true) {
112
216
  throw new MCPConnectionError('Failed to connect over Streamable HTTP', {
113
217
  cause: httpErr,
114
218
  });
115
219
  }
116
220
  // Fall back to SSE on a fresh client (the failed one may be half-initialized).
221
+ // Under `'auto'` this re-probes, so a probe-hostile server fails here too —
222
+ // which is why `connect()` retries the whole thing under `'legacy'` when the
223
+ // caller did not pin a negotiation mode.
117
224
  const sseClient = makeClient(options, listChanged);
118
225
  try {
119
- await connectWith(sseClient, buildSse(options));
226
+ await sseClient.connect(buildSse(options), connectRequestOptions(options));
120
227
  return wrap({
121
228
  client: sseClient,
122
229
  transport: 'sse',
@@ -124,8 +231,231 @@ export async function connect(options) {
124
231
  });
125
232
  }
126
233
  catch (sseErr) {
234
+ await closeQuietly(sseClient);
235
+ // Both failures are kept. `sseErr` is the `cause` because it is the last
236
+ // thing tried, but discarding `httpErr` loses information the caller needs:
237
+ // an `UnauthorizedError` from the HTTP attempt is what tells `connect()` not
238
+ // to retry, and a 404/405 from an endpoint that simply isn't an SSE URL
239
+ // would otherwise hide it. `errors` matches `AggregateError`'s shape so the
240
+ // pair is discoverable without a bespoke field.
127
241
  throw new MCPConnectionError('Failed to connect over Streamable HTTP and SSE', {
128
242
  cause: sseErr,
243
+ errors: [
244
+ httpErr,
245
+ sseErr,
246
+ ],
247
+ });
248
+ }
249
+ }
250
+ }
251
+ /**
252
+ * Reduce one pass's failure to the list of attempts behind it.
253
+ *
254
+ * A pass that tried both transports is already an `MCPConnectionError` carrying
255
+ * them in `errors`; unwrapping keeps `errors` a flat list of real attempts rather
256
+ * than a tree of wrappers, so a caller can iterate it without recursing. A pass
257
+ * that tried one transport contributes itself.
258
+ */
259
+ function flattenAttempts(err) {
260
+ if (err instanceof MCPConnectionError) {
261
+ if (err.errors.length > 0) {
262
+ return err.errors;
263
+ }
264
+ // A single-transport pass wraps its one failure with only `cause` set, so
265
+ // returning it as-is would put an opaque wrapper in a list documented as
266
+ // holding real attempts — and a caller scanning for a rejected token would
267
+ // have to know to dig through `cause` on some entries but not others.
268
+ if (err.cause !== undefined) {
269
+ return [
270
+ err.cause,
271
+ ];
272
+ }
273
+ }
274
+ return [
275
+ err,
276
+ ];
277
+ }
278
+ /**
279
+ * Does this error carry an HTTP status that means "your credentials were the
280
+ * problem"?
281
+ *
282
+ * 401 only, deliberately. The suppression this feeds exists to avoid re-driving
283
+ * OAuth side effects, and in SDK v2 those side effects (`saveCodeVerifier`,
284
+ * `redirectToAuthorization`) live exclusively behind the transport's
285
+ * `status === 401 && this._authProvider` branch — a 403 never enters the OAuth
286
+ * flow at all. Meanwhile proxies and WAFs commonly answer an unknown method
287
+ * like `server/discover` with 403 without consulting credentials, so treating
288
+ * 403 as a credential rejection made exactly the deployments the legacy retry
289
+ * exists to rescue permanently unreachable, for OAuth users only. A 403 retry
290
+ * re-drives nothing; a 401 retry re-drives the flow. Match the side effect,
291
+ * not the status class.
292
+ *
293
+ * Read via a duck-typed `status` rather than `instanceof SdkHttpError` plus an
294
+ * `SdkErrorCode` comparison: the numeric status is the stable half of that
295
+ * contract, and it also catches a gateway surfacing 401 in some other error
296
+ * shape.
297
+ *
298
+ * Only consulted when the caller configured OAuth — see {@link isAuthFailure}.
299
+ */
300
+ function isAuthStatus(err) {
301
+ // Errors only. A plain object carrying a `status` is far more likely to be a
302
+ // response, a log record, or some other payload that happens to travel in a
303
+ // `cause` than an authoritative rejection — the SDK itself builds log entries
304
+ // with `status: 0`, and treating one of those as a credential failure would
305
+ // silently suppress the retry and make a probe-hostile server unreachable.
306
+ if (!(err instanceof Error) || !('status' in err)) {
307
+ return false;
308
+ }
309
+ const { status } = err;
310
+ return status === 401;
311
+ }
312
+ /**
313
+ * Did any attempt behind this failure end in the server rejecting our
314
+ * credentials?
315
+ *
316
+ * Searches the whole tree rather than the `cause` spine, because a single
317
+ * `connect()` can fail more than once: `connectWithNegotiation` wraps transport
318
+ * errors in `MCPConnectionError`, and on the two-transport path it records both
319
+ * attempts in `errors`. Checking only `cause` would miss an `UnauthorizedError`
320
+ * from the Streamable HTTP attempt whenever the SSE fallback then failed for an
321
+ * unrelated reason — a URL that isn't an SSE endpoint answering 404 never
322
+ * reaches the auth path — and the retry would re-drive the OAuth flow anyway.
323
+ *
324
+ * Depth-capped: a cyclic `cause` would otherwise hang, and real chains here are
325
+ * two or three links.
326
+ *
327
+ * Exported for `rehydrateMCPTools`, whose fallback-to-`freshConnect` is a third
328
+ * reconnect layer with the same duplicated-OAuth hazard; deliberately not
329
+ * re-exported from the package entrypoint.
330
+ */
331
+ export function isAuthFailure(err, auth, depth = 0) {
332
+ if (depth >= 8) {
333
+ return false;
334
+ }
335
+ // `UnauthorizedError` is unconditional: the SDK only throws it from the
336
+ // provider-wrapped fetch and the authorization flow itself, so it inherently
337
+ // means an OAuth provider is in play and its flow would be re-driven.
338
+ if (err instanceof UnauthorizedError) {
339
+ return true;
340
+ }
341
+ // A bare 401 status counts only when the caller configured OAuth — the one
342
+ // auth kind where a 401 retry has side effects (a second
343
+ // `redirectToAuthorization`, an overwritten PKCE verifier). 403 never
344
+ // suppresses, even under OAuth: the SDK's PKCE writes live exclusively behind
345
+ // its 401 branch, so a 403 retry re-drives nothing — while proxies and WAFs
346
+ // commonly answer an unknown method like `server/discover` with 403, exactly
347
+ // the probe-hostile infrastructure the retry exists to rescue. With bearer,
348
+ // headers, or no auth, no status suppresses.
349
+ if (isOAuthAuth(auth) && isAuthStatus(err)) {
350
+ return true;
351
+ }
352
+ if (!(err instanceof Error)) {
353
+ return false;
354
+ }
355
+ // Covers our own `errors` and `AggregateError`'s alike — Node's
356
+ // happy-eyeballs path and some fetch implementations report a 401 as an
357
+ // `AggregateError` member rather than as a `cause`, which a spine-only walk
358
+ // would miss.
359
+ const { errors } = err;
360
+ if (Array.isArray(errors)) {
361
+ for (const nested of errors) {
362
+ if (isAuthFailure(nested, auth, depth + 1)) {
363
+ return true;
364
+ }
365
+ }
366
+ }
367
+ return err.cause !== undefined && isAuthFailure(err.cause, auth, depth + 1);
368
+ }
369
+ /**
370
+ * Connect a `Client` to the MCP server, degrading to the 2025-era handshake if
371
+ * protocol negotiation gets in the way.
372
+ *
373
+ * We default `protocolNegotiation` to `'auto'` where the SDK defaults to
374
+ * `'legacy'`, so every connection's first request is a `server/discover` probe.
375
+ * On its own that would be a connectivity regression: a proxy, WAF, or strict
376
+ * gateway that hangs or 5xx's on an unknown method takes a server from working
377
+ * to failing, and the SSE fallback re-probes and fails identically — so the
378
+ * two-transport fallback, which looks like defense in depth, collapses to a
379
+ * single point of failure against exactly the infrastructure it should rescue.
380
+ *
381
+ * So when the caller left `protocolNegotiation` unset, a failed connect is
382
+ * retried once with `'legacy'`. That makes `'auto'` strictly additive: modern
383
+ * servers get the new revision, everything else lands exactly where it did
384
+ * before this package started probing. The cost is one extra attempt on a path
385
+ * that is already failing, which is where latency matters least.
386
+ *
387
+ * The retry fires on **any** failure rather than only on ones that look like
388
+ * probe timeouts. Inspecting the cause would avoid a wasted attempt against a
389
+ * genuinely dead server, but it would couple this to SDK error codes — and a
390
+ * reshaped error would silently disable the degradation, which is the failure
391
+ * mode this package keeps getting bitten by.
392
+ *
393
+ * Two bounds keep the retry from making a bad situation worse:
394
+ *
395
+ * 1. **It is skipped on an auth failure.** `UnauthorizedError` means the
396
+ * transport reached the server and the credentials were rejected — nothing a
397
+ * different protocol revision changes. Retrying would re-drive the OAuth
398
+ * provider's authorization flow: a second `redirectToAuthorization`, a second
399
+ * saved PKCE verifier overwriting the first. Replaying a failure that had
400
+ * side effects is worse than not retrying.
401
+ * 2. **It re-walks the same transport ladder the first pass did.** An earlier
402
+ * revision pinned Streamable HTTP to cap the attempt count, which broke the
403
+ * case this whole mechanism exists for: a legacy server reachable *only* over
404
+ * SSE, behind infrastructure that chokes on the probe, would fail under
405
+ * `'auto'` and then never be offered SSE again. Correctness wins over the
406
+ * attempt count here — a server that used to connect must still connect.
407
+ *
408
+ * So the worst case for a genuinely unreachable server is four `connect()`
409
+ * calls, two per negotiation mode. That is the honest cost of guaranteeing no
410
+ * working setup regresses, and it is paid only on a path that was already going
411
+ * to fail. The bound that matters is that it is a fixed multiple rather than a
412
+ * retry loop.
413
+ *
414
+ * An **explicit** `protocolNegotiation` is honoured exactly, including
415
+ * `'auto'`: asking for a mode means asking for its failures too, and silently
416
+ * ignoring a caller's `{ pin }` would defeat the point of pinning.
417
+ */
418
+ export async function connect(options) {
419
+ if (options.protocolNegotiation !== undefined) {
420
+ return connectWithNegotiation(options);
421
+ }
422
+ try {
423
+ return await connectWithNegotiation(options);
424
+ }
425
+ catch (autoErr) {
426
+ // A caller-initiated abort is not a server problem: retrying under
427
+ // `'legacy'` would immediately re-abort (or worse, outlive the caller's
428
+ // deadline). Surface the abort as-is.
429
+ if (options.signal?.aborted === true) {
430
+ throw autoErr;
431
+ }
432
+ if (isAuthFailure(autoErr, options.auth)) {
433
+ throw autoErr;
434
+ }
435
+ // Not inspecting the error for *whether* the probe was at fault: that would
436
+ // couple us to SDK error codes, and a reshaped error would silently disable
437
+ // the whole degradation. `UnauthorizedError` above is the one exception,
438
+ // because there the cost of retrying is a duplicated side effect rather than
439
+ // a wasted dial — and if that check ever stops matching we merely retry,
440
+ // which is the pre-existing behavior rather than a silent loss of function.
441
+ try {
442
+ return await connectWithNegotiation({
443
+ ...options,
444
+ protocolNegotiation: 'legacy',
445
+ });
446
+ }
447
+ catch (legacyErr) {
448
+ // Carry both passes. Letting `legacyErr` propagate untouched would drop
449
+ // everything the `'auto'` pass learned — up to half the attempts, and any
450
+ // auth-shaped rejection that `isAuthFailure` did not match — which would
451
+ // also make `MCPConnectionError.errors` a lie about being every failure in
452
+ // attempt order.
453
+ throw new MCPConnectionError('Failed to connect over any transport or protocol revision', {
454
+ cause: legacyErr,
455
+ errors: [
456
+ ...flattenAttempts(autoErr),
457
+ ...flattenAttempts(legacyErr),
458
+ ],
129
459
  });
130
460
  }
131
461
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-connection.js","sourceRoot":"","sources":["../src/mcp-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,iFAAiF;AACjF,+DAA+D;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAEnG,OAAO,EACL,mBAAmB,EACnB,iCAAiC,GAClC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGjD,MAAM,mBAAmB,GAAG;IAC1B,IAAI,EAAE,iBAAiB;IACvB,OAAO,EAAE,OAAO;CACjB,CAAC;AA2BF,SAAS,mBAAmB,CAAC,OAAuB;IAClD,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,OAAO,IAAI,6BAA6B,CAAC,OAAO,CAAC,GAAG,EAAE;QACpD,WAAW,EAAE;YACX,OAAO;SACR;QACD,GAAG,CAAC,YAAY,KAAK,SAAS,IAAI;YAChC,YAAY;SACb,CAAC;QACF,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI;YACjC,KAAK,EAAE,OAAO,CAAC,KAAK;SACrB,CAAC;QACF,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI;YACrC,SAAS,EAAE,OAAO,CAAC,SAAS;SAC7B,CAAC;KACH,CAAC,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,OAAuB;IACvC,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,OAAO,IAAI,kBAAkB,CAAC,OAAO,CAAC,GAAG,EAAE;QACzC,WAAW,EAAE;YACX,OAAO;SACR;QACD,GAAG,CAAC,YAAY,KAAK,SAAS,IAAI;YAChC,YAAY;SACb,CAAC;QACF,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI;YACjC,KAAK,EAAE,OAAO,CAAC,KAAK;SACrB,CAAC;KACH,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,SAAS,WAAW,CAAC,KAAwD;IAC3E,OAAO,CACL,OAAO,KAAK,CAAC,KAAK,KAAK,UAAU;QACjC,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU;QAChC,OAAO,KAAK,CAAC,KAAK,KAAK,UAAU,CAClC,CAAC;AACJ,CAAC;AAMD,SAAS,UAAU,CAAC,OAAuB,EAAE,WAA+B;IAC1E,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,IAAI,mBAAmB,EAAE;QACnE,YAAY,EAAE;YACZ,WAAW,EAAE,EAAE;SAChB;KACF,CAAC,CAAC;IAEH,MAAM,CAAC,iBAAiB,CACtB,mBAAmB,EACnB,6BAA6B,CAAC,OAAO,CAAC,aAAa,CAAC,CACrD,CAAC;IAEF,MAAM,CAAC,sBAAsB,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACpE,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,WAAW,CACxB,MAAc,EACd,SAA6D;IAE7D,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,kBAAkB,CAAC,yDAAyD,CAAC,CAAC;IAC1F,CAAC;IACD,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,OAAuB;IACnD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,gBAAgB,CAAC;IACxD,MAAM,WAAW,GAAuB;QACtC,OAAO,EAAE,SAAS;KACnB,CAAC;IAEF,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;QACxB,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAChD,MAAM,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC;YACV,MAAM;YACN,SAAS,EAAE,KAAK;YAChB,WAAW;SACZ,CAAC,CAAC;IACL,CAAC;IAED,uEAAuE;IACvE,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAChD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;QAC1C,MAAM,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;YACV,MAAM;YACN,SAAS,EAAE,gBAAgB;YAC3B,WAAW;YACX,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI;gBAClC,SAAS,EAAE,IAAI,CAAC,SAAS;aAC1B,CAAC;SACH,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,OAAO,EAAE,CAAC;QACjB,IAAI,OAAO,CAAC,SAAS,KAAK,gBAAgB,EAAE,CAAC;YAC3C,MAAM,IAAI,kBAAkB,CAAC,wCAAwC,EAAE;gBACrE,KAAK,EAAE,OAAO;aACf,CAAC,CAAC;QACL,CAAC;QACD,+EAA+E;QAC/E,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QACnD,IAAI,CAAC;YACH,MAAM,WAAW,CAAC,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;YAChD,OAAO,IAAI,CAAC;gBACV,MAAM,EAAE,SAAS;gBACjB,SAAS,EAAE,KAAK;gBAChB,WAAW;aACZ,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,kBAAkB,CAAC,gDAAgD,EAAE;gBAC7E,KAAK,EAAE,MAAM;aACd,CAAC,CAAC;QACL,CAAC;IACH,CAAC;AACH,CAAC;AASD,SAAS,IAAI,CAAC,IAAc;IAC1B,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC3D,OAAO;QACL,MAAM;QACN,SAAS;QACT,GAAG,CAAC,SAAS,KAAK,SAAS,IAAI;YAC7B,SAAS;SACV,CAAC;QACF,yBAAyB,EAAE,CAAC,OAAmB,EAAE,EAAE;YACjD,WAAW,CAAC,OAAO,GAAG,OAAO,CAAC;QAChC,CAAC;QACD,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE;KAC5B,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"mcp-connection.js","sourceRoot":"","sources":["../src/mcp-connection.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import type { MCPAuth } from './auth/auth-types.js';
2
2
  import type { MCPCacheStore } from './cache/cache-store.js';
3
3
  import type { SerializedMCPServer } from './cache/cache-types.js';
4
4
  import type { UnconvertibleSchemaMode } from './schema/json-schema-to-zod.js';
5
- import type { ElicitationHandler, MCPToolsHandle, ResourcesOption } from './types.js';
5
+ import type { CreateMCPToolsOptions, ElicitationHandler, MCPProtocolNegotiation, MCPToolsHandle, ResourcesOption } from './types.js';
6
6
  export interface RehydrateMCPToolsOptions {
7
7
  snapshot: SerializedMCPServer;
8
8
  /** Required when the snapshot carries no cached credentials. */
@@ -11,18 +11,49 @@ export interface RehydrateMCPToolsOptions {
11
11
  onUnconvertibleSchema?: UnconvertibleSchemaMode;
12
12
  onElicitation?: ElicitationHandler;
13
13
  signal?: AbortSignal;
14
- /** Cache to refresh on reconnect/fallback. */
14
+ /** Protocol-revision negotiation policy; defaults to `'auto'`. */
15
+ protocolNegotiation?: MCPProtocolNegotiation;
16
+ /** Ceiling on the `server/discover` probe, in ms; defaults to 30000. */
17
+ probeTimeoutMs?: number;
18
+ /** Re-list tools instead of replaying a snapshot older than this. */
19
+ staleness?: {
20
+ maxAgeMs?: number;
21
+ };
22
+ /**
23
+ * Cache to refresh on reconnect/fallback.
24
+ *
25
+ * A successful replay does **not** write here — the snapshot it would write is
26
+ * the one you passed in, so the write is skipped as a no-op. The store is
27
+ * written when something genuinely new exists: a `refresh()`, a
28
+ * `tools/list_changed` auto-refresh, or the fresh-connect fallback. To seed a
29
+ * store from a snapshot obtained elsewhere, write it yourself
30
+ * (`store.set(key, snapshot)`) or call `handle.refresh()` after rehydrating.
31
+ */
15
32
  cache?: {
16
33
  store: MCPCacheStore;
17
34
  key?: string;
18
35
  };
19
- /** On expiry / missing creds / connection failure, do a full reconnect. Default true. */
36
+ /**
37
+ * On expired cached tokens, missing credentials, an over-age snapshot
38
+ * (`staleness.maxAgeMs`), or a replay connection failure, transparently fall
39
+ * back to a full fresh connect. Default true.
40
+ *
41
+ * The fallback is skipped when the replay's connect ended in a credential
42
+ * rejection (the SDK's `UnauthorizedError`, or a 401 status when an OAuth
43
+ * provider is configured): the fallback reuses the same auth, so retrying
44
+ * cannot succeed and would re-drive an OAuth authorization flow. Those
45
+ * failures reject with `MCPCacheError` instead. A 403 does not skip it — the
46
+ * SDK's OAuth side effects only occur on 401, and gateways commonly answer
47
+ * unknown methods with 403.
48
+ */
20
49
  reconnectOnExpiry?: boolean;
21
50
  toolNamePrefix?: string;
22
51
  includeTools?: readonly string[];
23
52
  excludeTools?: readonly string[];
24
53
  resources?: ResourcesOption;
25
54
  emitProgress?: boolean;
55
+ /** Doom-loop identities for wrapped tools, keyed by unprefixed MCP name. */
56
+ loopKeys?: CreateMCPToolsOptions['loopKeys'];
26
57
  autoRefreshOnListChanged?: boolean;
27
58
  cacheCredentials?: boolean;
28
59
  clientInfo?: {
@@ -34,8 +65,12 @@ export interface RehydrateMCPToolsOptions {
34
65
  * Rebuild an {@link MCPToolsHandle} from a cached snapshot. On the happy path we
35
66
  * reconnect the transport and rebuild tools directly from the snapshot —
36
67
  * skipping `listTools()`. If cached tokens are expired, credentials are missing,
37
- * or the connection fails, we transparently fall back to a full
38
- * {@link createMCPTools} (unless `reconnectOnExpiry` is false).
68
+ * the snapshot is older than `staleness.maxAgeMs`, or the replay connection
69
+ * fails, we transparently fall back to a fresh connect (unless
70
+ * `reconnectOnExpiry` is false) — with one exception: a credential rejection is
71
+ * not retried, because the fallback reuses the same auth and re-attempting
72
+ * would re-drive an OAuth authorization flow. See {@link
73
+ * RehydrateMCPToolsOptions.reconnectOnExpiry}.
39
74
  */
40
75
  export declare function rehydrateMCPTools(options: RehydrateMCPToolsOptions): Promise<MCPToolsHandle>;
41
76
  //# sourceMappingURL=rehydrate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rehydrate.d.ts","sourceRoot":"","sources":["../src/rehydrate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAKlE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE9E,OAAO,KAAK,EAEV,kBAAkB,EAClB,cAAc,EACd,eAAe,EAChB,MAAM,YAAY,CAAC;AAKpB,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,gEAAgE;IAChE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,qBAAqB,CAAC,EAAE,uBAAuB,CAAC;IAChD,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,8CAA8C;IAC9C,KAAK,CAAC,EAAE;QACN,KAAK,EAAE,aAAa,CAAC;QACrB,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;IACF,yFAAyF;IACzF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAG5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AA2GD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,cAAc,CAAC,CA+DzB"}
1
+ {"version":3,"file":"rehydrate.d.ts","sourceRoot":"","sources":["../src/rehydrate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAOlE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE9E,OAAO,KAAK,EACV,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EACtB,cAAc,EACd,eAAe,EAChB,MAAM,YAAY,CAAC;AAKpB,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,gEAAgE;IAChE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,qBAAqB,CAAC,EAAE,uBAAuB,CAAC;IAChD,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,kEAAkE;IAClE,mBAAmB,CAAC,EAAE,sBAAsB,CAAC;IAC7C,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,SAAS,CAAC,EAAE;QACV,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE;QACN,KAAK,EAAE,aAAa,CAAC;QACrB,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;IACF;;;;;;;;;;;;OAYG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAG5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,qBAAqB,CAAC,UAAU,CAAC,CAAC;IAC7C,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAoaD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,cAAc,CAAC,CAgHzB"}