Skip to content

Commit 189cc84

Browse files
committed
Autocomplete on resource template variables
1 parent 8635c63 commit 189cc84

File tree

2 files changed

+294
-74
lines changed

2 files changed

+294
-74
lines changed

src/server/index.test.ts

Lines changed: 161 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -560,32 +560,34 @@ test("should handle request timeout", async () => {
560560

561561
describe("ResourceTemplate", () => {
562562
test("should create ResourceTemplate with string pattern", () => {
563-
const template = new ResourceTemplate("test://{category}/{id}", undefined);
563+
const template = new ResourceTemplate("test://{category}/{id}", {
564+
list: undefined,
565+
});
564566
expect(template.uriTemplate.toString()).toBe("test://{category}/{id}");
565567
expect(template.listCallback).toBeUndefined();
566568
});
567569

568570
test("should create ResourceTemplate with UriTemplate", () => {
569571
const uriTemplate = new UriTemplate("test://{category}/{id}");
570-
const template = new ResourceTemplate(uriTemplate, undefined);
572+
const template = new ResourceTemplate(uriTemplate, { list: undefined });
571573
expect(template.uriTemplate).toBe(uriTemplate);
572574
expect(template.listCallback).toBeUndefined();
573575
});
574576

575577
test("should create ResourceTemplate with list callback", async () => {
576-
const listCallback = jest.fn().mockResolvedValue({
578+
const list = jest.fn().mockResolvedValue({
577579
resources: [{ name: "Test", uri: "test://example" }],
578580
});
579581

580-
const template = new ResourceTemplate("test://{id}", listCallback);
581-
expect(template.listCallback).toBe(listCallback);
582+
const template = new ResourceTemplate("test://{id}", { list });
583+
expect(template.listCallback).toBe(list);
582584

583585
const abortController = new AbortController();
584586
const result = await template.listCallback?.({
585587
signal: abortController.signal,
586588
});
587589
expect(result?.resources).toHaveLength(1);
588-
expect(listCallback).toHaveBeenCalled();
590+
expect(list).toHaveBeenCalled();
589591
});
590592
});
591593

@@ -1068,7 +1070,7 @@ describe("Server.resource", () => {
10681070

10691071
server.resource(
10701072
"test",
1071-
new ResourceTemplate("test://resource/{id}", undefined),
1073+
new ResourceTemplate("test://resource/{id}", { list: undefined }),
10721074
async () => ({
10731075
contents: [
10741076
{
@@ -1113,18 +1115,20 @@ describe("Server.resource", () => {
11131115

11141116
server.resource(
11151117
"test",
1116-
new ResourceTemplate("test://resource/{id}", async () => ({
1117-
resources: [
1118-
{
1119-
name: "Resource 1",
1120-
uri: "test://resource/1",
1121-
},
1122-
{
1123-
name: "Resource 2",
1124-
uri: "test://resource/2",
1125-
},
1126-
],
1127-
})),
1118+
new ResourceTemplate("test://resource/{id}", {
1119+
list: async () => ({
1120+
resources: [
1121+
{
1122+
name: "Resource 1",
1123+
uri: "test://resource/1",
1124+
},
1125+
{
1126+
name: "Resource 2",
1127+
uri: "test://resource/2",
1128+
},
1129+
],
1130+
}),
1131+
}),
11281132
async (uri) => ({
11291133
contents: [
11301134
{
@@ -1169,7 +1173,9 @@ describe("Server.resource", () => {
11691173

11701174
server.resource(
11711175
"test",
1172-
new ResourceTemplate("test://resource/{category}/{id}", undefined),
1176+
new ResourceTemplate("test://resource/{category}/{id}", {
1177+
list: undefined,
1178+
}),
11731179
async (uri, { category, id }) => ({
11741180
contents: [
11751181
{
@@ -1236,7 +1242,7 @@ describe("Server.resource", () => {
12361242

12371243
server.resource(
12381244
"test",
1239-
new ResourceTemplate("test://resource/{id}", undefined),
1245+
new ResourceTemplate("test://resource/{id}", { list: undefined }),
12401246
async () => ({
12411247
contents: [
12421248
{
@@ -1250,7 +1256,7 @@ describe("Server.resource", () => {
12501256
expect(() => {
12511257
server.resource(
12521258
"test",
1253-
new ResourceTemplate("test://resource/{id}", undefined),
1259+
new ResourceTemplate("test://resource/{id}", { list: undefined }),
12541260
async () => ({
12551261
contents: [
12561262
{
@@ -1337,6 +1343,139 @@ describe("Server.resource", () => {
13371343
),
13381344
).rejects.toThrow(/Resource test:\/\/nonexistent not found/);
13391345
});
1346+
1347+
test("should support completion of resource template parameters", async () => {
1348+
const server = new Server({
1349+
name: "test server",
1350+
version: "1.0",
1351+
});
1352+
1353+
const client = new Client(
1354+
{
1355+
name: "test client",
1356+
version: "1.0",
1357+
},
1358+
{
1359+
capabilities: {
1360+
resources: {},
1361+
},
1362+
},
1363+
);
1364+
1365+
server.resource(
1366+
"test",
1367+
new ResourceTemplate("test://resource/{category}", {
1368+
list: undefined,
1369+
complete: {
1370+
category: () => ["books", "movies", "music"],
1371+
},
1372+
}),
1373+
async () => ({
1374+
contents: [
1375+
{
1376+
uri: "test://resource/test",
1377+
text: "Test content",
1378+
},
1379+
],
1380+
}),
1381+
);
1382+
1383+
const [clientTransport, serverTransport] =
1384+
InMemoryTransport.createLinkedPair();
1385+
1386+
await Promise.all([
1387+
client.connect(clientTransport),
1388+
server.connect(serverTransport),
1389+
]);
1390+
1391+
const result = await client.request(
1392+
{
1393+
method: "completion/complete",
1394+
params: {
1395+
ref: {
1396+
type: "ref/resource",
1397+
uri: "test://resource/{category}",
1398+
},
1399+
argument: {
1400+
name: "category",
1401+
value: "",
1402+
},
1403+
},
1404+
},
1405+
CompleteResultSchema,
1406+
);
1407+
1408+
expect(result.completion.values).toEqual(["books", "movies", "music"]);
1409+
expect(result.completion.total).toBe(3);
1410+
});
1411+
1412+
test("should support filtered completion of resource template parameters", async () => {
1413+
const server = new Server({
1414+
name: "test server",
1415+
version: "1.0",
1416+
});
1417+
1418+
const client = new Client(
1419+
{
1420+
name: "test client",
1421+
version: "1.0",
1422+
},
1423+
{
1424+
capabilities: {
1425+
resources: {},
1426+
},
1427+
},
1428+
);
1429+
1430+
server.resource(
1431+
"test",
1432+
new ResourceTemplate("test://resource/{category}", {
1433+
list: undefined,
1434+
complete: {
1435+
category: (test) =>
1436+
["books", "movies", "music"].filter((value) =>
1437+
value.startsWith(test),
1438+
),
1439+
},
1440+
}),
1441+
async () => ({
1442+
contents: [
1443+
{
1444+
uri: "test://resource/test",
1445+
text: "Test content",
1446+
},
1447+
],
1448+
}),
1449+
);
1450+
1451+
const [clientTransport, serverTransport] =
1452+
InMemoryTransport.createLinkedPair();
1453+
1454+
await Promise.all([
1455+
client.connect(clientTransport),
1456+
server.connect(serverTransport),
1457+
]);
1458+
1459+
const result = await client.request(
1460+
{
1461+
method: "completion/complete",
1462+
params: {
1463+
ref: {
1464+
type: "ref/resource",
1465+
uri: "test://resource/{category}",
1466+
},
1467+
argument: {
1468+
name: "category",
1469+
value: "m",
1470+
},
1471+
},
1472+
},
1473+
CompleteResultSchema,
1474+
);
1475+
1476+
expect(result.completion.values).toEqual(["movies", "music"]);
1477+
expect(result.completion.total).toBe(2);
1478+
});
13401479
});
13411480

13421481
describe("Server.prompt", () => {

0 commit comments

Comments
 (0)