@tambo-ai/react 0.67.1 → 0.68.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 (164) hide show
  1. package/README.md +2 -4
  2. package/dist/context-helpers/current-interactables-context-helper.d.ts.map +1 -1
  3. package/dist/context-helpers/current-interactables-context-helper.js +4 -1
  4. package/dist/context-helpers/current-interactables-context-helper.js.map +1 -1
  5. package/dist/hoc/with-tambo-interactable.d.ts +50 -4
  6. package/dist/hoc/with-tambo-interactable.d.ts.map +1 -1
  7. package/dist/hoc/with-tambo-interactable.js +20 -5
  8. package/dist/hoc/with-tambo-interactable.js.map +1 -1
  9. package/dist/hooks/use-component-state.d.ts +3 -8
  10. package/dist/hooks/use-component-state.d.ts.map +1 -1
  11. package/dist/hooks/use-component-state.js +8 -0
  12. package/dist/hooks/use-component-state.js.map +1 -1
  13. package/dist/hooks/use-component-state.test.js +37 -0
  14. package/dist/hooks/use-component-state.test.js.map +1 -1
  15. package/dist/hooks/use-tambo-threads.js +1 -1
  16. package/dist/hooks/use-tambo-threads.js.map +1 -1
  17. package/dist/mcp/mcp-constants.d.ts +19 -0
  18. package/dist/mcp/mcp-constants.d.ts.map +1 -0
  19. package/dist/mcp/mcp-constants.js +21 -0
  20. package/dist/mcp/mcp-constants.js.map +1 -0
  21. package/dist/mcp/mcp-hooks.d.ts +32 -3
  22. package/dist/mcp/mcp-hooks.d.ts.map +1 -1
  23. package/dist/mcp/mcp-hooks.js +40 -29
  24. package/dist/mcp/mcp-hooks.js.map +1 -1
  25. package/dist/mcp/mcp-hooks.test.js +8 -5
  26. package/dist/mcp/mcp-hooks.test.js.map +1 -1
  27. package/dist/mcp/tambo-mcp-provider.d.ts +7 -0
  28. package/dist/mcp/tambo-mcp-provider.d.ts.map +1 -1
  29. package/dist/mcp/tambo-mcp-provider.js +202 -155
  30. package/dist/mcp/tambo-mcp-provider.js.map +1 -1
  31. package/dist/model/component-metadata.d.ts +1 -1
  32. package/dist/model/component-metadata.d.ts.map +1 -1
  33. package/dist/model/component-metadata.js.map +1 -1
  34. package/dist/model/tambo-interactable.d.ts +7 -5
  35. package/dist/model/tambo-interactable.d.ts.map +1 -1
  36. package/dist/model/tambo-interactable.js.map +1 -1
  37. package/dist/providers/__tests__/thread-input-resource-resolution.test.d.ts +2 -0
  38. package/dist/providers/__tests__/thread-input-resource-resolution.test.d.ts.map +1 -0
  39. package/dist/providers/__tests__/thread-input-resource-resolution.test.js +592 -0
  40. package/dist/providers/__tests__/thread-input-resource-resolution.test.js.map +1 -0
  41. package/dist/providers/tambo-interactable-provider-partial-updates.test.js +22 -21
  42. package/dist/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
  43. package/dist/providers/tambo-interactable-provider.d.ts +3 -2
  44. package/dist/providers/tambo-interactable-provider.d.ts.map +1 -1
  45. package/dist/providers/tambo-interactable-provider.js +98 -14
  46. package/dist/providers/tambo-interactable-provider.js.map +1 -1
  47. package/dist/providers/tambo-interactable-provider.test.js +242 -0
  48. package/dist/providers/tambo-interactable-provider.test.js.map +1 -1
  49. package/dist/providers/tambo-provider.d.ts.map +1 -1
  50. package/dist/providers/tambo-provider.js +7 -5
  51. package/dist/providers/tambo-provider.js.map +1 -1
  52. package/dist/providers/tambo-thread-input-provider.d.ts.map +1 -1
  53. package/dist/providers/tambo-thread-input-provider.js +21 -3
  54. package/dist/providers/tambo-thread-input-provider.js.map +1 -1
  55. package/dist/schema/index.d.ts +1 -1
  56. package/dist/schema/index.d.ts.map +1 -1
  57. package/dist/schema/index.js +2 -1
  58. package/dist/schema/index.js.map +1 -1
  59. package/dist/schema/json-schema.d.ts +7 -0
  60. package/dist/schema/json-schema.d.ts.map +1 -1
  61. package/dist/schema/json-schema.js +11 -0
  62. package/dist/schema/json-schema.js.map +1 -1
  63. package/dist/schema/json-schema.test.d.ts +2 -0
  64. package/dist/schema/json-schema.test.d.ts.map +1 -0
  65. package/dist/schema/json-schema.test.js +204 -0
  66. package/dist/schema/json-schema.test.js.map +1 -0
  67. package/dist/setupTests.js +3 -0
  68. package/dist/setupTests.js.map +1 -1
  69. package/dist/util/message-builder.d.ts +3 -1
  70. package/dist/util/message-builder.d.ts.map +1 -1
  71. package/dist/util/message-builder.js +20 -3
  72. package/dist/util/message-builder.js.map +1 -1
  73. package/dist/util/message-builder.test.js +269 -0
  74. package/dist/util/message-builder.test.js.map +1 -1
  75. package/dist/util/resource-content-resolver.d.ts +20 -0
  76. package/dist/util/resource-content-resolver.d.ts.map +1 -0
  77. package/dist/util/resource-content-resolver.js +93 -0
  78. package/dist/util/resource-content-resolver.js.map +1 -0
  79. package/dist/util/resource-content-resolver.test.d.ts +2 -0
  80. package/dist/util/resource-content-resolver.test.d.ts.map +1 -0
  81. package/dist/util/resource-content-resolver.test.js +254 -0
  82. package/dist/util/resource-content-resolver.test.js.map +1 -0
  83. package/esm/context-helpers/current-interactables-context-helper.d.ts.map +1 -1
  84. package/esm/context-helpers/current-interactables-context-helper.js +4 -1
  85. package/esm/context-helpers/current-interactables-context-helper.js.map +1 -1
  86. package/esm/hoc/with-tambo-interactable.d.ts +50 -4
  87. package/esm/hoc/with-tambo-interactable.d.ts.map +1 -1
  88. package/esm/hoc/with-tambo-interactable.js +20 -5
  89. package/esm/hoc/with-tambo-interactable.js.map +1 -1
  90. package/esm/hooks/use-component-state.d.ts +3 -8
  91. package/esm/hooks/use-component-state.d.ts.map +1 -1
  92. package/esm/hooks/use-component-state.js +8 -0
  93. package/esm/hooks/use-component-state.js.map +1 -1
  94. package/esm/hooks/use-component-state.test.js +37 -0
  95. package/esm/hooks/use-component-state.test.js.map +1 -1
  96. package/esm/hooks/use-tambo-threads.js +1 -1
  97. package/esm/hooks/use-tambo-threads.js.map +1 -1
  98. package/esm/mcp/mcp-constants.d.ts +19 -0
  99. package/esm/mcp/mcp-constants.d.ts.map +1 -0
  100. package/esm/mcp/mcp-constants.js +18 -0
  101. package/esm/mcp/mcp-constants.js.map +1 -0
  102. package/esm/mcp/mcp-hooks.d.ts +32 -3
  103. package/esm/mcp/mcp-hooks.d.ts.map +1 -1
  104. package/esm/mcp/mcp-hooks.js +40 -30
  105. package/esm/mcp/mcp-hooks.js.map +1 -1
  106. package/esm/mcp/mcp-hooks.test.js +8 -5
  107. package/esm/mcp/mcp-hooks.test.js.map +1 -1
  108. package/esm/mcp/tambo-mcp-provider.d.ts +7 -0
  109. package/esm/mcp/tambo-mcp-provider.d.ts.map +1 -1
  110. package/esm/mcp/tambo-mcp-provider.js +201 -154
  111. package/esm/mcp/tambo-mcp-provider.js.map +1 -1
  112. package/esm/model/component-metadata.d.ts +1 -1
  113. package/esm/model/component-metadata.d.ts.map +1 -1
  114. package/esm/model/component-metadata.js.map +1 -1
  115. package/esm/model/tambo-interactable.d.ts +7 -5
  116. package/esm/model/tambo-interactable.d.ts.map +1 -1
  117. package/esm/model/tambo-interactable.js.map +1 -1
  118. package/esm/providers/__tests__/thread-input-resource-resolution.test.d.ts +2 -0
  119. package/esm/providers/__tests__/thread-input-resource-resolution.test.d.ts.map +1 -0
  120. package/esm/providers/__tests__/thread-input-resource-resolution.test.js +587 -0
  121. package/esm/providers/__tests__/thread-input-resource-resolution.test.js.map +1 -0
  122. package/esm/providers/tambo-interactable-provider-partial-updates.test.js +22 -21
  123. package/esm/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
  124. package/esm/providers/tambo-interactable-provider.d.ts +3 -2
  125. package/esm/providers/tambo-interactable-provider.d.ts.map +1 -1
  126. package/esm/providers/tambo-interactable-provider.js +98 -14
  127. package/esm/providers/tambo-interactable-provider.js.map +1 -1
  128. package/esm/providers/tambo-interactable-provider.test.js +242 -0
  129. package/esm/providers/tambo-interactable-provider.test.js.map +1 -1
  130. package/esm/providers/tambo-provider.d.ts.map +1 -1
  131. package/esm/providers/tambo-provider.js +7 -5
  132. package/esm/providers/tambo-provider.js.map +1 -1
  133. package/esm/providers/tambo-thread-input-provider.d.ts.map +1 -1
  134. package/esm/providers/tambo-thread-input-provider.js +21 -3
  135. package/esm/providers/tambo-thread-input-provider.js.map +1 -1
  136. package/esm/schema/index.d.ts +1 -1
  137. package/esm/schema/index.d.ts.map +1 -1
  138. package/esm/schema/index.js +1 -1
  139. package/esm/schema/index.js.map +1 -1
  140. package/esm/schema/json-schema.d.ts +7 -0
  141. package/esm/schema/json-schema.d.ts.map +1 -1
  142. package/esm/schema/json-schema.js +10 -0
  143. package/esm/schema/json-schema.js.map +1 -1
  144. package/esm/schema/json-schema.test.d.ts +2 -0
  145. package/esm/schema/json-schema.test.d.ts.map +1 -0
  146. package/esm/schema/json-schema.test.js +202 -0
  147. package/esm/schema/json-schema.test.js.map +1 -0
  148. package/esm/setupTests.js +3 -0
  149. package/esm/setupTests.js.map +1 -1
  150. package/esm/util/message-builder.d.ts +3 -1
  151. package/esm/util/message-builder.d.ts.map +1 -1
  152. package/esm/util/message-builder.js +20 -3
  153. package/esm/util/message-builder.js.map +1 -1
  154. package/esm/util/message-builder.test.js +269 -0
  155. package/esm/util/message-builder.test.js.map +1 -1
  156. package/esm/util/resource-content-resolver.d.ts +20 -0
  157. package/esm/util/resource-content-resolver.d.ts.map +1 -0
  158. package/esm/util/resource-content-resolver.js +89 -0
  159. package/esm/util/resource-content-resolver.js.map +1 -0
  160. package/esm/util/resource-content-resolver.test.d.ts +2 -0
  161. package/esm/util/resource-content-resolver.test.d.ts.map +1 -0
  162. package/esm/util/resource-content-resolver.test.js +252 -0
  163. package/esm/util/resource-content-resolver.test.js.map +1 -0
  164. package/package.json +1 -1
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.useTamboElicitationContext = exports.useTamboMcpElicitation = exports.useTamboMcpServers = exports.TamboMcpProvider = void 0;
37
37
  exports.extractErrorMessage = extractErrorMessage;
