Skip to content

Commit 098266a

Browse files
committed
Add simplified API for registering resources and resource templates
1 parent e4b3820 commit 098266a

File tree

2 files changed

+512
-5
lines changed

2 files changed

+512
-5
lines changed

src/server/index.test.ts

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@ import {
1717
ErrorCode,
1818
ListToolsResultSchema,
1919
CallToolResultSchema,
20+
ListResourcesResultSchema,
21+
ListResourceTemplatesResultSchema,
22+
ReadResourceResultSchema,
2023
} from "../types.js";
2124
import { Transport } from "../shared/transport.js";
2225
import { InMemoryTransport } from "../inMemory.js";
2326
import { Client } from "../client/index.js";
27+
import { UriTemplate } from "../shared/uriTemplate.js";
2428

2529
test("should accept latest protocol version", async () => {
2630
let sendPromiseResolve: (value: unknown) => void;
@@ -478,6 +482,7 @@ test("should handle server cancelling a request", async () => {
478482
// Request should be rejected
479483
await expect(createMessagePromise).rejects.toBe("Cancelled by test");
480484
});
485+
481486
test("should handle request timeout", async () => {
482487
const server = new Server(
483488
{
@@ -927,3 +932,273 @@ describe("Server.tool", () => {
927932
).rejects.toThrow(/Tool nonexistent-tool not found/);
928933
});
929934
});
935+
936+
describe("Server.resource", () => {
937+
test("should register resource with uri and readCallback", async () => {
938+
const server = new Server({
939+
name: "test server",
940+
version: "1.0",
941+
});
942+
const client = new Client({
943+
name: "test client",
944+
version: "1.0",
945+
});
946+
947+
server.resource("test", "test://resource", async () => ({
948+
contents: [
949+
{
950+
uri: "test://resource",
951+
text: "Test content",
952+
},
953+
],
954+
}));
955+
956+
const [clientTransport, serverTransport] =
957+
InMemoryTransport.createLinkedPair();
958+
959+
await Promise.all([
960+
client.connect(clientTransport),
961+
server.connect(serverTransport),
962+
]);
963+
964+
const result = await client.request(
965+
{
966+
method: "resources/list",
967+
},
968+
ListResourcesResultSchema,
969+
);
970+
971+
expect(result.resources).toHaveLength(1);
972+
expect(result.resources[0].name).toBe("test");
973+
expect(result.resources[0].uri).toBe("test://resource");
974+
});
975+
976+
test("should register resource with metadata", async () => {
977+
const server = new Server({
978+
name: "test server",
979+
version: "1.0",
980+
});
981+
const client = new Client({
982+
name: "test client",
983+
version: "1.0",
984+
});
985+
986+
server.resource(
987+
"test",
988+
"test://resource",
989+
{
990+
description: "Test resource",
991+
mimeType: "text/plain",
992+
},
993+
async () => ({
994+
contents: [
995+
{
996+
uri: "test://resource",
997+
text: "Test content",
998+
},
999+
],
1000+
}),
1001+
);
1002+
1003+
const [clientTransport, serverTransport] =
1004+
InMemoryTransport.createLinkedPair();
1005+
1006+
await Promise.all([
1007+
client.connect(clientTransport),
1008+
server.connect(serverTransport),
1009+
]);
1010+
1011+
const result = await client.request(
1012+
{
1013+
method: "resources/list",
1014+
},
1015+
ListResourcesResultSchema,
1016+
);
1017+
1018+
expect(result.resources).toHaveLength(1);
1019+
expect(result.resources[0].description).toBe("Test resource");
1020+
expect(result.resources[0].mimeType).toBe("text/plain");
1021+
});
1022+
1023+
test("should register resource template", async () => {
1024+
const server = new Server({
1025+
name: "test server",
1026+
version: "1.0",
1027+
});
1028+
const client = new Client({
1029+
name: "test client",
1030+
version: "1.0",
1031+
});
1032+
1033+
server.resource(
1034+
"test",
1035+
new UriTemplate("test://resource/{id}"),
1036+
async () => ({
1037+
contents: [
1038+
{
1039+
uri: "test://resource/123",
1040+
text: "Test content",
1041+
},
1042+
],
1043+
}),
1044+
);
1045+
1046+
const [clientTransport, serverTransport] =
1047+
InMemoryTransport.createLinkedPair();
1048+
1049+
await Promise.all([
1050+
client.connect(clientTransport),
1051+
server.connect(serverTransport),
1052+
]);
1053+
1054+
const result = await client.request(
1055+
{
1056+
method: "resources/templates/list",
1057+
},
1058+
ListResourceTemplatesResultSchema,
1059+
);
1060+
1061+
expect(result.resourceTemplates).toHaveLength(1);
1062+
expect(result.resourceTemplates[0].name).toBe("test");
1063+
expect(result.resourceTemplates[0].uriTemplate).toBe(
1064+
"test://resource/{id}",
1065+
);
1066+
});
1067+
1068+
test("should prevent duplicate resource registration", () => {
1069+
const server = new Server({
1070+
name: "test server",
1071+
version: "1.0",
1072+
});
1073+
1074+
server.resource("test", "test://resource", async () => ({
1075+
contents: [
1076+
{
1077+
uri: "test://resource",
1078+
text: "Test content",
1079+
},
1080+
],
1081+
}));
1082+
1083+
expect(() => {
1084+
server.resource("test2", "test://resource", async () => ({
1085+
contents: [
1086+
{
1087+
uri: "test://resource",
1088+
text: "Test content 2",
1089+
},
1090+
],
1091+
}));
1092+
}).toThrow(/already registered/);
1093+
});
1094+
1095+
test("should prevent duplicate resource template registration", () => {
1096+
const server = new Server({
1097+
name: "test server",
1098+
version: "1.0",
1099+
});
1100+
1101+
server.resource(
1102+
"test",
1103+
new UriTemplate("test://resource/{id}"),
1104+
async () => ({
1105+
contents: [
1106+
{
1107+
uri: "test://resource/123",
1108+
text: "Test content",
1109+
},
1110+
],
1111+
}),
1112+
);
1113+
1114+
expect(() => {
1115+
server.resource(
1116+
"test",
1117+
new UriTemplate("test://resource/{id}"),
1118+
async () => ({
1119+
contents: [
1120+
{
1121+
uri: "test://resource/123",
1122+
text: "Test content 2",
1123+
},
1124+
],
1125+
}),
1126+
);
1127+
}).toThrow(/already registered/);
1128+
});
1129+
1130+
test("should handle resource read errors gracefully", async () => {
1131+
const server = new Server({
1132+
name: "test server",
1133+
version: "1.0",
1134+
});
1135+
const client = new Client({
1136+
name: "test client",
1137+
version: "1.0",
1138+
});
1139+
1140+
server.resource("error-test", "test://error", async () => {
1141+
throw new Error("Resource read failed");
1142+
});
1143+
1144+
const [clientTransport, serverTransport] =
1145+
InMemoryTransport.createLinkedPair();
1146+
1147+
await Promise.all([
1148+
client.connect(clientTransport),
1149+
server.connect(serverTransport),
1150+
]);
1151+
1152+
await expect(
1153+
client.request(
1154+
{
1155+
method: "resources/read",
1156+
params: {
1157+
uri: "test://error",
1158+
},
1159+
},
1160+
ReadResourceResultSchema,
1161+
),
1162+
).rejects.toThrow(/Resource read failed/);
1163+
});
1164+
1165+
test("should throw McpError for invalid resource URI", async () => {
1166+
const server = new Server({
1167+
name: "test server",
1168+
version: "1.0",
1169+
});
1170+
const client = new Client({
1171+
name: "test client",
1172+
version: "1.0",
1173+
});
1174+
1175+
server.resource("test", "test://resource", async () => ({
1176+
contents: [
1177+
{
1178+
uri: "test://resource",
1179+
text: "Test content",
1180+
},
1181+
],
1182+
}));
1183+
1184+
const [clientTransport, serverTransport] =
1185+
InMemoryTransport.createLinkedPair();
1186+
1187+
await Promise.all([
1188+
client.connect(clientTransport),
1189+
server.connect(serverTransport),
1190+
]);
1191+
1192+
await expect(
1193+
client.request(
1194+
{
1195+
method: "resources/read",
1196+
params: {
1197+
uri: "test://nonexistent",
1198+
},
1199+
},
1200+
ReadResourceResultSchema,
1201+
),
1202+
).rejects.toThrow(/Resource test:\/\/nonexistent not found/);
1203+
});
1204+
});

0 commit comments

Comments
 (0)