929 lines
20 KiB
TypeScript
929 lines
20 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
/* eslint-disable no-constant-binary-expression */
|
|
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
|
import { Server } from "./index.js";
|
|
import { z } from "zod";
|
|
import {
|
|
RequestSchema,
|
|
NotificationSchema,
|
|
ResultSchema,
|
|
LATEST_PROTOCOL_VERSION,
|
|
SUPPORTED_PROTOCOL_VERSIONS,
|
|
CreateMessageRequestSchema,
|
|
ListPromptsRequestSchema,
|
|
ListResourcesRequestSchema,
|
|
ListToolsRequestSchema,
|
|
SetLevelRequestSchema,
|
|
ErrorCode,
|
|
ListToolsResultSchema,
|
|
CallToolResultSchema,
|
|
} from "../types.js";
|
|
import { Transport } from "../shared/transport.js";
|
|
import { InMemoryTransport } from "../inMemory.js";
|
|
import { Client } from "../client/index.js";
|
|
|
|
test("should accept latest protocol version", async () => {
|
|
let sendPromiseResolve: (value: unknown) => void;
|
|
const sendPromise = new Promise((resolve) => {
|
|
sendPromiseResolve = resolve;
|
|
});
|
|
|
|
const serverTransport: Transport = {
|
|
start: jest.fn().mockResolvedValue(undefined),
|
|
close: jest.fn().mockResolvedValue(undefined),
|
|
send: jest.fn().mockImplementation((message) => {
|
|
if (message.id === 1 && message.result) {
|
|
expect(message.result).toEqual({
|
|
protocolVersion: LATEST_PROTOCOL_VERSION,
|
|
capabilities: expect.any(Object),
|
|
serverInfo: {
|
|
name: "test server",
|
|
version: "1.0",
|
|
},
|
|
});
|
|
sendPromiseResolve(undefined);
|
|
}
|
|
return Promise.resolve();
|
|
}),
|
|
};
|
|
|
|
const server = new Server(
|
|
{
|
|
name: "test server",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
prompts: {},
|
|
resources: {},
|
|
tools: {},
|
|
logging: {},
|
|
},
|
|
},
|
|
);
|
|
|
|
await server.connect(serverTransport);
|
|
|
|
// Simulate initialize request with latest version
|
|
serverTransport.onmessage?.({
|
|
jsonrpc: "2.0",
|
|
id: 1,
|
|
method: "initialize",
|
|
params: {
|
|
protocolVersion: LATEST_PROTOCOL_VERSION,
|
|
capabilities: {},
|
|
clientInfo: {
|
|
name: "test client",
|
|
version: "1.0",
|
|
},
|
|
},
|
|
});
|
|
|
|
await expect(sendPromise).resolves.toBeUndefined();
|
|
});
|
|
|
|
test("should accept supported older protocol version", async () => {
|
|
const OLD_VERSION = SUPPORTED_PROTOCOL_VERSIONS[1];
|
|
let sendPromiseResolve: (value: unknown) => void;
|
|
const sendPromise = new Promise((resolve) => {
|
|
sendPromiseResolve = resolve;
|
|
});
|
|
|
|
const serverTransport: Transport = {
|
|
start: jest.fn().mockResolvedValue(undefined),
|
|
close: jest.fn().mockResolvedValue(undefined),
|
|
send: jest.fn().mockImplementation((message) => {
|
|
if (message.id === 1 && message.result) {
|
|
expect(message.result).toEqual({
|
|
protocolVersion: OLD_VERSION,
|
|
capabilities: expect.any(Object),
|
|
serverInfo: {
|
|
name: "test server",
|
|
version: "1.0",
|
|
},
|
|
});
|
|
sendPromiseResolve(undefined);
|
|
}
|
|
return Promise.resolve();
|
|
}),
|
|
};
|
|
|
|
const server = new Server(
|
|
{
|
|
name: "test server",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
prompts: {},
|
|
resources: {},
|
|
tools: {},
|
|
logging: {},
|
|
},
|
|
},
|
|
);
|
|
|
|
await server.connect(serverTransport);
|
|
|
|
// Simulate initialize request with older version
|
|
serverTransport.onmessage?.({
|
|
jsonrpc: "2.0",
|
|
id: 1,
|
|
method: "initialize",
|
|
params: {
|
|
protocolVersion: OLD_VERSION,
|
|
capabilities: {},
|
|
clientInfo: {
|
|
name: "test client",
|
|
version: "1.0",
|
|
},
|
|
},
|
|
});
|
|
|
|
await expect(sendPromise).resolves.toBeUndefined();
|
|
});
|
|
|
|
test("should handle unsupported protocol version", async () => {
|
|
let sendPromiseResolve: (value: unknown) => void;
|
|
const sendPromise = new Promise((resolve) => {
|
|
sendPromiseResolve = resolve;
|
|
});
|
|
|
|
const serverTransport: Transport = {
|
|
start: jest.fn().mockResolvedValue(undefined),
|
|
close: jest.fn().mockResolvedValue(undefined),
|
|
send: jest.fn().mockImplementation((message) => {
|
|
if (message.id === 1 && message.result) {
|
|
expect(message.result).toEqual({
|
|
protocolVersion: LATEST_PROTOCOL_VERSION,
|
|
capabilities: expect.any(Object),
|
|
serverInfo: {
|
|
name: "test server",
|
|
version: "1.0",
|
|
},
|
|
});
|
|
sendPromiseResolve(undefined);
|
|
}
|
|
return Promise.resolve();
|
|
}),
|
|
};
|
|
|
|
const server = new Server(
|
|
{
|
|
name: "test server",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
prompts: {},
|
|
resources: {},
|
|
tools: {},
|
|
logging: {},
|
|
},
|
|
},
|
|
);
|
|
|
|
await server.connect(serverTransport);
|
|
|
|
// Simulate initialize request with unsupported version
|
|
serverTransport.onmessage?.({
|
|
jsonrpc: "2.0",
|
|
id: 1,
|
|
method: "initialize",
|
|
params: {
|
|
protocolVersion: "invalid-version",
|
|
capabilities: {},
|
|
clientInfo: {
|
|
name: "test client",
|
|
version: "1.0",
|
|
},
|
|
},
|
|
});
|
|
|
|
await expect(sendPromise).resolves.toBeUndefined();
|
|
});
|
|
|
|
test("should respect client capabilities", async () => {
|
|
const server = new Server(
|
|
{
|
|
name: "test server",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
prompts: {},
|
|
resources: {},
|
|
tools: {},
|
|
logging: {},
|
|
},
|
|
enforceStrictCapabilities: true,
|
|
},
|
|
);
|
|
|
|
const client = new Client(
|
|
{
|
|
name: "test client",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
sampling: {},
|
|
},
|
|
},
|
|
);
|
|
|
|
// 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] =
|
|
InMemoryTransport.createLinkedPair();
|
|
|
|
await Promise.all([
|
|
client.connect(clientTransport),
|
|
server.connect(serverTransport),
|
|
]);
|
|
|
|
expect(server.getClientCapabilities()).toEqual({ sampling: {} });
|
|
|
|
// 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/);
|
|
});
|
|
|
|
test("should respect server notification capabilities", async () => {
|
|
const server = new Server(
|
|
{
|
|
name: "test server",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
logging: {},
|
|
},
|
|
enforceStrictCapabilities: true,
|
|
},
|
|
);
|
|
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
|
|
await server.connect(serverTransport);
|
|
|
|
// This should work because logging is supported by the server
|
|
await expect(
|
|
server.sendLoggingMessage({
|
|
level: "info",
|
|
data: "Test log message",
|
|
}),
|
|
).resolves.not.toThrow();
|
|
|
|
// This should throw because resource notificaitons are not supported by the server
|
|
await expect(
|
|
server.sendResourceUpdated({ uri: "test://resource" }),
|
|
).rejects.toThrow(/^Server does not support/);
|
|
});
|
|
|
|
test("should only allow setRequestHandler for declared capabilities", () => {
|
|
const server = new Server(
|
|
{
|
|
name: "test server",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
prompts: {},
|
|
resources: {},
|
|
},
|
|
},
|
|
);
|
|
|
|
// These should work because the capabilities are declared
|
|
expect(() => {
|
|
server.setRequestHandler(ListPromptsRequestSchema, () => ({ prompts: [] }));
|
|
}).not.toThrow();
|
|
|
|
expect(() => {
|
|
server.setRequestHandler(ListResourcesRequestSchema, () => ({
|
|
resources: [],
|
|
}));
|
|
}).not.toThrow();
|
|
|
|
// These should throw because the capabilities are not declared
|
|
expect(() => {
|
|
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: [] }));
|
|
}).toThrow(/^Server does not support tools/);
|
|
|
|
expect(() => {
|
|
server.setRequestHandler(SetLevelRequestSchema, () => ({}));
|
|
}).toThrow(/^Server does not support logging/);
|
|
});
|
|
|
|
/*
|
|
Test that custom request/notification/result schemas can be used with the Server class.
|
|
*/
|
|
test("should typecheck", () => {
|
|
const GetWeatherRequestSchema = RequestSchema.extend({
|
|
method: z.literal("weather/get"),
|
|
params: z.object({
|
|
city: z.string(),
|
|
}),
|
|
});
|
|
|
|
const GetForecastRequestSchema = RequestSchema.extend({
|
|
method: z.literal("weather/forecast"),
|
|
params: z.object({
|
|
city: z.string(),
|
|
days: z.number(),
|
|
}),
|
|
});
|
|
|
|
const WeatherForecastNotificationSchema = NotificationSchema.extend({
|
|
method: z.literal("weather/alert"),
|
|
params: z.object({
|
|
severity: z.enum(["warning", "watch"]),
|
|
message: z.string(),
|
|
}),
|
|
});
|
|
|
|
const WeatherRequestSchema = GetWeatherRequestSchema.or(
|
|
GetForecastRequestSchema,
|
|
);
|
|
const WeatherNotificationSchema = WeatherForecastNotificationSchema;
|
|
const WeatherResultSchema = ResultSchema.extend({
|
|
temperature: z.number(),
|
|
conditions: z.string(),
|
|
});
|
|
|
|
type WeatherRequest = z.infer<typeof WeatherRequestSchema>;
|
|
type WeatherNotification = z.infer<typeof WeatherNotificationSchema>;
|
|
type WeatherResult = z.infer<typeof WeatherResultSchema>;
|
|
|
|
// Create a typed Server for weather data
|
|
const weatherServer = new Server<
|
|
WeatherRequest,
|
|
WeatherNotification,
|
|
WeatherResult
|
|
>(
|
|
{
|
|
name: "WeatherServer",
|
|
version: "1.0.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
prompts: {},
|
|
resources: {},
|
|
tools: {},
|
|
logging: {},
|
|
},
|
|
},
|
|
);
|
|
|
|
// Typecheck that only valid weather requests/notifications/results are allowed
|
|
weatherServer.setRequestHandler(GetWeatherRequestSchema, (request) => {
|
|
return {
|
|
temperature: 72,
|
|
conditions: "sunny",
|
|
};
|
|
});
|
|
|
|
weatherServer.setNotificationHandler(
|
|
WeatherForecastNotificationSchema,
|
|
(notification) => {
|
|
console.log(`Weather alert: ${notification.params.message}`);
|
|
},
|
|
);
|
|
});
|
|
|
|
test("should handle server cancelling a request", async () => {
|
|
const server = new Server(
|
|
{
|
|
name: "test server",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
sampling: {},
|
|
},
|
|
},
|
|
);
|
|
|
|
const client = new Client(
|
|
{
|
|
name: "test client",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
sampling: {},
|
|
},
|
|
},
|
|
);
|
|
|
|
// Set up client to delay responding to createMessage
|
|
client.setRequestHandler(
|
|
CreateMessageRequestSchema,
|
|
async (_request, extra) => {
|
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
return {
|
|
model: "test",
|
|
role: "assistant",
|
|
content: {
|
|
type: "text",
|
|
text: "Test response",
|
|
},
|
|
};
|
|
},
|
|
);
|
|
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
|
|
await Promise.all([
|
|
client.connect(clientTransport),
|
|
server.connect(serverTransport),
|
|
]);
|
|
|
|
// Set up abort controller
|
|
const controller = new AbortController();
|
|
|
|
// Issue request but cancel it immediately
|
|
const createMessagePromise = server.createMessage(
|
|
{
|
|
messages: [],
|
|
maxTokens: 10,
|
|
},
|
|
{
|
|
signal: controller.signal,
|
|
},
|
|
);
|
|
controller.abort("Cancelled by test");
|
|
|
|
// Request should be rejected
|
|
await expect(createMessagePromise).rejects.toBe("Cancelled by test");
|
|
});
|
|
test("should handle request timeout", async () => {
|
|
const server = new Server(
|
|
{
|
|
name: "test server",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
sampling: {},
|
|
},
|
|
},
|
|
);
|
|
|
|
// Set up client that delays responses
|
|
const client = new Client(
|
|
{
|
|
name: "test client",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
sampling: {},
|
|
},
|
|
},
|
|
);
|
|
|
|
client.setRequestHandler(
|
|
CreateMessageRequestSchema,
|
|
async (_request, extra) => {
|
|
await new Promise((resolve, reject) => {
|
|
const timeout = setTimeout(resolve, 100);
|
|
extra.signal.addEventListener("abort", () => {
|
|
clearTimeout(timeout);
|
|
reject(extra.signal.reason);
|
|
});
|
|
});
|
|
|
|
return {
|
|
model: "test",
|
|
role: "assistant",
|
|
content: {
|
|
type: "text",
|
|
text: "Test response",
|
|
},
|
|
};
|
|
},
|
|
);
|
|
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
|
|
await Promise.all([
|
|
client.connect(clientTransport),
|
|
server.connect(serverTransport),
|
|
]);
|
|
|
|
// Request with 0 msec timeout should fail immediately
|
|
await expect(
|
|
server.createMessage(
|
|
{
|
|
messages: [],
|
|
maxTokens: 10,
|
|
},
|
|
{ timeout: 0 },
|
|
),
|
|
).rejects.toMatchObject({
|
|
code: ErrorCode.RequestTimeout,
|
|
});
|
|
});
|
|
|
|
describe("Server.tool", () => {
|
|
test("should register zero-argument tool", async () => {
|
|
const server = new Server({
|
|
name: "test server",
|
|
version: "1.0",
|
|
});
|
|
const client = new Client({
|
|
name: "test client",
|
|
version: "1.0",
|
|
});
|
|
|
|
server.tool("test", async () => ({
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: "Test response",
|
|
},
|
|
],
|
|
}));
|
|
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
|
|
await Promise.all([
|
|
client.connect(clientTransport),
|
|
server.connect(serverTransport),
|
|
]);
|
|
|
|
const result = await client.request(
|
|
{
|
|
method: "tools/list",
|
|
},
|
|
ListToolsResultSchema,
|
|
);
|
|
|
|
expect(result.tools).toHaveLength(1);
|
|
expect(result.tools[0].name).toBe("test");
|
|
expect(result.tools[0].inputSchema).toEqual({
|
|
type: "object",
|
|
});
|
|
});
|
|
|
|
test("should register tool with args schema", async () => {
|
|
const server = new Server({
|
|
name: "test server",
|
|
version: "1.0",
|
|
});
|
|
const client = new Client({
|
|
name: "test client",
|
|
version: "1.0",
|
|
});
|
|
|
|
server.tool(
|
|
"test",
|
|
{
|
|
name: z.string(),
|
|
value: z.number(),
|
|
},
|
|
async ({ name, value }) => ({
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: `${name}: ${value}`,
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
|
|
await Promise.all([
|
|
client.connect(clientTransport),
|
|
server.connect(serverTransport),
|
|
]);
|
|
|
|
const result = await client.request(
|
|
{
|
|
method: "tools/list",
|
|
},
|
|
ListToolsResultSchema,
|
|
);
|
|
|
|
expect(result.tools).toHaveLength(1);
|
|
expect(result.tools[0].name).toBe("test");
|
|
expect(result.tools[0].inputSchema).toMatchObject({
|
|
type: "object",
|
|
properties: {
|
|
name: { type: "string" },
|
|
value: { type: "number" },
|
|
},
|
|
});
|
|
});
|
|
|
|
test("should register tool with description", async () => {
|
|
const server = new Server({
|
|
name: "test server",
|
|
version: "1.0",
|
|
});
|
|
const client = new Client({
|
|
name: "test client",
|
|
version: "1.0",
|
|
});
|
|
|
|
server.tool("test", "Test description", async () => ({
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: "Test response",
|
|
},
|
|
],
|
|
}));
|
|
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
|
|
await Promise.all([
|
|
client.connect(clientTransport),
|
|
server.connect(serverTransport),
|
|
]);
|
|
|
|
const result = await client.request(
|
|
{
|
|
method: "tools/list",
|
|
},
|
|
ListToolsResultSchema,
|
|
);
|
|
|
|
expect(result.tools).toHaveLength(1);
|
|
expect(result.tools[0].name).toBe("test");
|
|
expect(result.tools[0].description).toBe("Test description");
|
|
});
|
|
|
|
test("should validate tool args", async () => {
|
|
const server = new Server({
|
|
name: "test server",
|
|
version: "1.0",
|
|
});
|
|
|
|
const client = new Client(
|
|
{
|
|
name: "test client",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
tools: {},
|
|
},
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
"test",
|
|
{
|
|
name: z.string(),
|
|
value: z.number(),
|
|
},
|
|
async ({ name, value }) => ({
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: `${name}: ${value}`,
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
|
|
await Promise.all([
|
|
client.connect(clientTransport),
|
|
server.connect(serverTransport),
|
|
]);
|
|
|
|
await expect(
|
|
client.request(
|
|
{
|
|
method: "tools/call",
|
|
params: {
|
|
name: "test",
|
|
arguments: {
|
|
name: "test",
|
|
value: "not a number",
|
|
},
|
|
},
|
|
},
|
|
CallToolResultSchema,
|
|
),
|
|
).rejects.toThrow(/Invalid arguments/);
|
|
});
|
|
|
|
test("should prevent duplicate tool registration", () => {
|
|
const server = new Server({
|
|
name: "test server",
|
|
version: "1.0",
|
|
});
|
|
|
|
server.tool("test", async () => ({
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: "Test response",
|
|
},
|
|
],
|
|
}));
|
|
|
|
expect(() => {
|
|
server.tool("test", async () => ({
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: "Test response 2",
|
|
},
|
|
],
|
|
}));
|
|
}).toThrow(/already registered/);
|
|
});
|
|
|
|
test("should allow client to call server tools", async () => {
|
|
const server = new Server({
|
|
name: "test server",
|
|
version: "1.0",
|
|
});
|
|
|
|
const client = new Client(
|
|
{
|
|
name: "test client",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
tools: {},
|
|
},
|
|
},
|
|
);
|
|
|
|
server.tool(
|
|
"test",
|
|
"Test tool",
|
|
{
|
|
input: z.string(),
|
|
},
|
|
async ({ input }) => ({
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: `Processed: ${input}`,
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
|
|
await Promise.all([
|
|
client.connect(clientTransport),
|
|
server.connect(serverTransport),
|
|
]);
|
|
|
|
const result = await client.request(
|
|
{
|
|
method: "tools/call",
|
|
params: {
|
|
name: "test",
|
|
arguments: {
|
|
input: "hello",
|
|
},
|
|
},
|
|
},
|
|
CallToolResultSchema,
|
|
);
|
|
|
|
expect(result.content).toEqual([
|
|
{
|
|
type: "text",
|
|
text: "Processed: hello",
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("should handle server tool errors gracefully", async () => {
|
|
const server = new Server({
|
|
name: "test server",
|
|
version: "1.0",
|
|
});
|
|
|
|
const client = new Client(
|
|
{
|
|
name: "test client",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
tools: {},
|
|
},
|
|
},
|
|
);
|
|
|
|
server.tool("error-test", async () => {
|
|
throw new Error("Tool execution failed");
|
|
});
|
|
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
|
|
await Promise.all([
|
|
client.connect(clientTransport),
|
|
server.connect(serverTransport),
|
|
]);
|
|
|
|
const result = await client.request(
|
|
{
|
|
method: "tools/call",
|
|
params: {
|
|
name: "error-test",
|
|
},
|
|
},
|
|
CallToolResultSchema,
|
|
);
|
|
|
|
expect(result.isError).toBe(true);
|
|
expect(result.content).toEqual([
|
|
{
|
|
type: "text",
|
|
text: "Tool execution failed",
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("should throw McpError for invalid tool name", async () => {
|
|
const server = new Server({
|
|
name: "test server",
|
|
version: "1.0",
|
|
});
|
|
|
|
const client = new Client(
|
|
{
|
|
name: "test client",
|
|
version: "1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
tools: {},
|
|
},
|
|
},
|
|
);
|
|
|
|
server.tool("test-tool", async () => ({
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: "Test response",
|
|
},
|
|
],
|
|
}));
|
|
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
|
|
await Promise.all([
|
|
client.connect(clientTransport),
|
|
server.connect(serverTransport),
|
|
]);
|
|
|
|
await expect(
|
|
client.request(
|
|
{
|
|
method: "tools/call",
|
|
params: {
|
|
name: "nonexistent-tool",
|
|
},
|
|
},
|
|
CallToolResultSchema,
|
|
),
|
|
).rejects.toThrow(/Tool nonexistent-tool not found/);
|
|
});
|
|
});
|