Use explicit capabilities lists for now

This commit is contained in:
Justin Spahr-Summers 2024-11-11 22:38:43 +00:00
commit 7ab5880569
No known key found for this signature in database
5 changed files with 179 additions and 83 deletions

View file

@ -17,10 +17,15 @@ import { StdioServerTransport } from "./server/stdio.js";
import { ListResourcesResultSchema } from "./types.js";
async function runClient(url_or_command: string, args: string[]) {
const client = new Client({
name: "mcp-typescript test client",
version: "0.1.0",
});
const client = new Client(
{
name: "mcp-typescript test client",
version: "0.1.0",
},
{
sampling: {},
},
);
let clientTransport;
@ -97,10 +102,18 @@ async function runServer(port: number | null) {
console.log(`Server running on http://localhost:${port}/sse`);
});
} else {
const server = new Server({
name: "mcp-typescript test server",
version: "0.1.0",
});
const server = new Server(
{
name: "mcp-typescript test server",
version: "0.1.0",
},
{
prompts: {},
resources: {},
tools: {},
logging: {},
},
);
const transport = new StdioServerTransport();
await server.connect(transport);

View file

@ -40,10 +40,15 @@ test("should initialize with matching protocol version", async () => {
}),
};
const client = new Client({
name: "test client",
version: "1.0",
});
const client = new Client(
{
name: "test client",
version: "1.0",
},
{
sampling: {},
},
);
await client.connect(clientTransport);
@ -82,10 +87,15 @@ test("should initialize with supported older protocol version", async () => {
}),
};
const client = new Client({
name: "test client",
version: "1.0",
});
const client = new Client(
{
name: "test client",
version: "1.0",
},
{
sampling: {},
},
);
await client.connect(clientTransport);
@ -119,10 +129,15 @@ test("should reject unsupported protocol version", async () => {
}),
};
const client = new Client({
name: "test client",
version: "1.0",
});
const client = new Client(
{
name: "test client",
version: "1.0",
},
{
sampling: {},
},
);
await expect(client.connect(clientTransport)).rejects.toThrow(
"Server's protocol version is not supported: invalid-version",
@ -132,12 +147,18 @@ test("should reject unsupported protocol version", async () => {
});
test("should respect server capabilities", async () => {
const server = new Server({
name: "test server",
version: "1.0",
});
const server = new Server(
{
name: "test server",
version: "1.0",
},
{
resources: {},
tools: {},
},
);
server.setRequestHandler(InitializeRequestSchema, (request) => ({
server.setRequestHandler(InitializeRequestSchema, (_request) => ({
protocolVersion: LATEST_PROTOCOL_VERSION,
capabilities: {
resources: {},
@ -160,10 +181,15 @@ test("should respect server capabilities", async () => {
const [clientTransport, serverTransport] =
InMemoryTransport.createLinkedPair();
const client = new Client({
name: "test client",
version: "1.0",
});
const client = new Client(
{
name: "test client",
version: "1.0",
},
{
sampling: {},
},
);
await Promise.all([
client.connect(clientTransport),
@ -231,10 +257,15 @@ test("should typecheck", () => {
WeatherRequest,
WeatherNotification,
WeatherResult
>({
name: "WeatherClient",
version: "1.0.0",
});
>(
{
name: "WeatherClient",
version: "1.0.0",
},
{
sampling: {},
},
);
// Typecheck that only valid weather requests/notifications/results are allowed
false &&

View file

@ -3,6 +3,7 @@ import { Transport } from "../shared/transport.js";
import {
CallToolRequest,
CallToolResultSchema,
ClientCapabilities,
ClientNotification,
ClientRequest,
ClientResult,
@ -75,7 +76,10 @@ export class Client<
/**
* Initializes this client with the given name and version information.
*/
constructor(private _clientInfo: Implementation) {
constructor(
private _clientInfo: Implementation,
private _capabilities: ClientCapabilities,
) {
super();
}
@ -88,7 +92,7 @@ export class Client<
method: "initialize",
params: {
protocolVersion: LATEST_PROTOCOL_VERSION,
capabilities: {},
capabilities: this._capabilities,
clientInfo: this._clientInfo,
},
},

View file

@ -9,6 +9,7 @@ import {
ResultSchema,
LATEST_PROTOCOL_VERSION,
SUPPORTED_PROTOCOL_VERSIONS,
CreateMessageRequestSchema,
} from "../types.js";
import { Transport } from "../shared/transport.js";
import { InMemoryTransport } from "../inMemory.js";
@ -39,10 +40,18 @@ test("should accept latest protocol version", async () => {
}),
};
const server = new Server({
name: "test server",
version: "1.0",
});
const server = new Server(
{
name: "test server",
version: "1.0",
},
{
prompts: {},
resources: {},
tools: {},
logging: {},
},
);
await server.connect(serverTransport);
@ -90,10 +99,18 @@ test("should accept supported older protocol version", async () => {
}),
};
const server = new Server({
name: "test server",
version: "1.0",
});
const server = new Server(
{
name: "test server",
version: "1.0",
},
{
prompts: {},
resources: {},
tools: {},
logging: {},
},
);
await server.connect(serverTransport);
@ -140,10 +157,18 @@ test("should handle unsupported protocol version", async () => {
}),
};
const server = new Server({
name: "test server",
version: "1.0",
});
const server = new Server(
{
name: "test server",
version: "1.0",
},
{
prompts: {},
resources: {},
tools: {},
logging: {},
},
);
await server.connect(serverTransport);
@ -166,14 +191,39 @@ test("should handle unsupported protocol version", async () => {
});
test("should respect client capabilities", async () => {
const server = new Server({
name: "test server",
version: "1.0",
});
const server = new Server(
{
name: "test server",
version: "1.0",
},
{
prompts: {},
resources: {},
tools: {},
logging: {},
},
);
const client = new Client(
{
name: "test client",
version: "1.0",
},
{
sampling: {},
},
);
const client = new Client({
name: "test client",
version: "1.0",
// Implement request handler for sampling/createMessage
client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
// Mock implementation of createMessage
return {
model: "test-model",
role: "assistant",
content: {
type: "text",
text: "This is a test response",
},
};
});
const [clientTransport, serverTransport] =
@ -184,9 +234,17 @@ test("should respect client capabilities", async () => {
server.connect(serverTransport),
]);
expect(server.getClientCapabilities()).toEqual({});
expect(server.getClientCapabilities()).toEqual({ sampling: {} });
// This should throw because roots are not supported by the client
// This should work because sampling is supported by the client
await expect(
server.createMessage({
messages: [],
maxTokens: 10,
}),
).resolves.not.toThrow();
// This should still throw because roots are not supported by the client
await expect(server.listRoots()).rejects.toThrow(
"Client does not support roots",
);
@ -237,10 +295,18 @@ test("should typecheck", () => {
WeatherRequest,
WeatherNotification,
WeatherResult
>({
name: "WeatherServer",
version: "1.0.0",
});
>(
{
name: "WeatherServer",
version: "1.0.0",
},
{
prompts: {},
resources: {},
tools: {},
logging: {},
},
);
// Typecheck that only valid weather requests/notifications/results are allowed
weatherServer.setRequestHandler(GetWeatherRequestSchema, (request) => {

View file

@ -73,7 +73,10 @@ export class Server<
/**
* Initializes this server with the given name and version information.
*/
constructor(private _serverInfo: Implementation) {
constructor(
private _serverInfo: Implementation,
private _capabilities: ServerCapabilities,
) {
super();
this.setRequestHandler(InitializeRequestSchema, (request) =>
@ -116,28 +119,7 @@ export class Server<
}
private getCapabilities(): ServerCapabilities {
return {
prompts: this._requestHandlers.has(
ListPromptsRequestSchema.shape.method.value as string,
)
? {}
: undefined,
resources: this._requestHandlers.has(
ListResourcesRequestSchema.shape.method.value as string,
)
? {}
: undefined,
tools: this._requestHandlers.has(
ListToolsRequestSchema.shape.method.value as string,
)
? {}
: undefined,
logging: this._requestHandlers.has(
SetLevelRequestSchema.shape.method.value as string,
)
? {}
: undefined,
};
return this._capabilities;
}
private assertCapability(