Skip to content

Commit 90e91cd

Browse files
committed
Tests and assertions for client/server capabilities
Resolves #45.
1 parent 852ebb9 commit 90e91cd

File tree

4 files changed

+128
-4
lines changed

4 files changed

+128
-4
lines changed

src/client/index.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ import {
99
ResultSchema,
1010
LATEST_PROTOCOL_VERSION,
1111
SUPPORTED_PROTOCOL_VERSIONS,
12+
InitializeRequestSchema,
13+
ListResourcesRequestSchema,
14+
ListToolsRequestSchema,
1215
} from "../types.js";
1316
import { Transport } from "../shared/transport.js";
17+
import { Server } from "../server/index.js";
18+
import { InMemoryTransport } from "../inMemory.js";
1419

1520
test("should initialize with matching protocol version", async () => {
1621
const clientTransport: Transport = {
@@ -126,6 +131,61 @@ test("should reject unsupported protocol version", async () => {
126131
expect(clientTransport.close).toHaveBeenCalled();
127132
});
128133

134+
test("should respect server capabilities", async () => {
135+
const server = new Server({
136+
name: "test server",
137+
version: "1.0",
138+
});
139+
140+
server.setRequestHandler(InitializeRequestSchema, (request) => ({
141+
protocolVersion: LATEST_PROTOCOL_VERSION,
142+
capabilities: {
143+
resources: {},
144+
tools: {},
145+
},
146+
serverInfo: {
147+
name: "test",
148+
version: "1.0",
149+
},
150+
}));
151+
152+
server.setRequestHandler(ListResourcesRequestSchema, () => ({
153+
resources: [],
154+
}));
155+
156+
server.setRequestHandler(ListToolsRequestSchema, () => ({
157+
tools: [],
158+
}));
159+
160+
const [clientTransport, serverTransport] =
161+
InMemoryTransport.createLinkedPair();
162+
163+
const client = new Client({
164+
name: "test client",
165+
version: "1.0",
166+
});
167+
168+
await Promise.all([
169+
client.connect(clientTransport),
170+
server.connect(serverTransport),
171+
]);
172+
173+
// Server supports resources and tools, but not prompts
174+
expect(client.getServerCapabilities()).toEqual({
175+
resources: {},
176+
tools: {},
177+
});
178+
179+
// These should work
180+
await expect(client.listResources()).resolves.not.toThrow();
181+
await expect(client.listTools()).resolves.not.toThrow();
182+
183+
// This should throw because prompts are not supported
184+
await expect(client.listPrompts()).rejects.toThrow(
185+
"Server does not support prompts",
186+
);
187+
});
188+
129189
/*
130190
Test that custom request/notification/result schemas can be used with the Client class.
131191
*/

src/client/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,17 @@ export class Client<
132132
return this._serverVersion;
133133
}
134134

135+
private assertCapability(
136+
capability: keyof ServerCapabilities,
137+
method: string,
138+
) {
139+
if (!this._serverCapabilities?.[capability]) {
140+
throw new Error(
141+
`Server does not support ${capability} (required for ${method})`,
142+
);
143+
}
144+
}
145+
135146
async ping() {
136147
return this.request({ method: "ping" }, EmptyResultSchema);
137148
}
@@ -140,6 +151,7 @@ export class Client<
140151
params: CompleteRequest["params"],
141152
onprogress?: ProgressCallback,
142153
) {
154+
this.assertCapability("prompts", "completion/complete");
143155
return this.request(
144156
{ method: "completion/complete", params },
145157
CompleteResultSchema,
@@ -148,6 +160,7 @@ export class Client<
148160
}
149161

150162
async setLoggingLevel(level: LoggingLevel) {
163+
this.assertCapability("logging", "logging/setLevel");
151164
return this.request(
152165
{ method: "logging/setLevel", params: { level } },
153166
EmptyResultSchema,
@@ -158,6 +171,7 @@ export class Client<
158171
params: GetPromptRequest["params"],
159172
onprogress?: ProgressCallback,
160173
) {
174+
this.assertCapability("prompts", "prompts/get");
161175
return this.request(
162176
{ method: "prompts/get", params },
163177
GetPromptResultSchema,
@@ -169,6 +183,7 @@ export class Client<
169183
params?: ListPromptsRequest["params"],
170184
onprogress?: ProgressCallback,
171185
) {
186+
this.assertCapability("prompts", "prompts/list");
172187
return this.request(
173188
{ method: "prompts/list", params },
174189
ListPromptsResultSchema,
@@ -180,6 +195,7 @@ export class Client<
180195
params?: ListResourcesRequest["params"],
181196
onprogress?: ProgressCallback,
182197
) {
198+
this.assertCapability("resources", "resources/list");
183199
return this.request(
184200
{ method: "resources/list", params },
185201
ListResourcesResultSchema,
@@ -191,6 +207,7 @@ export class Client<
191207
params?: ListResourceTemplatesRequest["params"],
192208
onprogress?: ProgressCallback,
193209
) {
210+
this.assertCapability("resources", "resources/templates/list");
194211
return this.request(
195212
{ method: "resources/templates/list", params },
196213
ListResourceTemplatesResultSchema,
@@ -202,6 +219,7 @@ export class Client<
202219
params: ReadResourceRequest["params"],
203220
onprogress?: ProgressCallback,
204221
) {
222+
this.assertCapability("resources", "resources/read");
205223
return this.request(
206224
{ method: "resources/read", params },
207225
ReadResourceResultSchema,
@@ -210,13 +228,15 @@ export class Client<
210228
}
211229

212230
async subscribeResource(params: SubscribeRequest["params"]) {
231+
this.assertCapability("resources", "resources/subscribe");
213232
return this.request(
214233
{ method: "resources/subscribe", params },
215234
EmptyResultSchema,
216235
);
217236
}
218237

219238
async unsubscribeResource(params: UnsubscribeRequest["params"]) {
239+
this.assertCapability("resources", "resources/unsubscribe");
220240
return this.request(
221241
{ method: "resources/unsubscribe", params },
222242
EmptyResultSchema,
@@ -230,6 +250,7 @@ export class Client<
230250
| typeof CompatibilityCallToolResultSchema = CallToolResultSchema,
231251
onprogress?: ProgressCallback,
232252
) {
253+
this.assertCapability("tools", "tools/call");
233254
return this.request(
234255
{ method: "tools/call", params },
235256
resultSchema,
@@ -241,6 +262,7 @@ export class Client<
241262
params?: ListToolsRequest["params"],
242263
onprogress?: ProgressCallback,
243264
) {
265+
this.assertCapability("tools", "tools/list");
244266
return this.request(
245267
{ method: "tools/list", params },
246268
ListToolsResultSchema,

src/server/index.test.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import {
99
ResultSchema,
1010
LATEST_PROTOCOL_VERSION,
1111
SUPPORTED_PROTOCOL_VERSIONS,
12-
InitializeRequestSchema,
13-
InitializeResultSchema,
1412
} from "../types.js";
1513
import { Transport } from "../shared/transport.js";
14+
import { InMemoryTransport } from "../inMemory.js";
15+
import { Client } from "../client/index.js";
1616

1717
test("should accept latest protocol version", async () => {
1818
let sendPromiseResolve: (value: unknown) => void;
@@ -165,6 +165,33 @@ test("should handle unsupported protocol version", async () => {
165165
await expect(sendPromise).resolves.toBeUndefined();
166166
});
167167

168+
test("should respect client capabilities", async () => {
169+
const server = new Server({
170+
name: "test server",
171+
version: "1.0",
172+
});
173+
174+
const client = new Client({
175+
name: "test client",
176+
version: "1.0",
177+
});
178+
179+
const [clientTransport, serverTransport] =
180+
InMemoryTransport.createLinkedPair();
181+
182+
await Promise.all([
183+
client.connect(clientTransport),
184+
server.connect(serverTransport),
185+
]);
186+
187+
expect(server.getClientCapabilities()).toEqual({});
188+
189+
// This should throw because roots are not supported by the client
190+
await expect(server.listRoots()).rejects.toThrow(
191+
"Client does not support roots",
192+
);
193+
});
194+
168195
/*
169196
Test that custom request/notification/result schemas can be used with the Server class.
170197
*/

src/server/index.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
ServerRequest,
2626
ServerResult,
2727
SetLevelRequestSchema,
28-
SUPPORTED_PROTOCOL_VERSIONS
28+
SUPPORTED_PROTOCOL_VERSIONS,
2929
} from "../types.js";
3030

3131
/**
@@ -93,7 +93,9 @@ export class Server<
9393
this._clientVersion = request.params.clientInfo;
9494

9595
return {
96-
protocolVersion: SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion) ? requestedVersion : LATEST_PROTOCOL_VERSION,
96+
protocolVersion: SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion)
97+
? requestedVersion
98+
: LATEST_PROTOCOL_VERSION,
9799
capabilities: this.getCapabilities(),
98100
serverInfo: this._serverInfo,
99101
};
@@ -138,6 +140,17 @@ export class Server<
138140
};
139141
}
140142

143+
private assertCapability(
144+
capability: keyof ClientCapabilities,
145+
method: string,
146+
) {
147+
if (!this._clientCapabilities?.[capability]) {
148+
throw new Error(
149+
`Client does not support ${capability} (required for ${method})`,
150+
);
151+
}
152+
}
153+
141154
async ping() {
142155
return this.request({ method: "ping" }, EmptyResultSchema);
143156
}
@@ -146,6 +159,7 @@ export class Server<
146159
params: CreateMessageRequest["params"],
147160
onprogress?: ProgressCallback,
148161
) {
162+
this.assertCapability("sampling", "sampling/createMessage");
149163
return this.request(
150164
{ method: "sampling/createMessage", params },
151165
CreateMessageResultSchema,
@@ -157,6 +171,7 @@ export class Server<
157171
params?: ListRootsRequest["params"],
158172
onprogress?: ProgressCallback,
159173
) {
174+
this.assertCapability("roots", "roots/list");
160175
return this.request(
161176
{ method: "roots/list", params },
162177
ListRootsResultSchema,

0 commit comments

Comments
 (0)