fastmcp 3.6.2 → 3.8.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/README.md +104 -0
- package/dist/FastMCP.d.ts +95 -32
- package/dist/FastMCP.js +48 -9
- package/dist/FastMCP.js.map +1 -1
- package/eslint.config.ts +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/FastMCP.oauth.test.ts +177 -0
- package/src/FastMCP.test.ts +577 -0
- package/src/FastMCP.ts +231 -71
- package/src/examples/oauth-server.ts +101 -0
package/src/FastMCP.test.ts
CHANGED
|
@@ -2123,6 +2123,583 @@ test("provides auth to tools", async () => {
|
|
|
2123
2123
|
);
|
|
2124
2124
|
});
|
|
2125
2125
|
|
|
2126
|
+
test("provides auth to resources", async () => {
|
|
2127
|
+
const port = await getRandomPort();
|
|
2128
|
+
|
|
2129
|
+
const authenticate = vi.fn(async () => {
|
|
2130
|
+
return {
|
|
2131
|
+
role: "admin",
|
|
2132
|
+
userId: 42,
|
|
2133
|
+
};
|
|
2134
|
+
});
|
|
2135
|
+
|
|
2136
|
+
const server = new FastMCP<{ role: string; userId: number }>({
|
|
2137
|
+
authenticate,
|
|
2138
|
+
name: "Test",
|
|
2139
|
+
version: "1.0.0",
|
|
2140
|
+
});
|
|
2141
|
+
|
|
2142
|
+
const resourceLoad = vi.fn(async (auth) => {
|
|
2143
|
+
return {
|
|
2144
|
+
text: `User ${auth?.userId} with role ${auth?.role} loaded this resource`,
|
|
2145
|
+
};
|
|
2146
|
+
});
|
|
2147
|
+
|
|
2148
|
+
server.addResource({
|
|
2149
|
+
load: resourceLoad,
|
|
2150
|
+
mimeType: "text/plain",
|
|
2151
|
+
name: "Auth Resource",
|
|
2152
|
+
uri: "auth://resource",
|
|
2153
|
+
});
|
|
2154
|
+
|
|
2155
|
+
await server.start({
|
|
2156
|
+
httpStream: {
|
|
2157
|
+
port,
|
|
2158
|
+
},
|
|
2159
|
+
transportType: "httpStream",
|
|
2160
|
+
});
|
|
2161
|
+
|
|
2162
|
+
const client = new Client(
|
|
2163
|
+
{
|
|
2164
|
+
name: "example-client",
|
|
2165
|
+
version: "1.0.0",
|
|
2166
|
+
},
|
|
2167
|
+
{
|
|
2168
|
+
capabilities: {},
|
|
2169
|
+
},
|
|
2170
|
+
);
|
|
2171
|
+
|
|
2172
|
+
const transport = new SSEClientTransport(
|
|
2173
|
+
new URL(`http://localhost:${port}/sse`),
|
|
2174
|
+
{
|
|
2175
|
+
eventSourceInit: {
|
|
2176
|
+
fetch: async (url, init) => {
|
|
2177
|
+
return fetch(url, {
|
|
2178
|
+
...init,
|
|
2179
|
+
headers: {
|
|
2180
|
+
...init?.headers,
|
|
2181
|
+
"x-api-key": "123",
|
|
2182
|
+
},
|
|
2183
|
+
});
|
|
2184
|
+
},
|
|
2185
|
+
},
|
|
2186
|
+
},
|
|
2187
|
+
);
|
|
2188
|
+
|
|
2189
|
+
await client.connect(transport);
|
|
2190
|
+
|
|
2191
|
+
const result = await client.readResource({
|
|
2192
|
+
uri: "auth://resource",
|
|
2193
|
+
});
|
|
2194
|
+
|
|
2195
|
+
expect(resourceLoad).toHaveBeenCalledTimes(1);
|
|
2196
|
+
expect(resourceLoad).toHaveBeenCalledWith({
|
|
2197
|
+
role: "admin",
|
|
2198
|
+
userId: 42,
|
|
2199
|
+
});
|
|
2200
|
+
|
|
2201
|
+
expect(result).toEqual({
|
|
2202
|
+
contents: [
|
|
2203
|
+
{
|
|
2204
|
+
mimeType: "text/plain",
|
|
2205
|
+
name: "Auth Resource",
|
|
2206
|
+
text: "User 42 with role admin loaded this resource",
|
|
2207
|
+
uri: "auth://resource",
|
|
2208
|
+
},
|
|
2209
|
+
],
|
|
2210
|
+
});
|
|
2211
|
+
});
|
|
2212
|
+
|
|
2213
|
+
test("provides auth to resource templates", async () => {
|
|
2214
|
+
const port = await getRandomPort();
|
|
2215
|
+
|
|
2216
|
+
const authenticate = vi.fn(async () => {
|
|
2217
|
+
return {
|
|
2218
|
+
permissions: ["read", "write"],
|
|
2219
|
+
userId: 99,
|
|
2220
|
+
};
|
|
2221
|
+
});
|
|
2222
|
+
|
|
2223
|
+
const server = new FastMCP<{ permissions: string[]; userId: number }>({
|
|
2224
|
+
authenticate,
|
|
2225
|
+
name: "Test",
|
|
2226
|
+
version: "1.0.0",
|
|
2227
|
+
});
|
|
2228
|
+
|
|
2229
|
+
const templateLoad = vi.fn(async (args, auth) => {
|
|
2230
|
+
return {
|
|
2231
|
+
text: `Resource ${args.resourceId} accessed by user ${auth?.userId} with permissions: ${auth?.permissions?.join(", ")}`,
|
|
2232
|
+
};
|
|
2233
|
+
});
|
|
2234
|
+
|
|
2235
|
+
server.addResourceTemplate({
|
|
2236
|
+
arguments: [
|
|
2237
|
+
{
|
|
2238
|
+
name: "resourceId",
|
|
2239
|
+
required: true,
|
|
2240
|
+
},
|
|
2241
|
+
],
|
|
2242
|
+
load: templateLoad,
|
|
2243
|
+
mimeType: "text/plain",
|
|
2244
|
+
name: "Auth Template",
|
|
2245
|
+
uriTemplate: "auth://template/{resourceId}",
|
|
2246
|
+
});
|
|
2247
|
+
|
|
2248
|
+
await server.start({
|
|
2249
|
+
httpStream: {
|
|
2250
|
+
port,
|
|
2251
|
+
},
|
|
2252
|
+
transportType: "httpStream",
|
|
2253
|
+
});
|
|
2254
|
+
|
|
2255
|
+
const client = new Client(
|
|
2256
|
+
{
|
|
2257
|
+
name: "example-client",
|
|
2258
|
+
version: "1.0.0",
|
|
2259
|
+
},
|
|
2260
|
+
{
|
|
2261
|
+
capabilities: {},
|
|
2262
|
+
},
|
|
2263
|
+
);
|
|
2264
|
+
|
|
2265
|
+
const transport = new SSEClientTransport(
|
|
2266
|
+
new URL(`http://localhost:${port}/sse`),
|
|
2267
|
+
{
|
|
2268
|
+
eventSourceInit: {
|
|
2269
|
+
fetch: async (url, init) => {
|
|
2270
|
+
return fetch(url, {
|
|
2271
|
+
...init,
|
|
2272
|
+
headers: {
|
|
2273
|
+
...init?.headers,
|
|
2274
|
+
"x-api-key": "123",
|
|
2275
|
+
},
|
|
2276
|
+
});
|
|
2277
|
+
},
|
|
2278
|
+
},
|
|
2279
|
+
},
|
|
2280
|
+
);
|
|
2281
|
+
|
|
2282
|
+
await client.connect(transport);
|
|
2283
|
+
|
|
2284
|
+
const result = await client.readResource({
|
|
2285
|
+
uri: "auth://template/resource-123",
|
|
2286
|
+
});
|
|
2287
|
+
|
|
2288
|
+
expect(templateLoad).toHaveBeenCalledTimes(1);
|
|
2289
|
+
expect(templateLoad).toHaveBeenCalledWith(
|
|
2290
|
+
{ resourceId: "resource-123" },
|
|
2291
|
+
{ permissions: ["read", "write"], userId: 99 },
|
|
2292
|
+
);
|
|
2293
|
+
|
|
2294
|
+
expect(result).toEqual({
|
|
2295
|
+
contents: [
|
|
2296
|
+
{
|
|
2297
|
+
mimeType: "text/plain",
|
|
2298
|
+
name: "Auth Template",
|
|
2299
|
+
text: "Resource resource-123 accessed by user 99 with permissions: read, write",
|
|
2300
|
+
uri: "auth://template/resource-123",
|
|
2301
|
+
},
|
|
2302
|
+
],
|
|
2303
|
+
});
|
|
2304
|
+
});
|
|
2305
|
+
|
|
2306
|
+
test("provides auth to resource templates returning arrays", async () => {
|
|
2307
|
+
const port = await getRandomPort();
|
|
2308
|
+
|
|
2309
|
+
const authenticate = vi.fn(async () => {
|
|
2310
|
+
return {
|
|
2311
|
+
accessLevel: 3,
|
|
2312
|
+
teamId: "team-alpha",
|
|
2313
|
+
};
|
|
2314
|
+
});
|
|
2315
|
+
|
|
2316
|
+
const server = new FastMCP<{ accessLevel: number; teamId: string }>({
|
|
2317
|
+
authenticate,
|
|
2318
|
+
name: "Test",
|
|
2319
|
+
version: "1.0.0",
|
|
2320
|
+
});
|
|
2321
|
+
|
|
2322
|
+
const templateLoad = vi.fn(async (args, auth) => {
|
|
2323
|
+
return [
|
|
2324
|
+
{
|
|
2325
|
+
text: `Document 1 for ${args.category} - Team: ${auth?.teamId}`,
|
|
2326
|
+
},
|
|
2327
|
+
{
|
|
2328
|
+
text: `Document 2 for ${args.category} - Access Level: ${auth?.accessLevel}`,
|
|
2329
|
+
},
|
|
2330
|
+
];
|
|
2331
|
+
});
|
|
2332
|
+
|
|
2333
|
+
server.addResourceTemplate({
|
|
2334
|
+
arguments: [
|
|
2335
|
+
{
|
|
2336
|
+
name: "category",
|
|
2337
|
+
required: true,
|
|
2338
|
+
},
|
|
2339
|
+
],
|
|
2340
|
+
load: templateLoad,
|
|
2341
|
+
mimeType: "text/plain",
|
|
2342
|
+
name: "Multi Doc Template",
|
|
2343
|
+
uriTemplate: "docs://category/{category}",
|
|
2344
|
+
});
|
|
2345
|
+
|
|
2346
|
+
await server.start({
|
|
2347
|
+
httpStream: {
|
|
2348
|
+
port,
|
|
2349
|
+
},
|
|
2350
|
+
transportType: "httpStream",
|
|
2351
|
+
});
|
|
2352
|
+
|
|
2353
|
+
const client = new Client(
|
|
2354
|
+
{
|
|
2355
|
+
name: "example-client",
|
|
2356
|
+
version: "1.0.0",
|
|
2357
|
+
},
|
|
2358
|
+
{
|
|
2359
|
+
capabilities: {},
|
|
2360
|
+
},
|
|
2361
|
+
);
|
|
2362
|
+
|
|
2363
|
+
const transport = new SSEClientTransport(
|
|
2364
|
+
new URL(`http://localhost:${port}/sse`),
|
|
2365
|
+
{
|
|
2366
|
+
eventSourceInit: {
|
|
2367
|
+
fetch: async (url, init) => {
|
|
2368
|
+
return fetch(url, {
|
|
2369
|
+
...init,
|
|
2370
|
+
headers: {
|
|
2371
|
+
...init?.headers,
|
|
2372
|
+
"x-api-key": "123",
|
|
2373
|
+
},
|
|
2374
|
+
});
|
|
2375
|
+
},
|
|
2376
|
+
},
|
|
2377
|
+
},
|
|
2378
|
+
);
|
|
2379
|
+
|
|
2380
|
+
await client.connect(transport);
|
|
2381
|
+
|
|
2382
|
+
const result = await client.readResource({
|
|
2383
|
+
uri: "docs://category/reports",
|
|
2384
|
+
});
|
|
2385
|
+
|
|
2386
|
+
expect(templateLoad).toHaveBeenCalledTimes(1);
|
|
2387
|
+
expect(templateLoad).toHaveBeenCalledWith(
|
|
2388
|
+
{ category: "reports" },
|
|
2389
|
+
{ accessLevel: 3, teamId: "team-alpha" },
|
|
2390
|
+
);
|
|
2391
|
+
|
|
2392
|
+
expect(result).toEqual({
|
|
2393
|
+
contents: [
|
|
2394
|
+
{
|
|
2395
|
+
mimeType: "text/plain",
|
|
2396
|
+
name: "Multi Doc Template",
|
|
2397
|
+
text: "Document 1 for reports - Team: team-alpha",
|
|
2398
|
+
uri: "docs://category/reports",
|
|
2399
|
+
},
|
|
2400
|
+
{
|
|
2401
|
+
mimeType: "text/plain",
|
|
2402
|
+
name: "Multi Doc Template",
|
|
2403
|
+
text: "Document 2 for reports - Access Level: 3",
|
|
2404
|
+
uri: "docs://category/reports",
|
|
2405
|
+
},
|
|
2406
|
+
],
|
|
2407
|
+
});
|
|
2408
|
+
});
|
|
2409
|
+
|
|
2410
|
+
test("provides auth to prompt argument completion", async () => {
|
|
2411
|
+
const port = await getRandomPort();
|
|
2412
|
+
|
|
2413
|
+
const authenticate = vi.fn(async () => {
|
|
2414
|
+
return {
|
|
2415
|
+
department: "engineering",
|
|
2416
|
+
userId: 100,
|
|
2417
|
+
};
|
|
2418
|
+
});
|
|
2419
|
+
|
|
2420
|
+
const server = new FastMCP<{ department: string; userId: number }>({
|
|
2421
|
+
authenticate,
|
|
2422
|
+
name: "Test",
|
|
2423
|
+
version: "1.0.0",
|
|
2424
|
+
});
|
|
2425
|
+
|
|
2426
|
+
const promptCompleter = vi.fn(async (value: string, auth) => {
|
|
2427
|
+
return {
|
|
2428
|
+
values: [
|
|
2429
|
+
`${value}_user${auth?.userId}`,
|
|
2430
|
+
`${value}_dept${auth?.department}`,
|
|
2431
|
+
],
|
|
2432
|
+
};
|
|
2433
|
+
});
|
|
2434
|
+
|
|
2435
|
+
server.addPrompt({
|
|
2436
|
+
arguments: [
|
|
2437
|
+
{
|
|
2438
|
+
complete: promptCompleter,
|
|
2439
|
+
description: "Project name",
|
|
2440
|
+
name: "project",
|
|
2441
|
+
required: true,
|
|
2442
|
+
},
|
|
2443
|
+
],
|
|
2444
|
+
async load(args) {
|
|
2445
|
+
return `Loading project: ${args.project}`;
|
|
2446
|
+
},
|
|
2447
|
+
name: "load-project",
|
|
2448
|
+
});
|
|
2449
|
+
|
|
2450
|
+
await server.start({
|
|
2451
|
+
httpStream: {
|
|
2452
|
+
port,
|
|
2453
|
+
},
|
|
2454
|
+
transportType: "httpStream",
|
|
2455
|
+
});
|
|
2456
|
+
|
|
2457
|
+
const client = new Client(
|
|
2458
|
+
{
|
|
2459
|
+
name: "example-client",
|
|
2460
|
+
version: "1.0.0",
|
|
2461
|
+
},
|
|
2462
|
+
{
|
|
2463
|
+
capabilities: {},
|
|
2464
|
+
},
|
|
2465
|
+
);
|
|
2466
|
+
|
|
2467
|
+
const transport = new SSEClientTransport(
|
|
2468
|
+
new URL(`http://localhost:${port}/sse`),
|
|
2469
|
+
{
|
|
2470
|
+
eventSourceInit: {
|
|
2471
|
+
fetch: async (url, init) => {
|
|
2472
|
+
return fetch(url, {
|
|
2473
|
+
...init,
|
|
2474
|
+
headers: {
|
|
2475
|
+
...init?.headers,
|
|
2476
|
+
"x-api-key": "123",
|
|
2477
|
+
},
|
|
2478
|
+
});
|
|
2479
|
+
},
|
|
2480
|
+
},
|
|
2481
|
+
},
|
|
2482
|
+
);
|
|
2483
|
+
|
|
2484
|
+
await client.connect(transport);
|
|
2485
|
+
|
|
2486
|
+
const completionResult = await client.complete({
|
|
2487
|
+
argument: {
|
|
2488
|
+
name: "project",
|
|
2489
|
+
value: "test",
|
|
2490
|
+
},
|
|
2491
|
+
ref: {
|
|
2492
|
+
name: "load-project",
|
|
2493
|
+
type: "ref/prompt",
|
|
2494
|
+
},
|
|
2495
|
+
});
|
|
2496
|
+
|
|
2497
|
+
expect(promptCompleter).toHaveBeenCalledTimes(1);
|
|
2498
|
+
expect(promptCompleter).toHaveBeenCalledWith("test", {
|
|
2499
|
+
department: "engineering",
|
|
2500
|
+
userId: 100,
|
|
2501
|
+
});
|
|
2502
|
+
|
|
2503
|
+
expect(completionResult).toEqual({
|
|
2504
|
+
completion: {
|
|
2505
|
+
values: ["test_user100", "test_deptengineering"],
|
|
2506
|
+
},
|
|
2507
|
+
});
|
|
2508
|
+
});
|
|
2509
|
+
|
|
2510
|
+
test("provides auth to prompt load function", async () => {
|
|
2511
|
+
const port = await getRandomPort();
|
|
2512
|
+
|
|
2513
|
+
const authenticate = vi.fn(async () => {
|
|
2514
|
+
return {
|
|
2515
|
+
level: "admin",
|
|
2516
|
+
username: "testuser",
|
|
2517
|
+
};
|
|
2518
|
+
});
|
|
2519
|
+
|
|
2520
|
+
const server = new FastMCP<{ level: string; username: string }>({
|
|
2521
|
+
authenticate,
|
|
2522
|
+
name: "Test",
|
|
2523
|
+
version: "1.0.0",
|
|
2524
|
+
});
|
|
2525
|
+
|
|
2526
|
+
const promptLoad = vi.fn(async (args, auth) => {
|
|
2527
|
+
return `Welcome ${auth?.username} (${auth?.level}): You selected ${args.option}`;
|
|
2528
|
+
});
|
|
2529
|
+
|
|
2530
|
+
server.addPrompt({
|
|
2531
|
+
arguments: [
|
|
2532
|
+
{
|
|
2533
|
+
description: "Option to select",
|
|
2534
|
+
name: "option",
|
|
2535
|
+
required: true,
|
|
2536
|
+
},
|
|
2537
|
+
],
|
|
2538
|
+
load: promptLoad,
|
|
2539
|
+
name: "auth-prompt",
|
|
2540
|
+
});
|
|
2541
|
+
|
|
2542
|
+
await server.start({
|
|
2543
|
+
httpStream: {
|
|
2544
|
+
port,
|
|
2545
|
+
},
|
|
2546
|
+
transportType: "httpStream",
|
|
2547
|
+
});
|
|
2548
|
+
|
|
2549
|
+
const client = new Client(
|
|
2550
|
+
{
|
|
2551
|
+
name: "example-client",
|
|
2552
|
+
version: "1.0.0",
|
|
2553
|
+
},
|
|
2554
|
+
{
|
|
2555
|
+
capabilities: {},
|
|
2556
|
+
},
|
|
2557
|
+
);
|
|
2558
|
+
|
|
2559
|
+
const transport = new SSEClientTransport(
|
|
2560
|
+
new URL(`http://localhost:${port}/sse`),
|
|
2561
|
+
{
|
|
2562
|
+
eventSourceInit: {
|
|
2563
|
+
fetch: async (url, init) => {
|
|
2564
|
+
return fetch(url, {
|
|
2565
|
+
...init,
|
|
2566
|
+
headers: {
|
|
2567
|
+
...init?.headers,
|
|
2568
|
+
"x-api-key": "123",
|
|
2569
|
+
},
|
|
2570
|
+
});
|
|
2571
|
+
},
|
|
2572
|
+
},
|
|
2573
|
+
},
|
|
2574
|
+
);
|
|
2575
|
+
|
|
2576
|
+
await client.connect(transport);
|
|
2577
|
+
|
|
2578
|
+
const result = await client.getPrompt({
|
|
2579
|
+
arguments: { option: "dashboard" },
|
|
2580
|
+
name: "auth-prompt",
|
|
2581
|
+
});
|
|
2582
|
+
|
|
2583
|
+
expect(promptLoad).toHaveBeenCalledTimes(1);
|
|
2584
|
+
expect(promptLoad).toHaveBeenCalledWith(
|
|
2585
|
+
{ option: "dashboard" },
|
|
2586
|
+
{ level: "admin", username: "testuser" },
|
|
2587
|
+
);
|
|
2588
|
+
|
|
2589
|
+
expect(result).toEqual({
|
|
2590
|
+
messages: [
|
|
2591
|
+
{
|
|
2592
|
+
content: {
|
|
2593
|
+
text: "Welcome testuser (admin): You selected dashboard",
|
|
2594
|
+
type: "text",
|
|
2595
|
+
},
|
|
2596
|
+
role: "user",
|
|
2597
|
+
},
|
|
2598
|
+
],
|
|
2599
|
+
});
|
|
2600
|
+
});
|
|
2601
|
+
|
|
2602
|
+
test("provides auth to resource template argument completion", async () => {
|
|
2603
|
+
const port = await getRandomPort();
|
|
2604
|
+
|
|
2605
|
+
const authenticate = vi.fn(async () => {
|
|
2606
|
+
return {
|
|
2607
|
+
region: "us-west",
|
|
2608
|
+
teamId: "alpha",
|
|
2609
|
+
};
|
|
2610
|
+
});
|
|
2611
|
+
|
|
2612
|
+
const server = new FastMCP<{ region: string; teamId: string }>({
|
|
2613
|
+
authenticate,
|
|
2614
|
+
name: "Test",
|
|
2615
|
+
version: "1.0.0",
|
|
2616
|
+
});
|
|
2617
|
+
|
|
2618
|
+
const resourceCompleter = vi.fn(async (value: string, auth) => {
|
|
2619
|
+
return {
|
|
2620
|
+
values: [`${value}_${auth?.region}`, `${value}_team_${auth?.teamId}`],
|
|
2621
|
+
};
|
|
2622
|
+
});
|
|
2623
|
+
|
|
2624
|
+
server.addResourceTemplate({
|
|
2625
|
+
arguments: [
|
|
2626
|
+
{
|
|
2627
|
+
complete: resourceCompleter,
|
|
2628
|
+
description: "Service ID",
|
|
2629
|
+
name: "serviceId",
|
|
2630
|
+
required: true,
|
|
2631
|
+
},
|
|
2632
|
+
],
|
|
2633
|
+
async load(args) {
|
|
2634
|
+
return {
|
|
2635
|
+
text: `Service ${args.serviceId} data`,
|
|
2636
|
+
};
|
|
2637
|
+
},
|
|
2638
|
+
mimeType: "text/plain",
|
|
2639
|
+
name: "Service Resource",
|
|
2640
|
+
uriTemplate: "service://{serviceId}",
|
|
2641
|
+
});
|
|
2642
|
+
|
|
2643
|
+
await server.start({
|
|
2644
|
+
httpStream: {
|
|
2645
|
+
port,
|
|
2646
|
+
},
|
|
2647
|
+
transportType: "httpStream",
|
|
2648
|
+
});
|
|
2649
|
+
|
|
2650
|
+
const client = new Client(
|
|
2651
|
+
{
|
|
2652
|
+
name: "example-client",
|
|
2653
|
+
version: "1.0.0",
|
|
2654
|
+
},
|
|
2655
|
+
{
|
|
2656
|
+
capabilities: {},
|
|
2657
|
+
},
|
|
2658
|
+
);
|
|
2659
|
+
|
|
2660
|
+
const transport = new SSEClientTransport(
|
|
2661
|
+
new URL(`http://localhost:${port}/sse`),
|
|
2662
|
+
{
|
|
2663
|
+
eventSourceInit: {
|
|
2664
|
+
fetch: async (url, init) => {
|
|
2665
|
+
return fetch(url, {
|
|
2666
|
+
...init,
|
|
2667
|
+
headers: {
|
|
2668
|
+
...init?.headers,
|
|
2669
|
+
"x-api-key": "123",
|
|
2670
|
+
},
|
|
2671
|
+
});
|
|
2672
|
+
},
|
|
2673
|
+
},
|
|
2674
|
+
},
|
|
2675
|
+
);
|
|
2676
|
+
|
|
2677
|
+
await client.connect(transport);
|
|
2678
|
+
|
|
2679
|
+
const completionResult = await client.complete({
|
|
2680
|
+
argument: {
|
|
2681
|
+
name: "serviceId",
|
|
2682
|
+
value: "api",
|
|
2683
|
+
},
|
|
2684
|
+
ref: {
|
|
2685
|
+
type: "ref/resource",
|
|
2686
|
+
uri: "service://{serviceId}",
|
|
2687
|
+
},
|
|
2688
|
+
});
|
|
2689
|
+
|
|
2690
|
+
expect(resourceCompleter).toHaveBeenCalledTimes(1);
|
|
2691
|
+
expect(resourceCompleter).toHaveBeenCalledWith("api", {
|
|
2692
|
+
region: "us-west",
|
|
2693
|
+
teamId: "alpha",
|
|
2694
|
+
});
|
|
2695
|
+
|
|
2696
|
+
expect(completionResult).toEqual({
|
|
2697
|
+
completion: {
|
|
2698
|
+
values: ["api_us-west", "api_team_alpha"],
|
|
2699
|
+
},
|
|
2700
|
+
});
|
|
2701
|
+
});
|
|
2702
|
+
|
|
2126
2703
|
test("supports streaming output from tools", async () => {
|
|
2127
2704
|
let streamResult: { content: Array<{ text: string; type: string }> };
|
|
2128
2705
|
|