@tambo-ai/react 0.59.0 → 0.60.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 (83) hide show
  1. package/dist/hooks/react-query-hooks.d.ts +14 -1
  2. package/dist/hooks/react-query-hooks.d.ts.map +1 -1
  3. package/dist/hooks/react-query-hooks.js +13 -0
  4. package/dist/hooks/react-query-hooks.js.map +1 -1
  5. package/dist/index.js +2 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/mcp/__tests__/elicitation.test.d.ts +2 -0
  8. package/dist/mcp/__tests__/elicitation.test.d.ts.map +1 -0
  9. package/dist/mcp/__tests__/elicitation.test.js +261 -0
  10. package/dist/mcp/__tests__/elicitation.test.js.map +1 -0
  11. package/dist/mcp/__tests__/mcp-hooks.test.d.ts +2 -0
  12. package/dist/mcp/__tests__/mcp-hooks.test.d.ts.map +1 -0
  13. package/dist/mcp/__tests__/mcp-hooks.test.js +504 -0
  14. package/dist/mcp/__tests__/mcp-hooks.test.js.map +1 -0
  15. package/dist/mcp/__tests__/tambo-mcp-provider.test.js +151 -12
  16. package/dist/mcp/__tests__/tambo-mcp-provider.test.js.map +1 -1
  17. package/dist/mcp/elicitation.d.ts +80 -0
  18. package/dist/mcp/elicitation.d.ts.map +1 -0
  19. package/dist/mcp/elicitation.js +55 -0
  20. package/dist/mcp/elicitation.js.map +1 -0
  21. package/dist/mcp/index.d.ts +3 -2
  22. package/dist/mcp/index.d.ts.map +1 -1
  23. package/dist/mcp/index.js +2 -1
  24. package/dist/mcp/index.js.map +1 -1
  25. package/dist/mcp/mcp-client.d.ts +41 -9
  26. package/dist/mcp/mcp-client.d.ts.map +1 -1
  27. package/dist/mcp/mcp-client.js.map +1 -1
  28. package/dist/mcp/mcp-hooks.d.ts +20 -6
  29. package/dist/mcp/mcp-hooks.d.ts.map +1 -1
  30. package/dist/mcp/mcp-hooks.js +71 -24
  31. package/dist/mcp/mcp-hooks.js.map +1 -1
  32. package/dist/mcp/tambo-mcp-provider.d.ts +60 -6
  33. package/dist/mcp/tambo-mcp-provider.d.ts.map +1 -1
  34. package/dist/mcp/tambo-mcp-provider.js +231 -172
  35. package/dist/mcp/tambo-mcp-provider.js.map +1 -1
  36. package/dist/providers/tambo-interactable-provider.d.ts.map +1 -1
  37. package/dist/providers/tambo-interactable-provider.js +11 -4
  38. package/dist/providers/tambo-interactable-provider.js.map +1 -1
  39. package/dist/providers/tambo-provider.d.ts.map +1 -1
  40. package/dist/providers/tambo-provider.js +0 -4
  41. package/dist/providers/tambo-provider.js.map +1 -1
  42. package/esm/hooks/react-query-hooks.d.ts +14 -1
  43. package/esm/hooks/react-query-hooks.d.ts.map +1 -1
  44. package/esm/hooks/react-query-hooks.js +13 -1
  45. package/esm/hooks/react-query-hooks.js.map +1 -1
  46. package/esm/index.js +2 -0
  47. package/esm/index.js.map +1 -1
  48. package/esm/mcp/__tests__/elicitation.test.d.ts +2 -0
  49. package/esm/mcp/__tests__/elicitation.test.d.ts.map +1 -0
  50. package/esm/mcp/__tests__/elicitation.test.js +259 -0
  51. package/esm/mcp/__tests__/elicitation.test.js.map +1 -0
  52. package/esm/mcp/__tests__/mcp-hooks.test.d.ts +2 -0
  53. package/esm/mcp/__tests__/mcp-hooks.test.d.ts.map +1 -0
  54. package/esm/mcp/__tests__/mcp-hooks.test.js +469 -0
  55. package/esm/mcp/__tests__/mcp-hooks.test.js.map +1 -0
  56. package/esm/mcp/__tests__/tambo-mcp-provider.test.js +151 -12
  57. package/esm/mcp/__tests__/tambo-mcp-provider.test.js.map +1 -1
  58. package/esm/mcp/elicitation.d.ts +80 -0
  59. package/esm/mcp/elicitation.d.ts.map +1 -0
  60. package/esm/mcp/elicitation.js +52 -0
  61. package/esm/mcp/elicitation.js.map +1 -0
  62. package/esm/mcp/index.d.ts +3 -2
  63. package/esm/mcp/index.d.ts.map +1 -1
  64. package/esm/mcp/index.js +1 -1
  65. package/esm/mcp/index.js.map +1 -1
  66. package/esm/mcp/mcp-client.d.ts +41 -9
  67. package/esm/mcp/mcp-client.d.ts.map +1 -1
  68. package/esm/mcp/mcp-client.js.map +1 -1
  69. package/esm/mcp/mcp-hooks.d.ts +20 -6
  70. package/esm/mcp/mcp-hooks.d.ts.map +1 -1
  71. package/esm/mcp/mcp-hooks.js +73 -26
  72. package/esm/mcp/mcp-hooks.js.map +1 -1
  73. package/esm/mcp/tambo-mcp-provider.d.ts +60 -6
  74. package/esm/mcp/tambo-mcp-provider.d.ts.map +1 -1
  75. package/esm/mcp/tambo-mcp-provider.js +231 -173
  76. package/esm/mcp/tambo-mcp-provider.js.map +1 -1
  77. package/esm/providers/tambo-interactable-provider.d.ts.map +1 -1
  78. package/esm/providers/tambo-interactable-provider.js +11 -4
  79. package/esm/providers/tambo-interactable-provider.js.map +1 -1
  80. package/esm/providers/tambo-provider.d.ts.map +1 -1
  81. package/esm/providers/tambo-provider.js +0 -4
  82. package/esm/providers/tambo-provider.js.map +1 -1
  83. package/package.json +8 -8
@@ -33,13 +33,13 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.useTamboMcpServers = exports.TamboMcpProvider = void 0;
36
+ exports.useTamboElicitationContext = exports.useTamboMcpServers = exports.TamboMcpProvider = void 0;
37
37
  exports.extractErrorMessage = extractErrorMessage;
38
- const fast_equals_1 = require("fast-equals");
39
38
  const react_1 = __importStar(require("react"));
40
39
  const tambo_mcp_token_provider_1 = require("../providers/tambo-mcp-token-provider");
