@tambo-ai/react 0.54.0 → 0.55.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 (84) hide show
  1. package/README.md +49 -14
  2. package/dist/hooks/__tests__/use-tambo-threads.test.js +1 -9
  3. package/dist/hooks/__tests__/use-tambo-threads.test.js.map +1 -1
  4. package/dist/mcp/__tests__/mcp-client.test.js +21 -21
  5. package/dist/mcp/__tests__/mcp-client.test.js.map +1 -1
  6. package/dist/mcp/__tests__/tambo-mcp-provider.test.js +115 -0
  7. package/dist/mcp/__tests__/tambo-mcp-provider.test.js.map +1 -1
  8. package/dist/mcp/mcp-client.d.ts +61 -210
  9. package/dist/mcp/mcp-client.d.ts.map +1 -1
  10. package/dist/mcp/mcp-client.js +43 -17
  11. package/dist/mcp/mcp-client.js.map +1 -1
  12. package/dist/mcp/tambo-mcp-provider.d.ts +1 -1
  13. package/dist/mcp/tambo-mcp-provider.d.ts.map +1 -1
  14. package/dist/mcp/tambo-mcp-provider.js +22 -4
  15. package/dist/mcp/tambo-mcp-provider.js.map +1 -1
  16. package/dist/providers/__tests__/tambo-interactable-provider-partial-updates.test.js +15 -0
  17. package/dist/providers/__tests__/tambo-interactable-provider-partial-updates.test.js.map +1 -1
  18. package/dist/providers/__tests__/tambo-registry-provider.test.js +44 -9
  19. package/dist/providers/__tests__/tambo-registry-provider.test.js.map +1 -1
  20. package/dist/providers/hooks/use-tambo-session-token.d.ts +2 -2
  21. package/dist/providers/hooks/use-tambo-session-token.js +2 -2
  22. package/dist/providers/hooks/use-tambo-session-token.js.map +1 -1
  23. package/dist/providers/tambo-context-helpers-provider.d.ts +7 -2
  24. package/dist/providers/tambo-context-helpers-provider.d.ts.map +1 -1
  25. package/dist/providers/tambo-context-helpers-provider.js +12 -7
  26. package/dist/providers/tambo-context-helpers-provider.js.map +1 -1
  27. package/dist/providers/tambo-interactable-provider.d.ts.map +1 -1
  28. package/dist/providers/tambo-interactable-provider.js +3 -0
  29. package/dist/providers/tambo-interactable-provider.js.map +1 -1
  30. package/dist/providers/tambo-prop-stream-provider/index.d.ts +1 -1
  31. package/dist/providers/tambo-prop-stream-provider/index.js +1 -1
  32. package/dist/providers/tambo-prop-stream-provider/index.js.map +1 -1
  33. package/dist/providers/tambo-provider.d.ts +1 -1
  34. package/dist/providers/tambo-provider.js +1 -1
  35. package/dist/providers/tambo-provider.js.map +1 -1
  36. package/dist/providers/tambo-registry-provider.d.ts.map +1 -1
  37. package/dist/providers/tambo-registry-provider.js +12 -1
  38. package/dist/providers/tambo-registry-provider.js.map +1 -1
  39. package/dist/util/validate-component-name.d.ts +7 -0
  40. package/dist/util/validate-component-name.d.ts.map +1 -0
  41. package/dist/util/validate-component-name.js +14 -0
  42. package/dist/util/validate-component-name.js.map +1 -0
  43. package/esm/hooks/__tests__/use-tambo-threads.test.js +1 -9
  44. package/esm/hooks/__tests__/use-tambo-threads.test.js.map +1 -1
  45. package/esm/mcp/__tests__/mcp-client.test.js +21 -21
  46. package/esm/mcp/__tests__/mcp-client.test.js.map +1 -1
  47. package/esm/mcp/__tests__/tambo-mcp-provider.test.js +83 -1
  48. package/esm/mcp/__tests__/tambo-mcp-provider.test.js.map +1 -1
  49. package/esm/mcp/mcp-client.d.ts +61 -210
  50. package/esm/mcp/mcp-client.d.ts.map +1 -1
  51. package/esm/mcp/mcp-client.js +43 -17
  52. package/esm/mcp/mcp-client.js.map +1 -1
  53. package/esm/mcp/tambo-mcp-provider.d.ts +1 -1
  54. package/esm/mcp/tambo-mcp-provider.d.ts.map +1 -1
  55. package/esm/mcp/tambo-mcp-provider.js +22 -4
  56. package/esm/mcp/tambo-mcp-provider.js.map +1 -1
  57. package/esm/providers/__tests__/tambo-interactable-provider-partial-updates.test.js +15 -0
  58. package/esm/providers/__tests__/tambo-interactable-provider-partial-updates.test.js.map +1 -1
  59. package/esm/providers/__tests__/tambo-registry-provider.test.js +44 -9
  60. package/esm/providers/__tests__/tambo-registry-provider.test.js.map +1 -1
  61. package/esm/providers/hooks/use-tambo-session-token.d.ts +2 -2
  62. package/esm/providers/hooks/use-tambo-session-token.js +2 -2
  63. package/esm/providers/hooks/use-tambo-session-token.js.map +1 -1
  64. package/esm/providers/tambo-context-helpers-provider.d.ts +7 -2
  65. package/esm/providers/tambo-context-helpers-provider.d.ts.map +1 -1
  66. package/esm/providers/tambo-context-helpers-provider.js +12 -7
  67. package/esm/providers/tambo-context-helpers-provider.js.map +1 -1
  68. package/esm/providers/tambo-interactable-provider.d.ts.map +1 -1
  69. package/esm/providers/tambo-interactable-provider.js +3 -0
  70. package/esm/providers/tambo-interactable-provider.js.map +1 -1
  71. package/esm/providers/tambo-prop-stream-provider/index.d.ts +1 -1
  72. package/esm/providers/tambo-prop-stream-provider/index.js +1 -1
  73. package/esm/providers/tambo-prop-stream-provider/index.js.map +1 -1
  74. package/esm/providers/tambo-provider.d.ts +1 -1
  75. package/esm/providers/tambo-provider.js +1 -1
  76. package/esm/providers/tambo-provider.js.map +1 -1
  77. package/esm/providers/tambo-registry-provider.d.ts.map +1 -1
  78. package/esm/providers/tambo-registry-provider.js +12 -1
  79. package/esm/providers/tambo-registry-provider.js.map +1 -1
  80. package/esm/util/validate-component-name.d.ts +7 -0
  81. package/esm/util/validate-component-name.d.ts.map +1 -0
  82. package/esm/util/validate-component-name.js +11 -0
  83. package/esm/util/validate-component-name.js.map +1 -0
  84. package/package.json +12 -11
