@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
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const react_1 = require("@testing-library/react");
|
|
37
|
+
const react_query_1 = require("@tanstack/react-query");
|
|
38
|
+
const react_2 = __importStar(require("react"));
|
|
39
|
+
const tambo_client_provider_1 = require("../../providers/tambo-client-provider");
|
|
40
|
+
const tambo_mcp_token_provider_1 = require("../../providers/tambo-mcp-token-provider");
|
|
41
|
+
const tambo_registry_provider_1 = require("../../providers/tambo-registry-provider");
|
|
42
|
+
const mcp_client_1 = require("../mcp-client");
|
|
43
|
+
const tambo_mcp_provider_1 = require("../tambo-mcp-provider");
|
|
44
|
+
const mcp_hooks_1 = require("../mcp-hooks");
|
|
45
|
+
// Mock the MCP client to avoid ES module issues
|
|
46
|
+
let createImpl = jest.fn();
|
|
47
|
+
jest.mock("../mcp-client", () => ({
|
|
48
|
+
MCPClient: { create: (...args) => createImpl(...args) },
|
|
49
|
+
MCPTransport: { SSE: "sse", HTTP: "http" },
|
|
50
|
+
}));
|
|
51
|
+
describe("useTamboMcpPromptList - individual server caching", () => {
|
|
52
|
+
let queryClient;
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
createImpl = jest.fn();
|
|
55
|
+
queryClient = new react_query_1.QueryClient({
|
|
56
|
+
defaultOptions: {
|
|
57
|
+
queries: {
|
|
58
|
+
retry: false, // Disable retries for tests
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
queryClient.clear();
|
|
65
|
+
});
|
|
66
|
+
it("should fetch and combine prompts from multiple servers", async () => {
|
|
67
|
+
// Mock two servers with different prompts
|
|
68
|
+
const serverAPrompts = {
|
|
69
|
+
prompts: [
|
|
70
|
+
{ name: "prompt-a1", description: "Prompt A1" },
|
|
71
|
+
{ name: "prompt-a2", description: "Prompt A2" },
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
const serverBPrompts = {
|
|
75
|
+
prompts: [
|
|
76
|
+
{ name: "prompt-b1", description: "Prompt B1" },
|
|
77
|
+
{ name: "prompt-b2", description: "Prompt B2" },
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
const mockClientA = {
|
|
81
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
82
|
+
listPrompts: jest.fn().mockResolvedValue(serverAPrompts),
|
|
83
|
+
close: jest.fn(),
|
|
84
|
+
};
|
|
85
|
+
const mockClientB = {
|
|
86
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
87
|
+
listPrompts: jest.fn().mockResolvedValue(serverBPrompts),
|
|
88
|
+
close: jest.fn(),
|
|
89
|
+
};
|
|
90
|
+
const clientA = {
|
|
91
|
+
client: mockClientA,
|
|
92
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
93
|
+
close: jest.fn(),
|
|
94
|
+
};
|
|
95
|
+
const clientB = {
|
|
96
|
+
client: mockClientB,
|
|
97
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
98
|
+
close: jest.fn(),
|
|
99
|
+
};
|
|
100
|
+
// Mock MCPClient.create to return different clients for different URLs
|
|
101
|
+
createImpl.mockImplementation(async (url) => {
|
|
102
|
+
if (url === "https://server-a.example")
|
|
103
|
+
return clientA;
|
|
104
|
+
if (url === "https://server-b.example")
|
|
105
|
+
return clientB;
|
|
106
|
+
throw new Error(`Unexpected URL: ${url}`);
|
|
107
|
+
});
|
|
108
|
+
let capturedPrompts = [];
|
|
109
|
+
const Capture = () => {
|
|
110
|
+
const { data: prompts } = (0, mcp_hooks_1.useTamboMcpPromptList)();
|
|
111
|
+
(0, react_2.useEffect)(() => {
|
|
112
|
+
if (prompts) {
|
|
113
|
+
capturedPrompts = prompts;
|
|
114
|
+
}
|
|
115
|
+
}, [prompts]);
|
|
116
|
+
return null;
|
|
117
|
+
};
|
|
118
|
+
(0, react_1.render)(react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
|
|
119
|
+
client: { baseURL: "https://api.tambo.co" },
|
|
120
|
+
queryClient,
|
|
121
|
+
isUpdatingToken: false,
|
|
122
|
+
} },
|
|
123
|
+
react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, null,
|
|
124
|
+
react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
|
|
125
|
+
react_2.default.createElement(tambo_mcp_provider_1.TamboMcpProvider, { mcpServers: [
|
|
126
|
+
{
|
|
127
|
+
url: "https://server-a.example",
|
|
128
|
+
transport: mcp_client_1.MCPTransport.SSE,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
url: "https://server-b.example",
|
|
132
|
+
transport: mcp_client_1.MCPTransport.SSE,
|
|
133
|
+
},
|
|
134
|
+
] },
|
|
135
|
+
react_2.default.createElement(Capture, null))))));
|
|
136
|
+
// Wait for all prompts to be loaded
|
|
137
|
+
await (0, react_1.waitFor)(() => {
|
|
138
|
+
expect(capturedPrompts.length).toBe(4);
|
|
139
|
+
});
|
|
140
|
+
// Verify all prompts are present
|
|
141
|
+
const promptNames = capturedPrompts.map((p) => p.prompt.name);
|
|
142
|
+
expect(promptNames).toContain("prompt-a1");
|
|
143
|
+
expect(promptNames).toContain("prompt-a2");
|
|
144
|
+
expect(promptNames).toContain("prompt-b1");
|
|
145
|
+
expect(promptNames).toContain("prompt-b2");
|
|
146
|
+
// Verify each prompt has the correct server info
|
|
147
|
+
const promptA1 = capturedPrompts.find((p) => p.prompt.name === "prompt-a1");
|
|
148
|
+
expect(promptA1?.server.url).toBe("https://server-a.example");
|
|
149
|
+
const promptB1 = capturedPrompts.find((p) => p.prompt.name === "prompt-b1");
|
|
150
|
+
expect(promptB1?.server.url).toBe("https://server-b.example");
|
|
151
|
+
});
|
|
152
|
+
it("should remove prompts when a server is removed", async () => {
|
|
153
|
+
// Mock two servers with different prompts
|
|
154
|
+
const serverAPrompts = {
|
|
155
|
+
prompts: [
|
|
156
|
+
{ name: "prompt-a1", description: "Prompt A1" },
|
|
157
|
+
{ name: "prompt-a2", description: "Prompt A2" },
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
const serverBPrompts = {
|
|
161
|
+
prompts: [
|
|
162
|
+
{ name: "prompt-b1", description: "Prompt B1" },
|
|
163
|
+
{ name: "prompt-b2", description: "Prompt B2" },
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
const mockClientA = {
|
|
167
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
168
|
+
listPrompts: jest.fn().mockResolvedValue(serverAPrompts),
|
|
169
|
+
close: jest.fn(),
|
|
170
|
+
};
|
|
171
|
+
const mockClientB = {
|
|
172
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
173
|
+
listPrompts: jest.fn().mockResolvedValue(serverBPrompts),
|
|
174
|
+
close: jest.fn(),
|
|
175
|
+
};
|
|
176
|
+
const clientA = {
|
|
177
|
+
client: mockClientA,
|
|
178
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
179
|
+
close: jest.fn(),
|
|
180
|
+
};
|
|
181
|
+
const clientB = {
|
|
182
|
+
client: mockClientB,
|
|
183
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
184
|
+
close: jest.fn(),
|
|
185
|
+
};
|
|
186
|
+
createImpl.mockImplementation(async (url) => {
|
|
187
|
+
if (url === "https://server-a.example")
|
|
188
|
+
return clientA;
|
|
189
|
+
if (url === "https://server-b.example")
|
|
190
|
+
return clientB;
|
|
191
|
+
throw new Error(`Unexpected URL: ${url}`);
|
|
192
|
+
});
|
|
193
|
+
let capturedPrompts = [];
|
|
194
|
+
const Capture = () => {
|
|
195
|
+
const { data: prompts } = (0, mcp_hooks_1.useTamboMcpPromptList)();
|
|
196
|
+
(0, react_2.useEffect)(() => {
|
|
197
|
+
if (prompts) {
|
|
198
|
+
capturedPrompts = prompts;
|
|
199
|
+
}
|
|
200
|
+
}, [prompts]);
|
|
201
|
+
return null;
|
|
202
|
+
};
|
|
203
|
+
const { rerender } = (0, react_1.render)(react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
|
|
204
|
+
client: { baseURL: "https://api.tambo.co" },
|
|
205
|
+
queryClient,
|
|
206
|
+
isUpdatingToken: false,
|
|
207
|
+
} },
|
|
208
|
+
react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, null,
|
|
209
|
+
react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
|
|
210
|
+
react_2.default.createElement(tambo_mcp_provider_1.TamboMcpProvider, { mcpServers: [
|
|
211
|
+
{
|
|
212
|
+
url: "https://server-a.example",
|
|
213
|
+
transport: mcp_client_1.MCPTransport.SSE,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
url: "https://server-b.example",
|
|
217
|
+
transport: mcp_client_1.MCPTransport.SSE,
|
|
218
|
+
},
|
|
219
|
+
] },
|
|
220
|
+
react_2.default.createElement(Capture, null))))));
|
|
221
|
+
// Wait for initial prompts to be loaded
|
|
222
|
+
await (0, react_1.waitFor)(() => {
|
|
223
|
+
expect(capturedPrompts.length).toBe(4);
|
|
224
|
+
});
|
|
225
|
+
const initialPromptNames = capturedPrompts.map((p) => p.prompt.name);
|
|
226
|
+
expect(initialPromptNames).toContain("prompt-a1");
|
|
227
|
+
expect(initialPromptNames).toContain("prompt-a2");
|
|
228
|
+
expect(initialPromptNames).toContain("prompt-b1");
|
|
229
|
+
expect(initialPromptNames).toContain("prompt-b2");
|
|
230
|
+
// Remove server B
|
|
231
|
+
rerender(react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
|
|
232
|
+
client: { baseURL: "https://api.tambo.co" },
|
|
233
|
+
queryClient,
|
|
234
|
+
isUpdatingToken: false,
|
|
235
|
+
} },
|
|
236
|
+
react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, null,
|
|
237
|
+
react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
|
|
238
|
+
react_2.default.createElement(tambo_mcp_provider_1.TamboMcpProvider, { mcpServers: [
|
|
239
|
+
{
|
|
240
|
+
url: "https://server-a.example",
|
|
241
|
+
transport: mcp_client_1.MCPTransport.SSE,
|
|
242
|
+
},
|
|
243
|
+
] },
|
|
244
|
+
react_2.default.createElement(Capture, null))))));
|
|
245
|
+
// Wait for prompts to be updated (server B prompts should disappear)
|
|
246
|
+
await (0, react_1.waitFor)(() => {
|
|
247
|
+
expect(capturedPrompts.length).toBe(2);
|
|
248
|
+
});
|
|
249
|
+
const updatedPromptNames = capturedPrompts.map((p) => p.prompt.name);
|
|
250
|
+
expect(updatedPromptNames).toContain("prompt-a1");
|
|
251
|
+
expect(updatedPromptNames).toContain("prompt-a2");
|
|
252
|
+
expect(updatedPromptNames).not.toContain("prompt-b1");
|
|
253
|
+
expect(updatedPromptNames).not.toContain("prompt-b2");
|
|
254
|
+
// Verify server B's client was closed
|
|
255
|
+
expect(clientB.close).toHaveBeenCalled();
|
|
256
|
+
});
|
|
257
|
+
it("should cache prompts individually per server using server key", async () => {
|
|
258
|
+
const serverAPrompts = {
|
|
259
|
+
prompts: [{ name: "prompt-a", description: "Prompt A" }],
|
|
260
|
+
};
|
|
261
|
+
const serverBPrompts = {
|
|
262
|
+
prompts: [{ name: "prompt-b", description: "Prompt B" }],
|
|
263
|
+
};
|
|
264
|
+
const mockClientA = {
|
|
265
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
266
|
+
listPrompts: jest.fn().mockResolvedValue(serverAPrompts),
|
|
267
|
+
close: jest.fn(),
|
|
268
|
+
};
|
|
269
|
+
const mockClientB = {
|
|
270
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
271
|
+
listPrompts: jest.fn().mockResolvedValue(serverBPrompts),
|
|
272
|
+
close: jest.fn(),
|
|
273
|
+
};
|
|
274
|
+
const clientA = {
|
|
275
|
+
client: mockClientA,
|
|
276
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
277
|
+
close: jest.fn(),
|
|
278
|
+
};
|
|
279
|
+
const clientB = {
|
|
280
|
+
client: mockClientB,
|
|
281
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
282
|
+
close: jest.fn(),
|
|
283
|
+
};
|
|
284
|
+
createImpl.mockImplementation(async (url) => {
|
|
285
|
+
if (url === "https://server-a.example")
|
|
286
|
+
return clientA;
|
|
287
|
+
if (url === "https://server-b.example")
|
|
288
|
+
return clientB;
|
|
289
|
+
throw new Error(`Unexpected URL: ${url}`);
|
|
290
|
+
});
|
|
291
|
+
let capturedPrompts = [];
|
|
292
|
+
let capturedServerKeys = [];
|
|
293
|
+
const Capture = () => {
|
|
294
|
+
const { data: prompts } = (0, mcp_hooks_1.useTamboMcpPromptList)();
|
|
295
|
+
const servers = (0, tambo_mcp_provider_1.useTamboMcpServers)();
|
|
296
|
+
(0, react_2.useEffect)(() => {
|
|
297
|
+
if (prompts) {
|
|
298
|
+
capturedPrompts = prompts;
|
|
299
|
+
}
|
|
300
|
+
capturedServerKeys = servers.map((s) => s.key);
|
|
301
|
+
}, [prompts, servers]);
|
|
302
|
+
return null;
|
|
303
|
+
};
|
|
304
|
+
(0, react_1.render)(react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
|
|
305
|
+
client: { baseURL: "https://api.tambo.co" },
|
|
306
|
+
queryClient,
|
|
307
|
+
isUpdatingToken: false,
|
|
308
|
+
} },
|
|
309
|
+
react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, null,
|
|
310
|
+
react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
|
|
311
|
+
react_2.default.createElement(tambo_mcp_provider_1.TamboMcpProvider, { mcpServers: [
|
|
312
|
+
{
|
|
313
|
+
url: "https://server-a.example",
|
|
314
|
+
transport: mcp_client_1.MCPTransport.SSE,
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
url: "https://server-b.example",
|
|
318
|
+
transport: mcp_client_1.MCPTransport.SSE,
|
|
319
|
+
},
|
|
320
|
+
] },
|
|
321
|
+
react_2.default.createElement(Capture, null))))));
|
|
322
|
+
// Wait for prompts to be loaded
|
|
323
|
+
await (0, react_1.waitFor)(() => {
|
|
324
|
+
expect(capturedPrompts.length).toBe(2);
|
|
325
|
+
});
|
|
326
|
+
// Verify that listPrompts was called once per server
|
|
327
|
+
expect(mockClientA.listPrompts).toHaveBeenCalledTimes(1);
|
|
328
|
+
expect(mockClientB.listPrompts).toHaveBeenCalledTimes(1);
|
|
329
|
+
// Verify the query cache has separate entries for each server
|
|
330
|
+
const cacheKeys = queryClient
|
|
331
|
+
.getQueryCache()
|
|
332
|
+
.getAll()
|
|
333
|
+
.map((query) => query.queryKey);
|
|
334
|
+
// Should have separate cache entries for each server's prompts
|
|
335
|
+
const promptCacheKeys = cacheKeys.filter((key) => Array.isArray(key) && key[0] === "mcp-prompts");
|
|
336
|
+
expect(promptCacheKeys.length).toBe(2);
|
|
337
|
+
// Ensure provider keys are populated before asserting
|
|
338
|
+
await (0, react_1.waitFor)(() => expect(capturedServerKeys.length).toBe(2));
|
|
339
|
+
// Verify each connected server key is present exactly once in the cache keys
|
|
340
|
+
const promptKeySet = new Set(promptCacheKeys.map((k) => String(k[1])));
|
|
341
|
+
// The server keys are captured from the provider to avoid relying on string substrings
|
|
342
|
+
capturedServerKeys.forEach((key) => {
|
|
343
|
+
expect(promptKeySet.has(key)).toBe(true);
|
|
344
|
+
});
|
|
345
|
+
// And no duplicates
|
|
346
|
+
expect(promptKeySet.size).toBe(capturedServerKeys.length);
|
|
347
|
+
});
|
|
348
|
+
it("should handle server errors gracefully without affecting other servers", async () => {
|
|
349
|
+
const serverAPrompts = {
|
|
350
|
+
prompts: [{ name: "prompt-a", description: "Prompt A" }],
|
|
351
|
+
};
|
|
352
|
+
const mockClientA = {
|
|
353
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
354
|
+
listPrompts: jest.fn().mockResolvedValue(serverAPrompts),
|
|
355
|
+
close: jest.fn(),
|
|
356
|
+
};
|
|
357
|
+
const clientA = {
|
|
358
|
+
client: mockClientA,
|
|
359
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
360
|
+
close: jest.fn(),
|
|
361
|
+
};
|
|
362
|
+
// Server B will fail to connect
|
|
363
|
+
createImpl.mockImplementation(async (url) => {
|
|
364
|
+
if (url === "https://server-a.example")
|
|
365
|
+
return clientA;
|
|
366
|
+
if (url === "https://server-b.example") {
|
|
367
|
+
throw new Error("Connection failed");
|
|
368
|
+
}
|
|
369
|
+
throw new Error(`Unexpected URL: ${url}`);
|
|
370
|
+
});
|
|
371
|
+
let capturedPrompts = [];
|
|
372
|
+
let mcpServersCount = 0;
|
|
373
|
+
const Capture = () => {
|
|
374
|
+
const { data: prompts } = (0, mcp_hooks_1.useTamboMcpPromptList)();
|
|
375
|
+
const servers = (0, tambo_mcp_provider_1.useTamboMcpServers)();
|
|
376
|
+
(0, react_2.useEffect)(() => {
|
|
377
|
+
if (prompts) {
|
|
378
|
+
capturedPrompts = prompts;
|
|
379
|
+
}
|
|
380
|
+
mcpServersCount = servers.length;
|
|
381
|
+
}, [prompts, servers]);
|
|
382
|
+
return null;
|
|
383
|
+
};
|
|
384
|
+
(0, react_1.render)(react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
|
|
385
|
+
client: { baseURL: "https://api.tambo.co" },
|
|
386
|
+
queryClient,
|
|
387
|
+
isUpdatingToken: false,
|
|
388
|
+
} },
|
|
389
|
+
react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, null,
|
|
390
|
+
react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
|
|
391
|
+
react_2.default.createElement(tambo_mcp_provider_1.TamboMcpProvider, { mcpServers: [
|
|
392
|
+
{
|
|
393
|
+
url: "https://server-a.example",
|
|
394
|
+
transport: mcp_client_1.MCPTransport.SSE,
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
url: "https://server-b.example",
|
|
398
|
+
transport: mcp_client_1.MCPTransport.SSE,
|
|
399
|
+
},
|
|
400
|
+
] },
|
|
401
|
+
react_2.default.createElement(Capture, null))))));
|
|
402
|
+
// Wait for server A prompts to be loaded
|
|
403
|
+
// Server B should fail but not block server A
|
|
404
|
+
await (0, react_1.waitFor)(() => {
|
|
405
|
+
expect(capturedPrompts.length).toBe(1);
|
|
406
|
+
expect(mcpServersCount).toBe(2); // Both servers should be in the list
|
|
407
|
+
});
|
|
408
|
+
// Verify only server A's prompts are present
|
|
409
|
+
const promptNames = capturedPrompts.map((p) => p.prompt.name);
|
|
410
|
+
expect(promptNames).toContain("prompt-a");
|
|
411
|
+
expect(promptNames).not.toContain("prompt-b");
|
|
412
|
+
});
|
|
413
|
+
it("should add prompts when a new server is added", async () => {
|
|
414
|
+
const serverAPrompts = {
|
|
415
|
+
prompts: [{ name: "prompt-a", description: "Prompt A" }],
|
|
416
|
+
};
|
|
417
|
+
const serverBPrompts = {
|
|
418
|
+
prompts: [{ name: "prompt-b", description: "Prompt B" }],
|
|
419
|
+
};
|
|
420
|
+
const mockClientA = {
|
|
421
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
422
|
+
listPrompts: jest.fn().mockResolvedValue(serverAPrompts),
|
|
423
|
+
close: jest.fn(),
|
|
424
|
+
};
|
|
425
|
+
const mockClientB = {
|
|
426
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
427
|
+
listPrompts: jest.fn().mockResolvedValue(serverBPrompts),
|
|
428
|
+
close: jest.fn(),
|
|
429
|
+
};
|
|
430
|
+
const clientA = {
|
|
431
|
+
client: mockClientA,
|
|
432
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
433
|
+
close: jest.fn(),
|
|
434
|
+
};
|
|
435
|
+
const clientB = {
|
|
436
|
+
client: mockClientB,
|
|
437
|
+
listTools: jest.fn().mockResolvedValue([]),
|
|
438
|
+
close: jest.fn(),
|
|
439
|
+
};
|
|
440
|
+
createImpl.mockImplementation(async (url) => {
|
|
441
|
+
if (url === "https://server-a.example")
|
|
442
|
+
return clientA;
|
|
443
|
+
if (url === "https://server-b.example")
|
|
444
|
+
return clientB;
|
|
445
|
+
throw new Error(`Unexpected URL: ${url}`);
|
|
446
|
+
});
|
|
447
|
+
let capturedPrompts = [];
|
|
448
|
+
const Capture = () => {
|
|
449
|
+
const { data: prompts } = (0, mcp_hooks_1.useTamboMcpPromptList)();
|
|
450
|
+
(0, react_2.useEffect)(() => {
|
|
451
|
+
if (prompts) {
|
|
452
|
+
capturedPrompts = prompts;
|
|
453
|
+
}
|
|
454
|
+
}, [prompts]);
|
|
455
|
+
return null;
|
|
456
|
+
};
|
|
457
|
+
const { rerender } = (0, react_1.render)(react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
|
|
458
|
+
client: { baseURL: "https://api.tambo.co" },
|
|
459
|
+
queryClient,
|
|
460
|
+
isUpdatingToken: false,
|
|
461
|
+
} },
|
|
462
|
+
react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, null,
|
|
463
|
+
react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
|
|
464
|
+
react_2.default.createElement(tambo_mcp_provider_1.TamboMcpProvider, { mcpServers: [
|
|
465
|
+
{
|
|
466
|
+
url: "https://server-a.example",
|
|
467
|
+
transport: mcp_client_1.MCPTransport.SSE,
|
|
468
|
+
},
|
|
469
|
+
] },
|
|
470
|
+
react_2.default.createElement(Capture, null))))));
|
|
471
|
+
// Wait for initial prompts from server A
|
|
472
|
+
await (0, react_1.waitFor)(() => {
|
|
473
|
+
expect(capturedPrompts.length).toBe(1);
|
|
474
|
+
});
|
|
475
|
+
expect(capturedPrompts.map((p) => p.prompt.name)).toContain("prompt-a");
|
|
476
|
+
// Add server B
|
|
477
|
+
rerender(react_2.default.createElement(tambo_client_provider_1.TamboClientContext.Provider, { value: {
|
|
478
|
+
client: { baseURL: "https://api.tambo.co" },
|
|
479
|
+
queryClient,
|
|
480
|
+
isUpdatingToken: false,
|
|
481
|
+
} },
|
|
482
|
+
react_2.default.createElement(tambo_registry_provider_1.TamboRegistryProvider, null,
|
|
483
|
+
react_2.default.createElement(tambo_mcp_token_provider_1.TamboMcpTokenProvider, null,
|
|
484
|
+
react_2.default.createElement(tambo_mcp_provider_1.TamboMcpProvider, { mcpServers: [
|
|
485
|
+
{
|
|
486
|
+
url: "https://server-a.example",
|
|
487
|
+
transport: mcp_client_1.MCPTransport.SSE,
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
url: "https://server-b.example",
|
|
491
|
+
transport: mcp_client_1.MCPTransport.SSE,
|
|
492
|
+
},
|
|
493
|
+
] },
|
|
494
|
+
react_2.default.createElement(Capture, null))))));
|
|
495
|
+
// Wait for server B prompts to be added
|
|
496
|
+
await (0, react_1.waitFor)(() => {
|
|
497
|
+
expect(capturedPrompts.length).toBe(2);
|
|
498
|
+
});
|
|
499
|
+
const promptNames = capturedPrompts.map((p) => p.prompt.name);
|
|
500
|
+
expect(promptNames).toContain("prompt-a");
|
|
501
|
+
expect(promptNames).toContain("prompt-b");
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
//# sourceMappingURL=mcp-hooks.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-hooks.test.js","sourceRoot":"","sources":["../../../src/mcp/__tests__/mcp-hooks.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import { render, waitFor } from \"@testing-library/react\";\nimport { QueryClient } from \"@tanstack/react-query\";\nimport React, { useEffect } from \"react\";\nimport { TamboClientContext } from \"../../providers/tambo-client-provider\";\nimport { TamboMcpTokenProvider } from \"../../providers/tambo-mcp-token-provider\";\nimport { TamboRegistryProvider } from \"../../providers/tambo-registry-provider\";\nimport { MCPTransport } from \"../mcp-client\";\nimport { TamboMcpProvider, useTamboMcpServers } from \"../tambo-mcp-provider\";\nimport { useTamboMcpPromptList, type ListPromptEntry } from \"../mcp-hooks\";\n\n// Mock the MCP client to avoid ES module issues\nlet createImpl: jest.Mock<any, any> = jest.fn();\njest.mock(\"../mcp-client\", () => ({\n MCPClient: { create: (...args: any[]) => createImpl(...args) },\n MCPTransport: { SSE: \"sse\", HTTP: \"http\" },\n}));\n\ndescribe(\"useTamboMcpPromptList - individual server caching\", () => {\n let queryClient: QueryClient;\n\n beforeEach(() => {\n createImpl = jest.fn();\n queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n retry: false, // Disable retries for tests\n },\n },\n });\n });\n\n afterEach(() => {\n queryClient.clear();\n });\n\n it(\"should fetch and combine prompts from multiple servers\", async () => {\n // Mock two servers with different prompts\n const serverAPrompts = {\n prompts: [\n { name: \"prompt-a1\", description: \"Prompt A1\" },\n { name: \"prompt-a2\", description: \"Prompt A2\" },\n ],\n };\n const serverBPrompts = {\n prompts: [\n { name: \"prompt-b1\", description: \"Prompt B1\" },\n { name: \"prompt-b2\", description: \"Prompt B2\" },\n ],\n };\n\n const mockClientA = {\n listTools: jest.fn().mockResolvedValue([]),\n listPrompts: jest.fn().mockResolvedValue(serverAPrompts),\n close: jest.fn(),\n };\n const mockClientB = {\n listTools: jest.fn().mockResolvedValue([]),\n listPrompts: jest.fn().mockResolvedValue(serverBPrompts),\n close: jest.fn(),\n };\n\n const clientA = {\n client: mockClientA,\n listTools: jest.fn().mockResolvedValue([]),\n close: jest.fn(),\n };\n const clientB = {\n client: mockClientB,\n listTools: jest.fn().mockResolvedValue([]),\n close: jest.fn(),\n };\n\n // Mock MCPClient.create to return different clients for different URLs\n createImpl.mockImplementation(async (url: string) => {\n if (url === \"https://server-a.example\") return clientA;\n if (url === \"https://server-b.example\") return clientB;\n throw new Error(`Unexpected URL: ${url}`);\n });\n\n let capturedPrompts: ListPromptEntry[] = [];\n const Capture: React.FC = () => {\n const { data: prompts } = useTamboMcpPromptList();\n useEffect(() => {\n if (prompts) {\n capturedPrompts = prompts;\n }\n }, [prompts]);\n return null;\n };\n\n render(\n <TamboClientContext.Provider\n value={{\n client: { baseURL: \"https://api.tambo.co\" } as any,\n queryClient,\n isUpdatingToken: false,\n }}\n >\n <TamboRegistryProvider>\n <TamboMcpTokenProvider>\n <TamboMcpProvider\n mcpServers={[\n {\n url: \"https://server-a.example\",\n transport: MCPTransport.SSE,\n },\n {\n url: \"https://server-b.example\",\n transport: MCPTransport.SSE,\n },\n ]}\n >\n <Capture />\n </TamboMcpProvider>\n </TamboMcpTokenProvider>\n </TamboRegistryProvider>\n </TamboClientContext.Provider>,\n );\n\n // Wait for all prompts to be loaded\n await waitFor(() => {\n expect(capturedPrompts.length).toBe(4);\n });\n\n // Verify all prompts are present\n const promptNames = capturedPrompts.map((p) => p.prompt.name);\n expect(promptNames).toContain(\"prompt-a1\");\n expect(promptNames).toContain(\"prompt-a2\");\n expect(promptNames).toContain(\"prompt-b1\");\n expect(promptNames).toContain(\"prompt-b2\");\n\n // Verify each prompt has the correct server info\n const promptA1 = capturedPrompts.find((p) => p.prompt.name === \"prompt-a1\");\n expect(promptA1?.server.url).toBe(\"https://server-a.example\");\n\n const promptB1 = capturedPrompts.find((p) => p.prompt.name === \"prompt-b1\");\n expect(promptB1?.server.url).toBe(\"https://server-b.example\");\n });\n\n it(\"should remove prompts when a server is removed\", async () => {\n // Mock two servers with different prompts\n const serverAPrompts = {\n prompts: [\n { name: \"prompt-a1\", description: \"Prompt A1\" },\n { name: \"prompt-a2\", description: \"Prompt A2\" },\n ],\n };\n const serverBPrompts = {\n prompts: [\n { name: \"prompt-b1\", description: \"Prompt B1\" },\n { name: \"prompt-b2\", description: \"Prompt B2\" },\n ],\n };\n\n const mockClientA = {\n listTools: jest.fn().mockResolvedValue([]),\n listPrompts: jest.fn().mockResolvedValue(serverAPrompts),\n close: jest.fn(),\n };\n const mockClientB = {\n listTools: jest.fn().mockResolvedValue([]),\n listPrompts: jest.fn().mockResolvedValue(serverBPrompts),\n close: jest.fn(),\n };\n\n const clientA = {\n client: mockClientA,\n listTools: jest.fn().mockResolvedValue([]),\n close: jest.fn(),\n };\n const clientB = {\n client: mockClientB,\n listTools: jest.fn().mockResolvedValue([]),\n close: jest.fn(),\n };\n\n createImpl.mockImplementation(async (url: string) => {\n if (url === \"https://server-a.example\") return clientA;\n if (url === \"https://server-b.example\") return clientB;\n throw new Error(`Unexpected URL: ${url}`);\n });\n\n let capturedPrompts: ListPromptEntry[] = [];\n const Capture: React.FC = () => {\n const { data: prompts } = useTamboMcpPromptList();\n useEffect(() => {\n if (prompts) {\n capturedPrompts = prompts;\n }\n }, [prompts]);\n return null;\n };\n\n const { rerender } = render(\n <TamboClientContext.Provider\n value={{\n client: { baseURL: \"https://api.tambo.co\" } as any,\n queryClient,\n isUpdatingToken: false,\n }}\n >\n <TamboRegistryProvider>\n <TamboMcpTokenProvider>\n <TamboMcpProvider\n mcpServers={[\n {\n url: \"https://server-a.example\",\n transport: MCPTransport.SSE,\n },\n {\n url: \"https://server-b.example\",\n transport: MCPTransport.SSE,\n },\n ]}\n >\n <Capture />\n </TamboMcpProvider>\n </TamboMcpTokenProvider>\n </TamboRegistryProvider>\n </TamboClientContext.Provider>,\n );\n\n // Wait for initial prompts to be loaded\n await waitFor(() => {\n expect(capturedPrompts.length).toBe(4);\n });\n\n const initialPromptNames = capturedPrompts.map((p) => p.prompt.name);\n expect(initialPromptNames).toContain(\"prompt-a1\");\n expect(initialPromptNames).toContain(\"prompt-a2\");\n expect(initialPromptNames).toContain(\"prompt-b1\");\n expect(initialPromptNames).toContain(\"prompt-b2\");\n\n // Remove server B\n rerender(\n <TamboClientContext.Provider\n value={{\n client: { baseURL: \"https://api.tambo.co\" } as any,\n queryClient,\n isUpdatingToken: false,\n }}\n >\n <TamboRegistryProvider>\n <TamboMcpTokenProvider>\n <TamboMcpProvider\n mcpServers={[\n {\n url: \"https://server-a.example\",\n transport: MCPTransport.SSE,\n },\n ]}\n >\n <Capture />\n </TamboMcpProvider>\n </TamboMcpTokenProvider>\n </TamboRegistryProvider>\n </TamboClientContext.Provider>,\n );\n\n // Wait for prompts to be updated (server B prompts should disappear)\n await waitFor(() => {\n expect(capturedPrompts.length).toBe(2);\n });\n\n const updatedPromptNames = capturedPrompts.map((p) => p.prompt.name);\n expect(updatedPromptNames).toContain(\"prompt-a1\");\n expect(updatedPromptNames).toContain(\"prompt-a2\");\n expect(updatedPromptNames).not.toContain(\"prompt-b1\");\n expect(updatedPromptNames).not.toContain(\"prompt-b2\");\n\n // Verify server B's client was closed\n expect(clientB.close).toHaveBeenCalled();\n });\n\n it(\"should cache prompts individually per server using server key\", async () => {\n const serverAPrompts = {\n prompts: [{ name: \"prompt-a\", description: \"Prompt A\" }],\n };\n const serverBPrompts = {\n prompts: [{ name: \"prompt-b\", description: \"Prompt B\" }],\n };\n\n const mockClientA = {\n listTools: jest.fn().mockResolvedValue([]),\n listPrompts: jest.fn().mockResolvedValue(serverAPrompts),\n close: jest.fn(),\n };\n const mockClientB = {\n listTools: jest.fn().mockResolvedValue([]),\n listPrompts: jest.fn().mockResolvedValue(serverBPrompts),\n close: jest.fn(),\n };\n\n const clientA = {\n client: mockClientA,\n listTools: jest.fn().mockResolvedValue([]),\n close: jest.fn(),\n };\n const clientB = {\n client: mockClientB,\n listTools: jest.fn().mockResolvedValue([]),\n close: jest.fn(),\n };\n\n createImpl.mockImplementation(async (url: string) => {\n if (url === \"https://server-a.example\") return clientA;\n if (url === \"https://server-b.example\") return clientB;\n throw new Error(`Unexpected URL: ${url}`);\n });\n\n let capturedPrompts: ListPromptEntry[] = [];\n let capturedServerKeys: string[] = [];\n const Capture: React.FC = () => {\n const { data: prompts } = useTamboMcpPromptList();\n const servers = useTamboMcpServers();\n useEffect(() => {\n if (prompts) {\n capturedPrompts = prompts;\n }\n capturedServerKeys = servers.map((s) => s.key);\n }, [prompts, servers]);\n return null;\n };\n\n render(\n <TamboClientContext.Provider\n value={{\n client: { baseURL: \"https://api.tambo.co\" } as any,\n queryClient,\n isUpdatingToken: false,\n }}\n >\n <TamboRegistryProvider>\n <TamboMcpTokenProvider>\n <TamboMcpProvider\n mcpServers={[\n {\n url: \"https://server-a.example\",\n transport: MCPTransport.SSE,\n },\n {\n url: \"https://server-b.example\",\n transport: MCPTransport.SSE,\n },\n ]}\n >\n <Capture />\n </TamboMcpProvider>\n </TamboMcpTokenProvider>\n </TamboRegistryProvider>\n </TamboClientContext.Provider>,\n );\n\n // Wait for prompts to be loaded\n await waitFor(() => {\n expect(capturedPrompts.length).toBe(2);\n });\n\n // Verify that listPrompts was called once per server\n expect(mockClientA.listPrompts).toHaveBeenCalledTimes(1);\n expect(mockClientB.listPrompts).toHaveBeenCalledTimes(1);\n\n // Verify the query cache has separate entries for each server\n const cacheKeys = queryClient\n .getQueryCache()\n .getAll()\n .map((query) => query.queryKey);\n\n // Should have separate cache entries for each server's prompts\n const promptCacheKeys = cacheKeys.filter(\n (key) => Array.isArray(key) && key[0] === \"mcp-prompts\",\n );\n expect(promptCacheKeys.length).toBe(2);\n // Ensure provider keys are populated before asserting\n await waitFor(() => expect(capturedServerKeys.length).toBe(2));\n\n // Verify each connected server key is present exactly once in the cache keys\n const promptKeySet = new Set(promptCacheKeys.map((k) => String(k[1])));\n // The server keys are captured from the provider to avoid relying on string substrings\n capturedServerKeys.forEach((key) => {\n expect(promptKeySet.has(key)).toBe(true);\n });\n // And no duplicates\n expect(promptKeySet.size).toBe(capturedServerKeys.length);\n });\n\n it(\"should handle server errors gracefully without affecting other servers\", async () => {\n const serverAPrompts = {\n prompts: [{ name: \"prompt-a\", description: \"Prompt A\" }],\n };\n\n const mockClientA = {\n listTools: jest.fn().mockResolvedValue([]),\n listPrompts: jest.fn().mockResolvedValue(serverAPrompts),\n close: jest.fn(),\n };\n\n const clientA = {\n client: mockClientA,\n listTools: jest.fn().mockResolvedValue([]),\n close: jest.fn(),\n };\n\n // Server B will fail to connect\n createImpl.mockImplementation(async (url: string) => {\n if (url === \"https://server-a.example\") return clientA;\n if (url === \"https://server-b.example\") {\n throw new Error(\"Connection failed\");\n }\n throw new Error(`Unexpected URL: ${url}`);\n });\n\n let capturedPrompts: ListPromptEntry[] = [];\n let mcpServersCount = 0;\n const Capture: React.FC = () => {\n const { data: prompts } = useTamboMcpPromptList();\n const servers = useTamboMcpServers();\n useEffect(() => {\n if (prompts) {\n capturedPrompts = prompts;\n }\n mcpServersCount = servers.length;\n }, [prompts, servers]);\n return null;\n };\n\n render(\n <TamboClientContext.Provider\n value={{\n client: { baseURL: \"https://api.tambo.co\" } as any,\n queryClient,\n isUpdatingToken: false,\n }}\n >\n <TamboRegistryProvider>\n <TamboMcpTokenProvider>\n <TamboMcpProvider\n mcpServers={[\n {\n url: \"https://server-a.example\",\n transport: MCPTransport.SSE,\n },\n {\n url: \"https://server-b.example\",\n transport: MCPTransport.SSE,\n },\n ]}\n >\n <Capture />\n </TamboMcpProvider>\n </TamboMcpTokenProvider>\n </TamboRegistryProvider>\n </TamboClientContext.Provider>,\n );\n\n // Wait for server A prompts to be loaded\n // Server B should fail but not block server A\n await waitFor(() => {\n expect(capturedPrompts.length).toBe(1);\n expect(mcpServersCount).toBe(2); // Both servers should be in the list\n });\n\n // Verify only server A's prompts are present\n const promptNames = capturedPrompts.map((p) => p.prompt.name);\n expect(promptNames).toContain(\"prompt-a\");\n expect(promptNames).not.toContain(\"prompt-b\");\n });\n\n it(\"should add prompts when a new server is added\", async () => {\n const serverAPrompts = {\n prompts: [{ name: \"prompt-a\", description: \"Prompt A\" }],\n };\n const serverBPrompts = {\n prompts: [{ name: \"prompt-b\", description: \"Prompt B\" }],\n };\n\n const mockClientA = {\n listTools: jest.fn().mockResolvedValue([]),\n listPrompts: jest.fn().mockResolvedValue(serverAPrompts),\n close: jest.fn(),\n };\n const mockClientB = {\n listTools: jest.fn().mockResolvedValue([]),\n listPrompts: jest.fn().mockResolvedValue(serverBPrompts),\n close: jest.fn(),\n };\n\n const clientA = {\n client: mockClientA,\n listTools: jest.fn().mockResolvedValue([]),\n close: jest.fn(),\n };\n const clientB = {\n client: mockClientB,\n listTools: jest.fn().mockResolvedValue([]),\n close: jest.fn(),\n };\n\n createImpl.mockImplementation(async (url: string) => {\n if (url === \"https://server-a.example\") return clientA;\n if (url === \"https://server-b.example\") return clientB;\n throw new Error(`Unexpected URL: ${url}`);\n });\n\n let capturedPrompts: ListPromptEntry[] = [];\n const Capture: React.FC = () => {\n const { data: prompts } = useTamboMcpPromptList();\n useEffect(() => {\n if (prompts) {\n capturedPrompts = prompts;\n }\n }, [prompts]);\n return null;\n };\n\n const { rerender } = render(\n <TamboClientContext.Provider\n value={{\n client: { baseURL: \"https://api.tambo.co\" } as any,\n queryClient,\n isUpdatingToken: false,\n }}\n >\n <TamboRegistryProvider>\n <TamboMcpTokenProvider>\n <TamboMcpProvider\n mcpServers={[\n {\n url: \"https://server-a.example\",\n transport: MCPTransport.SSE,\n },\n ]}\n >\n <Capture />\n </TamboMcpProvider>\n </TamboMcpTokenProvider>\n </TamboRegistryProvider>\n </TamboClientContext.Provider>,\n );\n\n // Wait for initial prompts from server A\n await waitFor(() => {\n expect(capturedPrompts.length).toBe(1);\n });\n\n expect(capturedPrompts.map((p) => p.prompt.name)).toContain(\"prompt-a\");\n\n // Add server B\n rerender(\n <TamboClientContext.Provider\n value={{\n client: { baseURL: \"https://api.tambo.co\" } as any,\n queryClient,\n isUpdatingToken: false,\n }}\n >\n <TamboRegistryProvider>\n <TamboMcpTokenProvider>\n <TamboMcpProvider\n mcpServers={[\n {\n url: \"https://server-a.example\",\n transport: MCPTransport.SSE,\n },\n {\n url: \"https://server-b.example\",\n transport: MCPTransport.SSE,\n },\n ]}\n >\n <Capture />\n </TamboMcpProvider>\n </TamboMcpTokenProvider>\n </TamboRegistryProvider>\n </TamboClientContext.Provider>,\n );\n\n // Wait for server B prompts to be added\n await waitFor(() => {\n expect(capturedPrompts.length).toBe(2);\n });\n\n const promptNames = capturedPrompts.map((p) => p.prompt.name);\n expect(promptNames).toContain(\"prompt-a\");\n expect(promptNames).toContain(\"prompt-b\");\n });\n});\n"]}
|