41
40
  const tambo_registry_provider_1 = require("../providers/tambo-registry-provider");
42
41
  const content_parts_1 = require("../util/content-parts");
42
+ const elicitation_1 = require("./elicitation");
43
43
  const mcp_client_1 = require("./mcp-client");
44
44
  /**
45
45
  * Extracts error message from MCP tool result content.
@@ -53,7 +53,7 @@ function extractErrorMessage(content) {
53
53
  }
54
54
  if (Array.isArray(content)) {
55
55
  const textItems = content
56
- .filter((item) => item && item.type === "text" && typeof item.text === "string")
56
+ .filter((item) => item?.type === "text" && typeof item.text === "string")
57
57
  .map((item) => item.text);
58
58
  return textItems.length > 0
59
59
  ? textItems.join(" ")
@@ -64,7 +64,11 @@ function extractErrorMessage(content) {
64
64
  }
65
65
  return `${content}`;
66
66
  }
67
- const McpProviderContext = (0, react_1.createContext)([]);
67
+ const McpProviderContext = (0, react_1.createContext)({
68
+ servers: [],
69
+ elicitation: null,
70
+ resolveElicitation: null,
71
+ });
68
72
  // Constant for the internal Tambo MCP server name
69
73
  const TAMBO_INTERNAL_MCP_SERVER_NAME = "__tambo_internal_mcp_server__";
70
74
  /**
@@ -79,15 +83,24 @@ const TAMBO_INTERNAL_MCP_SERVER_NAME = "__tambo_internal_mcp_server__";
79
83
  const TamboMcpProvider = ({ mcpServers, handlers, children }) => {
80
84
  const { registerTool } = (0, tambo_registry_provider_1.useTamboRegistry)();
81
85
  const { mcpAccessToken, tamboBaseUrl } = (0, tambo_mcp_token_provider_1.useTamboMcpToken)();
82
- const providerElicitationHandler = handlers?.elicitation;
83
86
  const providerSamplingHandler = handlers?.sampling;
87
+ // Elicitation state and default handler
88
+ const { elicitation, resolveElicitation, defaultElicitationHandler } = (0, elicitation_1.useElicitation)();
89
+ // Use provided handler or fall back to default
90
+ const providerElicitationHandler = handlers?.elicitation ?? defaultElicitationHandler;
91
+ // Stable reference to track active clients by server key
92
+ const clientMapRef = (0, react_1.useRef)(new Map());
93
+ // Track tool ownership to prevent duplicate registrations across servers
94
+ // toolOwnerRef: tool name -> server key; keyToToolsRef: server key -> set of tool names
95
+ const toolOwnerRef = (0, react_1.useRef)(new Map());
96
+ const keyToToolsRef = (0, react_1.useRef)(new Map());
97
+ // State for exposing connected servers to consumers
84
98
  const [connectedMcpServers, setConnectedMcpServers] = (0, react_1.useState)([]);
85
- // Combine user-provided MCP servers with the internal Tambo MCP server
86
- const allMcpServers = (0, react_1.useMemo)(() => {
99
+ // Stable map of current server configurations keyed by server key
100
+ const currentServersMap = (0, react_1.useMemo)(() => {
87
101
  const servers = [...mcpServers];
88
102
  // Add internal Tambo MCP server if we have an access token and a base URL
89
103
  if (mcpAccessToken && tamboBaseUrl) {
90
- // Build the internal MCP URL robustly, preserving any existing base path
91
104
  const base = new URL(tamboBaseUrl);
92
105
  base.pathname = `${base.pathname.replace(/\/+$/, "")}/mcp`;
93
106
  const tamboMcpUrl = base.toString();
@@ -100,193 +113,187 @@ const TamboMcpProvider = ({ mcpServers, handlers, children }) => {
100
113
  },
101
114
  });
102
115
  }
103
- return servers;
116
+ // Create a map of server key -> server info for efficient lookups
117
+ const serverMap = new Map();
118
+ servers.forEach((server) => {
119
+ const serverInfo = normalizeServerInfo(server);
120
+ // Store without cloning to avoid unnecessary allocation
121
+ serverMap.set(serverInfo.key, serverInfo);
122
+ });
123
+ return serverMap;
104
124
  }, [mcpServers, mcpAccessToken, tamboBaseUrl]);
125
+ // Main effect: manage client lifecycle (create/remove)
105
126
  (0, react_1.useEffect)(() => {
106
- if (!allMcpServers.length) {
107
- return;
108
- }
109
- async function registerMcpServers(mcpServerInfos) {
110
- // Maps tool names to the MCP client that registered them
111
- const mcpServerMap = new Map();
112
- setConnectedMcpServers((prev) =>
113
- // remove any servers that are not in the new list
114
- prev.filter((s) => mcpServerInfos.some((mcpServerInfo) => equalsMcpServer(s, mcpServerInfo))));
115
- // initialize the MCP clients, converting McpServerInfo -> McpServer
116
- const mcpServers = await Promise.allSettled(mcpServerInfos.map(async (mcpServerInfo) => {
127
+ const clientMap = clientMapRef.current;
128
+ const currentKeys = new Set(currentServersMap.keys());
129
+ const existingKeys = new Set(clientMap.keys());
130
+ // 1. Remove clients that are no longer in the current server list
131
+ const keysToRemove = Array.from(existingKeys).filter((key) => !currentKeys.has(key));
132
+ keysToRemove.forEach((key) => {
133
+ const server = clientMap.get(key);
134
+ if (server?.client) {
135
+ try {
136
+ server.client.close();
137
+ }
138
+ catch (error) {
139
+ // Avoid logging sensitive data embedded in the key (headers)
140
+ const url = server.url ?? "(unknown url)";
141
+ console.error(`Error closing MCP client for ${url}:`, error);
142
+ }
143
+ }
144
+ // Release tool ownership for this server
145
+ const owned = keyToToolsRef.current.get(key);
146
+ if (owned) {
147
+ for (const name of owned)
148
+ toolOwnerRef.current.delete(name);
149
+ keyToToolsRef.current.delete(key);
150
+ }
151
+ clientMap.delete(key);
152
+ });
153
+ // 2. Add new clients for servers that don't exist yet
154
+ const keysToAdd = Array.from(currentKeys).filter((key) => !existingKeys.has(key));
155
+ if (keysToAdd.length > 0) {
156
+ // Async initialization of new clients
157
+ Promise.allSettled(keysToAdd.map(async (key) => {
158
+ const serverInfo = currentServersMap.get(key);
117
159
  try {
118
- // Merge provider handlers with per-server handlers (per-server takes precedence)
160
+ // Build effective handlers (per-server overrides provider)
119
161
  const effectiveHandlers = {};
120
- // Apply provider elicitation handler if present and not overridden
121
- if (mcpServerInfo.handlers?.elicitation) {
122
- effectiveHandlers.elicitation =
123
- mcpServerInfo.handlers.elicitation;
162
+ if (serverInfo.handlers?.elicitation) {
163
+ effectiveHandlers.elicitation = serverInfo.handlers.elicitation;
124
164
  }
125
165
  else if (providerElicitationHandler) {
126
- const elicitationHandler = providerElicitationHandler;
127
- effectiveHandlers.elicitation = async (request) => await elicitationHandler(request, mcpServerInfo);
166
+ effectiveHandlers.elicitation = async (request, extra) => await providerElicitationHandler(request, extra, serverInfo);
128
167
  }
129
- // Apply provider sampling handler if present and not overridden
130
- if (mcpServerInfo.handlers?.sampling) {
131
- effectiveHandlers.sampling = mcpServerInfo.handlers.sampling;
168
+ if (serverInfo.handlers?.sampling) {
169
+ effectiveHandlers.sampling = serverInfo.handlers.sampling;
132
170
  }
133
171
  else if (providerSamplingHandler) {
134
- const samplingHandler = providerSamplingHandler;
135
- effectiveHandlers.sampling = async (request) => await samplingHandler(request, mcpServerInfo);
172
+ effectiveHandlers.sampling = async (request, extra) => await providerSamplingHandler(request, extra, serverInfo);
136
173
  }
137
- const client = await mcp_client_1.MCPClient.create(mcpServerInfo.url, mcpServerInfo.transport, mcpServerInfo.customHeaders, undefined, // no oauth support yet
138
- undefined, // starting with no session id at first.
139
- effectiveHandlers);
140
- const connectedMcpServer = {
141
- ...mcpServerInfo,
142
- client: client,
174
+ const client = await mcp_client_1.MCPClient.create(serverInfo.url, serverInfo.transport, serverInfo.customHeaders, undefined, undefined, effectiveHandlers);
175
+ const connectedServer = {
176
+ ...serverInfo,
177
+ client,
143
178
  };
144
- // note because the promises may resolve in any order, the resulting
145
- // array may not be in the same order as the input array
146
- setConnectedMcpServers((prev) => {
147
- const existing = prev.find((s) => equalsMcpServer(s, mcpServerInfo));
148
- try {
149
- existing?.client?.close();
150
- }
151
- catch {
152
- // best-effort cleanup
153
- }
154
- return [
155
- // replace the server if it already exists
156
- ...prev.filter((s) => !equalsMcpServer(s, mcpServerInfo)),
157
- connectedMcpServer,
158
- ];
159
- });
160
- return connectedMcpServer;
179
+ clientMap.set(key, connectedServer);
180
+ setConnectedMcpServers(Array.from(clientMap.values()));
181
+ // Register tools from this server (deduplicated by ownership)
182
+ try {
183
+ const tools = await client.listTools();
184
+ tools.forEach((tool) => {
185
+ // Skip if another server already owns this tool
186
+ const currentOwner = toolOwnerRef.current.get(tool.name);
187
+ if (currentOwner && currentOwner !== key) {
188
+ return;
189
+ }
190
+ // Record ownership for this server key
191
+ if (!currentOwner) {
192
+ toolOwnerRef.current.set(tool.name, key);
193
+ if (!keyToToolsRef.current.has(key)) {
194
+ keyToToolsRef.current.set(key, new Set());
195
+ }
196
+ keyToToolsRef.current.get(key).add(tool.name);
197
+ }
198
+ registerTool({
199
+ description: tool.description ?? "",
200
+ name: tool.name,
201
+ tool: async (args = {}) => {
202
+ const server = clientMap.get(key);
203
+ if (!server?.client) {
204
+ throw new Error(`MCP server for tool ${tool.name} is not connected`);
205
+ }
206
+ const result = await server.client.callTool(tool.name, args);
207
+ if (result.isError) {
208
+ const errorMessage = extractErrorMessage(result.content);
209
+ throw new Error(errorMessage);
210
+ }
211
+ return result.content;
212
+ },
213
+ toolSchema: tool.inputSchema,
214
+ transformToContent: (content) => {
215
+ if ((0, content_parts_1.isContentPartArray)(content)) {
216
+ return content;
217
+ }
218
+ return [{ type: "text", text: (0, content_parts_1.toText)(content) }];
219
+ },
220
+ });
221
+ });
222
+ }
223
+ catch (error) {
224
+ console.error(`Failed to register tools from MCP server ${serverInfo.url}:`, error);
225
+ }
161
226
  }
162
227
  catch (error) {
163
- const failedMcpServer = {
164
- ...mcpServerInfo,
228
+ const failedServer = {
229
+ ...serverInfo,
165
230
  connectionError: error,
166
231
  };
167
- // note because the promises may resolve in any order, the resulting
168
- // array may not be in the same order as the input array
169
- setConnectedMcpServers((prev) => {
170
- const existing = prev.find((s) => equalsMcpServer(s, mcpServerInfo));
171
- try {
172
- existing?.client?.close();
173
- }
174
- catch {
175
- // best-effort cleanup
176
- }
177
- return [
178
- // replace the server if it already exists
179
- ...prev.filter((s) => !equalsMcpServer(s, mcpServerInfo)),
180
- failedMcpServer,
181
- ];
182
- });
183
- return failedMcpServer;
232
+ clientMap.set(key, failedServer);
233
+ setConnectedMcpServers(Array.from(clientMap.values()));
234
+ console.error(`Failed to connect to MCP server ${serverInfo.url}:`, error);
184
235
  }
185
236
  }));
186
- // note do not rely on the state
187
- const connectedMcpServers = mcpServers
188
- .filter((result) => result.status === "fulfilled")
189
- .map((result) => result.value);
190
- // Now create a map of tool name to MCP client
191
- const serverToolLists = connectedMcpServers.map(async (mcpServer) => {
192
- const tools = (await mcpServer.client?.listTools()) ?? [];
193
- tools.forEach((tool) => {
194
- mcpServerMap.set(tool.name, mcpServer);
195
- });
196
- return tools;
197
- });
198
- const toolResults = await Promise.allSettled(serverToolLists);
199
- // Just log the failed tools, we can't do anything about them
200
- const failedTools = toolResults.filter((result) => result.status === "rejected");
201
- if (failedTools.length > 0) {
202
- console.error("Failed to register tools from MCP servers:", failedTools.map((result) => result.reason));
203
- }
204
- // Register the successful tools
205
- const allTools = toolResults
206
- .filter((result) => result.status === "fulfilled")
207
- .map((result) => result.value)
208
- .flat();
209
- allTools.forEach((tool) => {
210
- registerTool({
211
- description: tool.description ?? "",
212
- name: tool.name,
213
- tool: async (args) => {
214
- const mcpServer = mcpServerMap.get(tool.name);
215
- if (!mcpServer) {
216
- // should never happen
217
- throw new Error(`MCP server for tool ${tool.name} not found`);
218
- }
219
- if (!mcpServer.client) {
220
- // this can't actually happen because the tool can't be registered if the server is not connected
221
- throw new Error(`MCP server for tool ${tool.name} is not connected`);
222
- }
223
- const result = await mcpServer.client.callTool(tool.name, args);
224
- if (result.isError) {
225
- const errorMessage = extractErrorMessage(result.content);
226
- throw new Error(errorMessage);
227
- }
228
- return result.content;
229
- },
230
- toolSchema: tool.inputSchema,
231
- transformToContent: (content) => {
232
- // MCP tools can return content in various formats; pass through arrays of content parts
233
- // unchanged, otherwise stringify into a text content part.
234
- if ((0, content_parts_1.isContentPartArray)(content)) {
235
- return content;
236
- }
237
- return [{ type: "text", text: (0, content_parts_1.toText)(content) }];
238
- },
239
- });
240
- });
241
237
  }
242
- // normalize the server infos
243
- const mcpServerInfos = allMcpServers.map((mcpServer) => typeof mcpServer === "string"
244
- ? { url: mcpServer, transport: mcp_client_1.MCPTransport.SSE }
245
- : mcpServer);
246
- registerMcpServers(mcpServerInfos);
238
+ // Update state after removals (additions update state asynchronously above)
239
+ if (keysToRemove.length > 0) {
240
+ setConnectedMcpServers(Array.from(clientMap.values()));
241
+ }
247
242
  // eslint-disable-next-line react-hooks/exhaustive-deps
248
- }, [allMcpServers, registerTool]);
249
- // Update handlers when they change
250
- // We depend only on the handler refs and server identities. The linter cannot
251
- // infer this shape with optional chaining and derived wrapper functions.
243
+ }, [currentServersMap, registerTool]);
244
+ // Update handlers when they change (without recreating clients)
252
245
  (0, react_1.useEffect)(() => {
253
- const mcpServerInfos = allMcpServers.map((mcpServer) => typeof mcpServer === "string"
254
- ? { url: mcpServer, transport: mcp_client_1.MCPTransport.SSE }
255
- : mcpServer);
256
- connectedMcpServers.forEach((connectedServer) => {
257
- if (!connectedServer.client) {
258
- // Skip failed servers
259
- return;
246
+ const clientMap = clientMapRef.current;
247
+ clientMap.forEach((server, key) => {
248
+ if (!server.client) {
249
+ return; // Skip failed servers
260
250
  }
261
- // Find the matching server info to get the current handlers
262
- const serverInfo = mcpServerInfos.find((info) => equalsMcpServer(connectedServer, info));
251
+ const serverInfo = currentServersMap.get(key);
263
252
  if (!serverInfo) {
264
- // Server was removed, will be handled by the main effect
265
- return;
253
+ return; // Server was removed, handled by main effect
266
254
  }
267
- // Determine effective elicitation handler (per-server overrides provider)
268
- const defaultElicitationHandler = providerElicitationHandler;
255
+ // Determine effective handlers
269
256
  const effectiveElicitationHandler = serverInfo.handlers?.elicitation ??
270
- (defaultElicitationHandler
271
- ? async (request) => await defaultElicitationHandler(request, serverInfo)
257
+ (providerElicitationHandler
258
+ ? async (request, extra) => await providerElicitationHandler(request, extra, serverInfo)
272
259
  : undefined);
273
- // Determine effective sampling handler (per-server overrides provider)
274
- const defaultSamplingHandler = providerSamplingHandler;
275
260
  const effectiveSamplingHandler = serverInfo.handlers?.sampling ??
276
- (defaultSamplingHandler
277
- ? async (request) => await defaultSamplingHandler(request, serverInfo)
261
+ (providerSamplingHandler
262
+ ? async (request, extra) => await providerSamplingHandler(request, extra, serverInfo)
278
263
  : undefined);
279
- // Apply handler updates unconditionally so removals propagate
280
- connectedServer.client.updateElicitationHandler?.(effectiveElicitationHandler);
281
- connectedServer.client.updateSamplingHandler?.(effectiveSamplingHandler);
264
+ // Update handlers unconditionally (allows removal by passing undefined)
265
+ server.client.updateElicitationHandler?.(effectiveElicitationHandler);
266
+ server.client.updateSamplingHandler?.(effectiveSamplingHandler);
282
267
  });
283
- }, [
284
- allMcpServers,
285
- connectedMcpServers,
286
- providerElicitationHandler,
287
- providerSamplingHandler,
288
- ]);
289
- return (react_1.default.createElement(McpProviderContext.Provider, { value: connectedMcpServers }, children));
268
+ }, [currentServersMap, providerElicitationHandler, providerSamplingHandler]);
269
+ // Cleanup on unmount: close all clients
270
+ (0, react_1.useEffect)(() => {
271
+ const clientMap = clientMapRef.current;
272
+ const ownerMapAtMount = toolOwnerRef.current;
273
+ const keyToToolsAtMount = keyToToolsRef.current;
274
+ return () => {
275
+ clientMap.forEach((server, _key) => {
276
+ if (server.client) {
277
+ try {
278
+ server.client.close();
279
+ }
280
+ catch (error) {
281
+ const url = server.url ?? "(unknown url)";
282
+ console.error(`Error closing MCP client on unmount for ${url}:`, error);
283
+ }
284
+ }
285
+ });
286
+ clientMap.clear();
287
+ ownerMapAtMount.clear();
288
+ keyToToolsAtMount.clear();
289
+ };
290
+ }, []);
291
+ const contextValue = (0, react_1.useMemo)(() => ({
292
+ servers: connectedMcpServers,
293
+ elicitation,
294
+ resolveElicitation,
295
+ }), [connectedMcpServers, elicitation, resolveElicitation]);
296
+ return (react_1.default.createElement(McpProviderContext.Provider, { value: contextValue }, children));
290
297
  };
291
298
  exports.TamboMcpProvider = TamboMcpProvider;
292
299
  /**
@@ -313,12 +320,64 @@ exports.TamboMcpProvider = TamboMcpProvider;
313
320
  * @returns The MCP servers
314
321
  */
