Skip to content

Include context into completions #634

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 16, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add to readme
  • Loading branch information
ihrpr committed Jun 16, 2025
commit 42c3967d8b04f2a95550102967f65763719fceb5
111 changes: 110 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,33 @@ server.registerResource(
}]
})
);

// Resource with context-aware completion
server.registerResource(
"repository",
new ResourceTemplate("github://repos/{owner}/{repo}", {
list: undefined,
complete: {
// Provide intelligent completions based on previously resolved parameters
repo: (value, context) => {
if (context?.arguments?.["owner"] === "org1") {
return ["project1", "project2", "project3"].filter(r => r.startsWith(value));
}
return ["default-repo"].filter(r => r.startsWith(value));
}
}
}),
{
title: "GitHub Repository",
description: "Repository information"
},
async (uri, { owner, repo }) => ({
contents: [{
uri: uri.href,
text: `Repository: ${owner}/${repo}`
}]
})
);
```

### Tools
Expand Down Expand Up @@ -233,12 +260,14 @@ Tools can return `ResourceLink` objects to reference resources without embedding
Prompts are reusable templates that help LLMs interact with your server effectively:

```typescript
import { completable } from "@modelcontextprotocol/sdk/server/completable.js";

server.registerPrompt(
"review-code",
{
title: "Code Review",
description: "Review code for best practices and potential issues",
arguments: { code: z.string() }
argsSchema: { code: z.string() }
},
({ code }) => ({
messages: [{
Expand All @@ -250,6 +279,35 @@ server.registerPrompt(
}]
})
);

// Prompt with context-aware completion
server.registerPrompt(
"team-greeting",
{
title: "Team Greeting",
description: "Generate a greeting for team members",
argsSchema: {
// Completable arguments can use context for intelligent suggestions
name: completable(z.string(), (value, context) => {
if (context?.arguments?.["department"] === "engineering") {
return ["Alice", "Bob", "Charlie"].filter(n => n.startsWith(value));
} else if (context?.arguments?.["department"] === "sales") {
return ["David", "Eve", "Frank"].filter(n => n.startsWith(value));
}
return ["Guest"].filter(n => n.startsWith(value));
})
}
},
({ name }) => ({
messages: [{
role: "assistant",
content: {
type: "text",
text: `Hello ${name}, welcome to the team!`
}
}]
})
);
```

### Display Names and Metadata
Expand Down Expand Up @@ -637,6 +695,57 @@ 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:
Expand Down
28 changes: 18 additions & 10 deletions src/server/mcp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3747,7 +3747,7 @@ describe("Tool title precedence", () => {
version: "1.0",
});

mcpServer.resource(
mcpServer.registerResource(
"test",
new ResourceTemplate("github://repos/{owner}/{repo}", {
list: undefined,
Expand All @@ -3762,6 +3762,10 @@ describe("Tool title precedence", () => {
},
},
}),
{
title: "GitHub Repository",
description: "Repository information"
},
async () => ({
contents: [
{
Expand Down Expand Up @@ -3865,17 +3869,21 @@ describe("Tool title precedence", () => {
version: "1.0",
});

mcpServer.prompt(
mcpServer.registerPrompt(
"test-prompt",
{
name: completable(z.string(), (value, context) => {
if (context?.arguments?.["category"] === "developers") {
return ["Alice", "Bob", "Charlie"].filter(n => n.startsWith(value));
} else if (context?.arguments?.["category"] === "managers") {
return ["David", "Eve", "Frank"].filter(n => n.startsWith(value));
}
return ["Guest"].filter(n => n.startsWith(value));
}),
title: "Team Greeting",
description: "Generate a greeting for team members",
argsSchema: {
name: completable(z.string(), (value, context) => {
if (context?.arguments?.["category"] === "developers") {
return ["Alice", "Bob", "Charlie"].filter(n => n.startsWith(value));
} else if (context?.arguments?.["category"] === "managers") {
return ["David", "Eve", "Frank"].filter(n => n.startsWith(value));
}
return ["Guest"].filter(n => n.startsWith(value));
}),
}
},
async ({ name }) => ({
messages: [
Expand Down