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