315
322
  const useTamboMcpServers = () => {
316
- return (0, react_1.useContext)(McpProviderContext);
323
+ return (0, react_1.useContext)(McpProviderContext).servers;
317
324
  };
318
325
  exports.useTamboMcpServers = useTamboMcpServers;
319
- function equalsMcpServer(s, mcpServerInfo) {
320
- return (s.url === mcpServerInfo.url &&
321
- s.transport === mcpServerInfo.transport &&
322
- (0, fast_equals_1.deepEqual)(s.customHeaders, mcpServerInfo.customHeaders));
326
+ /**
327
+ * Hook to access elicitation context from TamboMcpProvider.
328
+ * This provides access to the current elicitation request and methods to respond to it.
329
+ *
330
+ * The elicitation state is automatically managed by TamboMcpProvider when MCP servers
331
+ * request user input through the elicitation protocol.
332
+ * @returns The elicitation context with current request and response handlers
333
+ * @example
334
+ * ```tsx
335
+ * function ElicitationUI() {
336
+ * const { elicitation, resolveElicitation } = useTamboElicitationContext();
337
+ *
338
+ * if (!elicitation) return null;
339
+ *
340
+ * return (
341
+ * <div>
342
+ * <p>{elicitation.message}</p>
343
+ * <button onClick={() => resolveElicitation?.({ action: "accept", content: {} })}>
344
+ * Accept
345
+ * </button>
346
+ * </div>
347
+ * );
348
+ * }
349
+ * ```
350
+ */
351
+ const useTamboElicitationContext = () => {
352
+ const context = (0, react_1.useContext)(McpProviderContext);
353
+ return {
354
+ elicitation: context.elicitation,
355
+ resolveElicitation: context.resolveElicitation,
356
+ };
357
+ };
358
+ exports.useTamboElicitationContext = useTamboElicitationContext;
359
+ /**
360
+ * Creates a stable identifier for an MCP server based on its connection properties.
361
+ * Two servers with the same URL, transport, and headers will have the same key.
362
+ * @returns A stable string key identifying the server
363
+ */
364
+ function getServerKey(serverInfo) {
365
+ const headerStr = serverInfo.customHeaders
366
+ ? JSON.stringify(Object.entries(serverInfo.customHeaders)
367
+ .map(([k, v]) => [k.toLowerCase(), v])
368
+ .sort(([a], [b]) => a.localeCompare(b)))
369
+ : "";
370
+ return `${serverInfo.url}|${serverInfo.transport ?? mcp_client_1.MCPTransport.SSE}|${headerStr}`;
371
+ }
372
+ /**
373
+ * Normalizes a server definition (string or object) into a McpServerInfo.
374
+ * @returns The normalized McpServerInfo object
375
+ */
376
+ function normalizeServerInfo(server) {
377
+ const s = typeof server === "string"
378
+ ? { url: server, transport: mcp_client_1.MCPTransport.SSE }
379
+ : server;
380
+ const key = getServerKey(s);
381
+ return { ...s, key };
323
382
  }
324
383
  //# 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 { deepEqual } from \"fast-equals\";\nimport React, {\n createContext,\n FC,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\nimport { TamboTool } from \"../model/component-metadata\";\nimport { useTamboMcpToken } from \"../providers/tambo-mcp-token-provider\";\nimport { useTamboRegistry } from \"../providers/tambo-registry-provider\";\nimport { isContentPartArray, toText } from \"../util/content-parts\";\nimport { MCPClient, MCPHandlers, 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\n/**\n * Configuration for connecting to an MCP server.\n */\nexport interface McpServerInfo {\n /** Optional name for the MCP server */\n name?: string;\n /** The URL of the MCP server to connect to */\n url: string;\n /** Optional description of the MCP server */\n description?: string;\n /** The transport type to use (SSE or HTTP). Defaults to SSE for string URLs */\n transport?: MCPTransport;\n /** Optional custom headers to include in requests */\n customHeaders?: Record<string, string>;\n /**\n * Optional handlers for elicitation and sampling requests from the server.\n * Note: These callbacks should be stable (e.g., wrapped in useCallback or defined outside the component)\n * to avoid constant re-registration of the MCP server on every render.\n */\n handlers?: Partial<MCPHandlers>;\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\n/**\n * Provider-level MCP handlers that receive the McpServerInfo as context in addition to the request.\n * These handlers are applied to all MCP servers unless overridden by per-server handlers.\n */\nexport interface ProviderMCPHandlers {\n elicitation?: (\n request: Parameters<MCPHandlers[\"elicitation\"]>[0],\n serverInfo: McpServerInfo,\n ) => ReturnType<MCPHandlers[\"elicitation\"]>;\n sampling?: (\n request: Parameters<MCPHandlers[\"sampling\"]>[0],\n serverInfo: McpServerInfo,\n ) => ReturnType<MCPHandlers[\"sampling\"]>;\n}\n\nconst McpProviderContext = createContext<McpServer[]>([]);\n\n// Constant for the internal Tambo MCP server name\nconst TAMBO_INTERNAL_MCP_SERVER_NAME = \"__tambo_internal_mcp_server__\";\n\n/**\n * This provider is used to register tools from MCP servers.\n * It automatically includes an internal Tambo MCP server when an MCP access token is available.\n * @param props - The provider props\n * @param props.mcpServers - Array of MCP server configurations\n * @param props.handlers - Optional handlers applied to all MCP servers unless overridden per-server\n * @param props.children - The children to wrap\n * @returns The TamboMcpProvider component\n */\nexport const TamboMcpProvider: FC<{\n mcpServers: (McpServerInfo | string)[];\n handlers?: ProviderMCPHandlers;\n children: React.ReactNode;\n}> = ({ mcpServers, handlers, children }) => {\n const { registerTool } = useTamboRegistry();\n const { mcpAccessToken, tamboBaseUrl } = useTamboMcpToken();\n const providerElicitationHandler = handlers?.elicitation;\n const providerSamplingHandler = handlers?.sampling;\n const [connectedMcpServers, setConnectedMcpServers] = useState<McpServer[]>(\n [],\n );\n\n // Combine user-provided MCP servers with the internal Tambo MCP server\n const allMcpServers = useMemo(() => {\n const servers = [...mcpServers];\n\n // Add internal Tambo MCP server if we have an access token and a base URL\n if (mcpAccessToken && tamboBaseUrl) {\n // Build the internal MCP URL robustly, preserving any existing base path\n const base = new URL(tamboBaseUrl);\n base.pathname = `${base.pathname.replace(/\\/+$/, \"\")}/mcp`;\n const tamboMcpUrl = base.toString();\n servers.push({\n name: TAMBO_INTERNAL_MCP_SERVER_NAME,\n url: tamboMcpUrl,\n transport: MCPTransport.HTTP,\n customHeaders: {\n Authorization: `Bearer ${mcpAccessToken}`,\n },\n });\n }\n\n return servers;\n }, [mcpServers, mcpAccessToken, tamboBaseUrl]);\n\n useEffect(() => {\n if (!allMcpServers.length) {\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 // Merge provider handlers with per-server handlers (per-server takes precedence)\n const effectiveHandlers: Partial<MCPHandlers> = {};\n\n // Apply provider elicitation handler if present and not overridden\n if (mcpServerInfo.handlers?.elicitation) {\n effectiveHandlers.elicitation =\n mcpServerInfo.handlers.elicitation;\n } else if (providerElicitationHandler) {\n const elicitationHandler = providerElicitationHandler;\n effectiveHandlers.elicitation = async (request) =>\n await elicitationHandler(request, mcpServerInfo);\n }\n\n // Apply provider sampling handler if present and not overridden\n if (mcpServerInfo.handlers?.sampling) {\n effectiveHandlers.sampling = mcpServerInfo.handlers.sampling;\n } else if (providerSamplingHandler) {\n const samplingHandler = providerSamplingHandler;\n effectiveHandlers.sampling = async (request) =>\n await samplingHandler(request, mcpServerInfo);\n }\n\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 effectiveHandlers,\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 const existing = prev.find((s) =>\n equalsMcpServer(s, mcpServerInfo),\n );\n try {\n existing?.client?.close();\n } catch {\n // best-effort cleanup\n }\n return [\n // replace the server if it already exists\n ...prev.filter((s) => !equalsMcpServer(s, mcpServerInfo)),\n connectedMcpServer,\n ];\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 const existing = prev.find((s) =>\n equalsMcpServer(s, mcpServerInfo),\n );\n try {\n existing?.client?.close();\n } catch {\n // best-effort cleanup\n }\n return [\n // replace the server if it already exists\n ...prev.filter((s) => !equalsMcpServer(s, mcpServerInfo)),\n failedMcpServer,\n ];\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 transformToContent: (content: unknown) => {\n // MCP tools can return content in various formats; pass through arrays of content parts\n // unchanged, otherwise stringify into a text content part.\n if (isContentPartArray(content)) {\n return content;\n }\n return [{ type: \"text\", text: toText(content) }];\n },\n });\n });\n }\n\n // normalize the server infos\n const mcpServerInfos = allMcpServers.map((mcpServer) =>\n typeof mcpServer === \"string\"\n ? { url: mcpServer, transport: MCPTransport.SSE }\n : mcpServer,\n );\n\n registerMcpServers(mcpServerInfos);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [allMcpServers, registerTool]);\n\n // Update handlers when they change\n // We depend only on the handler refs and server identities. The linter cannot\n // infer this shape with optional chaining and derived wrapper functions.\n useEffect(() => {\n const mcpServerInfos = allMcpServers.map((mcpServer) =>\n typeof mcpServer === \"string\"\n ? { url: mcpServer, transport: MCPTransport.SSE }\n : mcpServer,\n );\n\n connectedMcpServers.forEach((connectedServer) => {\n if (!connectedServer.client) {\n // Skip failed servers\n return;\n }\n\n // Find the matching server info to get the current handlers\n const serverInfo = mcpServerInfos.find((info) =>\n equalsMcpServer(connectedServer, info),\n );\n\n if (!serverInfo) {\n // Server was removed, will be handled by the main effect\n return;\n }\n\n // Determine effective elicitation handler (per-server overrides provider)\n const defaultElicitationHandler = providerElicitationHandler;\n const effectiveElicitationHandler =\n serverInfo.handlers?.elicitation ??\n (defaultElicitationHandler\n ? async (request) =>\n await defaultElicitationHandler(request, serverInfo)\n : undefined);\n\n // Determine effective sampling handler (per-server overrides provider)\n const defaultSamplingHandler = providerSamplingHandler;\n const effectiveSamplingHandler =\n serverInfo.handlers?.sampling ??\n (defaultSamplingHandler\n ? async (request) => await defaultSamplingHandler(request, serverInfo)\n : undefined);\n\n // Apply handler updates unconditionally so removals propagate\n connectedServer.client.updateElicitationHandler?.(\n effectiveElicitationHandler,\n );\n connectedServer.client.updateSamplingHandler?.(effectiveSamplingHandler);\n });\n }, [\n allMcpServers,\n connectedMcpServers,\n providerElicitationHandler,\n providerSamplingHandler,\n ]);\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"]}
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 useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { TamboTool } from \"../model/component-metadata\";\nimport { useTamboMcpToken } from \"../providers/tambo-mcp-token-provider\";\nimport { useTamboRegistry } from \"../providers/tambo-registry-provider\";\nimport { isContentPartArray, toText } from \"../util/content-parts\";\nimport { type ElicitationContextState, useElicitation } from \"./elicitation\";\nimport {\n MCPClient,\n MCPElicitationHandler,\n MCPHandlers,\n MCPSamplingHandler,\n MCPTransport,\n} 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((item) => item?.type === \"text\" && typeof item.text === \"string\")\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\n/**\n * User-provided configuration for an MCP server.\n */\nexport interface McpServerInfo {\n /** Optional name for the MCP server */\n name?: string;\n /** The URL of the MCP server to connect to */\n url: string;\n /** Optional description of the MCP server */\n description?: string;\n /** The transport type to use (SSE or HTTP). Defaults to SSE for string URLs */\n transport?: MCPTransport;\n /** Optional custom headers to include in requests */\n customHeaders?: Record<string, string>;\n /**\n * Optional handlers for elicitation and sampling requests from the server.\n * Note: These callbacks should be stable (e.g., wrapped in useCallback or defined outside the component)\n * to avoid constant re-registration of the MCP server on every render.\n */\n handlers?: Partial<MCPHandlers>;\n}\n\n/**\n * Normalized server information with a stable derived key.\n */\ninterface McpServerConfig extends McpServerInfo {\n /**\n * Stable identity for this server derived from its URL/transport/headers.\n * Present for all server states (connected or failed).\n */\n key: string;\n}\n\n/**\n * Connected MCP server with an active client.\n */\nexport interface ConnectedMcpServer extends McpServerConfig {\n client: MCPClient;\n}\n\n/**\n * Failed MCP server with a connection error.\n */\nexport interface FailedMcpServer extends McpServerConfig {\n client?: never;\n connectionError: Error;\n}\n\n/**\n * An active or failed MCP server, with access to the MCP client.\n */\nexport type McpServer = ConnectedMcpServer | FailedMcpServer;\n\n/**\n * Provider-level MCP handlers that receive the McpServerInfo as context in addition to the request.\n * These handlers are applied to all MCP servers unless overridden by per-server handlers.\n *\n * Handlers receive three parameters:\n * 1. request - The MCP request\n * 2. extra - RequestHandlerExtra containing AbortSignal and other metadata\n * 3. serverInfo - Configuration of the MCP server that triggered this request\n */\nexport interface ProviderMCPHandlers {\n elicitation?: (\n request: Parameters<MCPElicitationHandler>[0],\n extra: Parameters<MCPElicitationHandler>[1],\n serverInfo: McpServerConfig,\n ) => ReturnType<MCPElicitationHandler>;\n sampling?: (\n request: Parameters<MCPSamplingHandler>[0],\n extra: Parameters<MCPSamplingHandler>[1],\n serverInfo: McpServerConfig,\n ) => ReturnType<MCPSamplingHandler>;\n}\n\n/**\n * Context value for MCP provider including server list and elicitation state\n */\ninterface McpProviderContextValue extends ElicitationContextState {\n servers: McpServer[];\n}\n\nconst McpProviderContext = createContext<McpProviderContextValue>({\n servers: [],\n elicitation: null,\n resolveElicitation: null,\n});\n\n// Constant for the internal Tambo MCP server name\nconst TAMBO_INTERNAL_MCP_SERVER_NAME = \"__tambo_internal_mcp_server__\";\n\n/**\n * This provider is used to register tools from MCP servers.\n * It automatically includes an internal Tambo MCP server when an MCP access token is available.\n * @param props - The provider props\n * @param props.mcpServers - Array of MCP server configurations\n * @param props.handlers - Optional handlers applied to all MCP servers unless overridden per-server\n * @param props.children - The children to wrap\n * @returns The TamboMcpProvider component\n */\nexport const TamboMcpProvider: FC<{\n mcpServers: (McpServerInfo | string)[];\n handlers?: ProviderMCPHandlers;\n children: React.ReactNode;\n}> = ({ mcpServers, handlers, children }) => {\n const { registerTool } = useTamboRegistry();\n const { mcpAccessToken, tamboBaseUrl } = useTamboMcpToken();\n const providerSamplingHandler = handlers?.sampling;\n\n // Elicitation state and default handler\n const { elicitation, resolveElicitation, defaultElicitationHandler } =\n useElicitation();\n\n // Use provided handler or fall back to default\n const providerElicitationHandler =\n handlers?.elicitation ?? defaultElicitationHandler;\n\n // Stable reference to track active clients by server key\n const clientMapRef = useRef<Map<string, McpServer>>(new Map());\n // Track tool ownership to prevent duplicate registrations across servers\n // toolOwnerRef: tool name -> server key; keyToToolsRef: server key -> set of tool names\n const toolOwnerRef = useRef<Map<string, string>>(new Map());\n const keyToToolsRef = useRef<Map<string, Set<string>>>(new Map());\n\n // State for exposing connected servers to consumers\n const [connectedMcpServers, setConnectedMcpServers] = useState<McpServer[]>(\n [],\n );\n\n // Stable map of current server configurations keyed by server key\n const currentServersMap = useMemo(() => {\n const servers = [...mcpServers];\n\n // Add internal Tambo MCP server if we have an access token and a base URL\n if (mcpAccessToken && tamboBaseUrl) {\n const base = new URL(tamboBaseUrl);\n base.pathname = `${base.pathname.replace(/\\/+$/, \"\")}/mcp`;\n const tamboMcpUrl = base.toString();\n servers.push({\n name: TAMBO_INTERNAL_MCP_SERVER_NAME,\n url: tamboMcpUrl,\n transport: MCPTransport.HTTP,\n customHeaders: {\n Authorization: `Bearer ${mcpAccessToken}`,\n },\n });\n }\n\n // Create a map of server key -> server info for efficient lookups\n const serverMap = new Map<string, McpServerConfig>();\n servers.forEach((server) => {\n const serverInfo = normalizeServerInfo(server);\n // Store without cloning to avoid unnecessary allocation\n serverMap.set(serverInfo.key, serverInfo);\n });\n\n return serverMap;\n }, [mcpServers, mcpAccessToken, tamboBaseUrl]);\n\n // Main effect: manage client lifecycle (create/remove)\n useEffect(() => {\n const clientMap = clientMapRef.current;\n const currentKeys = new Set(currentServersMap.keys());\n const existingKeys = new Set(clientMap.keys());\n\n // 1. Remove clients that are no longer in the current server list\n const keysToRemove = Array.from(existingKeys).filter(\n (key) => !currentKeys.has(key),\n );\n keysToRemove.forEach((key) => {\n const server = clientMap.get(key);\n if (server?.client) {\n try {\n server.client.close();\n } catch (error) {\n // Avoid logging sensitive data embedded in the key (headers)\n const url = (server as McpServer).url ?? \"(unknown url)\";\n console.error(`Error closing MCP client for ${url}:`, error);\n }\n }\n // Release tool ownership for this server\n const owned = keyToToolsRef.current.get(key);\n if (owned) {\n for (const name of owned) toolOwnerRef.current.delete(name);\n keyToToolsRef.current.delete(key);\n }\n clientMap.delete(key);\n });\n\n // 2. Add new clients for servers that don't exist yet\n const keysToAdd = Array.from(currentKeys).filter(\n (key) => !existingKeys.has(key),\n );\n\n if (keysToAdd.length > 0) {\n // Async initialization of new clients\n Promise.allSettled(\n keysToAdd.map(async (key) => {\n const serverInfo = currentServersMap.get(key)!;\n\n try {\n // Build effective handlers (per-server overrides provider)\n const effectiveHandlers: Partial<MCPHandlers> = {};\n\n if (serverInfo.handlers?.elicitation) {\n effectiveHandlers.elicitation = serverInfo.handlers.elicitation;\n } else if (providerElicitationHandler) {\n effectiveHandlers.elicitation = async (request, extra) =>\n await providerElicitationHandler(request, extra, serverInfo);\n }\n\n if (serverInfo.handlers?.sampling) {\n effectiveHandlers.sampling = serverInfo.handlers.sampling;\n } else if (providerSamplingHandler) {\n effectiveHandlers.sampling = async (request, extra) =>\n await providerSamplingHandler(request, extra, serverInfo);\n }\n\n const client = await MCPClient.create(\n serverInfo.url,\n serverInfo.transport,\n serverInfo.customHeaders,\n undefined,\n undefined,\n effectiveHandlers,\n );\n\n const connectedServer: ConnectedMcpServer = {\n ...serverInfo,\n client,\n };\n\n clientMap.set(key, connectedServer);\n setConnectedMcpServers(Array.from(clientMap.values()));\n\n // Register tools from this server (deduplicated by ownership)\n try {\n const tools = await client.listTools();\n tools.forEach((tool) => {\n // Skip if another server already owns this tool\n const currentOwner = toolOwnerRef.current.get(tool.name);\n if (currentOwner && currentOwner !== key) {\n return;\n }\n\n // Record ownership for this server key\n if (!currentOwner) {\n toolOwnerRef.current.set(tool.name, key);\n if (!keyToToolsRef.current.has(key)) {\n keyToToolsRef.current.set(key, new Set());\n }\n keyToToolsRef.current.get(key)!.add(tool.name);\n }\n\n registerTool({\n description: tool.description ?? \"\",\n name: tool.name,\n tool: async (args: Record<string, unknown> = {}) => {\n const server = clientMap.get(key);\n if (!server?.client) {\n throw new Error(\n `MCP server for tool ${tool.name} is not connected`,\n );\n }\n const result = await server.client.callTool(\n tool.name,\n args,\n );\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 transformToContent: (content: unknown) => {\n if (isContentPartArray(content)) {\n return content;\n }\n return [{ type: \"text\", text: toText(content) }];\n },\n });\n });\n } catch (error) {\n console.error(\n `Failed to register tools from MCP server ${serverInfo.url}:`,\n error,\n );\n }\n } catch (error) {\n const failedServer: FailedMcpServer = {\n ...serverInfo,\n connectionError: error as Error,\n };\n clientMap.set(key, failedServer);\n setConnectedMcpServers(Array.from(clientMap.values()));\n console.error(\n `Failed to connect to MCP server ${serverInfo.url}:`,\n error,\n );\n }\n }),\n );\n }\n\n // Update state after removals (additions update state asynchronously above)\n if (keysToRemove.length > 0) {\n setConnectedMcpServers(Array.from(clientMap.values()));\n }\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [currentServersMap, registerTool]);\n\n // Update handlers when they change (without recreating clients)\n useEffect(() => {\n const clientMap = clientMapRef.current;\n\n clientMap.forEach((server, key) => {\n if (!server.client) {\n return; // Skip failed servers\n }\n\n const serverInfo = currentServersMap.get(key);\n if (!serverInfo) {\n return; // Server was removed, handled by main effect\n }\n\n // Determine effective handlers\n const effectiveElicitationHandler =\n serverInfo.handlers?.elicitation ??\n (providerElicitationHandler\n ? async (request, extra) =>\n await providerElicitationHandler(request, extra, serverInfo)\n : undefined);\n\n const effectiveSamplingHandler =\n serverInfo.handlers?.sampling ??\n (providerSamplingHandler\n ? async (request, extra) =>\n await providerSamplingHandler(request, extra, serverInfo)\n : undefined);\n\n // Update handlers unconditionally (allows removal by passing undefined)\n server.client.updateElicitationHandler?.(effectiveElicitationHandler);\n server.client.updateSamplingHandler?.(effectiveSamplingHandler);\n });\n }, [currentServersMap, providerElicitationHandler, providerSamplingHandler]);\n\n // Cleanup on unmount: close all clients\n useEffect(() => {\n const clientMap = clientMapRef.current;\n const ownerMapAtMount = toolOwnerRef.current;\n const keyToToolsAtMount = keyToToolsRef.current;\n return () => {\n clientMap.forEach((server, _key) => {\n if (server.client) {\n try {\n server.client.close();\n } catch (error) {\n const url = (server as McpServer).url ?? \"(unknown url)\";\n console.error(\n `Error closing MCP client on unmount for ${url}:`,\n error,\n );\n }\n }\n });\n clientMap.clear();\n ownerMapAtMount.clear();\n keyToToolsAtMount.clear();\n };\n }, []);\n\n const contextValue = useMemo(\n () => ({\n servers: connectedMcpServers,\n elicitation,\n resolveElicitation,\n }),\n [connectedMcpServers, elicitation, resolveElicitation],\n );\n\n return (\n <McpProviderContext.Provider value={contextValue}>\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).servers;\n};\n\n/**\n * Hook to access elicitation context from TamboMcpProvider.\n * This provides access to the current elicitation request and methods to respond to it.\n *\n * The elicitation state is automatically managed by TamboMcpProvider when MCP servers\n * request user input through the elicitation protocol.\n * @returns The elicitation context with current request and response handlers\n * @example\n * ```tsx\n * function ElicitationUI() {\n * const { elicitation, resolveElicitation } = useTamboElicitationContext();\n *\n * if (!elicitation) return null;\n *\n * return (\n * <div>\n * <p>{elicitation.message}</p>\n * <button onClick={() => resolveElicitation?.({ action: \"accept\", content: {} })}>\n * Accept\n * </button>\n * </div>\n * );\n * }\n * ```\n */\nexport const useTamboElicitationContext = () => {\n const context = useContext(McpProviderContext);\n return {\n elicitation: context.elicitation,\n resolveElicitation: context.resolveElicitation,\n };\n};\n\n/**\n * Creates a stable identifier for an MCP server based on its connection properties.\n * Two servers with the same URL, transport, and headers will have the same key.\n * @returns A stable string key identifying the server\n */\nfunction getServerKey(\n serverInfo: Pick<McpServerInfo, \"url\" | \"transport\" | \"customHeaders\">,\n): string {\n const headerStr = serverInfo.customHeaders\n ? JSON.stringify(\n Object.entries(serverInfo.customHeaders)\n .map(([k, v]) => [k.toLowerCase(), v] as const)\n .sort(([a], [b]) => a.localeCompare(b)),\n )\n : \"\";\n return `${serverInfo.url}|${serverInfo.transport ?? MCPTransport.SSE}|${headerStr}`;\n}\n\n/**\n * Normalizes a server definition (string or object) into a McpServerInfo.\n * @returns The normalized McpServerInfo object\n */\nfunction normalizeServerInfo(server: McpServerInfo | string): McpServerConfig {\n const s =\n typeof server === \"string\"\n ? { url: server, transport: MCPTransport.SSE }\n : server;\n const key = getServerKey(s);\n return { ...s, key };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"tambo-interactable-provider.d.ts","sourceRoot":"","sources":["../../src/providers/tambo-interactable-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAEZ,iBAAiB,EAKlB,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,6BAA6B,CAAC;AAKrC,QAAA,MAAM,wBAAwB,yCAQ5B,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA6QjE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,gCAEhC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B;;;;;;;;;;;;GAS3C,CAAC"}
1
+ {"version":3,"file":"tambo-interactable-provider.d.ts","sourceRoot":"","sources":["../../src/providers/tambo-interactable-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAEZ,iBAAiB,EAKlB,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,6BAA6B,CAAC;AAKrC,QAAA,MAAM,wBAAwB,yCAQ5B,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAuRjE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,gCAEhC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B;;;;;;;;;;;;GAS3C,CAAC"}