@@ -23,8 +23,10 @@ class MCPClient {
23
23
  client;
24
24
  transport;
25
25
  transportType;
26
+ sessionId;
26
27
  endpoint;
27
28
  headers;
29
+ authProvider;
28
30
  /**
29
31
  * Tracks an in-flight reconnect so concurrent triggers coalesce
30
32
  * (single-flight). When set, additional calls to `reconnect()` or
@@ -66,26 +68,33 @@ class MCPClient {
66
68
  * @param transportType - The transport to use for the MCP client
67
69
  * @param headers - Optional custom headers to include in requests
68
70
  */
69
- constructor(endpoint, transportType, headers = {}) {
71
+ constructor(endpoint, transportType, headers, authProvider, sessionId) {
70
72
  this.endpoint = endpoint;
71
- this.headers = headers;
73
+ this.headers = headers ?? {};
74
+ this.authProvider = authProvider;
72
75
  this.transportType = transportType;
73
- this.transport = this.initializeTransport(undefined);
76
+ this.transport = this.initializeTransport(sessionId);
74
77
  this.client = this.initializeClient();
75
78
  }
76
79
  /**
77
- * Creates and initializes a new MCPClient instance.
78
- * This is the recommended way to create an MCPClient as it handles both
79
- * instantiation and connection setup.
80
+ * Creates and initializes a new MCPClient instance. This is the recommended
81
+ * way to create an MCPClient as it handles both instantiation and connection
82
+ * setup.
80
83
  * @param endpoint - The URL of the MCP server to connect to
81
84
  * @param transportType - The transport type to use for the MCP client. Defaults to HTTP.
82
85
  * @param headers - Optional custom headers to include in requests
86
+ * @param authProvider - Optional auth provider to use for authentication
87
+ * @param sessionId - Optional session id to use for the MCP client - if not
88
+ * provided, a new session will be created
83
89
  * @returns A connected MCPClient instance ready for use
84
- * @throws Will throw an error if connection fails
90
+ * @throws {Error} Will throw an error if connection fails
85
91
  */
86
- static async create(endpoint, transportType = MCPTransport.HTTP, headers) {
87
- const mcpClient = new MCPClient(endpoint, transportType, headers);
92
+ static async create(endpoint, transportType = MCPTransport.HTTP, headers, authProvider, sessionId) {
93
+ const mcpClient = new MCPClient(endpoint, transportType, headers, authProvider, sessionId);
88
94
  await mcpClient.client.connect(mcpClient.transport);
95
+ if ("sessionId" in mcpClient.transport) {
96
+ mcpClient.sessionId = mcpClient.transport.sessionId;
97
+ }
89
98
  return mcpClient;
90
99
  }
91
100
  /**
@@ -105,6 +114,7 @@ class MCPClient {
105
114
  * @param newSession - Whether to create a new session (true) or reuse existing session ID (false)
106
115
  * @param reportErrorOnClose - Whether to report errors when closing the client
107
116
  * Note that only StreamableHTTPClientTransport supports session IDs.
117
+ * @returns A promise that resolves when the reconnect is complete
108
118
  */
109
119
  async reconnect(newSession = false, reportErrorOnClose = true) {
110
120
  // If a reconnect is already running, coalesce into it.
@@ -117,11 +127,7 @@ class MCPClient {
117
127
  this.reconnectTimer = undefined;
118
128
  }
119
129
  const doReconnect = async () => {
120
- const sessionId = newSession
121
- ? undefined
122
- : "sessionId" in this.transport
123
- ? this.transport.sessionId
124
- : undefined;
130
+ const sessionId = newSession ? undefined : this.sessionId;
125
131
  // Prevent re-entrant onclose during deliberate close by detaching
126
132
  // the handler from the previous client instance.
127
133
  const prevClient = this.client;
@@ -138,6 +144,14 @@ class MCPClient {
138
144
  this.transport = this.initializeTransport(sessionId);
139
145
  this.client = this.initializeClient();
140
146
  await this.client.connect(this.transport);
147
+ // We may have gotten a session id from the server, so we need to set it
148
+ if ("sessionId" in this.transport) {
149
+ this.sessionId = this.transport.sessionId;
150
+ if (sessionId !== this.sessionId) {
151
+ // This is a pretty unusual thing to happen, but it might be possible?
152
+ console.warn("Session id mismatch", sessionId, this.sessionId);
153
+ }
154
+ }
141
155
  };
142
156
  this.reconnecting = (async () => {
143
157
  try {
@@ -161,6 +175,7 @@ class MCPClient {
161
175
  }
162
176
  /**
163
177
  * Compute the next backoff delay with symmetric jitter.
178
+ * @returns The next backoff delay in milliseconds
164
179
  */
165
180
  computeBackoffDelayMs() {
166
181
  const base = Math.min(MCPClient.BACKOFF_MAX_MS, MCPClient.BACKOFF_INITIAL_MS *
@@ -206,13 +221,15 @@ class MCPClient {
206
221
  initializeTransport(sessionId) {
207
222
  if (this.transportType === MCPTransport.SSE) {
208
223
  return new sse_js_1.SSEClientTransport(new URL(this.endpoint), {
224
+ authProvider: this.authProvider,
209
225
  requestInit: { headers: this.headers },
210
226
  });
211
227
  }
212
228
  else {
213
229
  return new streamableHttp_js_1.StreamableHTTPClientTransport(new URL(this.endpoint), {
214
- sessionId,
230
+ authProvider: this.authProvider,
215
231
  requestInit: { headers: this.headers },
232
+ sessionId,
216
233
  });
217
234
  }
218
235
  }
@@ -228,7 +245,7 @@ class MCPClient {
228
245
  * Retrieves a complete list of all available tools from the MCP server.
229
246
  * Handles pagination automatically by following cursors until all tools are fetched.
230
247
  * @returns A complete list of all available tools and their descriptions
231
- * @throws Will throw an error if any server request fails during pagination
248
+ * @throws {Error} Will throw an error if any server request fails during pagination
232
249
  */
233
250
  async listTools() {
234
251
  const allTools = [];
@@ -255,12 +272,21 @@ class MCPClient {
255
272
  }
256
273
  return allTools;
257
274
  }
275
+ getServerCapabilities() {
276
+ return this.client.getServerCapabilities();
277
+ }
278
+ getServerVersion() {
279
+ return this.client.getServerVersion();
280
+ }
281
+ getInstructions() {
282
+ return this.client.getInstructions();
283
+ }
258
284
  /**
259
285
  * Calls a specific tool on the MCP server with the provided arguments.
260
286
  * @param name - The name of the tool to call
261
287
  * @param args - Arguments to pass to the tool, must match the tool's expected schema
262
288
  * @returns The result from the tool execution
263
- * @throws Will throw an error if the tool call fails or if arguments are invalid
289
+ * @throws {Error} Will throw an error if the tool call fails or if arguments are invalid
264
290
  */
265
291
  async callTool(name, args) {
266
292
  const result = await this.client.callTool({
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-client.js","sourceRoot":"","sources":["../../src/mcp/mcp-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { JSONSchema7 } from \"json-schema\";\n\nexport enum MCPTransport {\n SSE = \"sse\",\n HTTP = \"http\",\n}\n/**\n * A client for interacting with MCP (Model Context Protocol) servers.\n * Provides a simple interface for listing and calling tools exposed by the server.\n * @example\n * ```typescript\n * const mcp = await MCPClient.create('https://api.example.com/mcp');\n * const tools = await mcp.listTools();\n * const result = await mcp.callTool('toolName', { arg1: 'value1' });\n * ```\n */\nexport class MCPClient {\n private client: Client;\n private transport: SSEClientTransport | StreamableHTTPClientTransport;\n private transportType: MCPTransport;\n private endpoint: string;\n private headers: Record<string, string>;\n /**\n * Tracks an in-flight reconnect so concurrent triggers coalesce\n * (single-flight). When set, additional calls to `reconnect()` or\n * the automatic `onclose` handler will await the same Promise instead of\n * starting another reconnect sequence.\n */\n private reconnecting?: Promise<void>;\n /**\n * Timer id for a scheduled automatic reconnect (used by `onclose`).\n * Present only while waiting for the backoff delay to elapse.\n */\n private reconnectTimer?: ReturnType<typeof setTimeout>;\n /**\n * Count of consecutive automatic reconnect failures used to compute\n * exponential backoff. Reset to 0 after a successful connection.\n */\n private backoffAttempts = 0;\n\n /**\n * Backoff policy (discoverable constants)\n * - BACKOFF_INITIAL_MS: initial delay for the first automatic retry\n * - BACKOFF_MULTIPLIER: exponential growth factor for each failed attempt\n * - BACKOFF_MAX_MS: upper bound for the delay\n * - BACKOFF_JITTER_RATIO: jitter range as a fraction of the base delay\n *\n * Jitter is applied symmetrically in [-ratio, +ratio]. For example, with a\n * 500ms base delay and 0.2 ratio, the actual delay is in [400ms, 600ms].\n *\n * The backoff applies only to automatic reconnects started from the\n * `onclose` handler. Explicit/manual calls to `reconnect()` run immediately\n * (no backoff), and will preempt any scheduled automatic attempt.\n */\n static readonly BACKOFF_INITIAL_MS = 500;\n static readonly BACKOFF_MULTIPLIER = 2;\n static readonly BACKOFF_MAX_MS = 30_000;\n static readonly BACKOFF_JITTER_RATIO = 0.2;\n\n /**\n * Private constructor to enforce using the static create method.\n * @param endpoint - The URL of the MCP server to connect to\n * @param transportType - The transport to use for the MCP client\n * @param headers - Optional custom headers to include in requests\n */\n private constructor(\n endpoint: string,\n transportType: MCPTransport,\n headers: Record<string, string> = {},\n ) {\n this.endpoint = endpoint;\n this.headers = headers;\n this.transportType = transportType;\n this.transport = this.initializeTransport(undefined);\n this.client = this.initializeClient();\n }\n\n /**\n * Creates and initializes a new MCPClient instance.\n * This is the recommended way to create an MCPClient as it handles both\n * instantiation and connection setup.\n * @param endpoint - The URL of the MCP server to connect to\n * @param transportType - The transport type to use for the MCP client. Defaults to HTTP.\n * @param headers - Optional custom headers to include in requests\n * @returns A connected MCPClient instance ready for use\n * @throws Will throw an error if connection fails\n */\n static async create(\n endpoint: string,\n transportType: MCPTransport = MCPTransport.HTTP,\n headers?: Record<string, string>,\n ): Promise<MCPClient> {\n const mcpClient = new MCPClient(endpoint, transportType, headers);\n await mcpClient.client.connect(mcpClient.transport);\n return mcpClient;\n }\n /**\n * Reconnects to the MCP server, optionally retaining the same session ID.\n *\n * Single‑flight semantics:\n * - If a reconnect is already in progress (triggered either manually or by\n * the automatic `onclose` handler), additional calls will await the\n * in-flight reconnect rather than start another one.\n * - If an automatic reconnect has been scheduled but not yet started (i.e.,\n * we are waiting in a backoff delay), calling `reconnect()` manually will\n * cancel the scheduled attempt and perform an immediate reconnect.\n *\n * Backoff policy:\n * - Backoff delays with jitter are applied only for automatic reconnects\n * (via `onclose`). Manual calls to `reconnect()` do not use backoff.\n * @param newSession - Whether to create a new session (true) or reuse existing session ID (false)\n * @param reportErrorOnClose - Whether to report errors when closing the client\n * Note that only StreamableHTTPClientTransport supports session IDs.\n */\n async reconnect(newSession = false, reportErrorOnClose = true) {\n // If a reconnect is already running, coalesce into it.\n if (this.reconnecting) {\n return await this.reconnecting;\n }\n\n // Manual reconnect preempts any scheduled automatic attempt.\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n\n const doReconnect = async () => {\n const sessionId = newSession\n ? undefined\n : \"sessionId\" in this.transport\n ? this.transport.sessionId\n : undefined;\n\n // Prevent re-entrant onclose during deliberate close by detaching\n // the handler from the previous client instance.\n const prevClient = this.client;\n // Prevent re-entrant onclose callbacks from the previous client\n prevClient.onclose = undefined;\n\n try {\n await prevClient.close();\n } catch (error) {\n if (reportErrorOnClose) {\n console.error(\"Error closing Tambo MCP Client:\", error);\n }\n }\n\n this.transport = this.initializeTransport(sessionId);\n this.client = this.initializeClient();\n await this.client.connect(this.transport);\n };\n\n this.reconnecting = (async () => {\n try {\n await doReconnect();\n // Successful manual reconnect: reset backoff.\n this.backoffAttempts = 0;\n } finally {\n this.reconnecting = undefined;\n }\n })();\n\n return await this.reconnecting;\n }\n\n /**\n * Called by the underlying MCP SDK when the connection closes.\n * Schedules an automatic reconnect with bounded exponential backoff and\n * jitter. If a reconnect is already scheduled or running, this is a no-op.\n */\n private onclose() {\n this.scheduleAutoReconnect();\n }\n\n /**\n * Compute the next backoff delay with symmetric jitter.\n */\n private computeBackoffDelayMs(): number {\n const base = Math.min(\n MCPClient.BACKOFF_MAX_MS,\n MCPClient.BACKOFF_INITIAL_MS *\n Math.pow(MCPClient.BACKOFF_MULTIPLIER, this.backoffAttempts),\n );\n const jitterRange = MCPClient.BACKOFF_JITTER_RATIO * base;\n const jitter = (Math.random() * 2 - 1) * jitterRange; // [-range, +range]\n const ms = Math.max(0, Math.round(base + jitter));\n return ms;\n }\n\n /**\n * Schedule an automatic reconnect attempt if one is not already scheduled\n * or running. Uses the backoff policy and self-reschedules on failure.\n */\n private scheduleAutoReconnect() {\n if (this.reconnecting || this.reconnectTimer) {\n return;\n }\n\n const delayMs = this.computeBackoffDelayMs();\n console.warn(\n \"Tambo MCP Client closed; attempting automatic reconnect in\",\n `${delayMs}ms`,\n );\n\n this.reconnectTimer = setTimeout(async () => {\n this.reconnectTimer = undefined;\n // Start the actual reconnect (single-flight)\n const inFlight = (this.reconnecting = this.reconnect(false, false));\n try {\n await inFlight;\n // Success: reset attempts\n this.backoffAttempts = 0;\n } catch (err) {\n // Failure: increase attempts; scheduling occurs in finally below so the\n // new timer isn't blocked by `this.reconnecting` being truthy.\n this.backoffAttempts += 1;\n console.warn(\n \"Automatic reconnect failed; will retry with backoff.\",\n err,\n );\n } finally {\n this.reconnecting = undefined;\n if (this.backoffAttempts > 0) {\n this.scheduleAutoReconnect();\n }\n }\n }, delayMs);\n }\n\n private initializeTransport(sessionId: string | undefined) {\n if (this.transportType === MCPTransport.SSE) {\n return new SSEClientTransport(new URL(this.endpoint), {\n requestInit: { headers: this.headers },\n });\n } else {\n return new StreamableHTTPClientTransport(new URL(this.endpoint), {\n sessionId,\n requestInit: { headers: this.headers },\n });\n }\n }\n\n private initializeClient() {\n const client = new Client({\n name: \"tambo-mcp-client\",\n version: \"1.0.0\",\n });\n client.onclose = this.onclose.bind(this);\n return client;\n }\n\n /**\n * Retrieves a complete list of all available tools from the MCP server.\n * Handles pagination automatically by following cursors until all tools are fetched.\n * @returns A complete list of all available tools and their descriptions\n * @throws Will throw an error if any server request fails during pagination\n */\n async listTools(): Promise<MCPToolSpec[]> {\n const allTools: MCPToolSpec[] = [];\n let hasMore = true;\n let cursor: string | undefined = undefined;\n\n while (hasMore) {\n const response = await this.client.listTools({ cursor }, {});\n allTools.push(\n ...response.tools.map((tool): MCPToolSpec => {\n if (tool.inputSchema.type !== \"object\") {\n throw new Error(\n `Input schema for tool ${tool.name} is not an object`,\n );\n }\n\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema as JSONSchema7,\n };\n }),\n );\n\n if (response.nextCursor) {\n cursor = response.nextCursor;\n } else {\n hasMore = false;\n }\n }\n\n return allTools;\n }\n\n /**\n * Calls a specific tool on the MCP server with the provided arguments.\n * @param name - The name of the tool to call\n * @param args - Arguments to pass to the tool, must match the tool's expected schema\n * @returns The result from the tool execution\n * @throws Will throw an error if the tool call fails or if arguments are invalid\n */\n async callTool(name: string, args: Record<string, unknown>) {\n const result = await this.client.callTool({\n name,\n arguments: args,\n });\n return result;\n }\n}\n\n// Example usage:\n/*\nconst mcp = await MCPClient.create('https://api.example.com/mcp', MCPTransport.HTTP);\nconst tools = await mcp.listTools();\nconst result = await mcp.callTool('toolName', { arg1: 'value1' });\n*/\n\nexport interface MCPToolSpec {\n name: string;\n description?: string;\n inputSchema?: JSONSchema7;\n}\n"]}
1
+ {"version":3,"file":"mcp-client.js","sourceRoot":"","sources":["../../src/mcp/mcp-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { type OAuthClientProvider } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { JSONSchema7 } from \"json-schema\";\n\nexport enum MCPTransport {\n SSE = \"sse\",\n HTTP = \"http\",\n}\n/**\n * A client for interacting with MCP (Model Context Protocol) servers.\n * Provides a simple interface for listing and calling tools exposed by the server.\n * @example\n * ```typescript\n * const mcp = await MCPClient.create('https://api.example.com/mcp');\n * const tools = await mcp.listTools();\n * const result = await mcp.callTool('toolName', { arg1: 'value1' });\n * ```\n */\nexport class MCPClient {\n private client: Client;\n private transport: SSEClientTransport | StreamableHTTPClientTransport;\n private transportType: MCPTransport;\n public sessionId?: string;\n private endpoint: string;\n private headers: Record<string, string>;\n private authProvider?: OAuthClientProvider;\n /**\n * Tracks an in-flight reconnect so concurrent triggers coalesce\n * (single-flight). When set, additional calls to `reconnect()` or\n * the automatic `onclose` handler will await the same Promise instead of\n * starting another reconnect sequence.\n */\n private reconnecting?: Promise<void>;\n /**\n * Timer id for a scheduled automatic reconnect (used by `onclose`).\n * Present only while waiting for the backoff delay to elapse.\n */\n private reconnectTimer?: ReturnType<typeof setTimeout>;\n /**\n * Count of consecutive automatic reconnect failures used to compute\n * exponential backoff. Reset to 0 after a successful connection.\n */\n private backoffAttempts = 0;\n\n /**\n * Backoff policy (discoverable constants)\n * - BACKOFF_INITIAL_MS: initial delay for the first automatic retry\n * - BACKOFF_MULTIPLIER: exponential growth factor for each failed attempt\n * - BACKOFF_MAX_MS: upper bound for the delay\n * - BACKOFF_JITTER_RATIO: jitter range as a fraction of the base delay\n *\n * Jitter is applied symmetrically in [-ratio, +ratio]. For example, with a\n * 500ms base delay and 0.2 ratio, the actual delay is in [400ms, 600ms].\n *\n * The backoff applies only to automatic reconnects started from the\n * `onclose` handler. Explicit/manual calls to `reconnect()` run immediately\n * (no backoff), and will preempt any scheduled automatic attempt.\n */\n static readonly BACKOFF_INITIAL_MS = 500;\n static readonly BACKOFF_MULTIPLIER = 2;\n static readonly BACKOFF_MAX_MS = 30_000;\n static readonly BACKOFF_JITTER_RATIO = 0.2;\n\n /**\n * Private constructor to enforce using the static create method.\n * @param endpoint - The URL of the MCP server to connect to\n * @param transportType - The transport to use for the MCP client\n * @param headers - Optional custom headers to include in requests\n */\n private constructor(\n endpoint: string,\n transportType: MCPTransport,\n headers?: Record<string, string>,\n authProvider?: OAuthClientProvider,\n sessionId?: string,\n ) {\n this.endpoint = endpoint;\n this.headers = headers ?? {};\n this.authProvider = authProvider;\n this.transportType = transportType;\n this.transport = this.initializeTransport(sessionId);\n this.client = this.initializeClient();\n }\n\n /**\n * Creates and initializes a new MCPClient instance. This is the recommended\n * way to create an MCPClient as it handles both instantiation and connection\n * setup.\n * @param endpoint - The URL of the MCP server to connect to\n * @param transportType - The transport type to use for the MCP client. Defaults to HTTP.\n * @param headers - Optional custom headers to include in requests\n * @param authProvider - Optional auth provider to use for authentication\n * @param sessionId - Optional session id to use for the MCP client - if not\n * provided, a new session will be created\n * @returns A connected MCPClient instance ready for use\n * @throws {Error} Will throw an error if connection fails\n */\n static async create(\n endpoint: string,\n transportType: MCPTransport = MCPTransport.HTTP,\n headers: Record<string, string> | undefined,\n authProvider: OAuthClientProvider | undefined,\n sessionId: string | undefined,\n ): Promise<MCPClient> {\n const mcpClient = new MCPClient(\n endpoint,\n transportType,\n headers,\n authProvider,\n sessionId,\n );\n await mcpClient.client.connect(mcpClient.transport);\n if (\"sessionId\" in mcpClient.transport) {\n mcpClient.sessionId = mcpClient.transport.sessionId;\n }\n return mcpClient;\n }\n /**\n * Reconnects to the MCP server, optionally retaining the same session ID.\n *\n * Single‑flight semantics:\n * - If a reconnect is already in progress (triggered either manually or by\n * the automatic `onclose` handler), additional calls will await the\n * in-flight reconnect rather than start another one.\n * - If an automatic reconnect has been scheduled but not yet started (i.e.,\n * we are waiting in a backoff delay), calling `reconnect()` manually will\n * cancel the scheduled attempt and perform an immediate reconnect.\n *\n * Backoff policy:\n * - Backoff delays with jitter are applied only for automatic reconnects\n * (via `onclose`). Manual calls to `reconnect()` do not use backoff.\n * @param newSession - Whether to create a new session (true) or reuse existing session ID (false)\n * @param reportErrorOnClose - Whether to report errors when closing the client\n * Note that only StreamableHTTPClientTransport supports session IDs.\n * @returns A promise that resolves when the reconnect is complete\n */\n async reconnect(newSession = false, reportErrorOnClose = true) {\n // If a reconnect is already running, coalesce into it.\n if (this.reconnecting) {\n return await this.reconnecting;\n }\n\n // Manual reconnect preempts any scheduled automatic attempt.\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n\n const doReconnect = async () => {\n const sessionId = newSession ? undefined : this.sessionId;\n\n // Prevent re-entrant onclose during deliberate close by detaching\n // the handler from the previous client instance.\n const prevClient = this.client;\n // Prevent re-entrant onclose callbacks from the previous client\n prevClient.onclose = undefined;\n\n try {\n await prevClient.close();\n } catch (error) {\n if (reportErrorOnClose) {\n console.error(\"Error closing Tambo MCP Client:\", error);\n }\n }\n\n this.transport = this.initializeTransport(sessionId);\n this.client = this.initializeClient();\n await this.client.connect(this.transport);\n // We may have gotten a session id from the server, so we need to set it\n if (\"sessionId\" in this.transport) {\n this.sessionId = this.transport.sessionId;\n if (sessionId !== this.sessionId) {\n // This is a pretty unusual thing to happen, but it might be possible?\n console.warn(\"Session id mismatch\", sessionId, this.sessionId);\n }\n }\n };\n\n this.reconnecting = (async () => {\n try {\n await doReconnect();\n // Successful manual reconnect: reset backoff.\n this.backoffAttempts = 0;\n } finally {\n this.reconnecting = undefined;\n }\n })();\n\n return await this.reconnecting;\n }\n\n /**\n * Called by the underlying MCP SDK when the connection closes.\n * Schedules an automatic reconnect with bounded exponential backoff and\n * jitter. If a reconnect is already scheduled or running, this is a no-op.\n */\n private onclose() {\n this.scheduleAutoReconnect();\n }\n\n /**\n * Compute the next backoff delay with symmetric jitter.\n * @returns The next backoff delay in milliseconds\n */\n private computeBackoffDelayMs(): number {\n const base = Math.min(\n MCPClient.BACKOFF_MAX_MS,\n MCPClient.BACKOFF_INITIAL_MS *\n Math.pow(MCPClient.BACKOFF_MULTIPLIER, this.backoffAttempts),\n );\n const jitterRange = MCPClient.BACKOFF_JITTER_RATIO * base;\n const jitter = (Math.random() * 2 - 1) * jitterRange; // [-range, +range]\n const ms = Math.max(0, Math.round(base + jitter));\n return ms;\n }\n\n /**\n * Schedule an automatic reconnect attempt if one is not already scheduled\n * or running. Uses the backoff policy and self-reschedules on failure.\n */\n private scheduleAutoReconnect() {\n if (this.reconnecting || this.reconnectTimer) {\n return;\n }\n\n const delayMs = this.computeBackoffDelayMs();\n console.warn(\n \"Tambo MCP Client closed; attempting automatic reconnect in\",\n `${delayMs}ms`,\n );\n\n this.reconnectTimer = setTimeout(async () => {\n this.reconnectTimer = undefined;\n // Start the actual reconnect (single-flight)\n const inFlight = (this.reconnecting = this.reconnect(false, false));\n try {\n await inFlight;\n // Success: reset attempts\n this.backoffAttempts = 0;\n } catch (err) {\n // Failure: increase attempts; scheduling occurs in finally below so the\n // new timer isn't blocked by `this.reconnecting` being truthy.\n this.backoffAttempts += 1;\n console.warn(\n \"Automatic reconnect failed; will retry with backoff.\",\n err,\n );\n } finally {\n this.reconnecting = undefined;\n if (this.backoffAttempts > 0) {\n this.scheduleAutoReconnect();\n }\n }\n }, delayMs);\n }\n\n private initializeTransport(sessionId: string | undefined) {\n if (this.transportType === MCPTransport.SSE) {\n return new SSEClientTransport(new URL(this.endpoint), {\n authProvider: this.authProvider,\n requestInit: { headers: this.headers },\n });\n } else {\n return new StreamableHTTPClientTransport(new URL(this.endpoint), {\n authProvider: this.authProvider,\n requestInit: { headers: this.headers },\n sessionId,\n });\n }\n }\n\n private initializeClient() {\n const client = new Client({\n name: \"tambo-mcp-client\",\n version: \"1.0.0\",\n });\n client.onclose = this.onclose.bind(this);\n return client;\n }\n\n /**\n * Retrieves a complete list of all available tools from the MCP server.\n * Handles pagination automatically by following cursors until all tools are fetched.\n * @returns A complete list of all available tools and their descriptions\n * @throws {Error} Will throw an error if any server request fails during pagination\n */\n async listTools(): Promise<MCPToolSpec[]> {\n const allTools: MCPToolSpec[] = [];\n let hasMore = true;\n let cursor: string | undefined = undefined;\n\n while (hasMore) {\n const response = await this.client.listTools({ cursor }, {});\n allTools.push(\n ...response.tools.map((tool): MCPToolSpec => {\n if (tool.inputSchema.type !== \"object\") {\n throw new Error(\n `Input schema for tool ${tool.name} is not an object`,\n );\n }\n\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema as JSONSchema7,\n };\n }),\n );\n\n if (response.nextCursor) {\n cursor = response.nextCursor;\n } else {\n hasMore = false;\n }\n }\n\n return allTools;\n }\n\n getServerCapabilities() {\n return this.client.getServerCapabilities();\n }\n\n getServerVersion() {\n return this.client.getServerVersion();\n }\n\n getInstructions() {\n return this.client.getInstructions();\n }\n\n /**\n * Calls a specific tool on the MCP server with the provided arguments.\n * @param name - The name of the tool to call\n * @param args - Arguments to pass to the tool, must match the tool's expected schema\n * @returns The result from the tool execution\n * @throws {Error} Will throw an error if the tool call fails or if arguments are invalid\n */\n async callTool(\n name: string,\n args: Record<string, unknown>,\n ): Promise<MCPToolCallResult> {\n const result = await this.client.callTool({\n name,\n arguments: args,\n });\n return result;\n }\n}\n\n/**\n * The result of a tool call.\n * This is the same as the result of a tool call in the OpenAI SDK, but is reified here\n */\nexport type MCPToolCallResult = Awaited<\n ReturnType<typeof Client.prototype.callTool>\n>;\n\n// Example usage:\n/*\nconst mcp = await MCPClient.create('https://api.example.com/mcp', MCPTransport.HTTP);\nconst tools = await mcp.listTools();\nconst result = await mcp.callTool('toolName', { arg1: 'value1' });\n*/\n\nexport interface MCPToolSpec {\n name: string;\n description?: string;\n inputSchema?: JSONSchema7;\n}\n"]}
@@ -43,7 +43,7 @@ export declare const TamboMcpProvider: FC<{
43
43
  * For example, to forcibly disconnect and reconnect all MCP servers:
44
44
  *
45
45
  * ```tsx
46
- * const mcpServers = useMcpServers();
46
+ * const mcpServers = useTamboMcpServers();
47
47
  * mcpServers.forEach((mcpServer) => {
48
48
  * mcpServer.client?.reconnect();
49
49
  * });
@@ -1 +1 @@
1
- {"version":3,"file":"tambo-mcp-provider.d.ts","sourceRoot":"","sources":["../../src/mcp/tambo-mcp-provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAEZ,EAAE,EAIH,MAAM,OAAO,CAAC;AAGf,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAsB5D;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,MAAM,EAAE,SAAS,CAAC;CACnB;AAED,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,eAAe,EAAE,KAAK,CAAC;CACxB;AAED,MAAM,MAAM,SAAS,GAAG,kBAAkB,GAAG,eAAe,CAAC;AAG7D;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC;IAChC,UAAU,EAAE,CAAC,aAAa,GAAG,MAAM,CAAC,EAAE,CAAC;IACvC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAsHA,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,kBAAkB,mBAE9B,CAAC"}
1
+ {"version":3,"file":"tambo-mcp-provider.d.ts","sourceRoot":"","sources":["../../src/mcp/tambo-mcp-provider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAEZ,EAAE,EAIH,MAAM,OAAO,CAAC;AAGf,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAsB5D;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,MAAM,EAAE,SAAS,CAAC;CACnB;AAED,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,eAAe,EAAE,KAAK,CAAC;CACxB;AAED,MAAM,MAAM,SAAS,GAAG,kBAAkB,GAAG,eAAe,CAAC;AAG7D;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC;IAChC,UAAU,EAAE,CAAC,aAAa,GAAG,MAAM,CAAC,EAAE,CAAC;IACvC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAwIA,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,kBAAkB,mBAE9B,CAAC"}
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.useTamboMcpServers = exports.TamboMcpProvider = void 0;
37
37
  exports.extractErrorMessage = extractErrorMessage;
38
+ const fast_equals_1 = require("fast-equals");
38
39
  const react_1 = __importStar(require("react"));
39
40
  const tambo_registry_provider_1 = require("../providers/tambo-registry-provider");
40
41
  const mcp_client_1 = require("./mcp-client");
@@ -76,17 +77,25 @@ const TamboMcpProvider = ({ mcpServers, children }) => {
76
77
  async function registerMcpServers(mcpServerInfos) {
77
78
  // Maps tool names to the MCP client that registered them
78
79
  const mcpServerMap = new Map();
80
+ setConnectedMcpServers((prev) =>
81
+ // remove any servers that are not in the new list
82
+ prev.filter((s) => mcpServerInfos.some((mcpServerInfo) => equalsMcpServer(s, mcpServerInfo))));
79
83
  // initialize the MCP clients, converting McpServerInfo -> McpServer
80
84
  const mcpServers = await Promise.allSettled(mcpServerInfos.map(async (mcpServerInfo) => {
81
85
  try {
82
- const client = await mcp_client_1.MCPClient.create(mcpServerInfo.url, mcpServerInfo.transport, mcpServerInfo.customHeaders);
86
+ const client = await mcp_client_1.MCPClient.create(mcpServerInfo.url, mcpServerInfo.transport, mcpServerInfo.customHeaders, undefined, // no oauth support yet
87
+ undefined);
83
88
  const connectedMcpServer = {
84
89
  ...mcpServerInfo,
85
90
  client: client,
86
91
  };
87
92
  // note because the promises may resolve in any order, the resulting
88
93
  // array may not be in the same order as the input array
89
- setConnectedMcpServers((prev) => [...prev, connectedMcpServer]);
94
+ setConnectedMcpServers((prev) => [
95
+ // replace the server if it already exists
96
+ ...prev.filter((s) => !equalsMcpServer(s, mcpServerInfo)),
97
+ connectedMcpServer,
98
+ ]);
90
99
  return connectedMcpServer;
91
100
  }
92
101
  catch (error) {
@@ -96,7 +105,11 @@ const TamboMcpProvider = ({ mcpServers, children }) => {
96
105
  };
97
106
  // note because the promises may resolve in any order, the resulting
98
107
  // array may not be in the same order as the input array
99
- setConnectedMcpServers((prev) => [...prev, failedMcpServer]);
108
+ setConnectedMcpServers((prev) => [
109
+ // replace the server if it already exists
110
+ ...prev.filter((s) => !equalsMcpServer(s, mcpServerInfo)),
111
+ failedMcpServer,
112
+ ]);
100
113
  return failedMcpServer;
101
114
  }
102
115
  }));
@@ -170,7 +183,7 @@ exports.TamboMcpProvider = TamboMcpProvider;
170
183
  * For example, to forcibly disconnect and reconnect all MCP servers:
171
184
  *
172
185
  * ```tsx
173
- * const mcpServers = useMcpServers();
186
+ * const mcpServers = useTamboMcpServers();
174
187
  * mcpServers.forEach((mcpServer) => {
175
188
  * mcpServer.client?.reconnect();
176
189
  * });
@@ -184,4 +197,9 @@ const useTamboMcpServers = () => {
184
197
  return (0, react_1.useContext)(McpProviderContext);
185
198
  };
186
199
  exports.useTamboMcpServers = useTamboMcpServers;
200
+ function equalsMcpServer(s, mcpServerInfo) {
201
+ return (s.url === mcpServerInfo.url &&
202
+ s.transport === mcpServerInfo.transport &&
203
+ (0, fast_equals_1.deepEqual)(s.customHeaders, mcpServerInfo.customHeaders));
204
+ }
187
205
  //# sourceMappingURL=tambo-mcp-provider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tambo-mcp-provider.js","sourceRoot":"","sources":["../../src/mcp/tambo-mcp-provider.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {\n createContext,\n FC,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport { TamboTool } from \"../model/component-metadata\";\nimport { useTamboRegistry } from \"../providers/tambo-registry-provider\";\nimport { MCPClient, MCPTransport } from \"./mcp-client\";\n\n/**\n * Extracts error message from MCP tool result content.\n * Handles both array and string content formats.\n * Always returns a string, even for invalid/null inputs.\n * @returns The extracted error message as a string\n */\nexport function extractErrorMessage(content: unknown): string {\n if (content === undefined || content === null) {\n return \"Unknown error occurred\";\n }\n\n if (Array.isArray(content)) {\n const textItems = content\n .filter(\n (item) => item && item.type === \"text\" && typeof item.text === \"string\",\n )\n .map((item) => item.text);\n\n return textItems.length > 0\n ? textItems.join(\" \")\n : \"Error occurred but no details provided\";\n }\n\n if (typeof content === \"object\") {\n return JSON.stringify(content);\n }\n\n return `${content}`;\n}\n\nexport interface McpServerInfo {\n name?: string;\n url: string;\n description?: string;\n transport?: MCPTransport;\n customHeaders?: Record<string, string>;\n}\n\nexport interface ConnectedMcpServer extends McpServerInfo {\n client: MCPClient;\n}\n\nexport interface FailedMcpServer extends McpServerInfo {\n client?: never;\n connectionError: Error;\n}\n\nexport type McpServer = ConnectedMcpServer | FailedMcpServer;\n\nconst McpProviderContext = createContext<McpServer[]>([]);\n/**\n * This provider is used to register tools from MCP servers.\n * @returns the wrapped children\n */\nexport const TamboMcpProvider: FC<{\n mcpServers: (McpServerInfo | string)[];\n children: React.ReactNode;\n}> = ({ mcpServers, children }) => {\n const { registerTool } = useTamboRegistry();\n const [connectedMcpServers, setConnectedMcpServers] = useState<McpServer[]>(\n [],\n );\n\n useEffect(() => {\n if (!mcpServers) {\n return;\n }\n async function registerMcpServers(mcpServerInfos: McpServerInfo[]) {\n // Maps tool names to the MCP client that registered them\n const mcpServerMap = new Map<string, McpServer>();\n\n // initialize the MCP clients, converting McpServerInfo -> McpServer\n const mcpServers = await Promise.allSettled(\n mcpServerInfos.map(async (mcpServerInfo): Promise<McpServer> => {\n try {\n const client = await MCPClient.create(\n mcpServerInfo.url,\n mcpServerInfo.transport,\n mcpServerInfo.customHeaders,\n );\n const connectedMcpServer = {\n ...mcpServerInfo,\n client: client,\n };\n // note because the promises may resolve in any order, the resulting\n // array may not be in the same order as the input array\n setConnectedMcpServers((prev) => [...prev, connectedMcpServer]);\n return connectedMcpServer;\n } catch (error) {\n const failedMcpServer = {\n ...mcpServerInfo,\n connectionError: error as Error,\n };\n // note because the promises may resolve in any order, the resulting\n // array may not be in the same order as the input array\n setConnectedMcpServers((prev) => [...prev, failedMcpServer]);\n return failedMcpServer;\n }\n }),\n );\n\n // note do not rely on the state\n const connectedMcpServers = mcpServers\n .filter((result) => result.status === \"fulfilled\")\n .map((result) => result.value);\n\n // Now create a map of tool name to MCP client\n const serverToolLists = connectedMcpServers.map(async (mcpServer) => {\n const tools = (await mcpServer.client?.listTools()) ?? [];\n tools.forEach((tool) => {\n mcpServerMap.set(tool.name, mcpServer);\n });\n return tools;\n });\n const toolResults = await Promise.allSettled(serverToolLists);\n\n // Just log the failed tools, we can't do anything about them\n const failedTools = toolResults.filter(\n (result) => result.status === \"rejected\",\n );\n if (failedTools.length > 0) {\n console.error(\n \"Failed to register tools from MCP servers:\",\n failedTools.map((result) => result.reason),\n );\n }\n\n // Register the successful tools\n const allTools = toolResults\n .filter((result) => result.status === \"fulfilled\")\n .map((result) => result.value)\n .flat();\n allTools.forEach((tool) => {\n registerTool({\n description: tool.description ?? \"\",\n name: tool.name,\n tool: async (args: Record<string, unknown>) => {\n const mcpServer = mcpServerMap.get(tool.name);\n if (!mcpServer) {\n // should never happen\n throw new Error(`MCP server for tool ${tool.name} not found`);\n }\n if (!mcpServer.client) {\n // this can't actually happen because the tool can't be registered if the server is not connected\n throw new Error(\n `MCP server for tool ${tool.name} is not connected`,\n );\n }\n const result = await mcpServer.client.callTool(tool.name, args);\n if (result.isError) {\n const errorMessage = extractErrorMessage(result.content);\n throw new Error(errorMessage);\n }\n return result.content;\n },\n toolSchema: tool.inputSchema as TamboTool[\"toolSchema\"],\n });\n });\n }\n\n // normalize the server infos\n const mcpServerInfos = mcpServers.map((mcpServer) =>\n typeof mcpServer === \"string\"\n ? { url: mcpServer, transport: MCPTransport.SSE }\n : mcpServer,\n );\n\n registerMcpServers(mcpServerInfos);\n }, [mcpServers, registerTool]);\n\n return (\n <McpProviderContext.Provider value={connectedMcpServers}>\n {children}\n </McpProviderContext.Provider>\n );\n};\n\n/**\n * Hook to access the actual MCP servers, as they are connected (or fail to\n * connect).\n *\n * You can call methods on the MCP client that is included in the MCP server\n * object.\n *\n * If the server fails to connect, the `client` property will be `undefined` and\n * the `connectionError` property will be set.\n *\n * For example, to forcibly disconnect and reconnect all MCP servers:\n *\n * ```tsx\n * const mcpServers = useMcpServers();\n * mcpServers.forEach((mcpServer) => {\n * mcpServer.client?.reconnect();\n * });\n * ```\n *\n * Note that the MCP servers are not guaranteed to be in the same order as the\n * input array, because they are added as they are connected.\n * @returns The MCP servers\n */\nexport const useTamboMcpServers = () => {\n return useContext(McpProviderContext);\n};\n"]}
1
+ {"version":3,"file":"tambo-mcp-provider.js","sourceRoot":"","sources":["../../src/mcp/tambo-mcp-provider.tsx"],"names":[],"mappings":"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","sourcesContent":["import { deepEqual } from \"fast-equals\";\nimport React, {\n createContext,\n FC,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport { TamboTool } from \"../model/component-metadata\";\nimport { useTamboRegistry } from \"../providers/tambo-registry-provider\";\nimport { MCPClient, MCPTransport } from \"./mcp-client\";\n\n/**\n * Extracts error message from MCP tool result content.\n * Handles both array and string content formats.\n * Always returns a string, even for invalid/null inputs.\n * @returns The extracted error message as a string\n */\nexport function extractErrorMessage(content: unknown): string {\n if (content === undefined || content === null) {\n return \"Unknown error occurred\";\n }\n\n if (Array.isArray(content)) {\n const textItems = content\n .filter(\n (item) => item && item.type === \"text\" && typeof item.text === \"string\",\n )\n .map((item) => item.text);\n\n return textItems.length > 0\n ? textItems.join(\" \")\n : \"Error occurred but no details provided\";\n }\n\n if (typeof content === \"object\") {\n return JSON.stringify(content);\n }\n\n return `${content}`;\n}\n\nexport interface McpServerInfo {\n name?: string;\n url: string;\n description?: string;\n transport?: MCPTransport;\n customHeaders?: Record<string, string>;\n}\n\nexport interface ConnectedMcpServer extends McpServerInfo {\n client: MCPClient;\n}\n\nexport interface FailedMcpServer extends McpServerInfo {\n client?: never;\n connectionError: Error;\n}\n\nexport type McpServer = ConnectedMcpServer | FailedMcpServer;\n\nconst McpProviderContext = createContext<McpServer[]>([]);\n/**\n * This provider is used to register tools from MCP servers.\n * @returns the wrapped children\n */\nexport const TamboMcpProvider: FC<{\n mcpServers: (McpServerInfo | string)[];\n children: React.ReactNode;\n}> = ({ mcpServers, children }) => {\n const { registerTool } = useTamboRegistry();\n const [connectedMcpServers, setConnectedMcpServers] = useState<McpServer[]>(\n [],\n );\n\n useEffect(() => {\n if (!mcpServers) {\n return;\n }\n async function registerMcpServers(mcpServerInfos: McpServerInfo[]) {\n // Maps tool names to the MCP client that registered them\n const mcpServerMap = new Map<string, McpServer>();\n setConnectedMcpServers((prev) =>\n // remove any servers that are not in the new list\n prev.filter((s) =>\n mcpServerInfos.some((mcpServerInfo) =>\n equalsMcpServer(s, mcpServerInfo),\n ),\n ),\n );\n\n // initialize the MCP clients, converting McpServerInfo -> McpServer\n const mcpServers = await Promise.allSettled(\n mcpServerInfos.map(async (mcpServerInfo): Promise<McpServer> => {\n try {\n const client = await MCPClient.create(\n mcpServerInfo.url,\n mcpServerInfo.transport,\n mcpServerInfo.customHeaders,\n undefined, // no oauth support yet\n undefined, // starting with no session id at first.\n );\n const connectedMcpServer = {\n ...mcpServerInfo,\n client: client,\n };\n // note because the promises may resolve in any order, the resulting\n // array may not be in the same order as the input array\n setConnectedMcpServers((prev) => [\n // replace the server if it already exists\n ...prev.filter((s) => !equalsMcpServer(s, mcpServerInfo)),\n connectedMcpServer,\n ]);\n return connectedMcpServer;\n } catch (error) {\n const failedMcpServer = {\n ...mcpServerInfo,\n connectionError: error as Error,\n };\n // note because the promises may resolve in any order, the resulting\n // array may not be in the same order as the input array\n setConnectedMcpServers((prev) => [\n // replace the server if it already exists\n ...prev.filter((s) => !equalsMcpServer(s, mcpServerInfo)),\n failedMcpServer,\n ]);\n return failedMcpServer;\n }\n }),\n );\n\n // note do not rely on the state\n const connectedMcpServers = mcpServers\n .filter((result) => result.status === \"fulfilled\")\n .map((result) => result.value);\n\n // Now create a map of tool name to MCP client\n const serverToolLists = connectedMcpServers.map(async (mcpServer) => {\n const tools = (await mcpServer.client?.listTools()) ?? [];\n tools.forEach((tool) => {\n mcpServerMap.set(tool.name, mcpServer);\n });\n return tools;\n });\n const toolResults = await Promise.allSettled(serverToolLists);\n\n // Just log the failed tools, we can't do anything about them\n const failedTools = toolResults.filter(\n (result) => result.status === \"rejected\",\n );\n if (failedTools.length > 0) {\n console.error(\n \"Failed to register tools from MCP servers:\",\n failedTools.map((result) => result.reason),\n );\n }\n\n // Register the successful tools\n const allTools = toolResults\n .filter((result) => result.status === \"fulfilled\")\n .map((result) => result.value)\n .flat();\n allTools.forEach((tool) => {\n registerTool({\n description: tool.description ?? \"\",\n name: tool.name,\n tool: async (args: Record<string, unknown>) => {\n const mcpServer = mcpServerMap.get(tool.name);\n if (!mcpServer) {\n // should never happen\n throw new Error(`MCP server for tool ${tool.name} not found`);\n }\n if (!mcpServer.client) {\n // this can't actually happen because the tool can't be registered if the server is not connected\n throw new Error(\n `MCP server for tool ${tool.name} is not connected`,\n );\n }\n const result = await mcpServer.client.callTool(tool.name, args);\n if (result.isError) {\n const errorMessage = extractErrorMessage(result.content);\n throw new Error(errorMessage);\n }\n return result.content;\n },\n toolSchema: tool.inputSchema as TamboTool[\"toolSchema\"],\n });\n });\n }\n\n // normalize the server infos\n const mcpServerInfos = mcpServers.map((mcpServer) =>\n typeof mcpServer === \"string\"\n ? { url: mcpServer, transport: MCPTransport.SSE }\n : mcpServer,\n );\n\n registerMcpServers(mcpServerInfos);\n }, [mcpServers, registerTool]);\n\n return (\n <McpProviderContext.Provider value={connectedMcpServers}>\n {children}\n </McpProviderContext.Provider>\n );\n};\n\n/**\n * Hook to access the actual MCP servers, as they are connected (or fail to\n * connect).\n *\n * You can call methods on the MCP client that is included in the MCP server\n * object.\n *\n * If the server fails to connect, the `client` property will be `undefined` and\n * the `connectionError` property will be set.\n *\n * For example, to forcibly disconnect and reconnect all MCP servers:\n *\n * ```tsx\n * const mcpServers = useTamboMcpServers();\n * mcpServers.forEach((mcpServer) => {\n * mcpServer.client?.reconnect();\n * });\n * ```\n *\n * Note that the MCP servers are not guaranteed to be in the same order as the\n * input array, because they are added as they are connected.\n * @returns The MCP servers\n */\nexport const useTamboMcpServers = () => {\n return useContext(McpProviderContext);\n};\nfunction equalsMcpServer(s: McpServer, mcpServerInfo: McpServerInfo): boolean {\n return (\n s.url === mcpServerInfo.url &&\n s.transport === mcpServerInfo.transport &&\n deepEqual(s.customHeaders, mcpServerInfo.customHeaders)\n );\n}\n"]}
@@ -278,6 +278,21 @@ describe("updateInteractableComponentProps - Partial Updates", () => {
278
278
  });
279
279
  expect(updateResult).toBe("Updated successfully");
280
280
  });
281
+ it("should throw error when component name contains spaces", () => {
282
+ const { result } = (0, react_1.renderHook)(() => (0, tambo_interactable_provider_1.useTamboInteractable)(), { wrapper });
283
+ const invalidComponent = {
284
+ name: "Invalid Component Name", // Contains spaces
285
+ description: "A component with spaces in name",
286
+ component: () => react_2.default.createElement("div", null, "Invalid"),
287
+ props: { title: "Test" },
288
+ propsSchema: zod_1.z.object({ title: zod_1.z.string() }),
289
+ };
290
+ expect(() => {
291
+ (0, react_1.act)(() => {
292
+ result.current.addInteractableComponent(invalidComponent);
293
+ });
294
+ }).toThrow('component "Invalid Component Name" must only contain letters, numbers, underscores, and hyphens.');
295
+ });
281
296
  it("should return warning for empty props object", () => {
282
297
  const { result } = (0, react_1.renderHook)(() => (0, tambo_interactable_provider_1.useTamboInteractable)(), { wrapper });
283
298
  const component = {