- add new tool() api and tests
- fix schema validation - comment headers on tests
This commit is contained in:
parent
9686fc2ead
commit
6079cd8307
8 changed files with 578 additions and 401 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -15,7 +15,6 @@
|
|||
"eventsource": "^3.0.2",
|
||||
"express": "^5.0.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"json-schema-to-zod": "^2.6.1",
|
||||
"pkce-challenge": "^5.0.0",
|
||||
"raw-body": "^3.0.0",
|
||||
"zod": "^3.23.8",
|
||||
|
|
@ -4880,15 +4879,6 @@
|
|||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json-schema-to-zod": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-to-zod/-/json-schema-to-zod-2.6.1.tgz",
|
||||
"integrity": "sha512-uiHmWH21h9FjKJkRBntfVGTLpYlCZ1n98D0izIlByqQLqpmkQpNTBtfbdP04Na6+43lgsvrShFh2uWLkQDKJuQ==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"json-schema-to-zod": "dist/cjs/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ import { Transport } from "../shared/transport.js";
|
|||
import { Server } from "../server/index.js";
|
||||
import { InMemoryTransport } from "../inMemory.js";
|
||||
|
||||
/***
|
||||
* Test: Initialize with Matching Protocol Version
|
||||
*/
|
||||
test("should initialize with matching protocol version", async () => {
|
||||
const clientTransport: Transport = {
|
||||
start: jest.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -76,6 +79,9 @@ test("should initialize with matching protocol version", async () => {
|
|||
expect(client.getInstructions()).toEqual("test instructions");
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Initialize with Supported Older Protocol Version
|
||||
*/
|
||||
test("should initialize with supported older protocol version", async () => {
|
||||
const OLD_VERSION = SUPPORTED_PROTOCOL_VERSIONS[1];
|
||||
const clientTransport: Transport = {
|
||||
|
|
@ -124,6 +130,9 @@ test("should initialize with supported older protocol version", async () => {
|
|||
expect(client.getInstructions()).toBeUndefined();
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Reject Unsupported Protocol Version
|
||||
*/
|
||||
test("should reject unsupported protocol version", async () => {
|
||||
const clientTransport: Transport = {
|
||||
start: jest.fn().mockResolvedValue(undefined),
|
||||
|
|
@ -166,6 +175,9 @@ test("should reject unsupported protocol version", async () => {
|
|||
expect(clientTransport.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Connect New Client to Old Supported Server Version
|
||||
*/
|
||||
test("should connect new client to old, supported server version", async () => {
|
||||
const OLD_VERSION = SUPPORTED_PROTOCOL_VERSIONS[1];
|
||||
const server = new Server(
|
||||
|
|
@ -229,6 +241,9 @@ test("should connect new client to old, supported server version", async () => {
|
|||
});
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Version Negotiation with Old Client and Newer Server
|
||||
*/
|
||||
test("should negotiate version when client is old, and newer server supports its version", async () => {
|
||||
const OLD_VERSION = SUPPORTED_PROTOCOL_VERSIONS[1];
|
||||
const server = new Server(
|
||||
|
|
@ -292,6 +307,9 @@ test("should negotiate version when client is old, and newer server supports its
|
|||
});
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Throw when Old Client and Server Version Mismatch
|
||||
*/
|
||||
test("should throw when client is old, and server doesn't support its version", async () => {
|
||||
const OLD_VERSION = SUPPORTED_PROTOCOL_VERSIONS[1];
|
||||
const FUTURE_VERSION = "FUTURE_VERSION";
|
||||
|
|
@ -354,6 +372,9 @@ test("should throw when client is old, and server doesn't support its version",
|
|||
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Respect Server Capabilities
|
||||
*/
|
||||
test("should respect server capabilities", async () => {
|
||||
const server = new Server(
|
||||
{
|
||||
|
|
@ -434,6 +455,9 @@ test("should respect server capabilities", async () => {
|
|||
).rejects.toThrow("Server does not support completions");
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Respect Client Notification Capabilities
|
||||
*/
|
||||
test("should respect client notification capabilities", async () => {
|
||||
const server = new Server(
|
||||
{
|
||||
|
|
@ -490,6 +514,9 @@ test("should respect client notification capabilities", async () => {
|
|||
);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Respect Server Notification Capabilities
|
||||
*/
|
||||
test("should respect server notification capabilities", async () => {
|
||||
const server = new Server(
|
||||
{
|
||||
|
|
@ -536,6 +563,9 @@ test("should respect server notification capabilities", async () => {
|
|||
);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Only Allow setRequestHandler for Declared Capabilities
|
||||
*/
|
||||
test("should only allow setRequestHandler for declared capabilities", () => {
|
||||
const client = new Client(
|
||||
{
|
||||
|
|
@ -567,9 +597,10 @@ test("should only allow setRequestHandler for declared capabilities", () => {
|
|||
}).toThrow("Client does not support roots capability");
|
||||
});
|
||||
|
||||
/*
|
||||
Test that custom request/notification/result schemas can be used with the Client class.
|
||||
*/
|
||||
/***
|
||||
* Test: Type Checking
|
||||
* Test that custom request/notification/result schemas can be used with the Client class.
|
||||
*/
|
||||
test("should typecheck", () => {
|
||||
const GetWeatherRequestSchema = RequestSchema.extend({
|
||||
method: z.literal("weather/get"),
|
||||
|
|
@ -646,6 +677,9 @@ test("should typecheck", () => {
|
|||
});
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Handle Client Cancelling a Request
|
||||
*/
|
||||
test("should handle client cancelling a request", async () => {
|
||||
const server = new Server(
|
||||
{
|
||||
|
|
@ -701,6 +735,9 @@ test("should handle client cancelling a request", async () => {
|
|||
await expect(listResourcesPromise).rejects.toBe("Cancelled by test");
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Handle Request Timeout
|
||||
*/
|
||||
test("should handle request timeout", async () => {
|
||||
const server = new Server(
|
||||
{
|
||||
|
|
@ -757,6 +794,9 @@ test("should handle request timeout", async () => {
|
|||
});
|
||||
|
||||
describe('outputSchema validation', () => {
|
||||
/***
|
||||
* Test: Validate structuredContent Against outputSchema
|
||||
*/
|
||||
test('should validate structuredContent against outputSchema', async () => {
|
||||
const server = new Server({
|
||||
name: 'test-server',
|
||||
|
|
@ -828,6 +868,9 @@ describe('outputSchema validation', () => {
|
|||
expect(result.structuredContent).toEqual({ result: 'success', count: 42 });
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Throw Error when structuredContent Does Not Match Schema
|
||||
*/
|
||||
test('should throw error when structuredContent does not match schema', async () => {
|
||||
const server = new Server({
|
||||
name: 'test-server',
|
||||
|
|
@ -901,6 +944,9 @@ describe('outputSchema validation', () => {
|
|||
);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Throw Error when Tool with outputSchema Returns No structuredContent
|
||||
*/
|
||||
test('should throw error when tool with outputSchema returns no structuredContent', async () => {
|
||||
const server = new Server({
|
||||
name: 'test-server',
|
||||
|
|
@ -972,6 +1018,9 @@ describe('outputSchema validation', () => {
|
|||
);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Handle Tools Without outputSchema Normally
|
||||
*/
|
||||
test('should handle tools without outputSchema normally', async () => {
|
||||
const server = new Server({
|
||||
name: 'test-server',
|
||||
|
|
@ -1036,6 +1085,9 @@ describe('outputSchema validation', () => {
|
|||
expect(result.content).toEqual([{ type: 'text', text: 'Normal response' }]);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Handle Complex JSON Schema Validation
|
||||
*/
|
||||
test('should handle complex JSON schema validation', async () => {
|
||||
const server = new Server({
|
||||
name: 'test-server',
|
||||
|
|
@ -1131,6 +1183,9 @@ describe('outputSchema validation', () => {
|
|||
expect(structuredContent.age).toBe(30);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Fail Validation with Additional Properties When Not Allowed
|
||||
*/
|
||||
test('should fail validation with additional properties when not allowed', async () => {
|
||||
const server = new Server({
|
||||
name: 'test-server',
|
||||
|
|
@ -1206,6 +1261,9 @@ describe('outputSchema validation', () => {
|
|||
);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Throw Error when Tool Without outputSchema Returns structuredContent
|
||||
*/
|
||||
test('should throw error when tool without outputSchema returns structuredContent', async () => {
|
||||
const server = new Server({
|
||||
name: 'test-server',
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ import {
|
|||
McpError,
|
||||
} from "../types.js";
|
||||
import { z } from "zod";
|
||||
import { parseSchema } from "json-schema-to-zod";
|
||||
import { JsonSchema, parseSchema } from "json-schema-to-zod";
|
||||
|
||||
export type ClientOptions = ProtocolOptions & {
|
||||
/**
|
||||
|
|
@ -91,7 +91,6 @@ export class Client<
|
|||
private _serverVersion?: Implementation;
|
||||
private _capabilities: ClientCapabilities;
|
||||
private _instructions?: string;
|
||||
private _cachedTools: Map<string, Tool> = new Map();
|
||||
private _cachedToolOutputSchemas: Map<string, z.ZodTypeAny> = new Map();
|
||||
|
||||
/**
|
||||
|
|
@ -427,7 +426,7 @@ export class Client<
|
|||
);
|
||||
|
||||
// Check if the tool has an outputSchema
|
||||
const outputSchema = this._cachedToolOutputSchemas.get(params.name);
|
||||
const outputSchema = this.getToolOutputSchema(params.name);
|
||||
if (outputSchema) {
|
||||
// If tool has outputSchema, it MUST return structuredContent (unless it's an error)
|
||||
if (!result.structuredContent && !result.isError) {
|
||||
|
|
@ -472,6 +471,30 @@ export class Client<
|
|||
return result;
|
||||
}
|
||||
|
||||
private cacheToolOutputSchemas(tools: Tool[]) {
|
||||
this._cachedToolOutputSchemas.clear();
|
||||
|
||||
for (const tool of tools) {
|
||||
// If the tool has an outputSchema, create and cache the Zod schema
|
||||
if (tool.outputSchema) {
|
||||
try {
|
||||
const zodSchemaCode = parseSchema(tool.outputSchema as JsonSchema);
|
||||
// The library returns a string of Zod code, we need to evaluate it
|
||||
// Using Function constructor to safely evaluate the Zod schema
|
||||
const createSchema = new Function('z', `return ${zodSchemaCode}`);
|
||||
const zodSchema = createSchema(z);
|
||||
this._cachedToolOutputSchemas.set(tool.name, zodSchema);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to create Zod schema for tool ${tool.name}: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getToolOutputSchema(toolName: string): z.ZodTypeAny | undefined {
|
||||
return this._cachedToolOutputSchemas.get(toolName);
|
||||
}
|
||||
|
||||
async listTools(
|
||||
params?: ListToolsRequest["params"],
|
||||
options?: RequestOptions,
|
||||
|
|
@ -483,26 +506,7 @@ export class Client<
|
|||
);
|
||||
|
||||
// Cache the tools and their output schemas for future validation
|
||||
this._cachedTools.clear();
|
||||
this._cachedToolOutputSchemas.clear();
|
||||
|
||||
for (const tool of result.tools) {
|
||||
this._cachedTools.set(tool.name, tool);
|
||||
|
||||
// If the tool has an outputSchema, create and cache the Zod schema
|
||||
if (tool.outputSchema) {
|
||||
try {
|
||||
const zodSchemaCode = parseSchema(tool.outputSchema);
|
||||
// The library returns a string of Zod code, we need to evaluate it
|
||||
// Using Function constructor to safely evaluate the Zod schema
|
||||
const createSchema = new Function('z', `return ${zodSchemaCode}`);
|
||||
const zodSchema = createSchema(z);
|
||||
this._cachedToolOutputSchemas.set(tool.name, zodSchema);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to create Zod schema for tool ${tool.name}: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.cacheToolOutputSchemas(result.tools);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ server.tool(
|
|||
}
|
||||
},
|
||||
required: ["stats"]
|
||||
},
|
||||
},
|
||||
async ({ data }) => {
|
||||
const mean = data.reduce((a, b) => a + b, 0) / data.length;
|
||||
const sorted = [...data].sort((a, b) => a - b);
|
||||
|
|
|
|||
|
|
@ -1,222 +0,0 @@
|
|||
import { McpServer } from './mcp.js';
|
||||
import { Client } from '../client/index.js';
|
||||
import { InMemoryTransport } from '../inMemory.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
describe('McpServer outputSchema support', () => {
|
||||
let server: McpServer;
|
||||
let client: Client;
|
||||
let serverTransport: InMemoryTransport;
|
||||
let clientTransport: InMemoryTransport;
|
||||
|
||||
beforeEach(async () => {
|
||||
server = new McpServer({ name: 'test', version: '1.0' });
|
||||
client = new Client({ name: 'test-client', version: '1.0' });
|
||||
|
||||
[clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await client.close();
|
||||
await server.close();
|
||||
});
|
||||
|
||||
describe('tool registration with outputSchema', () => {
|
||||
it('should register a tool with outputSchema', async () => {
|
||||
const outputSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
result: { type: 'string' },
|
||||
count: { type: 'number' }
|
||||
},
|
||||
required: ['result', 'count']
|
||||
};
|
||||
|
||||
const tool = server.tool(
|
||||
'test-tool',
|
||||
'A test tool',
|
||||
{ input: z.string() },
|
||||
outputSchema,
|
||||
() => ({ structuredContent: { result: 'test', count: 42 } })
|
||||
);
|
||||
|
||||
expect(tool.outputSchema).toEqual(outputSchema);
|
||||
|
||||
// Connect after registering the tool
|
||||
await server.connect(serverTransport);
|
||||
await client.connect(clientTransport);
|
||||
});
|
||||
|
||||
it('should include outputSchema in ListToolsResult', async () => {
|
||||
const outputSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
result: { type: 'string' }
|
||||
}
|
||||
};
|
||||
|
||||
server.tool(
|
||||
'structured-tool',
|
||||
{ input: z.string() },
|
||||
outputSchema,
|
||||
() => ({ structuredContent: { result: 'test' } })
|
||||
);
|
||||
|
||||
// Now connect
|
||||
await server.connect(serverTransport);
|
||||
await client.connect(clientTransport);
|
||||
|
||||
const result = await client.listTools();
|
||||
expect(result.tools[0].outputSchema).toEqual(outputSchema);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tool execution with outputSchema', () => {
|
||||
it('should return structuredContent and auto-generate content for backward compatibility', async () => {
|
||||
const outputSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
result: { type: 'string' },
|
||||
count: { type: 'number' }
|
||||
}
|
||||
};
|
||||
|
||||
server.tool(
|
||||
'structured-tool',
|
||||
{ input: z.string() },
|
||||
outputSchema,
|
||||
() => ({
|
||||
structuredContent: { result: 'test', count: 42 }
|
||||
})
|
||||
);
|
||||
|
||||
// Now connect
|
||||
await server.connect(serverTransport);
|
||||
await client.connect(clientTransport);
|
||||
|
||||
// Need to call listTools first to cache the outputSchema
|
||||
await client.listTools();
|
||||
|
||||
const result = await client.callTool({ name: 'structured-tool', arguments: { input: 'test' } });
|
||||
|
||||
expect(result.structuredContent).toEqual({ result: 'test', count: 42 });
|
||||
expect(result.content).toEqual([{
|
||||
type: 'text',
|
||||
text: JSON.stringify({ result: 'test', count: 42 }, null, 2)
|
||||
}]);
|
||||
});
|
||||
|
||||
it('should preserve both content and structuredContent if tool provides both', async () => {
|
||||
const outputSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
result: { type: 'string' }
|
||||
}
|
||||
};
|
||||
|
||||
server.tool(
|
||||
'structured-tool',
|
||||
{ input: z.string() },
|
||||
outputSchema,
|
||||
() => ({
|
||||
structuredContent: { result: 'test' },
|
||||
content: [{ type: 'text', text: 'Custom text' }]
|
||||
})
|
||||
);
|
||||
|
||||
// Now connect
|
||||
await server.connect(serverTransport);
|
||||
await client.connect(clientTransport);
|
||||
|
||||
// Need to call listTools first to cache the outputSchema
|
||||
await client.listTools();
|
||||
|
||||
const result = await client.callTool({ name: 'structured-tool', arguments: { input: 'test' } });
|
||||
|
||||
expect(result.structuredContent).toEqual({ result: 'test' });
|
||||
expect(result.content).toEqual([{ type: 'text', text: 'Custom text' }]);
|
||||
});
|
||||
|
||||
it('should throw error if tool with outputSchema returns no structuredContent', async () => {
|
||||
const outputSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
result: { type: 'string' }
|
||||
}
|
||||
};
|
||||
|
||||
server.tool(
|
||||
'broken-tool',
|
||||
{ input: z.string() },
|
||||
outputSchema,
|
||||
() => ({
|
||||
content: [{ type: 'text', text: 'No structured content' }]
|
||||
})
|
||||
);
|
||||
|
||||
// Now connect
|
||||
await server.connect(serverTransport);
|
||||
await client.connect(clientTransport);
|
||||
|
||||
// Need to call listTools first to cache the outputSchema
|
||||
await client.listTools();
|
||||
|
||||
await expect(client.callTool({ name: 'broken-tool', arguments: { input: 'test' } }))
|
||||
.rejects.toThrow('has outputSchema but returned no structuredContent');
|
||||
});
|
||||
|
||||
it('should throw error if tool without outputSchema returns structuredContent', async () => {
|
||||
server.tool(
|
||||
'broken-tool',
|
||||
{ input: z.string() },
|
||||
() => ({
|
||||
structuredContent: { result: 'test' }
|
||||
})
|
||||
);
|
||||
|
||||
// Now connect
|
||||
await server.connect(serverTransport);
|
||||
await client.connect(clientTransport);
|
||||
|
||||
// Call listTools first (but in this case the tool has no outputSchema)
|
||||
await client.listTools();
|
||||
|
||||
await expect(client.callTool({ name: 'broken-tool', arguments: { input: 'test' } }))
|
||||
.rejects.toThrow('has no outputSchema but returned structuredContent');
|
||||
});
|
||||
|
||||
it('should handle error results properly for tools with outputSchema', async () => {
|
||||
const outputSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
result: { type: 'string' }
|
||||
}
|
||||
};
|
||||
|
||||
server.tool(
|
||||
'error-tool',
|
||||
{ input: z.string() },
|
||||
outputSchema,
|
||||
() => {
|
||||
throw new Error('Tool error');
|
||||
}
|
||||
);
|
||||
|
||||
// Now connect
|
||||
await server.connect(serverTransport);
|
||||
await client.connect(clientTransport);
|
||||
|
||||
// Need to call listTools first to cache the outputSchema
|
||||
await client.listTools();
|
||||
|
||||
const result = await client.callTool({ name: 'error-tool', arguments: { input: 'test' } });
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content).toEqual([{
|
||||
type: 'text',
|
||||
text: 'Tool error'
|
||||
}]);
|
||||
expect(result.structuredContent).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -13,12 +13,16 @@ import {
|
|||
CompleteResultSchema,
|
||||
LoggingMessageNotificationSchema,
|
||||
Notification,
|
||||
TextContent,
|
||||
} from "../types.js";
|
||||
import { ResourceTemplate } from "./mcp.js";
|
||||
import { completable } from "./completable.js";
|
||||
import { UriTemplate } from "../shared/uriTemplate.js";
|
||||
|
||||
describe("McpServer", () => {
|
||||
/***
|
||||
* Test: Basic Server Instance
|
||||
*/
|
||||
test("should expose underlying Server instance", () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -28,6 +32,9 @@ describe("McpServer", () => {
|
|||
expect(mcpServer.server).toBeDefined();
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Notification Sending via Server
|
||||
*/
|
||||
test("should allow sending notifications via Server", async () => {
|
||||
const mcpServer = new McpServer(
|
||||
{
|
||||
|
|
@ -75,6 +82,9 @@ describe("McpServer", () => {
|
|||
});
|
||||
|
||||
describe("ResourceTemplate", () => {
|
||||
/***
|
||||
* Test: ResourceTemplate Creation with String Pattern
|
||||
*/
|
||||
test("should create ResourceTemplate with string pattern", () => {
|
||||
const template = new ResourceTemplate("test://{category}/{id}", {
|
||||
list: undefined,
|
||||
|
|
@ -83,6 +93,9 @@ describe("ResourceTemplate", () => {
|
|||
expect(template.listCallback).toBeUndefined();
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: ResourceTemplate Creation with UriTemplate Instance
|
||||
*/
|
||||
test("should create ResourceTemplate with UriTemplate", () => {
|
||||
const uriTemplate = new UriTemplate("test://{category}/{id}");
|
||||
const template = new ResourceTemplate(uriTemplate, { list: undefined });
|
||||
|
|
@ -90,6 +103,9 @@ describe("ResourceTemplate", () => {
|
|||
expect(template.listCallback).toBeUndefined();
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: ResourceTemplate with List Callback
|
||||
*/
|
||||
test("should create ResourceTemplate with list callback", async () => {
|
||||
const list = jest.fn().mockResolvedValue({
|
||||
resources: [{ name: "Test", uri: "test://example" }],
|
||||
|
|
@ -111,6 +127,9 @@ describe("ResourceTemplate", () => {
|
|||
});
|
||||
|
||||
describe("tool()", () => {
|
||||
/***
|
||||
* Test: Zero-Argument Tool Registration
|
||||
*/
|
||||
test("should register zero-argument tool", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -178,6 +197,9 @@ describe("tool()", () => {
|
|||
])
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Updating Existing Tool
|
||||
*/
|
||||
test("should update existing tool", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -244,6 +266,9 @@ describe("tool()", () => {
|
|||
expect(notifications).toHaveLength(0)
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Updating Tool with Schema
|
||||
*/
|
||||
test("should update tool with schema", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -339,6 +364,9 @@ describe("tool()", () => {
|
|||
expect(notifications).toHaveLength(0)
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Tool List Changed Notifications
|
||||
*/
|
||||
test("should send tool list changed notifications when connected", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -404,6 +432,9 @@ describe("tool()", () => {
|
|||
])
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Tool Registration with Parameters
|
||||
*/
|
||||
test("should register tool with params", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -414,6 +445,7 @@ describe("tool()", () => {
|
|||
version: "1.0",
|
||||
});
|
||||
|
||||
// old api
|
||||
mcpServer.tool(
|
||||
"test",
|
||||
{
|
||||
|
|
@ -430,6 +462,17 @@ describe("tool()", () => {
|
|||
}),
|
||||
);
|
||||
|
||||
// new api
|
||||
mcpServer.tool(
|
||||
"test (new api)",
|
||||
{
|
||||
inputSchema: { name: z.string(), value: z.number() },
|
||||
callback: async ({ name, value }) => ({
|
||||
content: [{ type: "text", text: `${name}: ${value}` }],
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const [clientTransport, serverTransport] =
|
||||
InMemoryTransport.createLinkedPair();
|
||||
|
||||
|
|
@ -445,7 +488,7 @@ describe("tool()", () => {
|
|||
ListToolsResultSchema,
|
||||
);
|
||||
|
||||
expect(result.tools).toHaveLength(1);
|
||||
expect(result.tools).toHaveLength(2);
|
||||
expect(result.tools[0].name).toBe("test");
|
||||
expect(result.tools[0].inputSchema).toMatchObject({
|
||||
type: "object",
|
||||
|
|
@ -454,8 +497,13 @@ describe("tool()", () => {
|
|||
value: { type: "number" },
|
||||
},
|
||||
});
|
||||
expect(result.tools[1].name).toBe("test (new api)");
|
||||
expect(result.tools[1].inputSchema).toEqual(result.tools[0].inputSchema);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Tool Registration with Description
|
||||
*/
|
||||
test("should register tool with description", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -466,6 +514,7 @@ describe("tool()", () => {
|
|||
version: "1.0",
|
||||
});
|
||||
|
||||
// old api
|
||||
mcpServer.tool("test", "Test description", async () => ({
|
||||
content: [
|
||||
{
|
||||
|
|
@ -475,6 +524,20 @@ describe("tool()", () => {
|
|||
],
|
||||
}));
|
||||
|
||||
// new api
|
||||
mcpServer.tool("test (new api)", {
|
||||
description: "Test description",
|
||||
callback: async ({}) => ({
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Test response",
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
const [clientTransport, serverTransport] =
|
||||
InMemoryTransport.createLinkedPair();
|
||||
|
||||
|
|
@ -490,11 +553,16 @@ describe("tool()", () => {
|
|||
ListToolsResultSchema,
|
||||
);
|
||||
|
||||
expect(result.tools).toHaveLength(1);
|
||||
expect(result.tools).toHaveLength(2);
|
||||
expect(result.tools[0].name).toBe("test");
|
||||
expect(result.tools[0].description).toBe("Test description");
|
||||
expect(result.tools[1].name).toBe("test (new api)");
|
||||
expect(result.tools[1].description).toBe("Test description");
|
||||
});
|
||||
|
||||
|
||||
/***
|
||||
* Test: Tool Registration with Annotations
|
||||
*/
|
||||
test("should register tool with annotations", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -514,6 +582,18 @@ describe("tool()", () => {
|
|||
],
|
||||
}));
|
||||
|
||||
mcpServer.tool("test (new api)", {
|
||||
annotations: { title: "Test Tool", readOnlyHint: true },
|
||||
callback: async ({}) => ({
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Test response",
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const [clientTransport, serverTransport] =
|
||||
InMemoryTransport.createLinkedPair();
|
||||
|
||||
|
|
@ -529,11 +609,16 @@ describe("tool()", () => {
|
|||
ListToolsResultSchema,
|
||||
);
|
||||
|
||||
expect(result.tools).toHaveLength(1);
|
||||
expect(result.tools).toHaveLength(2);
|
||||
expect(result.tools[0].name).toBe("test");
|
||||
expect(result.tools[0].annotations).toEqual({ title: "Test Tool", readOnlyHint: true });
|
||||
expect(result.tools[1].name).toBe("test (new api)");
|
||||
expect(result.tools[1].annotations).toEqual({ title: "Test Tool", readOnlyHint: true });
|
||||
});
|
||||
|
||||
|
||||
/***
|
||||
* Test: Tool Registration with Parameters and Annotations
|
||||
*/
|
||||
test("should register tool with params and annotations", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -553,6 +638,14 @@ describe("tool()", () => {
|
|||
})
|
||||
);
|
||||
|
||||
mcpServer.tool("test (new api)", {
|
||||
inputSchema: { name: z.string() },
|
||||
annotations: { title: "Test Tool", readOnlyHint: true },
|
||||
callback: async ({ name }) => ({
|
||||
content: [{ type: "text", text: `Hello, ${name}!` }]
|
||||
})
|
||||
});
|
||||
|
||||
const [clientTransport, serverTransport] =
|
||||
InMemoryTransport.createLinkedPair();
|
||||
|
||||
|
|
@ -566,15 +659,21 @@ describe("tool()", () => {
|
|||
ListToolsResultSchema,
|
||||
);
|
||||
|
||||
expect(result.tools).toHaveLength(1);
|
||||
expect(result.tools).toHaveLength(2);
|
||||
expect(result.tools[0].name).toBe("test");
|
||||
expect(result.tools[0].inputSchema).toMatchObject({
|
||||
type: "object",
|
||||
properties: { name: { type: "string" } }
|
||||
});
|
||||
expect(result.tools[0].annotations).toEqual({ title: "Test Tool", readOnlyHint: true });
|
||||
expect(result.tools[1].name).toBe("test (new api)");
|
||||
expect(result.tools[1].inputSchema).toEqual(result.tools[0].inputSchema);
|
||||
expect(result.tools[1].annotations).toEqual(result.tools[0].annotations);
|
||||
});
|
||||
|
||||
|
||||
/***
|
||||
* Test: Tool Registration with Description, Parameters, and Annotations
|
||||
*/
|
||||
test("should register tool with description, params, and annotations", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -595,6 +694,15 @@ describe("tool()", () => {
|
|||
})
|
||||
);
|
||||
|
||||
mcpServer.tool("test (new api)", {
|
||||
description: "A tool with everything",
|
||||
inputSchema: { name: z.string() },
|
||||
annotations: { title: "Complete Test Tool", readOnlyHint: true, openWorldHint: false },
|
||||
callback: async ({ name }) => ({
|
||||
content: [{ type: "text", text: `Hello, ${name}!` }]
|
||||
})
|
||||
});
|
||||
|
||||
const [clientTransport, serverTransport] =
|
||||
InMemoryTransport.createLinkedPair();
|
||||
|
||||
|
|
@ -608,7 +716,7 @@ describe("tool()", () => {
|
|||
ListToolsResultSchema,
|
||||
);
|
||||
|
||||
expect(result.tools).toHaveLength(1);
|
||||
expect(result.tools).toHaveLength(2);
|
||||
expect(result.tools[0].name).toBe("test");
|
||||
expect(result.tools[0].description).toBe("A tool with everything");
|
||||
expect(result.tools[0].inputSchema).toMatchObject({
|
||||
|
|
@ -620,8 +728,15 @@ describe("tool()", () => {
|
|||
readOnlyHint: true,
|
||||
openWorldHint: false
|
||||
});
|
||||
expect(result.tools[1].name).toBe("test (new api)");
|
||||
expect(result.tools[1].description).toBe("A tool with everything");
|
||||
expect(result.tools[1].inputSchema).toEqual(result.tools[0].inputSchema);
|
||||
expect(result.tools[1].annotations).toEqual(result.tools[0].annotations);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Tool Registration with Description, Empty Parameters, and Annotations
|
||||
*/
|
||||
test("should register tool with description, empty params, and annotations", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -642,6 +757,15 @@ describe("tool()", () => {
|
|||
})
|
||||
);
|
||||
|
||||
mcpServer.tool("test (new api)", {
|
||||
description: "A tool with everything but empty params",
|
||||
inputSchema: {},
|
||||
annotations: { title: "Complete Test Tool with empty params", readOnlyHint: true, openWorldHint: false },
|
||||
callback: async ({}) => ({
|
||||
content: [{ type: "text", text: "Test response" }]
|
||||
})
|
||||
});
|
||||
|
||||
const [clientTransport, serverTransport] =
|
||||
InMemoryTransport.createLinkedPair();
|
||||
|
||||
|
|
@ -655,7 +779,7 @@ describe("tool()", () => {
|
|||
ListToolsResultSchema,
|
||||
);
|
||||
|
||||
expect(result.tools).toHaveLength(1);
|
||||
expect(result.tools).toHaveLength(2);
|
||||
expect(result.tools[0].name).toBe("test");
|
||||
expect(result.tools[0].description).toBe("A tool with everything but empty params");
|
||||
expect(result.tools[0].inputSchema).toMatchObject({
|
||||
|
|
@ -667,8 +791,15 @@ describe("tool()", () => {
|
|||
readOnlyHint: true,
|
||||
openWorldHint: false
|
||||
});
|
||||
expect(result.tools[1].name).toBe("test (new api)");
|
||||
expect(result.tools[1].description).toBe("A tool with everything but empty params");
|
||||
expect(result.tools[1].inputSchema).toEqual(result.tools[0].inputSchema);
|
||||
expect(result.tools[1].annotations).toEqual(result.tools[0].annotations);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Tool Argument Validation
|
||||
*/
|
||||
test("should validate tool args", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -703,6 +834,21 @@ describe("tool()", () => {
|
|||
}),
|
||||
);
|
||||
|
||||
mcpServer.tool("test (new api)", {
|
||||
inputSchema: {
|
||||
name: z.string(),
|
||||
value: z.number(),
|
||||
},
|
||||
callback: async ({ name, value }) => ({
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `${name}: ${value}`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const [clientTransport, serverTransport] =
|
||||
InMemoryTransport.createLinkedPair();
|
||||
|
||||
|
|
@ -726,8 +872,27 @@ describe("tool()", () => {
|
|||
CallToolResultSchema,
|
||||
),
|
||||
).rejects.toThrow(/Invalid arguments/);
|
||||
|
||||
await expect(
|
||||
client.request(
|
||||
{
|
||||
method: "tools/call",
|
||||
params: {
|
||||
name: "test (new api)",
|
||||
arguments: {
|
||||
name: "test",
|
||||
value: "not a number",
|
||||
},
|
||||
},
|
||||
},
|
||||
CallToolResultSchema,
|
||||
),
|
||||
).rejects.toThrow(/Invalid arguments/);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Preventing Duplicate Tool Registration
|
||||
*/
|
||||
test("should prevent duplicate tool registration", () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -755,6 +920,9 @@ describe("tool()", () => {
|
|||
}).toThrow(/already registered/);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Multiple Tool Registration
|
||||
*/
|
||||
test("should allow registering multiple tools", () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -768,6 +936,9 @@ describe("tool()", () => {
|
|||
mcpServer.tool("tool2", () => ({ content: [] }));
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Tool with Output Schema and Structured Content
|
||||
*/
|
||||
test("should support tool with outputSchema and structuredContent", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -787,35 +958,28 @@ describe("tool()", () => {
|
|||
);
|
||||
|
||||
// Register a tool with outputSchema
|
||||
const registeredTool = mcpServer.tool(
|
||||
mcpServer.tool(
|
||||
"test",
|
||||
"Test tool with structured output",
|
||||
{
|
||||
input: z.string(),
|
||||
description: "Test tool with structured output",
|
||||
inputSchema: {
|
||||
input: z.string(),
|
||||
},
|
||||
outputSchema: {
|
||||
processedInput: z.string(),
|
||||
resultType: z.string(),
|
||||
timestamp: z.string()
|
||||
},
|
||||
callback: async ({ input }) => ({
|
||||
structuredContent: {
|
||||
processedInput: input,
|
||||
resultType: "structured",
|
||||
timestamp: "2023-01-01T00:00:00Z"
|
||||
},
|
||||
}),
|
||||
},
|
||||
async ({ input }) => ({
|
||||
// When outputSchema is defined, return structuredContent instead of content
|
||||
structuredContent: {
|
||||
processedInput: input,
|
||||
resultType: "structured",
|
||||
timestamp: "2023-01-01T00:00:00Z"
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// Update the tool to add outputSchema
|
||||
registeredTool.update({
|
||||
outputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
processedInput: { type: "string" },
|
||||
resultType: { type: "string" },
|
||||
timestamp: { type: "string", format: "date-time" }
|
||||
},
|
||||
required: ["processedInput", "resultType", "timestamp"]
|
||||
}
|
||||
});
|
||||
|
||||
const [clientTransport, serverTransport] =
|
||||
InMemoryTransport.createLinkedPair();
|
||||
|
||||
|
|
@ -833,17 +997,17 @@ describe("tool()", () => {
|
|||
);
|
||||
|
||||
expect(listResult.tools).toHaveLength(1);
|
||||
expect(listResult.tools[0].outputSchema).toEqual({
|
||||
expect(listResult.tools[0].outputSchema).toMatchObject({
|
||||
type: "object",
|
||||
properties: {
|
||||
processedInput: { type: "string" },
|
||||
resultType: { type: "string" },
|
||||
timestamp: { type: "string", format: "date-time" }
|
||||
timestamp: { type: "string" }
|
||||
},
|
||||
required: ["processedInput", "resultType", "timestamp"]
|
||||
});
|
||||
|
||||
// Call the tool and verify it returns structuredContent
|
||||
// Call the tool and verify it returns valid structuredContent
|
||||
const result = await client.request(
|
||||
{
|
||||
method: "tools/call",
|
||||
|
|
@ -858,9 +1022,6 @@ describe("tool()", () => {
|
|||
);
|
||||
|
||||
expect(result.structuredContent).toBeDefined();
|
||||
// For backward compatibility, content is auto-generated from structuredContent
|
||||
expect(result.content).toBeDefined();
|
||||
|
||||
const structuredContent = result.structuredContent as {
|
||||
processedInput: string;
|
||||
resultType: string;
|
||||
|
|
@ -869,8 +1030,85 @@ describe("tool()", () => {
|
|||
expect(structuredContent.processedInput).toBe("hello");
|
||||
expect(structuredContent.resultType).toBe("structured");
|
||||
expect(structuredContent.timestamp).toBe("2023-01-01T00:00:00Z");
|
||||
|
||||
// For backward compatibility, content is auto-generated from structuredContent
|
||||
expect(result.content).toBeDefined();
|
||||
expect(result.content!).toHaveLength(1);
|
||||
expect(result.content![0]).toMatchObject({ type: "text" });
|
||||
const textContent = result.content![0] as TextContent;
|
||||
expect(JSON.parse(textContent.text)).toEqual(result.structuredContent);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Schema Validation Failure for Invalid Structured Content
|
||||
*/
|
||||
test("should fail schema validation when tool returns invalid structuredContent", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
version: "1.0",
|
||||
});
|
||||
|
||||
const client = new Client(
|
||||
{
|
||||
name: "test client",
|
||||
version: "1.0",
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Register a tool with outputSchema that returns invalid data
|
||||
mcpServer.tool(
|
||||
"test",
|
||||
{
|
||||
description: "Test tool with invalid structured output",
|
||||
inputSchema: {
|
||||
input: z.string(),
|
||||
},
|
||||
outputSchema: {
|
||||
processedInput: z.string(),
|
||||
resultType: z.string(),
|
||||
timestamp: z.string()
|
||||
},
|
||||
callback: async ({ input }) => ({
|
||||
structuredContent: {
|
||||
processedInput: input,
|
||||
resultType: "structured",
|
||||
// Missing required 'timestamp' field
|
||||
someExtraField: "unexpected" // Extra field not in schema
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const [clientTransport, serverTransport] =
|
||||
InMemoryTransport.createLinkedPair();
|
||||
|
||||
await Promise.all([
|
||||
client.connect(clientTransport),
|
||||
mcpServer.server.connect(serverTransport),
|
||||
]);
|
||||
|
||||
// First call listTools to cache the outputSchema in the client
|
||||
await client.listTools();
|
||||
|
||||
// Call the tool and expect it to throw a validation error
|
||||
await expect(
|
||||
client.callTool({
|
||||
name: "test",
|
||||
arguments: {
|
||||
input: "hello",
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow(/Structured content does not match the tool's output schema/);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Pass Session ID to Tool Callback
|
||||
*/
|
||||
test("should pass sessionId to tool callback via RequestHandlerExtra", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -924,6 +1162,9 @@ describe("tool()", () => {
|
|||
expect(receivedSessionId).toBe("test-session-123");
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Pass Request ID to Tool Callback
|
||||
*/
|
||||
test("should pass requestId to tool callback via RequestHandlerExtra", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -977,6 +1218,9 @@ describe("tool()", () => {
|
|||
expect(result.content && result.content[0].text).toContain("Received request ID:");
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Send Notification within Tool Call
|
||||
*/
|
||||
test("should provide sendNotification within tool call", async () => {
|
||||
const mcpServer = new McpServer(
|
||||
{
|
||||
|
|
@ -1034,6 +1278,9 @@ describe("tool()", () => {
|
|||
expect(receivedLogMessage).toBe(loggingMessage);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Client to Server Tool Call
|
||||
*/
|
||||
test("should allow client to call server tools", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1097,6 +1344,9 @@ describe("tool()", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Graceful Tool Error Handling
|
||||
*/
|
||||
test("should handle server tool errors gracefully", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1146,6 +1396,9 @@ describe("tool()", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: McpError for Invalid Tool Name
|
||||
*/
|
||||
test("should throw McpError for invalid tool name", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1196,6 +1449,9 @@ describe("tool()", () => {
|
|||
});
|
||||
|
||||
describe("resource()", () => {
|
||||
/***
|
||||
* Test: Resource Registration with URI and Read Callback
|
||||
*/
|
||||
test("should register resource with uri and readCallback", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1235,6 +1491,9 @@ describe("resource()", () => {
|
|||
expect(result.resources[0].uri).toBe("test://resource");
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Update Resource with URI
|
||||
*/
|
||||
test("should update resource with uri", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1297,6 +1556,9 @@ describe("resource()", () => {
|
|||
expect(notifications).toHaveLength(0);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Update Resource Template
|
||||
*/
|
||||
test("should update resource template", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1363,6 +1625,9 @@ describe("resource()", () => {
|
|||
expect(notifications).toHaveLength(0);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Resource List Changed Notification
|
||||
*/
|
||||
test("should send resource list changed notification when connected", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1416,6 +1681,9 @@ describe("resource()", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Remove Resource and Send Notification
|
||||
*/
|
||||
test("should remove resource and send notification when connected", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1477,6 +1745,9 @@ describe("resource()", () => {
|
|||
expect(result.resources[0].uri).toBe("test://resource2");
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Remove Resource Template and Send Notification
|
||||
*/
|
||||
test("should remove resource template and send notification when connected", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1541,6 +1812,9 @@ describe("resource()", () => {
|
|||
expect(result2.resourceTemplates).toHaveLength(0);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Resource Registration with Metadata
|
||||
*/
|
||||
test("should register resource with metadata", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1588,6 +1862,9 @@ describe("resource()", () => {
|
|||
expect(result.resources[0].mimeType).toBe("text/plain");
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Resource Template Registration
|
||||
*/
|
||||
test("should register resource template", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1633,6 +1910,9 @@ describe("resource()", () => {
|
|||
);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Resource Template with List Callback
|
||||
*/
|
||||
test("should register resource template with listCallback", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1691,6 +1971,9 @@ describe("resource()", () => {
|
|||
expect(result.resources[1].uri).toBe("test://resource/2");
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Template Variables to Read Callback
|
||||
*/
|
||||
test("should pass template variables to readCallback", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1737,6 +2020,9 @@ describe("resource()", () => {
|
|||
expect(result.contents[0].text).toBe("Category: books, ID: 123");
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Preventing Duplicate Resource Registration
|
||||
*/
|
||||
test("should prevent duplicate resource registration", () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1764,6 +2050,9 @@ describe("resource()", () => {
|
|||
}).toThrow(/already registered/);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Multiple Resource Registration
|
||||
*/
|
||||
test("should allow registering multiple resources", () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1791,6 +2080,9 @@ describe("resource()", () => {
|
|||
}));
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Preventing Duplicate Resource Template Registration
|
||||
*/
|
||||
test("should prevent duplicate resource template registration", () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1826,6 +2118,9 @@ describe("resource()", () => {
|
|||
}).toThrow(/already registered/);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Graceful Resource Read Error Handling
|
||||
*/
|
||||
test("should handle resource read errors gracefully", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1861,6 +2156,9 @@ describe("resource()", () => {
|
|||
).rejects.toThrow(/Resource read failed/);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: McpError for Invalid Resource URI
|
||||
*/
|
||||
test("should throw McpError for invalid resource URI", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1901,6 +2199,9 @@ describe("resource()", () => {
|
|||
).rejects.toThrow(/Resource test:\/\/nonexistent not found/);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Resource Template Parameter Completion
|
||||
*/
|
||||
test("should support completion of resource template parameters", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -1966,6 +2267,9 @@ describe("resource()", () => {
|
|||
expect(result.completion.total).toBe(3);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Filtered Resource Template Parameter Completion
|
||||
*/
|
||||
test("should support filtered completion of resource template parameters", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2034,6 +2338,9 @@ describe("resource()", () => {
|
|||
expect(result.completion.total).toBe(2);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Pass Request ID to Resource Callback
|
||||
*/
|
||||
test("should pass requestId to resource callback via RequestHandlerExtra", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2089,6 +2396,9 @@ describe("resource()", () => {
|
|||
});
|
||||
|
||||
describe("prompt()", () => {
|
||||
/***
|
||||
* Test: Zero-Argument Prompt Registration
|
||||
*/
|
||||
test("should register zero-argument prompt", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2130,6 +2440,9 @@ describe("prompt()", () => {
|
|||
expect(result.prompts[0].name).toBe("test");
|
||||
expect(result.prompts[0].arguments).toBeUndefined();
|
||||
});
|
||||
/***
|
||||
* Test: Updating Existing Prompt
|
||||
*/
|
||||
test("should update existing prompt", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2198,6 +2511,9 @@ describe("prompt()", () => {
|
|||
expect(notifications).toHaveLength(0);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Updating Prompt with Schema
|
||||
*/
|
||||
test("should update prompt with schema", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2291,6 +2607,9 @@ describe("prompt()", () => {
|
|||
expect(notifications).toHaveLength(0);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Prompt List Changed Notification
|
||||
*/
|
||||
test("should send prompt list changed notification when connected", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2350,6 +2669,9 @@ describe("prompt()", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Remove Prompt and Send Notification
|
||||
*/
|
||||
test("should remove prompt and send notification when connected", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2428,6 +2750,9 @@ describe("prompt()", () => {
|
|||
expect(result.prompts[0].name).toBe("prompt2");
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Prompt Registration with Arguments Schema
|
||||
*/
|
||||
test("should register prompt with args schema", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2480,6 +2805,9 @@ describe("prompt()", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Prompt Registration with Description
|
||||
*/
|
||||
test("should register prompt with description", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2522,6 +2850,9 @@ describe("prompt()", () => {
|
|||
expect(result.prompts[0].description).toBe("Test description");
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Prompt Argument Validation
|
||||
*/
|
||||
test("should validate prompt args", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2584,6 +2915,9 @@ describe("prompt()", () => {
|
|||
).rejects.toThrow(/Invalid arguments/);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Preventing Duplicate Prompt Registration
|
||||
*/
|
||||
test("should prevent duplicate prompt registration", () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2617,6 +2951,9 @@ describe("prompt()", () => {
|
|||
}).toThrow(/already registered/);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Multiple Prompt Registration
|
||||
*/
|
||||
test("should allow registering multiple prompts", () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2650,6 +2987,9 @@ describe("prompt()", () => {
|
|||
}));
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Prompt Registration with Arguments
|
||||
*/
|
||||
test("should allow registering prompts with arguments", () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2672,6 +3012,9 @@ describe("prompt()", () => {
|
|||
);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Resources and Prompts with Completion Handlers
|
||||
*/
|
||||
test("should allow registering both resources and prompts with completion handlers", () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2713,6 +3056,9 @@ describe("prompt()", () => {
|
|||
);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: McpError for Invalid Prompt Name
|
||||
*/
|
||||
test("should throw McpError for invalid prompt name", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2764,6 +3110,9 @@ describe("prompt()", () => {
|
|||
).rejects.toThrow(/Prompt nonexistent-prompt not found/);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Prompt Argument Completion
|
||||
*/
|
||||
test("should support completion of prompt arguments", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2829,6 +3178,9 @@ describe("prompt()", () => {
|
|||
expect(result.completion.total).toBe(3);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Filtered Prompt Argument Completion
|
||||
*/
|
||||
test("should support filtered completion of prompt arguments", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
@ -2896,6 +3248,9 @@ describe("prompt()", () => {
|
|||
expect(result.completion.total).toBe(1);
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Pass Request ID to Prompt Callback
|
||||
*/
|
||||
test("should pass requestId to prompt callback via RequestHandlerExtra", async () => {
|
||||
const mcpServer = new McpServer({
|
||||
name: "test server",
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ export class McpServer {
|
|||
if (this._toolHandlersInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.server.assertCanSetRequestHandler(
|
||||
ListToolsRequestSchema.shape.method.value,
|
||||
);
|
||||
|
|
@ -116,15 +116,17 @@ export class McpServer {
|
|||
description: tool.description,
|
||||
inputSchema: tool.inputSchema
|
||||
? (zodToJsonSchema(tool.inputSchema, {
|
||||
strictUnions: true,
|
||||
}) as Tool["inputSchema"])
|
||||
strictUnions: true,
|
||||
}) as Tool["inputSchema"])
|
||||
: EMPTY_OBJECT_JSON_SCHEMA,
|
||||
annotations: tool.annotations,
|
||||
};
|
||||
|
||||
// Only include outputSchema if it's defined
|
||||
if (tool.outputSchema) {
|
||||
toolDefinition.outputSchema = tool.outputSchema;
|
||||
toolDefinition.outputSchema = zodToJsonSchema(
|
||||
tool.outputSchema,
|
||||
{ strictUnions: true }
|
||||
) as Tool["outputSchema"];
|
||||
}
|
||||
|
||||
return toolDefinition;
|
||||
|
|
@ -447,7 +449,7 @@ export class McpServer {
|
|||
);
|
||||
|
||||
this.setCompletionRequestHandler();
|
||||
|
||||
|
||||
this._resourceHandlersInitialized = true;
|
||||
}
|
||||
|
||||
|
|
@ -530,7 +532,7 @@ export class McpServer {
|
|||
);
|
||||
|
||||
this.setCompletionRequestHandler();
|
||||
|
||||
|
||||
this._promptHandlersInitialized = true;
|
||||
}
|
||||
|
||||
|
|
@ -682,7 +684,7 @@ export class McpServer {
|
|||
paramsSchemaOrAnnotations: Args | ToolAnnotations,
|
||||
cb: ToolCallback<Args>,
|
||||
): RegisteredTool;
|
||||
|
||||
|
||||
/**
|
||||
* Registers a tool with both parameter schema and annotations.
|
||||
*/
|
||||
|
|
@ -692,7 +694,7 @@ export class McpServer {
|
|||
annotations: ToolAnnotations,
|
||||
cb: ToolCallback<Args>,
|
||||
): RegisteredTool;
|
||||
|
||||
|
||||
/**
|
||||
* Registers a tool with description, parameter schema, and annotations.
|
||||
*/
|
||||
|
|
@ -701,117 +703,92 @@ export class McpServer {
|
|||
description: string,
|
||||
paramsSchema: Args,
|
||||
annotations: ToolAnnotations,
|
||||
cb: ToolCallback<Args>,
|
||||
callback: ToolCallback<Args>,
|
||||
): RegisteredTool;
|
||||
|
||||
/**
|
||||
* Registers a tool with output schema.
|
||||
* Registers a tool from a config object.
|
||||
*/
|
||||
tool<Args extends ZodRawShape>(
|
||||
tool<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape>(
|
||||
name: string,
|
||||
paramsSchema: Args,
|
||||
outputSchema: Tool["outputSchema"],
|
||||
cb: ToolCallback<Args>,
|
||||
config: {
|
||||
description?: string;
|
||||
inputSchema?: InputArgs;
|
||||
outputSchema?: OutputArgs;
|
||||
annotations?: ToolAnnotations;
|
||||
callback: ToolCallback<InputArgs>
|
||||
}
|
||||
): RegisteredTool;
|
||||
|
||||
/**
|
||||
* Registers a tool with description and output schema.
|
||||
* tool() implementation. Parses arguments passed to overrides defined above.
|
||||
*/
|
||||
tool<Args extends ZodRawShape>(
|
||||
name: string,
|
||||
description: string,
|
||||
paramsSchema: Args,
|
||||
outputSchema: Tool["outputSchema"],
|
||||
cb: ToolCallback<Args>,
|
||||
): RegisteredTool;
|
||||
|
||||
/**
|
||||
* Registers a tool with parameter schema, output schema, and annotations.
|
||||
*/
|
||||
tool<Args extends ZodRawShape>(
|
||||
name: string,
|
||||
paramsSchema: Args,
|
||||
outputSchema: Tool["outputSchema"],
|
||||
annotations: ToolAnnotations,
|
||||
cb: ToolCallback<Args>,
|
||||
): RegisteredTool;
|
||||
|
||||
/**
|
||||
* Registers a tool with description, parameter schema, output schema, and annotations.
|
||||
*/
|
||||
tool<Args extends ZodRawShape>(
|
||||
name: string,
|
||||
description: string,
|
||||
paramsSchema: Args,
|
||||
outputSchema: Tool["outputSchema"],
|
||||
annotations: ToolAnnotations,
|
||||
cb: ToolCallback<Args>,
|
||||
): RegisteredTool;
|
||||
|
||||
tool(name: string, ...rest: unknown[]): RegisteredTool {
|
||||
if (this._registeredTools[name]) {
|
||||
throw new Error(`Tool ${name} is already registered`);
|
||||
}
|
||||
|
||||
let description: string | undefined;
|
||||
if (typeof rest[0] === "string") {
|
||||
description = rest.shift() as string;
|
||||
}
|
||||
|
||||
let paramsSchema: ZodRawShape | undefined;
|
||||
let outputSchema: Tool["outputSchema"] | undefined;
|
||||
let inputSchema: ZodRawShape | undefined;
|
||||
let outputSchema: ZodRawShape | undefined;
|
||||
let annotations: ToolAnnotations | undefined;
|
||||
|
||||
// Handle the different overload combinations
|
||||
if (rest.length > 1) {
|
||||
// We have at least two more args before the callback
|
||||
const firstArg = rest[0];
|
||||
|
||||
if (isZodRawShape(firstArg)) {
|
||||
// We have a params schema as the first arg
|
||||
paramsSchema = rest.shift() as ZodRawShape;
|
||||
|
||||
// Check if the next arg is potentially annotations or outputSchema
|
||||
if (rest.length > 1 && typeof rest[0] === "object" && rest[0] !== null) {
|
||||
const nextArg = rest[0];
|
||||
let callback: ToolCallback<ZodRawShape | undefined>;
|
||||
|
||||
// Check if it's a JSON Schema (outputSchema)
|
||||
if (typeof nextArg === "object" && "type" in nextArg) {
|
||||
outputSchema = rest.shift() as Tool["outputSchema"];
|
||||
if (rest.length == 1 && typeof rest[0] === "object" && rest[0] !== null && 'callback' in rest[0]) {
|
||||
// New style: tool(name, { description, inputSchema, outputSchema, annotations, callback })
|
||||
// Future additions to tool definition should be added to config object.
|
||||
|
||||
// Check if there's still an annotations object
|
||||
if (rest.length > 1 && typeof rest[0] === "object" && rest[0] !== null && !(isZodRawShape(rest[0]))) {
|
||||
annotations = rest.shift() as ToolAnnotations;
|
||||
}
|
||||
} else if (!(isZodRawShape(nextArg))) {
|
||||
// It's annotations
|
||||
annotations = rest.shift() as ToolAnnotations;
|
||||
}
|
||||
}
|
||||
} else if (typeof firstArg === "object" && firstArg !== null) {
|
||||
// Check if it's a JSON Schema (outputSchema)
|
||||
if ("type" in firstArg) {
|
||||
outputSchema = rest.shift() as Tool["outputSchema"];
|
||||
|
||||
// Check if there's still an annotations object
|
||||
const config = rest[0] as {
|
||||
description?: string;
|
||||
inputSchema?: ZodRawShape;
|
||||
outputSchema?: ZodRawShape;
|
||||
annotations?: ToolAnnotations;
|
||||
callback: ToolCallback<ZodRawShape>;
|
||||
};
|
||||
({ description, inputSchema, outputSchema, annotations, callback } = config) ;
|
||||
|
||||
} else {
|
||||
// Old style: tool properties are passed as separate arguments, with omissions allowed.
|
||||
// Support for this style is frozen as of protocol version 2025-03-26. Future additions
|
||||
// to tool definition should *NOT* be added.
|
||||
|
||||
if (typeof rest[0] === "string") {
|
||||
description = rest.shift() as string;
|
||||
}
|
||||
|
||||
// Handle the different overload combinations
|
||||
if (rest.length > 1) {
|
||||
// We have at least one more arg before the callback
|
||||
const firstArg = rest[0];
|
||||
|
||||
if (isZodRawShape(firstArg)) {
|
||||
// We have a params schema as the first arg
|
||||
inputSchema = rest.shift() as ZodRawShape;
|
||||
|
||||
// Check if the next arg is potentially annotations
|
||||
if (rest.length > 1 && typeof rest[0] === "object" && rest[0] !== null && !(isZodRawShape(rest[0]))) {
|
||||
// Case: tool(name, paramsSchema, annotations, cb)
|
||||
// Or: tool(name, description, paramsSchema, annotations, cb)
|
||||
annotations = rest.shift() as ToolAnnotations;
|
||||
}
|
||||
} else {
|
||||
// It's annotations
|
||||
} else if (typeof firstArg === "object" && firstArg !== null) {
|
||||
// Not a ZodRawShape, so must be annotations in this position
|
||||
// Case: tool(name, annotations, cb)
|
||||
// Or: tool(name, description, annotations, cb)
|
||||
annotations = rest.shift() as ToolAnnotations;
|
||||
}
|
||||
}
|
||||
callback = rest[0] as ToolCallback<ZodRawShape | undefined>;
|
||||
}
|
||||
|
||||
const cb = rest[0] as ToolCallback<ZodRawShape | undefined>;
|
||||
const registeredTool: RegisteredTool = {
|
||||
description,
|
||||
inputSchema:
|
||||
paramsSchema === undefined ? undefined : z.object(paramsSchema),
|
||||
outputSchema,
|
||||
inputSchema === undefined ? undefined : z.object(inputSchema),
|
||||
outputSchema:
|
||||
outputSchema === undefined ? undefined : z.object(outputSchema),
|
||||
annotations,
|
||||
callback: cb,
|
||||
callback: callback,
|
||||
enabled: true,
|
||||
disable: () => registeredTool.update({ enabled: false }),
|
||||
enable: () => registeredTool.update({ enabled: true }),
|
||||
|
|
@ -823,7 +800,6 @@ export class McpServer {
|
|||
}
|
||||
if (typeof updates.description !== "undefined") registeredTool.description = updates.description
|
||||
if (typeof updates.paramsSchema !== "undefined") registeredTool.inputSchema = z.object(updates.paramsSchema)
|
||||
if (typeof updates.outputSchema !== "undefined") registeredTool.outputSchema = updates.outputSchema
|
||||
if (typeof updates.callback !== "undefined") registeredTool.callback = updates.callback
|
||||
if (typeof updates.annotations !== "undefined") registeredTool.annotations = updates.annotations
|
||||
if (typeof updates.enabled !== "undefined") registeredTool.enabled = updates.enabled
|
||||
|
|
@ -1019,22 +995,31 @@ export class ResourceTemplate {
|
|||
*/
|
||||
export type ToolCallback<Args extends undefined | ZodRawShape = undefined> =
|
||||
Args extends ZodRawShape
|
||||
? (
|
||||
args: z.objectOutputType<Args, ZodTypeAny>,
|
||||
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
|
||||
) => CallToolResult | Promise<CallToolResult>
|
||||
: (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => CallToolResult | Promise<CallToolResult>;
|
||||
? (
|
||||
args: z.objectOutputType<Args, ZodTypeAny>,
|
||||
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
|
||||
) => CallToolResult | Promise<CallToolResult>
|
||||
: (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => CallToolResult | Promise<CallToolResult>;
|
||||
|
||||
export type RegisteredTool = {
|
||||
description?: string;
|
||||
inputSchema?: AnyZodObject;
|
||||
outputSchema?: Tool["outputSchema"];
|
||||
outputSchema?: AnyZodObject;
|
||||
annotations?: ToolAnnotations;
|
||||
callback: ToolCallback<undefined | ZodRawShape>;
|
||||
enabled: boolean;
|
||||
enable(): void;
|
||||
disable(): void;
|
||||
update<Args extends ZodRawShape>(updates: { name?: string | null, description?: string, paramsSchema?: Args, outputSchema?: Tool["outputSchema"], callback?: ToolCallback<Args>, annotations?: ToolAnnotations, enabled?: boolean }): void
|
||||
update<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape>(
|
||||
updates: {
|
||||
name?: string | null,
|
||||
description?: string,
|
||||
paramsSchema?: InputArgs,
|
||||
outputSchema?: OutputArgs,
|
||||
annotations?: ToolAnnotations,
|
||||
callback?: ToolCallback<InputArgs>,
|
||||
enabled?: boolean
|
||||
}): void
|
||||
remove(): void
|
||||
};
|
||||
|
||||
|
|
@ -1107,23 +1092,23 @@ export type RegisteredResourceTemplate = {
|
|||
enabled: boolean;
|
||||
enable(): void;
|
||||
disable(): void;
|
||||
update(updates: { name?: string | null, template?: ResourceTemplate, metadata?: ResourceMetadata, callback?: ReadResourceTemplateCallback, enabled?: boolean }): void
|
||||
update(updates: { name?: string | null, template?: ResourceTemplate, metadata?: ResourceMetadata, callback?: ReadResourceTemplateCallback, enabled?: boolean }): void
|
||||
remove(): void
|
||||
};
|
||||
|
||||
type PromptArgsRawShape = {
|
||||
[k: string]:
|
||||
| ZodType<string, ZodTypeDef, string>
|
||||
| ZodOptional<ZodType<string, ZodTypeDef, string>>;
|
||||
| ZodType<string, ZodTypeDef, string>
|
||||
| ZodOptional<ZodType<string, ZodTypeDef, string>>;
|
||||
};
|
||||
|
||||
export type PromptCallback<
|
||||
Args extends undefined | PromptArgsRawShape = undefined,
|
||||
> = Args extends PromptArgsRawShape
|
||||
? (
|
||||
args: z.objectOutputType<Args, ZodTypeAny>,
|
||||
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
|
||||
) => GetPromptResult | Promise<GetPromptResult>
|
||||
args: z.objectOutputType<Args, ZodTypeAny>,
|
||||
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
|
||||
) => GetPromptResult | Promise<GetPromptResult>
|
||||
: (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => GetPromptResult | Promise<GetPromptResult>;
|
||||
|
||||
export type RegisteredPrompt = {
|
||||
|
|
|
|||
|
|
@ -838,7 +838,14 @@ export const ToolSchema = z
|
|||
* If set, a CallToolResult for this Tool MUST contain a structuredContent field whose contents validate against this schema.
|
||||
* If not set, a CallToolResult for this Tool MUST NOT contain a structuredContent field and MUST contain a content field.
|
||||
*/
|
||||
outputSchema: z.optional(z.object({}).passthrough()),
|
||||
outputSchema: z.optional(
|
||||
z.object({
|
||||
type: z.literal("object"),
|
||||
properties: z.optional(z.object({}).passthrough()),
|
||||
required: z.optional(z.array(z.string())),
|
||||
})
|
||||
.passthrough()
|
||||
),
|
||||
/**
|
||||
* Optional additional tool information.
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue