@ai-sdk/mcp 2.0.0 → 2.0.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @ai-sdk/mcp
2
2
 
3
+ ## 2.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 241a8c5: Add Streamable HTTP session hooks, cached initialize metadata, and detach-on-close support for reattaching to MCP sessions.
8
+
3
9
  ## 2.0.0
4
10
 
5
11
  ### Major Changes
package/README.md CHANGED
@@ -76,7 +76,7 @@ const result = streamText({
76
76
  model: 'openai/gpt-5.4',
77
77
  tools: await mcpClient.tools(),
78
78
  prompt: 'Use the available tools to answer the user question.',
79
- onFinish: async () => {
79
+ onEnd: async () => {
80
80
  await mcpClient.close();
81
81
  },
82
82
  });
@@ -93,12 +93,35 @@ HTTP is recommended for production deployments:
93
93
  ```ts
94
94
  import { createMCPClient } from '@ai-sdk/mcp';
95
95
 
96
+ const savedSession = await loadMcpSession();
97
+ let currentSessionId = savedSession?.sessionId;
98
+
96
99
  const mcpClient = await createMCPClient({
97
100
  transport: {
98
101
  type: 'http',
99
102
  url: 'https://your-server.com/mcp',
103
+ initialSessionId: savedSession?.sessionId,
104
+ initialProtocolVersion: savedSession?.initializeResult.protocolVersion,
105
+ terminateSessionOnClose: false,
106
+ onSessionIdChange: sessionId => {
107
+ currentSessionId = sessionId;
108
+ },
109
+ onSessionExpired: sessionId => {
110
+ if (currentSessionId === sessionId) {
111
+ currentSessionId = undefined;
112
+ void clearMcpSession();
113
+ }
114
+ },
100
115
  },
116
+ initialInitializeResult: savedSession?.initializeResult,
101
117
  });
118
+
119
+ if (currentSessionId) {
120
+ await saveMcpSession({
121
+ sessionId: currentSessionId,
122
+ initializeResult: mcpClient.initializeResult,
123
+ });
124
+ }
102
125
  ```
103
126
 
104
127
  SSE is also supported for MCP servers that use Server-Sent Events:
package/dist/index.d.ts CHANGED
@@ -273,6 +273,37 @@ type MCPTransportConfig = {
273
273
  * @default 'error'
274
274
  */
275
275
  redirect?: 'follow' | 'error';
276
+ /**
277
+ * Initial MCP session id to send with resumed Streamable HTTP requests after
278
+ * initialization.
279
+ * Only used by the HTTP transport.
280
+ */
281
+ initialSessionId?: string;
282
+ /**
283
+ * Initial MCP protocol version to send before initialize negotiates one.
284
+ * Only used by the HTTP transport.
285
+ */
286
+ initialProtocolVersion?: string;
287
+ /**
288
+ * Called when the Streamable HTTP server creates, changes, or clears the MCP
289
+ * session id.
290
+ * Only used by the HTTP transport.
291
+ */
292
+ onSessionIdChange?: (sessionId: string | undefined) => void;
293
+ /**
294
+ * Called when a Streamable HTTP request returns 404 for an existing MCP
295
+ * session id. The transport clears the session id before reporting the
296
+ * underlying HTTP error.
297
+ * Only used by the HTTP transport.
298
+ */
299
+ onSessionExpired?: (sessionId: string) => void;
300
+ /**
301
+ * Whether close() should send DELETE for the current MCP session id.
302
+ * Set to false when the application intends to reattach to the session later.
303
+ * Only used by the HTTP transport.
304
+ * @default true
305
+ */
306
+ terminateSessionOnClose?: boolean;
276
307
  /**
277
308
  * Optional custom fetch implementation to use for HTTP requests.
278
309
  * Useful for runtimes that need a request-local fetch.
@@ -337,6 +368,34 @@ declare const ClientCapabilitiesSchema: z.ZodObject<{
337
368
  }, z.core.$loose>>;
338
369
  }, z.core.$loose>;
339
370
  type ClientCapabilities = z.infer<typeof ClientCapabilitiesSchema>;
371
+ declare const InitializeResultSchema: z.ZodObject<{
372
+ _meta: z.ZodOptional<z.ZodObject<{}, z.core.$loose>>;
373
+ protocolVersion: z.ZodString;
374
+ capabilities: z.ZodObject<{
375
+ experimental: z.ZodOptional<z.ZodObject<{}, z.core.$loose>>;
376
+ logging: z.ZodOptional<z.ZodObject<{}, z.core.$loose>>;
377
+ prompts: z.ZodOptional<z.ZodObject<{
378
+ listChanged: z.ZodOptional<z.ZodBoolean>;
379
+ }, z.core.$loose>>;
380
+ resources: z.ZodOptional<z.ZodObject<{
381
+ subscribe: z.ZodOptional<z.ZodBoolean>;
382
+ listChanged: z.ZodOptional<z.ZodBoolean>;
383
+ }, z.core.$loose>>;
384
+ tools: z.ZodOptional<z.ZodObject<{
385
+ listChanged: z.ZodOptional<z.ZodBoolean>;
386
+ }, z.core.$loose>>;
387
+ elicitation: z.ZodOptional<z.ZodObject<{
388
+ applyDefaults: z.ZodOptional<z.ZodBoolean>;
389
+ }, z.core.$loose>>;
390
+ }, z.core.$loose>;
391
+ serverInfo: z.ZodObject<{
392
+ name: z.ZodString;
393
+ version: z.ZodString;
394
+ title: z.ZodOptional<z.ZodString>;
395
+ }, z.core.$loose>;
396
+ instructions: z.ZodOptional<z.ZodString>;
397
+ }, z.core.$loose>;
398
+ type InitializeResult = z.infer<typeof InitializeResultSchema>;
340
399
  type PaginatedRequest = Request & {
341
400
  params?: BaseParams & {
342
401
  cursor?: string;
@@ -513,6 +572,12 @@ interface MCPClientConfig {
513
572
  transport: MCPTransportConfig | MCPTransport;
514
573
  /** Optional callback for uncaught errors */
515
574
  onUncaughtError?: (error: unknown) => void;
575
+ /**
576
+ * Initialize result from a previous MCP session. When provided, the client
577
+ * starts the transport and reuses this metadata without sending a new
578
+ * initialize request.
579
+ */
580
+ initialInitializeResult?: InitializeResult;
516
581
  /** Optional client name, defaults to 'ai-sdk-mcp-client' */
517
582
  clientName?: string;
518
583
  /**
@@ -537,6 +602,11 @@ interface MCPClient {
537
602
  * @see https://modelcontextprotocol.io/specification/2025-11-25/schema#implementation
538
603
  */
539
604
  readonly serverInfo: Configuration;
605
+ /**
606
+ * The full initialize result used by this client, either from the server
607
+ * during initialization or from `initialInitializeResult`.
608
+ */
609
+ readonly initializeResult: InitializeResult;
540
610
  /**
541
611
  * Optional instructions provided by the server during the initialize handshake.
542
612
  *
@@ -651,4 +721,4 @@ declare function readMCPAppResource({ client, uri, options, }: {
651
721
  options?: RequestOptions;
652
722
  }): Promise<MCPAppResource>;
653
723
 
654
- export { type CallToolResult, type Configuration, type ElicitResult, ElicitResultSchema, type ElicitationRequest, ElicitationRequestSchema, type JSONRPCError, type JSONRPCMessage, type JSONRPCNotification, type JSONRPCRequest, type JSONRPCResponse, type ListToolsResult, type MCPAppResource, type MCPAppResourceCSP, type MCPAppResourceMeta, type MCPClient, type ClientCapabilities as MCPClientCapabilities, type MCPClientConfig, type MCPTransport, MCP_APP_MIME_TYPE, type McpProviderMetadata, type OAuthAuthorizationServerInformation, type OAuthClientInformation, type OAuthClientMetadata, type OAuthClientProvider, type OAuthTokens, UnauthorizedError, auth, createMCPClient, type MCPClient as experimental_MCPClient, type ClientCapabilities as experimental_MCPClientCapabilities, type MCPClientConfig as experimental_MCPClientConfig, createMCPClient as experimental_createMCPClient, mcpAppClientCapabilities, readMCPAppResource, splitMCPAppTools };
724
+ export { type CallToolResult, type Configuration, type ElicitResult, ElicitResultSchema, type ElicitationRequest, ElicitationRequestSchema, type InitializeResult, type JSONRPCError, type JSONRPCMessage, type JSONRPCNotification, type JSONRPCRequest, type JSONRPCResponse, type ListToolsResult, type MCPAppResource, type MCPAppResourceCSP, type MCPAppResourceMeta, type MCPClient, type ClientCapabilities as MCPClientCapabilities, type MCPClientConfig, type MCPTransport, MCP_APP_MIME_TYPE, type McpProviderMetadata, type OAuthAuthorizationServerInformation, type OAuthClientInformation, type OAuthClientMetadata, type OAuthClientProvider, type OAuthTokens, UnauthorizedError, auth, createMCPClient, type MCPClient as experimental_MCPClient, type ClientCapabilities as experimental_MCPClientCapabilities, type MCPClientConfig as experimental_MCPClientConfig, createMCPClient as experimental_createMCPClient, mcpAppClientCapabilities, readMCPAppResource, splitMCPAppTools };
package/dist/index.js CHANGED
@@ -1503,6 +1503,11 @@ var HttpMCPTransport = class {
1503
1503
  headers,
1504
1504
  authProvider,
1505
1505
  redirect = "error",
1506
+ initialSessionId,
1507
+ initialProtocolVersion,
1508
+ onSessionIdChange,
1509
+ onSessionExpired,
1510
+ terminateSessionOnClose = true,
1506
1511
  fetch: fetchFn
1507
1512
  }) {
1508
1513
  this.inboundReconnectAttempts = 0;
@@ -1516,19 +1521,27 @@ var HttpMCPTransport = class {
1516
1521
  this.headers = headers;
1517
1522
  this.authProvider = authProvider;
1518
1523
  this.redirectMode = redirect;
1524
+ this.sessionId = initialSessionId;
1525
+ this.protocolVersion = initialProtocolVersion;
1526
+ this.onSessionIdChange = onSessionIdChange;
1527
+ this.onSessionExpired = onSessionExpired;
1528
+ this.terminateSessionOnClose = terminateSessionOnClose;
1519
1529
  this.fetchFn = fetchFn != null ? fetchFn : globalThis.fetch;
1520
1530
  }
1521
1531
  setProtocolVersion(version) {
1522
1532
  this.protocolVersion = version;
1523
1533
  }
1524
- async commonHeaders(base) {
1534
+ async commonHeaders({
1535
+ base,
1536
+ includeSessionId = true
1537
+ }) {
1525
1538
  var _a3;
1526
1539
  const headers = {
1527
1540
  ...this.headers,
1528
1541
  ...base,
1529
1542
  "mcp-protocol-version": (_a3 = this.protocolVersion) != null ? _a3 : LATEST_PROTOCOL_VERSION
1530
1543
  };
1531
- if (this.sessionId) {
1544
+ if (includeSessionId && this.sessionId) {
1532
1545
  headers["mcp-session-id"] = this.sessionId;
1533
1546
  }
1534
1547
  if (this.authProvider) {
@@ -1543,6 +1556,27 @@ var HttpMCPTransport = class {
1543
1556
  getRuntimeEnvironmentUserAgent2()
1544
1557
  );
1545
1558
  }
1559
+ setSessionId(sessionId) {
1560
+ var _a3;
1561
+ if (this.sessionId === sessionId) {
1562
+ return;
1563
+ }
1564
+ this.sessionId = sessionId;
1565
+ (_a3 = this.onSessionIdChange) == null ? void 0 : _a3.call(this, sessionId);
1566
+ }
1567
+ applySessionIdFromResponse(response) {
1568
+ const sessionId = response.headers.get("mcp-session-id");
1569
+ if (sessionId) {
1570
+ this.setSessionId(sessionId);
1571
+ }
1572
+ }
1573
+ expireSessionId(sessionId) {
1574
+ var _a3;
1575
+ if (this.sessionId === sessionId) {
1576
+ this.setSessionId(void 0);
1577
+ }
1578
+ (_a3 = this.onSessionExpired) == null ? void 0 : _a3.call(this, sessionId);
1579
+ }
1546
1580
  /**
1547
1581
  * Runs a single OAuth recovery flow for concurrent 401 responses.
1548
1582
  */
@@ -1574,8 +1608,8 @@ var HttpMCPTransport = class {
1574
1608
  var _a3, _b3, _c;
1575
1609
  (_a3 = this.inboundSseConnection) == null ? void 0 : _a3.close();
1576
1610
  try {
1577
- if (this.sessionId && this.abortController && !this.abortController.signal.aborted) {
1578
- const headers = await this.commonHeaders({});
1611
+ if (this.sessionId && this.terminateSessionOnClose && this.abortController && !this.abortController.signal.aborted) {
1612
+ const headers = await this.commonHeaders({ base: {} });
1579
1613
  await this.fetchFn(this.url.href, {
1580
1614
  method: "DELETE",
1581
1615
  headers,
@@ -1592,9 +1626,14 @@ var HttpMCPTransport = class {
1592
1626
  const attempt = async (triedAuth = false) => {
1593
1627
  var _a3, _b3, _c, _d, _e, _f, _g;
1594
1628
  try {
1629
+ const isInitializeRequest = "method" in message && message.method === "initialize";
1630
+ const sessionIdForRequest = isInitializeRequest ? void 0 : this.sessionId;
1595
1631
  const headers = await this.commonHeaders({
1596
- "Content-Type": "application/json",
1597
- Accept: "application/json, text/event-stream"
1632
+ base: {
1633
+ "Content-Type": "application/json",
1634
+ Accept: "application/json, text/event-stream"
1635
+ },
1636
+ includeSessionId: !isInitializeRequest
1598
1637
  });
1599
1638
  const init = {
1600
1639
  method: "POST",
@@ -1604,10 +1643,7 @@ var HttpMCPTransport = class {
1604
1643
  redirect: this.redirectMode
1605
1644
  };
1606
1645
  const response = await this.fetchFn(this.url.href, init);
1607
- const sessionId = response.headers.get("mcp-session-id");
1608
- if (sessionId) {
1609
- this.sessionId = sessionId;
1610
- }
1646
+ this.applySessionIdFromResponse(response);
1611
1647
  if (response.status === 401 && this.authProvider && !triedAuth) {
1612
1648
  this.resourceMetadataUrl = extractResourceMetadataUrl(response);
1613
1649
  try {
@@ -1632,7 +1668,12 @@ var HttpMCPTransport = class {
1632
1668
  const text = await response.text().catch(() => null);
1633
1669
  let errorMessage = `MCP HTTP Transport Error: POSTing to endpoint (HTTP ${response.status}): ${text}`;
1634
1670
  if (response.status === 404) {
1635
- errorMessage += ". This server does not support HTTP transport. Try using `sse` transport instead";
1671
+ if (sessionIdForRequest) {
1672
+ this.expireSessionId(sessionIdForRequest);
1673
+ errorMessage += ". The MCP session expired. Create a new client without `initialSessionId` to start a fresh session";
1674
+ } else {
1675
+ errorMessage += ". This server does not support HTTP transport. Try using `sse` transport instead";
1676
+ }
1636
1677
  }
1637
1678
  const error2 = new MCPClientError({
1638
1679
  message: errorMessage,
@@ -1749,8 +1790,11 @@ var HttpMCPTransport = class {
1749
1790
  async openInboundSse(triedAuth = false, resumeToken) {
1750
1791
  var _a3, _b3, _c, _d, _e, _f;
1751
1792
  try {
1793
+ const sessionIdForRequest = this.sessionId;
1752
1794
  const headers = await this.commonHeaders({
1753
- Accept: "text/event-stream"
1795
+ base: {
1796
+ Accept: "text/event-stream"
1797
+ }
1754
1798
  });
1755
1799
  if (resumeToken) {
1756
1800
  headers["last-event-id"] = resumeToken;
@@ -1761,10 +1805,7 @@ var HttpMCPTransport = class {
1761
1805
  signal: (_a3 = this.abortController) == null ? void 0 : _a3.signal,
1762
1806
  redirect: this.redirectMode
1763
1807
  });
1764
- const sessionId = response.headers.get("mcp-session-id");
1765
- if (sessionId) {
1766
- this.sessionId = sessionId;
1767
- }
1808
+ this.applySessionIdFromResponse(response);
1768
1809
  if (response.status === 401 && this.authProvider && !triedAuth) {
1769
1810
  this.resourceMetadataUrl = extractResourceMetadataUrl(response);
1770
1811
  try {
@@ -1784,6 +1825,9 @@ var HttpMCPTransport = class {
1784
1825
  return;
1785
1826
  }
1786
1827
  if (!response.ok || !response.body) {
1828
+ if (response.status === 404 && sessionIdForRequest) {
1829
+ this.expireSessionId(sessionIdForRequest);
1830
+ }
1787
1831
  const error = new MCPClientError({
1788
1832
  message: `MCP HTTP Transport Error: GET SSE failed: ${response.status} ${response.statusText}`,
1789
1833
  statusCode: response.status,
@@ -2005,15 +2049,22 @@ var DefaultMCPClient = class {
2005
2049
  clientName = name3 != null ? name3 : "ai-sdk-mcp-client",
2006
2050
  version = CLIENT_VERSION,
2007
2051
  onUncaughtError,
2008
- capabilities
2052
+ capabilities,
2053
+ initialInitializeResult
2009
2054
  }) {
2010
2055
  this.requestMessageId = 0;
2011
2056
  this.responseHandlers = /* @__PURE__ */ new Map();
2012
2057
  this.serverCapabilities = {};
2013
2058
  this._serverInfo = { name: "", version: "" };
2059
+ this._initializeResult = {
2060
+ protocolVersion: LATEST_PROTOCOL_VERSION,
2061
+ capabilities: {},
2062
+ serverInfo: this._serverInfo
2063
+ };
2014
2064
  this.isClosed = true;
2015
2065
  this.onUncaughtError = onUncaughtError;
2016
2066
  this.clientCapabilities = capabilities != null ? capabilities : {};
2067
+ this.initialInitializeResult = initialInitializeResult;
2017
2068
  if (isCustomMcpTransport(transportConfig)) {
2018
2069
  this.transport = transportConfig;
2019
2070
  } else {
@@ -2044,6 +2095,9 @@ var DefaultMCPClient = class {
2044
2095
  get serverInfo() {
2045
2096
  return this._serverInfo;
2046
2097
  }
2098
+ get initializeResult() {
2099
+ return this._initializeResult;
2100
+ }
2047
2101
  get instructions() {
2048
2102
  return this._serverInstructions;
2049
2103
  }
@@ -2051,6 +2105,13 @@ var DefaultMCPClient = class {
2051
2105
  try {
2052
2106
  await this.transport.start();
2053
2107
  this.isClosed = false;
2108
+ if (this.initialInitializeResult) {
2109
+ const result2 = InitializeResultSchema.parse(
2110
+ this.initialInitializeResult
2111
+ );
2112
+ this.applyInitializeResult(result2);
2113
+ return this;
2114
+ }
2054
2115
  const result = await this.request({
2055
2116
  request: {
2056
2117
  method: "initialize",
@@ -2067,19 +2128,7 @@ var DefaultMCPClient = class {
2067
2128
  message: "Server sent invalid initialize result"
2068
2129
  });
2069
2130
  }
2070
- if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) {
2071
- throw new MCPClientError({
2072
- message: `Server's protocol version is not supported: ${result.protocolVersion}`
2073
- });
2074
- }
2075
- this.serverCapabilities = result.capabilities;
2076
- this._serverInfo = result.serverInfo;
2077
- if (this.transport.setProtocolVersion) {
2078
- this.transport.setProtocolVersion(result.protocolVersion);
2079
- } else {
2080
- this.transport.protocolVersion = result.protocolVersion;
2081
- }
2082
- this._serverInstructions = result.instructions;
2131
+ this.applyInitializeResult(result);
2083
2132
  await this.notification({
2084
2133
  method: "notifications/initialized"
2085
2134
  });
@@ -2089,6 +2138,22 @@ var DefaultMCPClient = class {
2089
2138
  throw error;
2090
2139
  }
2091
2140
  }
2141
+ applyInitializeResult(result) {
2142
+ if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) {
2143
+ throw new MCPClientError({
2144
+ message: `Server's protocol version is not supported: ${result.protocolVersion}`
2145
+ });
2146
+ }
2147
+ this.serverCapabilities = result.capabilities;
2148
+ this._serverInfo = result.serverInfo;
2149
+ this._initializeResult = result;
2150
+ if (this.transport.setProtocolVersion) {
2151
+ this.transport.setProtocolVersion(result.protocolVersion);
2152
+ } else {
2153
+ this.transport.protocolVersion = result.protocolVersion;
2154
+ }
2155
+ this._serverInstructions = result.instructions;
2156
+ }
2092
2157
  async close() {
2093
2158
  var _a3;
2094
2159
  if (this.isClosed) return;