From 0e86a6d139e0a3da564101cb751f53d3c3fb00fe Mon Sep 17 00:00:00 2001 From: ihrpr Date: Mon, 16 Jun 2025 14:36:35 +0100 Subject: [PATCH] improve readme --- README.md | 68 ++++++++---------------------------------- src/server/mcp.test.ts | 51 ++++++++++++++++++++++++------- 2 files changed, 53 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 63acfb06..08b711c2 100644 --- a/README.md +++ b/README.md @@ -287,23 +287,30 @@ server.registerPrompt( title: "Team Greeting", description: "Generate a greeting for team members", argsSchema: { - // Completable arguments can use context for intelligent suggestions + department: completable(z.string(), (value) => { + // Department suggestions + return ["engineering", "sales", "marketing", "support"].filter(d => d.startsWith(value)); + }), name: completable(z.string(), (value, context) => { - if (context?.arguments?.["department"] === "engineering") { + // Name suggestions based on selected department + const department = context?.arguments?.["department"]; + if (department === "engineering") { return ["Alice", "Bob", "Charlie"].filter(n => n.startsWith(value)); - } else if (context?.arguments?.["department"] === "sales") { + } else if (department === "sales") { return ["David", "Eve", "Frank"].filter(n => n.startsWith(value)); + } else if (department === "marketing") { + return ["Grace", "Henry", "Iris"].filter(n => n.startsWith(value)); } return ["Guest"].filter(n => n.startsWith(value)); }) } }, - ({ name }) => ({ + ({ department, name }) => ({ messages: [{ role: "assistant", content: { type: "text", - text: `Hello ${name}, welcome to the team!` + text: `Hello ${name}, welcome to the ${department} team!` } }] }) @@ -695,57 +702,6 @@ server.registerTool( ## Advanced Usage -### Context-Aware Completions - -MCP supports intelligent completions that can use previously resolved values as context. This is useful for creating dependent parameter completions where later parameters depend on earlier ones: - -```typescript -import { completable } from "@modelcontextprotocol/sdk/server/completable.js"; - -// For resource templates -server.registerResource( - "database-query", - new ResourceTemplate("db://{database}/{table}/{query}", { - list: undefined, - complete: { - // Table completions depend on the selected database - table: (value, context) => { - const database = context?.arguments?.["database"]; - if (database === "users_db") { - return ["profiles", "sessions", "preferences"].filter(t => t.startsWith(value)); - } else if (database === "products_db") { - return ["items", "categories", "inventory"].filter(t => t.startsWith(value)); - } - return []; - } - } - }), - metadata, - handler -); - -// For prompts with completable arguments -server.registerPrompt( - "api-request", - { - argsSchema: { - endpoint: z.string(), - // Method completions can be context-aware - method: completable(z.string(), (value, context) => { - const endpoint = context?.arguments?.["endpoint"]; - if (endpoint?.includes("/readonly/")) { - return ["GET"].filter(m => m.startsWith(value.toUpperCase())); - } - return ["GET", "POST", "PUT", "DELETE"].filter(m => m.startsWith(value.toUpperCase())); - }) - } - }, - handler -); -``` - -The context object contains an `arguments` field with previously resolved parameter values, allowing you to provide more intelligent and contextual completions. - ### Dynamic Servers If you want to offer an initial set of tools/prompts/resources, but later add additional ones based on user action or external state change, you can add/update/remove them _after_ the Server is connected. This will automatically emit the corresponding `listChanged` notifications: diff --git a/src/server/mcp.test.ts b/src/server/mcp.test.ts index 685987bf..15be3d98 100644 --- a/src/server/mcp.test.ts +++ b/src/server/mcp.test.ts @@ -3875,23 +3875,29 @@ describe("Tool title precedence", () => { title: "Team Greeting", description: "Generate a greeting for team members", argsSchema: { + department: completable(z.string(), (value) => { + return ["engineering", "sales", "marketing", "support"].filter(d => d.startsWith(value)); + }), name: completable(z.string(), (value, context) => { - if (context?.arguments?.["category"] === "developers") { + const department = context?.arguments?.["department"]; + if (department === "engineering") { return ["Alice", "Bob", "Charlie"].filter(n => n.startsWith(value)); - } else if (context?.arguments?.["category"] === "managers") { + } else if (department === "sales") { return ["David", "Eve", "Frank"].filter(n => n.startsWith(value)); + } else if (department === "marketing") { + return ["Grace", "Henry", "Iris"].filter(n => n.startsWith(value)); } return ["Guest"].filter(n => n.startsWith(value)); }), } }, - async ({ name }) => ({ + async ({ department, name }) => ({ messages: [ { role: "assistant", content: { type: "text", - text: `Hello ${name}`, + text: `Hello ${name}, welcome to the ${department} team!`, }, }, ], @@ -3906,7 +3912,7 @@ describe("Tool title precedence", () => { mcpServer.server.connect(serverTransport), ]); - // Test with developers category + // Test with engineering department const result1 = await client.request( { method: "completion/complete", @@ -3921,7 +3927,7 @@ describe("Tool title precedence", () => { }, context: { arguments: { - category: "developers", + department: "engineering", }, }, }, @@ -3931,7 +3937,7 @@ describe("Tool title precedence", () => { expect(result1.completion.values).toEqual(["Alice"]); - // Test with managers category + // Test with sales department const result2 = await client.request( { method: "completion/complete", @@ -3946,7 +3952,7 @@ describe("Tool title precedence", () => { }, context: { arguments: { - category: "managers", + department: "sales", }, }, }, @@ -3956,8 +3962,33 @@ describe("Tool title precedence", () => { expect(result2.completion.values).toEqual(["David"]); - // Test with no resolved context + // Test with marketing department const result3 = await client.request( + { + method: "completion/complete", + params: { + ref: { + type: "ref/prompt", + name: "test-prompt", + }, + argument: { + name: "name", + value: "G", + }, + context: { + arguments: { + department: "marketing", + }, + }, + }, + }, + CompleteResultSchema, + ); + + expect(result3.completion.values).toEqual(["Grace"]); + + // Test with no resolved context + const result4 = await client.request( { method: "completion/complete", params: { @@ -3974,6 +4005,6 @@ describe("Tool title precedence", () => { CompleteResultSchema, ); - expect(result3.completion.values).toEqual(["Guest"]); + expect(result4.completion.values).toEqual(["Guest"]); }); });