@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
@@ -1,4 +1,5 @@
1
1
  import React, { createContext, useContext, useEffect, useMemo, useRef, useState, } from "react";
2
+ import { ServerType } from "./mcp-constants";
2
3
  import { getMcpServerUniqueKey, } from "../model/mcp-server-info";
3
4
  import { useTamboMcpToken } from "../providers/tambo-mcp-token-provider";
4
5
  import { useTamboMcpServerInfos, useTamboRegistry, } from "../providers/tambo-registry-provider";
@@ -53,6 +54,150 @@ function hashString(input) {
53
54
  }
54
55
  return (hash >>> 0).toString(36);
55
56
  }
57
+ /**
58
+ * Safely closes an MCP client, handling both sync and async close methods.
59
+ * Logs errors but doesn't throw.
60
+ */
61
+ function closeClientSafely(server) {
62
+ if (!server?.client?.close)
63
+ return;
64
+ try {
65
+ const closeResult = server.client.close();
66
+ // If it returns a promise, handle errors but don't wait
67
+ if (closeResult && typeof closeResult.catch === "function") {
68
+ void closeResult.catch((error) => {
69
+ const url = server.url ?? "(unknown url)";
70
+ console.error(`Error closing MCP client for ${url}:`, error);
71
+ });
72
+ }
73
+ }
74
+ catch (error) {
75
+ const url = server.url ?? "(unknown url)";
76
+ console.error(`Error closing MCP client for ${url}:`, error);
77
+ }
78
+ }
79
+ /**
80
+ * Builds effective handlers for a server, with per-server overrides taking
81
+ * precedence over provider-level handlers.
82
+ * @returns The effective handlers with per-server overrides applied
83
+ */
84
+ function buildEffectiveHandlers(serverInfo, providerElicitationHandler, providerSamplingHandler) {
85
+ const effectiveHandlers = {};
86
+ if (serverInfo.handlers?.elicitation) {
87
+ effectiveHandlers.elicitation = serverInfo.handlers.elicitation;
88
+ }
89
+ else if (providerElicitationHandler) {
90
+ effectiveHandlers.elicitation = async (request, extra) => await providerElicitationHandler(request, extra, serverInfo);
91
+ }
92
+ if (serverInfo.handlers?.sampling) {
93
+ effectiveHandlers.sampling = serverInfo.handlers.sampling;
94
+ }
95
+ else if (providerSamplingHandler) {
96
+ effectiveHandlers.sampling = async (request, extra) => await providerSamplingHandler(request, extra, serverInfo);
97
+ }
98
+ return effectiveHandlers;
99
+ }
100
+ /**
101
+ * Hook to compute the stable map of server configurations from registry servers
102
+ * and the internal Tambo MCP server (when token is available).
103
+ * @returns A map of server key to server configuration
104
+ */
105
+ function useServerConfigs(mcpServers, mcpAccessToken, tamboBaseUrl) {
106
+ return useMemo(() => {
107
+ const serverMap = new Map();
108
+ // Add user-provided MCP servers (browser-side)
109
+ mcpServers.forEach((server) => {
110
+ const serverInfo = normalizeServerInfo(server, ServerType.BROWSER_SIDE);
111
+ serverMap.set(serverInfo.key, serverInfo);
112
+ });
113
+ // Add internal Tambo MCP server if we have an access token and a base URL
114
+ if (mcpAccessToken && tamboBaseUrl) {
115
+ const base = new URL(tamboBaseUrl);
116
+ base.pathname = `${base.pathname.replace(/\/+$/, "")}/mcp`;
117
+ const tamboMcpUrl = base.toString();
118
+ const tokenHash = hashString(mcpAccessToken);
119
+ const internalServer = {
120
+ name: TAMBO_INTERNAL_MCP_SERVER_NAME,
121
+ url: tamboMcpUrl,
122
+ transport: MCPTransport.HTTP,
123
+ serverKey: `tambo-${tokenHash}`,
124
+ customHeaders: {
125
+ Authorization: `Bearer ${mcpAccessToken}`,
126
+ },
127
+ };
128
+ const serverInfo = normalizeServerInfo(internalServer, ServerType.TAMBO_INTERNAL);
129
+ serverMap.set(serverInfo.key, serverInfo);
130
+ }
131
+ return serverMap;
132
+ }, [mcpServers, mcpAccessToken, tamboBaseUrl]);
133
+ }
134
+ /**
135
+ * Registers tools from a connected MCP server with deduplication.
136
+ */
137
+ async function registerServerTools(client, serverInfo, key, shouldPrefix, clientMap, ownershipRefs, registerTool) {
138
+ const { toolOwnerRef, keyToToolsRef } = ownershipRefs;
139
+ try {
140
+ const tools = await client.listTools();
141
+ tools.forEach((tool) => {
142
+ const toolName = shouldPrefix
143
+ ? `${serverInfo.serverKey}__${tool.name}`
144
+ : tool.name;
145
+ // Skip if another server already owns this tool
146
+ const currentOwner = toolOwnerRef.current.get(toolName);
147
+ if (currentOwner && currentOwner !== key) {
148
+ return;
149
+ }
150
+ // Record ownership
151
+ if (!currentOwner) {
152
+ toolOwnerRef.current.set(toolName, key);
153
+ if (!keyToToolsRef.current.has(key)) {
154
+ keyToToolsRef.current.set(key, new Set());
155
+ }
156
+ keyToToolsRef.current.get(key).add(toolName);
157
+ }
158
+ registerTool({
159
+ description: tool.description ?? "",
160
+ name: toolName,
161
+ tool: async (args = {}) => {
162
+ const server = clientMap.get(key);
163
+ if (!server?.client) {
164
+ throw new Error(`MCP server for tool ${tool.name} is not connected`);
165
+ }
166
+ const result = await server.client.callTool(tool.name, args);
167
+ if (result.isError) {
168
+ const errorMessage = extractErrorMessage(result.content);
169
+ throw new Error(errorMessage);
170
+ }
171
+ return result.content;
172
+ },
173
+ inputSchema: tool.inputSchema ?? {},
174
+ outputSchema: {},
175
+ transformToContent: (content) => {
176
+ if (isContentPartArray(content)) {
177
+ return content;
178
+ }
179
+ return [{ type: "text", text: toText(content) }];
180
+ },
181
+ });
182
+ });
183
+ }
184
+ catch (error) {
185
+ console.error(`Failed to register tools from MCP server ${serverInfo.url}:`, error);
186
+ }
187
+ }
188
+ /**
189
+ * Releases tool ownership for a server being removed.
190
+ */
191
+ function releaseToolOwnership(key, ownershipRefs) {
192
+ const { toolOwnerRef, keyToToolsRef } = ownershipRefs;
193
+ const owned = keyToToolsRef.current.get(key);
194
+ if (owned) {
195
+ for (const name of owned) {
196
+ toolOwnerRef.current.delete(name);
197
+ }
198
+ keyToToolsRef.current.delete(key);
199
+ }
200
+ }
56
201
  /**
57
202
  * This provider is used to register tools from MCP servers.
58
203
  * It automatically includes an internal Tambo MCP server when an MCP access token is available.
@@ -78,41 +223,31 @@ export const TamboMcpProvider = ({ handlers, contextKey, children }) => {
78
223
  // Stable reference to track active clients by server key
79
224
  const clientMapRef = useRef(new Map());
80
225
  // Track tool ownership to prevent duplicate registrations across servers
81
- // toolOwnerRef: tool name -> server key; keyToToolsRef: server key -> set of tool names
82
226
  const toolOwnerRef = useRef(new Map());
83
227
  const keyToToolsRef = useRef(new Map());
228
+ const ownershipRefs = { toolOwnerRef, keyToToolsRef };
84
229
  // State for exposing connected servers to consumers
85
230
  const [connectedMcpServers, setConnectedMcpServers] = useState([]);
86
- // Stable map of current server configurations keyed by server key
87
- const currentServersMap = useMemo(() => {
88
- const servers = [...mcpServers];
89
- // Add internal Tambo MCP server if we have an access token and a base URL
90
- if (mcpAccessToken && tamboBaseUrl) {
91
- const base = new URL(tamboBaseUrl);
92
- base.pathname = `${base.pathname.replace(/\/+$/, "")}/mcp`;
93
- const tamboMcpUrl = base.toString();
94
- // Include token hash in serverKey so changing tokens trigger reconnection
95
- const tokenHash = hashString(mcpAccessToken);
96
- servers.push({
97
- name: TAMBO_INTERNAL_MCP_SERVER_NAME,
98
- url: tamboMcpUrl,
99
- transport: MCPTransport.HTTP,
100
- serverKey: `tambo-${tokenHash}`, // Include token hash in key to force reconnection on token change
101
- customHeaders: {
102
- Authorization: `Bearer ${mcpAccessToken}`,
103
- },
104
- });
105
- }
106
- // Create a map of server key -> server info for efficient lookups
107
- const serverMap = new Map();
108
- servers.forEach((server) => {
109
- const serverInfo = normalizeServerInfo(server);
110
- // Store without cloning to avoid unnecessary allocation
111
- serverMap.set(serverInfo.key, serverInfo);
112
- });
113
- return serverMap;
114
- }, [mcpServers, mcpAccessToken, tamboBaseUrl]);
231
+ // Compute server configurations from registry and internal server
232
+ const currentServersMap = useServerConfigs(mcpServers, mcpAccessToken, tamboBaseUrl);
115
233
  // Main effect: manage client lifecycle (create/remove)
234
+ useClientLifecycle(currentServersMap, clientMapRef, ownershipRefs, providerElicitationHandler, providerSamplingHandler, registerTool, setConnectedMcpServers);
235
+ // Update handlers when they change (without recreating clients)
236
+ useHandlerUpdates(currentServersMap, clientMapRef, providerElicitationHandler, providerSamplingHandler);
237
+ // Cleanup on unmount: close all clients
238
+ useCleanupOnUnmount(clientMapRef, ownershipRefs);
239
+ const contextValue = useMemo(() => ({
240
+ servers: connectedMcpServers,
241
+ elicitation,
242
+ resolveElicitation,
243
+ }), [connectedMcpServers, elicitation, resolveElicitation]);
244
+ return (React.createElement(McpProviderContext.Provider, { value: contextValue }, children));
245
+ };
246
+ /**
247
+ * Hook to manage client lifecycle: creating clients for new servers,
248
+ * removing clients for servers that are no longer in the list.
249
+ */
250
+ function useClientLifecycle(currentServersMap, clientMapRef, ownershipRefs, providerElicitationHandler, providerSamplingHandler, registerTool, setConnectedMcpServers) {
116
251
  useEffect(() => {
117
252
  const clientMap = clientMapRef.current;
118
253
  const currentKeys = new Set(currentServersMap.keys());
@@ -121,30 +256,10 @@ export const TamboMcpProvider = ({ handlers, contextKey, children }) => {
121
256
  const keysToRemove = Array.from(existingKeys).filter((key) => !currentKeys.has(key));
122
257
  keysToRemove.forEach((key) => {
123
258
  const server = clientMap.get(key);
124
- if (server?.client?.close) {
125
- try {
126
- // Call close() sync - it may or may not return a promise
127
- const closeResult = server.client.close();
128
- // If it returns a promise, handle errors but don't wait
129
- if (closeResult && typeof closeResult.catch === "function") {
130
- void closeResult.catch((error) => {
131
- const url = server.url ?? "(unknown url)";
132
- console.error(`Error closing MCP client for ${url}:`, error);
133
- });
134
- }
135
- }
136
- catch (error) {
137
- const url = server.url ?? "(unknown url)";
138
- console.error(`Error closing MCP client for ${url}:`, error);
139
- }
140
- }
141
- // Release tool ownership for this server
142
- const owned = keyToToolsRef.current.get(key);
143
- if (owned) {
144
- for (const name of owned)
145
- toolOwnerRef.current.delete(name);
146
- keyToToolsRef.current.delete(key);
259
+ if (server) {
260
+ closeClientSafely(server);
147
261
  }
262
+ releaseToolOwnership(key, ownershipRefs);
148
263
  clientMap.delete(key);
149
264
  });
150
265
  // 2. Add new clients for servers that don't exist yet
@@ -153,20 +268,7 @@ export const TamboMcpProvider = ({ handlers, contextKey, children }) => {
153
268
  await Promise.allSettled(keys.map(async (key) => {
154
269
  const serverInfo = currentServersMap.get(key);
155
270
  try {
156
- // Build effective handlers (per-server overrides provider)
157
- const effectiveHandlers = {};
158
- if (serverInfo.handlers?.elicitation) {
159
- effectiveHandlers.elicitation = serverInfo.handlers.elicitation;
160
- }
161
- else if (providerElicitationHandler) {
162
- effectiveHandlers.elicitation = async (request, extra) => await providerElicitationHandler(request, extra, serverInfo);
163
- }
164
- if (serverInfo.handlers?.sampling) {
165
- effectiveHandlers.sampling = serverInfo.handlers.sampling;
166
- }
167
- else if (providerSamplingHandler) {
168
- effectiveHandlers.sampling = async (request, extra) => await providerSamplingHandler(request, extra, serverInfo);
169
- }
271
+ const effectiveHandlers = buildEffectiveHandlers(serverInfo, providerElicitationHandler, providerSamplingHandler);
170
272
  const client = await MCPClient.create(serverInfo.url, serverInfo.transport, serverInfo.customHeaders, undefined, undefined, effectiveHandlers);
171
273
  const connectedServer = {
172
274
  ...serverInfo,
@@ -174,57 +276,9 @@ export const TamboMcpProvider = ({ handlers, contextKey, children }) => {
174
276
  };
175
277
  clientMap.set(key, connectedServer);
176
278
  setConnectedMcpServers(Array.from(clientMap.values()));
177
- // Register tools from this server (deduplicated by ownership)
178
- try {
179
- const tools = await client.listTools();
180
- const shouldPrefix = currentServersMap.size > 1;
181
- tools.forEach((tool) => {
182
- // Prefix tool name with serverKey if multiple servers are present
183
- const toolName = shouldPrefix
184
- ? `${serverInfo.serverKey}__${tool.name}`
185
- : tool.name;
186
- // Skip if another server already owns this tool (using final name for ownership)
187
- const currentOwner = toolOwnerRef.current.get(toolName);
188
- if (currentOwner && currentOwner !== key) {
189
- return;
190
- }
191
- // Record ownership for this server key (using final name)
192
- if (!currentOwner) {
193
- toolOwnerRef.current.set(toolName, key);
194
- if (!keyToToolsRef.current.has(key)) {
195
- keyToToolsRef.current.set(key, new Set());
196
- }
197
- keyToToolsRef.current.get(key).add(toolName);
198
- }
199
- registerTool({
200
- description: tool.description ?? "",
201
- name: toolName,
202
- tool: async (args = {}) => {
203
- const server = clientMap.get(key);
204
- if (!server?.client) {
205
- throw new Error(`MCP server for tool ${tool.name} is not connected`);
206
- }
207
- const result = await server.client.callTool(tool.name, args);
208
- if (result.isError) {
209
- const errorMessage = extractErrorMessage(result.content);
210
- throw new Error(errorMessage);
211
- }
212
- return result.content;
213
- },
214
- inputSchema: tool.inputSchema ?? {},
215
- outputSchema: {},
216
- transformToContent: (content) => {
217
- if (isContentPartArray(content)) {
218
- return content;
219
- }
220
- return [{ type: "text", text: toText(content) }];
221
- },
222
- });
223
- });
224
- }
225
- catch (error) {
226
- console.error(`Failed to register tools from MCP server ${serverInfo.url}:`, error);
227
- }
279
+ // Register tools from this server
280
+ const shouldPrefix = currentServersMap.size > 1;
281
+ await registerServerTools(client, serverInfo, key, shouldPrefix, clientMap, ownershipRefs, registerTool);
228
282
  }
229
283
  catch (error) {
230
284
  const failedServer = {
@@ -242,17 +296,24 @@ export const TamboMcpProvider = ({ handlers, contextKey, children }) => {
242
296
  console.error("Unexpected error in addClients:", err);
243
297
  });
244
298
  }
245
- // Update state after removals (additions update state asynchronously above)
299
+ // Update state after removals
246
300
  if (keysToRemove.length > 0) {
247
301
  setConnectedMcpServers(Array.from(clientMap.values()));
248
302
  }
303
+ // Note: refs (clientMapRef, ownershipRefs) and setters (setConnectedMcpServers)
304
+ // are intentionally excluded from deps as they are stable references
305
+ // eslint-disable-next-line react-hooks/exhaustive-deps
249
306
  }, [
250
307
  currentServersMap,
251
308
  providerElicitationHandler,
252
309
  providerSamplingHandler,
253
310
  registerTool,
254
311
  ]);
255
- // Update handlers when they change (without recreating clients)
312
+ }
313
+ /**
314
+ * Hook to update handlers on connected clients when provider handlers change.
315
+ */
316
+ function useHandlerUpdates(currentServersMap, clientMapRef, providerElicitationHandler, providerSamplingHandler) {
256
317
  useEffect(() => {
257
318
  const clientMap = clientMapRef.current;
258
319
  clientMap.forEach((server, key) => {
@@ -261,9 +322,9 @@ export const TamboMcpProvider = ({ handlers, contextKey, children }) => {
261
322
  }
262
323
  const serverInfo = currentServersMap.get(key);
263
324
  if (!serverInfo) {
264
- return; // Server was removed, handled by main effect
325
+ return; // Server was removed, handled by lifecycle effect
265
326
  }
266
- // Determine effective handlers
327
+ // Build effective handlers and update the client
267
328
  const effectiveElicitationHandler = serverInfo.handlers?.elicitation ??
268
329
  (providerElicitationHandler
269
330
  ? async (request, extra) => await providerElicitationHandler(request, extra, serverInfo)
@@ -272,48 +333,32 @@ export const TamboMcpProvider = ({ handlers, contextKey, children }) => {
272
333
  (providerSamplingHandler
273
334
  ? async (request, extra) => await providerSamplingHandler(request, extra, serverInfo)
274
335
  : undefined);
275
- // Update handlers unconditionally (allows removal by passing undefined)
276
336
  server.client.updateElicitationHandler?.(effectiveElicitationHandler);
277
337
  server.client.updateSamplingHandler?.(effectiveSamplingHandler);
278
338
  });
339
+ // Note: clientMapRef is intentionally excluded from deps as it's a stable ref
340
+ // eslint-disable-next-line react-hooks/exhaustive-deps
279
341
  }, [currentServersMap, providerElicitationHandler, providerSamplingHandler]);
280
- // Cleanup on unmount: close all clients
342
+ }
343
+ /**
344
+ * Hook to cleanup all clients and tool ownership on component unmount.
345
+ */
346
+ function useCleanupOnUnmount(clientMapRef, ownershipRefs) {
281
347
  useEffect(() => {
282
348
  const clientMap = clientMapRef.current;
283
- const ownerMapAtMount = toolOwnerRef.current;
284
- const keyToToolsAtMount = keyToToolsRef.current;
349
+ const { toolOwnerRef, keyToToolsRef } = ownershipRefs;
285
350
  return () => {
286
351
  clientMap.forEach((server) => {
287
- if (server?.client?.close) {
288
- try {
289
- // Call close() sync - it may or may not return a promise
290
- const closeResult = server.client.close();
291
- // If it returns a promise, handle errors but don't wait
292
- if (closeResult && typeof closeResult.catch === "function") {
293
- void closeResult.catch((error) => {
294
- const url = server.url ?? "(unknown url)";
295
- console.error(`Error closing MCP client on unmount for ${url}:`, error);
296
- });
297
- }
298
- }
299
- catch (error) {
300
- const url = server.url ?? "(unknown url)";
301
- console.error(`Error closing MCP client on unmount for ${url}:`, error);
302
- }
303
- }
352
+ closeClientSafely(server);
304
353
  });
305
354
  clientMap.clear();
306
- ownerMapAtMount.clear();
307
- keyToToolsAtMount.clear();
355
+ toolOwnerRef.current.clear();
356
+ keyToToolsRef.current.clear();
308
357
  };
358
+ // Only run cleanup on unmount
359
+ // eslint-disable-next-line react-hooks/exhaustive-deps
309
360
  }, []);
310
- const contextValue = useMemo(() => ({
311
- servers: connectedMcpServers,
312
- elicitation,
313
- resolveElicitation,
314
- }), [connectedMcpServers, elicitation, resolveElicitation]);
315
- return (React.createElement(McpProviderContext.Provider, { value: contextValue }, children));
316
- };
361
+ }
317
362
  /**
318
363
  * Hook to access the actual MCP servers, as they are connected (or fail to
319
364
  * connect).
@@ -384,9 +429,10 @@ export const useTamboElicitationContext = useTamboMcpElicitation;
384
429
  * `transport` and a `serverKey` derived by the registry, and narrows the
385
430
  * opaque `handlers` field to `Partial<MCPHandlers>`.
386
431
  * @param server - The normalized MCP server info from the registry
387
- * @returns The server config with typed handlers and unique key
432
+ * @param serverType - The type of server (internal vs browser-side)
433
+ * @returns The server config with typed handlers, unique key, and server type
388
434
  */
389
- function normalizeServerInfo(server) {
435
+ function normalizeServerInfo(server, serverType) {
390
436
  // Always use getMcpServerUniqueKey for connection identity to ensure
391
437
  // that changes to URL, transport, or customHeaders trigger client recreation.
392
438
  // The serverKey is kept for namespacing purposes (readable short name),
@@ -398,6 +444,7 @@ function normalizeServerInfo(server) {
398
444
  ...server,
399
445
  handlers,
400
446
  key,
447
+ serverType,
401
448
  };
402
449
  }
403
450
  //# 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;