38
38
  const react_1 = __importStar(require("react"));
39
+ const mcp_constants_1 = require("./mcp-constants");
39
40
  const mcp_server_info_1 = require("../model/mcp-server-info");
40
41
  const tambo_mcp_token_provider_1 = require("../providers/tambo-mcp-token-provider");
41
42
  const tambo_registry_provider_1 = require("../providers/tambo-registry-provider");
@@ -90,6 +91,150 @@ function hashString(input) {
90
91
  }
91
92
  return (hash >>> 0).toString(36);
92
93
  }
94
+ /**
95
+ * Safely closes an MCP client, handling both sync and async close methods.
96
+ * Logs errors but doesn't throw.
97
+ */
98
+ function closeClientSafely(server) {
99
+ if (!server?.client?.close)
100
+ return;
101
+ try {
102
+ const closeResult = server.client.close();
103
+ // If it returns a promise, handle errors but don't wait
104
+ if (closeResult && typeof closeResult.catch === "function") {
105
+ void closeResult.catch((error) => {
106
+ const url = server.url ?? "(unknown url)";
107
+ console.error(`Error closing MCP client for ${url}:`, error);
108
+ });
109
+ }
110
+ }
111
+ catch (error) {
112
+ const url = server.url ?? "(unknown url)";
113
+ console.error(`Error closing MCP client for ${url}:`, error);
114
+ }
115
+ }
116
+ /**
117
+ * Builds effective handlers for a server, with per-server overrides taking
118
+ * precedence over provider-level handlers.
119
+ * @returns The effective handlers with per-server overrides applied
120
+ */
121
+ function buildEffectiveHandlers(serverInfo, providerElicitationHandler, providerSamplingHandler) {
122
+ const effectiveHandlers = {};
123
+ if (serverInfo.handlers?.elicitation) {
124
+ effectiveHandlers.elicitation = serverInfo.handlers.elicitation;
125
+ }
126
+ else if (providerElicitationHandler) {
127
+ effectiveHandlers.elicitation = async (request, extra) => await providerElicitationHandler(request, extra, serverInfo);
128
+ }
129
+ if (serverInfo.handlers?.sampling) {
130
+ effectiveHandlers.sampling = serverInfo.handlers.sampling;
131
+ }
132
+ else if (providerSamplingHandler) {
133
+ effectiveHandlers.sampling = async (request, extra) => await providerSamplingHandler(request, extra, serverInfo);
134
+ }
135
+ return effectiveHandlers;
136
+ }
137
+ /**
138
+ * Hook to compute the stable map of server configurations from registry servers
139
+ * and the internal Tambo MCP server (when token is available).
140
+ * @returns A map of server key to server configuration
141
+ */
142
+ function useServerConfigs(mcpServers, mcpAccessToken, tamboBaseUrl) {
143
+ return (0, react_1.useMemo)(() => {
144
+ const serverMap = new Map();
145
+ // Add user-provided MCP servers (browser-side)
146
+ mcpServers.forEach((server) => {
147
+ const serverInfo = normalizeServerInfo(server, mcp_constants_1.ServerType.BROWSER_SIDE);
148
+ serverMap.set(serverInfo.key, serverInfo);
149
+ });
150
+ // Add internal Tambo MCP server if we have an access token and a base URL
151
+ if (mcpAccessToken && tamboBaseUrl) {
152
+ const base = new URL(tamboBaseUrl);
153
+ base.pathname = `${base.pathname.replace(/\/+$/, "")}/mcp`;
154
+ const tamboMcpUrl = base.toString();
155
+ const tokenHash = hashString(mcpAccessToken);
156
+ const internalServer = {
157
+ name: TAMBO_INTERNAL_MCP_SERVER_NAME,
158
+ url: tamboMcpUrl,
159
+ transport: mcp_client_1.MCPTransport.HTTP,
160
+ serverKey: `tambo-${tokenHash}`,
161
+ customHeaders: {
162
+ Authorization: `Bearer ${mcpAccessToken}`,
163
+ },
164
+ };
165
+ const serverInfo = normalizeServerInfo(internalServer, mcp_constants_1.ServerType.TAMBO_INTERNAL);
166
+ serverMap.set(serverInfo.key, serverInfo);
167
+ }
168
+ return serverMap;
169
+ }, [mcpServers, mcpAccessToken, tamboBaseUrl]);
170
+ }
171
+ /**
172
+ * Registers tools from a connected MCP server with deduplication.
173
+ */
174
+ async function registerServerTools(client, serverInfo, key, shouldPrefix, clientMap, ownershipRefs, registerTool) {
175
+ const { toolOwnerRef, keyToToolsRef } = ownershipRefs;
176
+ try {
177
+ const tools = await client.listTools();
178
+ tools.forEach((tool) => {
179
+ const toolName = shouldPrefix
180
+ ? `${serverInfo.serverKey}__${tool.name}`
181
+ : tool.name;
182
+ // Skip if another server already owns this tool
183
+ const currentOwner = toolOwnerRef.current.get(toolName);
184
+ if (currentOwner && currentOwner !== key) {
185
+ return;
186
+ }
187
+ // Record ownership
188
+ if (!currentOwner) {
189
+ toolOwnerRef.current.set(toolName, key);
190
+ if (!keyToToolsRef.current.has(key)) {
191
+ keyToToolsRef.current.set(key, new Set());
192
+ }
193
+ keyToToolsRef.current.get(key).add(toolName);
194
+ }
195
+ registerTool({
196
+ description: tool.description ?? "",
197
+ name: toolName,
198
+ tool: async (args = {}) => {
199
+ const server = clientMap.get(key);
200
+ if (!server?.client) {
201
+ throw new Error(`MCP server for tool ${tool.name} is not connected`);
202
+ }
203
+ const result = await server.client.callTool(tool.name, args);
204
+ if (result.isError) {
205
+ const errorMessage = extractErrorMessage(result.content);
206
+ throw new Error(errorMessage);
207
+ }
208
+ return result.content;
209
+ },
210
+ inputSchema: tool.inputSchema ?? {},
211
+ outputSchema: {},
212
+ transformToContent: (content) => {
213
+ if ((0, content_parts_1.isContentPartArray)(content)) {
214
+ return content;
215
+ }
216
+ return [{ type: "text", text: (0, content_parts_1.toText)(content) }];
217
+ },
218
+ });
219
+ });
220
+ }
221
+ catch (error) {
222
+ console.error(`Failed to register tools from MCP server ${serverInfo.url}:`, error);
223
+ }
224
+ }
225
+ /**
226
+ * Releases tool ownership for a server being removed.
227
+ */
228
+ function releaseToolOwnership(key, ownershipRefs) {
229
+ const { toolOwnerRef, keyToToolsRef } = ownershipRefs;
230
+ const owned = keyToToolsRef.current.get(key);
231
+ if (owned) {
232
+ for (const name of owned) {
233
+ toolOwnerRef.current.delete(name);
234
+ }
235
+ keyToToolsRef.current.delete(key);
236
+ }
237
+ }
93
238
  /**
94
239
  * This provider is used to register tools from MCP servers.
95
240
  * It automatically includes an internal Tambo MCP server when an MCP access token is available.
@@ -115,41 +260,32 @@ const TamboMcpProvider = ({ handlers, contextKey, children }) => {
115
260
  // Stable reference to track active clients by server key
116
261
  const clientMapRef = (0, react_1.useRef)(new Map());
117
262
  // Track tool ownership to prevent duplicate registrations across servers
118
- // toolOwnerRef: tool name -> server key; keyToToolsRef: server key -> set of tool names
119
263
  const toolOwnerRef = (0, react_1.useRef)(new Map());
120
264
  const keyToToolsRef = (0, react_1.useRef)(new Map());
265
+ const ownershipRefs = { toolOwnerRef, keyToToolsRef };
121
266
  // State for exposing connected servers to consumers
122
267
  const [connectedMcpServers, setConnectedMcpServers] = (0, react_1.useState)([]);
123
- // Stable map of current server configurations keyed by server key
124
- const currentServersMap = (0, react_1.useMemo)(() => {
125
- const servers = [...mcpServers];
126
- // Add internal Tambo MCP server if we have an access token and a base URL
127
- if (mcpAccessToken && tamboBaseUrl) {
128
- const base = new URL(tamboBaseUrl);
129
- base.pathname = `${base.pathname.replace(/\/+$/, "")}/mcp`;
130
- const tamboMcpUrl = base.toString();
131
- // Include token hash in serverKey so changing tokens trigger reconnection
132
- const tokenHash = hashString(mcpAccessToken);
133
- servers.push({
134
- name: TAMBO_INTERNAL_MCP_SERVER_NAME,
135
- url: tamboMcpUrl,
136
- transport: mcp_client_1.MCPTransport.HTTP,
137
- serverKey: `tambo-${tokenHash}`, // Include token hash in key to force reconnection on token change
138
- customHeaders: {
139
- Authorization: `Bearer ${mcpAccessToken}`,
140
- },
141
- });
142
- }
143
- // Create a map of server key -> server info for efficient lookups
144
- const serverMap = new Map();
145
- servers.forEach((server) => {
146
- const serverInfo = normalizeServerInfo(server);
147
- // Store without cloning to avoid unnecessary allocation
148
- serverMap.set(serverInfo.key, serverInfo);
149
- });
150
- return serverMap;
151
- }, [mcpServers, mcpAccessToken, tamboBaseUrl]);
268
+ // Compute server configurations from registry and internal server
269
+ const currentServersMap = useServerConfigs(mcpServers, mcpAccessToken, tamboBaseUrl);
152
270
  // Main effect: manage client lifecycle (create/remove)
271
+ useClientLifecycle(currentServersMap, clientMapRef, ownershipRefs, providerElicitationHandler, providerSamplingHandler, registerTool, setConnectedMcpServers);
272
+ // Update handlers when they change (without recreating clients)
273
+ useHandlerUpdates(currentServersMap, clientMapRef, providerElicitationHandler, providerSamplingHandler);
274
+ // Cleanup on unmount: close all clients
275
+ useCleanupOnUnmount(clientMapRef, ownershipRefs);
276
+ const contextValue = (0, react_1.useMemo)(() => ({
277
+ servers: connectedMcpServers,
278
+ elicitation,
279
+ resolveElicitation,
280
+ }), [connectedMcpServers, elicitation, resolveElicitation]);
281
+ return (react_1.default.createElement(McpProviderContext.Provider, { value: contextValue }, children));
282
+ };
283
+ exports.TamboMcpProvider = TamboMcpProvider;
284
+ /**
285
+ * Hook to manage client lifecycle: creating clients for new servers,
286
+ * removing clients for servers that are no longer in the list.
287
+ */
288
+ function useClientLifecycle(currentServersMap, clientMapRef, ownershipRefs, providerElicitationHandler, providerSamplingHandler, registerTool, setConnectedMcpServers) {
153
289
  (0, react_1.useEffect)(() => {
154
290
  const clientMap = clientMapRef.current;
155
291
  const currentKeys = new Set(currentServersMap.keys());
@@ -158,30 +294,10 @@ const TamboMcpProvider = ({ handlers, contextKey, children }) => {
158
294
  const keysToRemove = Array.from(existingKeys).filter((key) => !currentKeys.has(key));
159
295
  keysToRemove.forEach((key) => {
160
296
  const server = clientMap.get(key);
161
- if (server?.client?.close) {
162
- try {
163
- // Call close() sync - it may or may not return a promise
164
- const closeResult = server.client.close();
165
- // If it returns a promise, handle errors but don't wait
166
- if (closeResult && typeof closeResult.catch === "function") {
167
- void closeResult.catch((error) => {
168
- const url = server.url ?? "(unknown url)";
169
- console.error(`Error closing MCP client for ${url}:`, error);
170
- });
171
- }
172
- }
173
- catch (error) {
174
- const url = server.url ?? "(unknown url)";
175
- console.error(`Error closing MCP client for ${url}:`, error);
176
- }
177
- }
178
- // Release tool ownership for this server
179
- const owned = keyToToolsRef.current.get(key);
180
- if (owned) {
181
- for (const name of owned)
182
- toolOwnerRef.current.delete(name);
183
- keyToToolsRef.current.delete(key);
297
+ if (server) {
298
+ closeClientSafely(server);
184
299
  }
300
+ releaseToolOwnership(key, ownershipRefs);
185
301
  clientMap.delete(key);
186
302
  });
187
303
  // 2. Add new clients for servers that don't exist yet
@@ -190,20 +306,7 @@ const TamboMcpProvider = ({ handlers, contextKey, children }) => {
190
306
  await Promise.allSettled(keys.map(async (key) => {
191
307
  const serverInfo = currentServersMap.get(key);
192
308
  try {
193
- // Build effective handlers (per-server overrides provider)
194
- const effectiveHandlers = {};
195
- if (serverInfo.handlers?.elicitation) {
196
- effectiveHandlers.elicitation = serverInfo.handlers.elicitation;
197
- }
198
- else if (providerElicitationHandler) {
199
- effectiveHandlers.elicitation = async (request, extra) => await providerElicitationHandler(request, extra, serverInfo);
200
- }
201
- if (serverInfo.handlers?.sampling) {
202
- effectiveHandlers.sampling = serverInfo.handlers.sampling;
203
- }
204
- else if (providerSamplingHandler) {
205
- effectiveHandlers.sampling = async (request, extra) => await providerSamplingHandler(request, extra, serverInfo);
206
- }
309
+ const effectiveHandlers = buildEffectiveHandlers(serverInfo, providerElicitationHandler, providerSamplingHandler);
207
310
  const client = await mcp_client_1.MCPClient.create(serverInfo.url, serverInfo.transport, serverInfo.customHeaders, undefined, undefined, effectiveHandlers);
208
311
  const connectedServer = {
209
312
  ...serverInfo,
@@ -211,57 +314,9 @@ const TamboMcpProvider = ({ handlers, contextKey, children }) => {
211
314
  };
212
315
  clientMap.set(key, connectedServer);
213
316
  setConnectedMcpServers(Array.from(clientMap.values()));
214
- // Register tools from this server (deduplicated by ownership)
215
- try {
216
- const tools = await client.listTools();
217
- const shouldPrefix = currentServersMap.size > 1;
218
- tools.forEach((tool) => {
219
- // Prefix tool name with serverKey if multiple servers are present
220
- const toolName = shouldPrefix
221
- ? `${serverInfo.serverKey}__${tool.name}`
222
- : tool.name;
223
- // Skip if another server already owns this tool (using final name for ownership)
224
- const currentOwner = toolOwnerRef.current.get(toolName);
225
- if (currentOwner && currentOwner !== key) {
226
- return;
227
- }
228
- // Record ownership for this server key (using final name)
229
- if (!currentOwner) {
230
- toolOwnerRef.current.set(toolName, key);
231
- if (!keyToToolsRef.current.has(key)) {
232
- keyToToolsRef.current.set(key, new Set());
233
- }
234
- keyToToolsRef.current.get(key).add(toolName);
235
- }
236
- registerTool({
237
- description: tool.description ?? "",
238
- name: toolName,
239
- tool: async (args = {}) => {
240
- const server = clientMap.get(key);
241
- if (!server?.client) {
242
- throw new Error(`MCP server for tool ${tool.name} is not connected`);
243
- }
244
- const result = await server.client.callTool(tool.name, args);
245
- if (result.isError) {
246
- const errorMessage = extractErrorMessage(result.content);
247
- throw new Error(errorMessage);
248
- }
249
- return result.content;
250
- },
251
- inputSchema: tool.inputSchema ?? {},
252
- outputSchema: {},
253
- transformToContent: (content) => {
254
- if ((0, content_parts_1.isContentPartArray)(content)) {
255
- return content;
256
- }
257
- return [{ type: "text", text: (0, content_parts_1.toText)(content) }];
258
- },
259
- });
260
- });
261
- }
262
- catch (error) {
263
- console.error(`Failed to register tools from MCP server ${serverInfo.url}:`, error);
264
- }
317
+ // Register tools from this server
318
+ const shouldPrefix = currentServersMap.size > 1;
319
+ await registerServerTools(client, serverInfo, key, shouldPrefix, clientMap, ownershipRefs, registerTool);
265
320
  }
266
321
  catch (error) {
267
322
  const failedServer = {
@@ -279,17 +334,24 @@ const TamboMcpProvider = ({ handlers, contextKey, children }) => {
279
334
  console.error("Unexpected error in addClients:", err);
280
335
  });
281
336
  }
282
- // Update state after removals (additions update state asynchronously above)
337
+ // Update state after removals
283
338
  if (keysToRemove.length > 0) {
284
339
  setConnectedMcpServers(Array.from(clientMap.values()));
285
340
  }
341
+ // Note: refs (clientMapRef, ownershipRefs) and setters (setConnectedMcpServers)
342
+ // are intentionally excluded from deps as they are stable references
343
+ // eslint-disable-next-line react-hooks/exhaustive-deps
286
344
  }, [
287
345
  currentServersMap,
288
346
  providerElicitationHandler,
289
347
  providerSamplingHandler,
290
348
  registerTool,
291
349
  ]);
292
- // Update handlers when they change (without recreating clients)
350
+ }
351
+ /**
352
+ * Hook to update handlers on connected clients when provider handlers change.
353
+ */
354
+ function useHandlerUpdates(currentServersMap, clientMapRef, providerElicitationHandler, providerSamplingHandler) {
293
355
  (0, react_1.useEffect)(() => {
294
356
  const clientMap = clientMapRef.current;
295
357
  clientMap.forEach((server, key) => {
@@ -298,9 +360,9 @@ const TamboMcpProvider = ({ handlers, contextKey, children }) => {
298
360
  }
299
361
  const serverInfo = currentServersMap.get(key);
300
362
  if (!serverInfo) {
301
- return; // Server was removed, handled by main effect
363
+ return; // Server was removed, handled by lifecycle effect
302
364
  }
303
- // Determine effective handlers
365
+ // Build effective handlers and update the client
304
366
  const effectiveElicitationHandler = serverInfo.handlers?.elicitation ??
305
367
  (providerElicitationHandler
306
368
  ? async (request, extra) => await providerElicitationHandler(request, extra, serverInfo)
@@ -309,49 +371,32 @@ const TamboMcpProvider = ({ handlers, contextKey, children }) => {
309
371
  (providerSamplingHandler
310
372
  ? async (request, extra) => await providerSamplingHandler(request, extra, serverInfo)
311
373
  : undefined);
312
- // Update handlers unconditionally (allows removal by passing undefined)
313
374
  server.client.updateElicitationHandler?.(effectiveElicitationHandler);
314
375
  server.client.updateSamplingHandler?.(effectiveSamplingHandler);
315
376
  });
377
+ // Note: clientMapRef is intentionally excluded from deps as it's a stable ref
378
+ // eslint-disable-next-line react-hooks/exhaustive-deps
316
379
  }, [currentServersMap, providerElicitationHandler, providerSamplingHandler]);
317
- // Cleanup on unmount: close all clients
380
+ }
381
+ /**
382
+ * Hook to cleanup all clients and tool ownership on component unmount.
383
+ */
384
+ function useCleanupOnUnmount(clientMapRef, ownershipRefs) {
318
385
  (0, react_1.useEffect)(() => {
319
386
  const clientMap = clientMapRef.current;
320
- const ownerMapAtMount = toolOwnerRef.current;
321
- const keyToToolsAtMount = keyToToolsRef.current;
387
+ const { toolOwnerRef, keyToToolsRef } = ownershipRefs;
322
388
  return () => {
323
389
  clientMap.forEach((server) => {
324
- if (server?.client?.close) {
325
- try {
326
- // Call close() sync - it may or may not return a promise
327
- const closeResult = server.client.close();
328
- // If it returns a promise, handle errors but don't wait
329
- if (closeResult && typeof closeResult.catch === "function") {
330
- void closeResult.catch((error) => {
331
- const url = server.url ?? "(unknown url)";
332
- console.error(`Error closing MCP client on unmount for ${url}:`, error);
333
- });
334
- }
335
- }
336
- catch (error) {
337
- const url = server.url ?? "(unknown url)";
338
- console.error(`Error closing MCP client on unmount for ${url}:`, error);
339
- }
340
- }
390
+ closeClientSafely(server);
341
391
  });
342
392
  clientMap.clear();
343
- ownerMapAtMount.clear();
344
- keyToToolsAtMount.clear();
393
+ toolOwnerRef.current.clear();
394
+ keyToToolsRef.current.clear();
345
395
  };
396
+ // Only run cleanup on unmount
397
+ // eslint-disable-next-line react-hooks/exhaustive-deps
346
398
  }, []);
347
- const contextValue = (0, react_1.useMemo)(() => ({
348
- servers: connectedMcpServers,
349
- elicitation,
350
- resolveElicitation,
351
- }), [connectedMcpServers, elicitation, resolveElicitation]);
352
- return (react_1.default.createElement(McpProviderContext.Provider, { value: contextValue }, children));
353
- };
354
- exports.TamboMcpProvider = TamboMcpProvider;
399
+ }
355
400
  /**
356
401
  * Hook to access the actual MCP servers, as they are connected (or fail to
357
402
  * connect).
@@ -424,9 +469,10 @@ exports.useTamboElicitationContext = exports.useTamboMcpElicitation;
424
469
  * `transport` and a `serverKey` derived by the registry, and narrows the
425
470
  * opaque `handlers` field to `Partial<MCPHandlers>`.
426
471
  * @param server - The normalized MCP server info from the registry
427
- * @returns The server config with typed handlers and unique key
472
+ * @param serverType - The type of server (internal vs browser-side)
473
+ * @returns The server config with typed handlers, unique key, and server type
428
474
  */
429
- function normalizeServerInfo(server) {
475
+ function normalizeServerInfo(server, serverType) {
430
476
  // Always use getMcpServerUniqueKey for connection identity to ensure
431
477
  // that changes to URL, transport, or customHeaders trigger client recreation.
432
478
  // The serverKey is kept for namespacing purposes (readable short name),
@@ -438,6 +484,7 @@ function normalizeServerInfo(server) {
438
484
  ...server,
439
485
  handlers,
440
486
  key,
487
+ serverType,
441
488
  };
442
489
  }
443
490
  //# 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 useMemo,\n useRef,\n useState,\n} from \"react\";\nimport {\n getMcpServerUniqueKey,\n type NormalizedMcpServerInfo,\n} from \"../model/mcp-server-info\";\nimport { useTamboMcpToken } from \"../providers/tambo-mcp-token-provider\";\nimport {\n useTamboMcpServerInfos,\n useTamboRegistry,\n} 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 * Normalized MCP server information as consumed by the provider.\n *\n * Extends `NormalizedMcpServerInfo` from the core model by:\n * - narrowing `handlers` to `Partial<MCPHandlers>`\n * - adding a stable `key` derived from URL/transport/headers\n *\n * The registry is responsible for producing `NormalizedMcpServerInfo`\n * instances; this type adds the MCP-specific wiring needed to connect and\n * track clients.\n */\ninterface McpServerConfig extends NormalizedMcpServerInfo {\n /**\n * Optional handlers for elicitation and sampling requests from the server.\n * Interpreted as a partial set of MCP handlers.\n */\n handlers?: Partial<MCPHandlers>;\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 * Creates a stable hash of a string for use as a cache key.\n * Uses Java-style string hashing (DJB2-like) for deterministic results.\n *\n * Note: We use a synchronous hash instead of crypto.subtle.digest because:\n * - crypto.subtle.digest is async, which adds complexity in React hooks (useMemo, useEffect)\n * - This is not for security, just for creating a stable identifier to detect token changes\n * - Synchronous hashing avoids race conditions and simplifies component lifecycle\n * @param input - The string to hash\n * @returns A compact base36 hash string\n */\nfunction hashString(input: string): string {\n let hash = 0;\n for (let i = 0; i < input.length; i++) {\n hash = (Math.imul(31, hash) + input.charCodeAt(i)) | 0;\n }\n return (hash >>> 0).toString(36);\n}\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 *\n * **BREAKING CHANGE**: This provider no longer accepts `mcpServers` as a prop.\n * Instead, pass `mcpServers` to `TamboProvider` or `TamboRegistryProvider`.\n * This provider must be wrapped inside `TamboProvider` to access the MCP server registry.\n * @param props - The provider props\n * @param props.handlers - Optional handlers applied to all MCP servers unless overridden per-server\n * @param props.contextKey - Optional context key for fetching threadless MCP tokens when not in a thread\n * @param props.children - The children to wrap\n * @returns The TamboMcpProvider component\n */\nexport const TamboMcpProvider: FC<{\n handlers?: ProviderMCPHandlers;\n contextKey?: string;\n children: React.ReactNode;\n}> = ({ handlers, contextKey, children }) => {\n const { registerTool } = useTamboRegistry();\n const { mcpAccessToken, tamboBaseUrl } = useTamboMcpToken(contextKey);\n const mcpServers = useTamboMcpServerInfos();\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 // Include token hash in serverKey so changing tokens trigger reconnection\n const tokenHash = hashString(mcpAccessToken);\n servers.push({\n name: TAMBO_INTERNAL_MCP_SERVER_NAME,\n url: tamboMcpUrl,\n transport: MCPTransport.HTTP,\n serverKey: `tambo-${tokenHash}`, // Include token hash in key to force reconnection on token change\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?.close) {\n try {\n // Call close() sync - it may or may not return a promise\n const closeResult = server.client.close();\n // If it returns a promise, handle errors but don't wait\n if (closeResult && typeof closeResult.catch === \"function\") {\n void closeResult.catch((error) => {\n const url = (server as McpServer).url ?? \"(unknown url)\";\n console.error(`Error closing MCP client for ${url}:`, error);\n });\n }\n } catch (error) {\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 async function addClients(keys: string[]) {\n await Promise.allSettled(\n keys.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 (\n request: Parameters<MCPElicitationHandler>[0],\n extra: Parameters<MCPElicitationHandler>[1],\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 (\n request: Parameters<MCPSamplingHandler>[0],\n extra: Parameters<MCPSamplingHandler>[1],\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 const shouldPrefix = currentServersMap.size > 1;\n\n tools.forEach((tool) => {\n // Prefix tool name with serverKey if multiple servers are present\n const toolName = shouldPrefix\n ? `${serverInfo.serverKey}__${tool.name}`\n : tool.name;\n\n // Skip if another server already owns this tool (using final name for ownership)\n const currentOwner = toolOwnerRef.current.get(toolName);\n if (currentOwner && currentOwner !== key) {\n return;\n }\n\n // Record ownership for this server key (using final name)\n if (!currentOwner) {\n toolOwnerRef.current.set(toolName, key);\n if (!keyToToolsRef.current.has(key)) {\n keyToToolsRef.current.set(key, new Set());\n }\n keyToToolsRef.current.get(key)!.add(toolName);\n }\n\n registerTool({\n description: tool.description ?? \"\",\n name: toolName,\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 inputSchema: tool.inputSchema ?? {},\n outputSchema: {},\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 if (keysToAdd.length > 0) {\n addClients(keysToAdd).catch((err) => {\n console.error(\"Unexpected error in addClients:\", err);\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 currentServersMap,\n providerElicitationHandler,\n providerSamplingHandler,\n registerTool,\n ]);\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 (\n request: Parameters<MCPElicitationHandler>[0],\n extra: Parameters<MCPElicitationHandler>[1],\n ) => await providerElicitationHandler(request, extra, serverInfo)\n : undefined);\n\n const effectiveSamplingHandler =\n serverInfo.handlers?.sampling ??\n (providerSamplingHandler\n ? async (\n request: Parameters<MCPSamplingHandler>[0],\n extra: Parameters<MCPSamplingHandler>[1],\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) => {\n if (server?.client?.close) {\n try {\n // Call close() sync - it may or may not return a promise\n const closeResult = server.client.close();\n // If it returns a promise, handle errors but don't wait\n if (closeResult && typeof closeResult.catch === \"function\") {\n void closeResult.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 } 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 MCP elicitation state 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 state with current request and response handler\n * @example\n * ```tsx\n * function ElicitationUI() {\n * const { elicitation, resolveElicitation } = useTamboMcpElicitation();\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 useTamboMcpElicitation = (): ElicitationContextState => {\n const context = useContext(McpProviderContext);\n return {\n elicitation: context.elicitation,\n resolveElicitation: context.resolveElicitation,\n };\n};\n\n/**\n * @deprecated Use `useTamboMcpElicitation` instead.\n * This hook will be removed in a future version.\n */\nexport const useTamboElicitationContext = useTamboMcpElicitation;\n\n/**\n * Normalizes registry server metadata into a `McpServerConfig`.\n *\n * Accepts a `NormalizedMcpServerInfo`, which already guarantees a concrete\n * `transport` and a `serverKey` derived by the registry, and narrows the\n * opaque `handlers` field to `Partial<MCPHandlers>`.\n * @param server - The normalized MCP server info from the registry\n * @returns The server config with typed handlers and unique key\n */\nfunction normalizeServerInfo(server: NormalizedMcpServerInfo): McpServerConfig {\n // Always use getMcpServerUniqueKey for connection identity to ensure\n // that changes to URL, transport, or customHeaders trigger client recreation.\n // The serverKey is kept for namespacing purposes (readable short name),\n // but the connection identity key must include all connection properties.\n const key = getMcpServerUniqueKey(server);\n // Cast handlers to proper type if present\n const handlers = server.handlers as Partial<MCPHandlers> | undefined;\n return {\n ...server,\n handlers,\n key,\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 { ServerType } from \"./mcp-constants\";\nimport {\n getMcpServerUniqueKey,\n type NormalizedMcpServerInfo,\n} from \"../model/mcp-server-info\";\nimport { useTamboMcpToken } from \"../providers/tambo-mcp-token-provider\";\nimport {\n useTamboMcpServerInfos,\n useTamboRegistry,\n} 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 * Normalized MCP server information as consumed by the provider.\n *\n * Extends `NormalizedMcpServerInfo` from the core model by:\n * - narrowing `handlers` to `Partial<MCPHandlers>`\n * - adding a stable `key` derived from URL/transport/headers\n * - adding `serverType` to distinguish internal vs browser-side servers\n *\n * The registry is responsible for producing `NormalizedMcpServerInfo`\n * instances; this type adds the MCP-specific wiring needed to connect and\n * track clients.\n */\ninterface McpServerConfig extends NormalizedMcpServerInfo {\n /**\n * Optional handlers for elicitation and sampling requests from the server.\n * Interpreted as a partial set of MCP handlers.\n */\n handlers?: Partial<MCPHandlers>;\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 * Type of server - determines how resources are resolved.\n * Internal servers are resolved server-side, browser-side servers are resolved client-side.\n */\n serverType: ServerType;\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 * Creates a stable hash of a string for use as a cache key.\n * Uses Java-style string hashing (DJB2-like) for deterministic results.\n *\n * Note: We use a synchronous hash instead of crypto.subtle.digest because:\n * - crypto.subtle.digest is async, which adds complexity in React hooks (useMemo, useEffect)\n * - This is not for security, just for creating a stable identifier to detect token changes\n * - Synchronous hashing avoids race conditions and simplifies component lifecycle\n * @param input - The string to hash\n * @returns A compact base36 hash string\n */\nfunction hashString(input: string): string {\n let hash = 0;\n for (let i = 0; i < input.length; i++) {\n hash = (Math.imul(31, hash) + input.charCodeAt(i)) | 0;\n }\n return (hash >>> 0).toString(36);\n}\n\n/**\n * Safely closes an MCP client, handling both sync and async close methods.\n * Logs errors but doesn't throw.\n */\nfunction closeClientSafely(server: McpServer): void {\n if (!server?.client?.close) return;\n\n try {\n const closeResult = server.client.close();\n // If it returns a promise, handle errors but don't wait\n if (closeResult && typeof closeResult.catch === \"function\") {\n void closeResult.catch((error) => {\n const url = server.url ?? \"(unknown url)\";\n console.error(`Error closing MCP client for ${url}:`, error);\n });\n }\n } catch (error) {\n const url = server.url ?? \"(unknown url)\";\n console.error(`Error closing MCP client for ${url}:`, error);\n }\n}\n\n/**\n * Builds effective handlers for a server, with per-server overrides taking\n * precedence over provider-level handlers.\n * @returns The effective handlers with per-server overrides applied\n */\nfunction buildEffectiveHandlers(\n serverInfo: McpServerConfig,\n providerElicitationHandler: ProviderMCPHandlers[\"elicitation\"] | undefined,\n providerSamplingHandler: ProviderMCPHandlers[\"sampling\"] | undefined,\n): Partial<MCPHandlers> {\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 (\n request: Parameters<MCPElicitationHandler>[0],\n extra: Parameters<MCPElicitationHandler>[1],\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 (\n request: Parameters<MCPSamplingHandler>[0],\n extra: Parameters<MCPSamplingHandler>[1],\n ) => await providerSamplingHandler(request, extra, serverInfo);\n }\n\n return effectiveHandlers;\n}\n\n/**\n * Hook to compute the stable map of server configurations from registry servers\n * and the internal Tambo MCP server (when token is available).\n * @returns A map of server key to server configuration\n */\nfunction useServerConfigs(\n mcpServers: NormalizedMcpServerInfo[],\n mcpAccessToken: string | null | undefined,\n tamboBaseUrl: string | null | undefined,\n): Map<string, McpServerConfig> {\n return useMemo(() => {\n const serverMap = new Map<string, McpServerConfig>();\n\n // Add user-provided MCP servers (browser-side)\n mcpServers.forEach((server) => {\n const serverInfo = normalizeServerInfo(server, ServerType.BROWSER_SIDE);\n serverMap.set(serverInfo.key, serverInfo);\n });\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 const tokenHash = hashString(mcpAccessToken);\n const internalServer: NormalizedMcpServerInfo = {\n name: TAMBO_INTERNAL_MCP_SERVER_NAME,\n url: tamboMcpUrl,\n transport: MCPTransport.HTTP,\n serverKey: `tambo-${tokenHash}`,\n customHeaders: {\n Authorization: `Bearer ${mcpAccessToken}`,\n },\n };\n const serverInfo = normalizeServerInfo(\n internalServer,\n ServerType.TAMBO_INTERNAL,\n );\n serverMap.set(serverInfo.key, serverInfo);\n }\n\n return serverMap;\n }, [mcpServers, mcpAccessToken, tamboBaseUrl]);\n}\n\ninterface ToolOwnershipRefs {\n toolOwnerRef: React.MutableRefObject<Map<string, string>>;\n keyToToolsRef: React.MutableRefObject<Map<string, Set<string>>>;\n}\n\n/**\n * Registers tools from a connected MCP server with deduplication.\n */\nasync function registerServerTools(\n client: MCPClient,\n serverInfo: McpServerConfig,\n key: string,\n shouldPrefix: boolean,\n clientMap: Map<string, McpServer>,\n ownershipRefs: ToolOwnershipRefs,\n registerTool: ReturnType<typeof useTamboRegistry>[\"registerTool\"],\n): Promise<void> {\n const { toolOwnerRef, keyToToolsRef } = ownershipRefs;\n\n try {\n const tools = await client.listTools();\n\n tools.forEach((tool) => {\n const toolName = shouldPrefix\n ? `${serverInfo.serverKey}__${tool.name}`\n : tool.name;\n\n // Skip if another server already owns this tool\n const currentOwner = toolOwnerRef.current.get(toolName);\n if (currentOwner && currentOwner !== key) {\n return;\n }\n\n // Record ownership\n if (!currentOwner) {\n toolOwnerRef.current.set(toolName, key);\n if (!keyToToolsRef.current.has(key)) {\n keyToToolsRef.current.set(key, new Set());\n }\n keyToToolsRef.current.get(key)!.add(toolName);\n }\n\n registerTool({\n description: tool.description ?? \"\",\n name: toolName,\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(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 inputSchema: tool.inputSchema ?? {},\n outputSchema: {},\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}\n\n/**\n * Releases tool ownership for a server being removed.\n */\nfunction releaseToolOwnership(\n key: string,\n ownershipRefs: ToolOwnershipRefs,\n): void {\n const { toolOwnerRef, keyToToolsRef } = ownershipRefs;\n const owned = keyToToolsRef.current.get(key);\n if (owned) {\n for (const name of owned) {\n toolOwnerRef.current.delete(name);\n }\n keyToToolsRef.current.delete(key);\n }\n}\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 *\n * **BREAKING CHANGE**: This provider no longer accepts `mcpServers` as a prop.\n * Instead, pass `mcpServers` to `TamboProvider` or `TamboRegistryProvider`.\n * This provider must be wrapped inside `TamboProvider` to access the MCP server registry.\n * @param props - The provider props\n * @param props.handlers - Optional handlers applied to all MCP servers unless overridden per-server\n * @param props.contextKey - Optional context key for fetching threadless MCP tokens when not in a thread\n * @param props.children - The children to wrap\n * @returns The TamboMcpProvider component\n */\nexport const TamboMcpProvider: FC<{\n handlers?: ProviderMCPHandlers;\n contextKey?: string;\n children: React.ReactNode;\n}> = ({ handlers, contextKey, children }) => {\n const { registerTool } = useTamboRegistry();\n const { mcpAccessToken, tamboBaseUrl } = useTamboMcpToken(contextKey);\n const mcpServers = useTamboMcpServerInfos();\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 const toolOwnerRef = useRef<Map<string, string>>(new Map());\n const keyToToolsRef = useRef<Map<string, Set<string>>>(new Map());\n const ownershipRefs: ToolOwnershipRefs = { toolOwnerRef, keyToToolsRef };\n\n // State for exposing connected servers to consumers\n const [connectedMcpServers, setConnectedMcpServers] = useState<McpServer[]>(\n [],\n );\n\n // Compute server configurations from registry and internal server\n const currentServersMap = useServerConfigs(\n mcpServers,\n mcpAccessToken,\n tamboBaseUrl,\n );\n\n // Main effect: manage client lifecycle (create/remove)\n useClientLifecycle(\n currentServersMap,\n clientMapRef,\n ownershipRefs,\n providerElicitationHandler,\n providerSamplingHandler,\n registerTool,\n setConnectedMcpServers,\n );\n\n // Update handlers when they change (without recreating clients)\n useHandlerUpdates(\n currentServersMap,\n clientMapRef,\n providerElicitationHandler,\n providerSamplingHandler,\n );\n\n // Cleanup on unmount: close all clients\n useCleanupOnUnmount(clientMapRef, ownershipRefs);\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 manage client lifecycle: creating clients for new servers,\n * removing clients for servers that are no longer in the list.\n */\nfunction useClientLifecycle(\n currentServersMap: Map<string, McpServerConfig>,\n clientMapRef: React.MutableRefObject<Map<string, McpServer>>,\n ownershipRefs: ToolOwnershipRefs,\n providerElicitationHandler: ProviderMCPHandlers[\"elicitation\"] | undefined,\n providerSamplingHandler: ProviderMCPHandlers[\"sampling\"] | undefined,\n registerTool: ReturnType<typeof useTamboRegistry>[\"registerTool\"],\n setConnectedMcpServers: React.Dispatch<React.SetStateAction<McpServer[]>>,\n): void {\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) {\n closeClientSafely(server);\n }\n releaseToolOwnership(key, ownershipRefs);\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 async function addClients(keys: string[]) {\n await Promise.allSettled(\n keys.map(async (key) => {\n const serverInfo = currentServersMap.get(key)!;\n\n try {\n const effectiveHandlers = buildEffectiveHandlers(\n serverInfo,\n providerElicitationHandler,\n providerSamplingHandler,\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\n const shouldPrefix = currentServersMap.size > 1;\n await registerServerTools(\n client,\n serverInfo,\n key,\n shouldPrefix,\n clientMap,\n ownershipRefs,\n registerTool,\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 if (keysToAdd.length > 0) {\n addClients(keysToAdd).catch((err) => {\n console.error(\"Unexpected error in addClients:\", err);\n });\n }\n\n // Update state after removals\n if (keysToRemove.length > 0) {\n setConnectedMcpServers(Array.from(clientMap.values()));\n }\n // Note: refs (clientMapRef, ownershipRefs) and setters (setConnectedMcpServers)\n // are intentionally excluded from deps as they are stable references\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n currentServersMap,\n providerElicitationHandler,\n providerSamplingHandler,\n registerTool,\n ]);\n}\n\n/**\n * Hook to update handlers on connected clients when provider handlers change.\n */\nfunction useHandlerUpdates(\n currentServersMap: Map<string, McpServerConfig>,\n clientMapRef: React.MutableRefObject<Map<string, McpServer>>,\n providerElicitationHandler: ProviderMCPHandlers[\"elicitation\"] | undefined,\n providerSamplingHandler: ProviderMCPHandlers[\"sampling\"] | undefined,\n): void {\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 lifecycle effect\n }\n\n // Build effective handlers and update the client\n const effectiveElicitationHandler =\n serverInfo.handlers?.elicitation ??\n (providerElicitationHandler\n ? async (\n request: Parameters<MCPElicitationHandler>[0],\n extra: Parameters<MCPElicitationHandler>[1],\n ) => await providerElicitationHandler(request, extra, serverInfo)\n : undefined);\n\n const effectiveSamplingHandler =\n serverInfo.handlers?.sampling ??\n (providerSamplingHandler\n ? async (\n request: Parameters<MCPSamplingHandler>[0],\n extra: Parameters<MCPSamplingHandler>[1],\n ) => await providerSamplingHandler(request, extra, serverInfo)\n : undefined);\n\n server.client.updateElicitationHandler?.(effectiveElicitationHandler);\n server.client.updateSamplingHandler?.(effectiveSamplingHandler);\n });\n // Note: clientMapRef is intentionally excluded from deps as it's a stable ref\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [currentServersMap, providerElicitationHandler, providerSamplingHandler]);\n}\n\n/**\n * Hook to cleanup all clients and tool ownership on component unmount.\n */\nfunction useCleanupOnUnmount(\n clientMapRef: React.MutableRefObject<Map<string, McpServer>>,\n ownershipRefs: ToolOwnershipRefs,\n): void {\n useEffect(() => {\n const clientMap = clientMapRef.current;\n const { toolOwnerRef, keyToToolsRef } = ownershipRefs;\n return () => {\n clientMap.forEach((server) => {\n closeClientSafely(server);\n });\n clientMap.clear();\n toolOwnerRef.current.clear();\n keyToToolsRef.current.clear();\n };\n // Only run cleanup on unmount\n // eslint-disable-next-line react-hooks/exhaustive-deps\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 MCP elicitation state 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 state with current request and response handler\n * @example\n * ```tsx\n * function ElicitationUI() {\n * const { elicitation, resolveElicitation } = useTamboMcpElicitation();\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 useTamboMcpElicitation = (): ElicitationContextState => {\n const context = useContext(McpProviderContext);\n return {\n elicitation: context.elicitation,\n resolveElicitation: context.resolveElicitation,\n };\n};\n\n/**\n * @deprecated Use `useTamboMcpElicitation` instead.\n * This hook will be removed in a future version.\n */\nexport const useTamboElicitationContext = useTamboMcpElicitation;\n\n/**\n * Normalizes registry server metadata into a `McpServerConfig`.\n *\n * Accepts a `NormalizedMcpServerInfo`, which already guarantees a concrete\n * `transport` and a `serverKey` derived by the registry, and narrows the\n * opaque `handlers` field to `Partial<MCPHandlers>`.\n * @param server - The normalized MCP server info from the registry\n * @param serverType - The type of server (internal vs browser-side)\n * @returns The server config with typed handlers, unique key, and server type\n */\nfunction normalizeServerInfo(\n server: NormalizedMcpServerInfo,\n serverType: ServerType,\n): McpServerConfig {\n // Always use getMcpServerUniqueKey for connection identity to ensure\n // that changes to URL, transport, or customHeaders trigger client recreation.\n // The serverKey is kept for namespacing purposes (readable short name),\n // but the connection identity key must include all connection properties.\n const key = getMcpServerUniqueKey(server);\n // Cast handlers to proper type if present\n const handlers = server.handlers as Partial<MCPHandlers> | undefined;\n return {\n ...server,\n handlers,\n key,\n serverType,\n };\n}\n"]}
@@ -13,7 +13,7 @@ import { $ZodFunction as Zod4Function, $ZodFunctionArgs as Zod4FunctionArgs, $Zo
13
13
  * allowing us to accept any compliant validator without depending on a specific library.
14
14
  * @see https://standardschema.dev/
15
15
  */
16
- export type SupportedSchema = StandardSchemaV1 | JSONSchema7;
16
+ export type SupportedSchema<Shape = unknown> = StandardSchemaV1<Shape, Shape> | JSONSchema7;
17
17
  /** Extension of the ToolParameters interface from Tambo AI to include JSONSchema definition */
18
18
  export type ParameterSpec = TamboAI.ToolParameters & {
19
19
  schema?: JSONSchema7;