Skip to content

Commit ddc1a0c

Browse files
authored
Merge pull request #631 from modelcontextprotocol/ihrpr/title
Add title to tools, resources, prompts
2 parents 86dbcfe + f1a07a9 commit ddc1a0c

File tree

8 files changed

+880
-234
lines changed

8 files changed

+880
-234
lines changed

README.md

Lines changed: 112 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,30 @@ import { z } from "zod";
5454

5555
// Create an MCP server
5656
const server = new McpServer({
57-
name: "Demo",
57+
name: "demo-server",
5858
version: "1.0.0"
5959
});
6060

6161
// Add an addition tool
62-
server.tool("add",
63-
{ a: z.number(), b: z.number() },
62+
server.registerTool("add",
63+
{
64+
title: "Addition Tool",
65+
description: "Add two numbers",
66+
inputSchema: { a: z.number(), b: z.number() }
67+
},
6468
async ({ a, b }) => ({
6569
content: [{ type: "text", text: String(a + b) }]
6670
})
6771
);
6872

6973
// Add a dynamic greeting resource
70-
server.resource(
74+
server.registerResource(
7175
"greeting",
7276
new ResourceTemplate("greeting://{name}", { list: undefined }),
77+
{
78+
title: "Greeting Resource", // Display name for UI
79+
description: "Dynamic greeting generator"
80+
},
7381
async (uri, { name }) => ({
7482
contents: [{
7583
uri: uri.href,
@@ -100,7 +108,7 @@ The McpServer is your core interface to the MCP protocol. It handles connection
100108

101109
```typescript
102110
const server = new McpServer({
103-
name: "My App",
111+
name: "my-app",
104112
version: "1.0.0"
105113
});
106114
```
@@ -111,9 +119,14 @@ Resources are how you expose data to LLMs. They're similar to GET endpoints in a
111119

112120
```typescript
113121
// Static resource
114-
server.resource(
122+
server.registerResource(
115123
"config",
116124
"config://app",
125+
{
126+
title: "Application Config",
127+
description: "Application configuration data",
128+
mimeType: "text/plain"
129+
},
117130
async (uri) => ({
118131
contents: [{
119132
uri: uri.href,
@@ -123,9 +136,13 @@ server.resource(
123136
);
124137

125138
// Dynamic resource with parameters
126-
server.resource(
139+
server.registerResource(
127140
"user-profile",
128141
new ResourceTemplate("users://{userId}/profile", { list: undefined }),
142+
{
143+
title: "User Profile",
144+
description: "User profile information"
145+
},
129146
async (uri, { userId }) => ({
130147
contents: [{
131148
uri: uri.href,
@@ -141,11 +158,15 @@ Tools let LLMs take actions through your server. Unlike resources, tools are exp
141158

142159
```typescript
143160
// Simple tool with parameters
144-
server.tool(
161+
server.registerTool(
145162
"calculate-bmi",
146163
{
147-
weightKg: z.number(),
148-
heightM: z.number()
164+
title: "BMI Calculator",
165+
description: "Calculate Body Mass Index",
166+
inputSchema: {
167+
weightKg: z.number(),
168+
heightM: z.number()
169+
}
149170
},
150171
async ({ weightKg, heightM }) => ({
151172
content: [{
@@ -156,9 +177,13 @@ server.tool(
156177
);
157178

158179
// Async tool with external API call
159-
server.tool(
180+
server.registerTool(
160181
"fetch-weather",
161-
{ city: z.string() },
182+
{
183+
title: "Weather Fetcher",
184+
description: "Get weather data for a city",
185+
inputSchema: { city: z.string() }
186+
},
162187
async ({ city }) => {
163188
const response = await fetch(`https://api.weather.com/${city}`);
164189
const data = await response.text();
@@ -174,9 +199,13 @@ server.tool(
174199
Prompts are reusable templates that help LLMs interact with your server effectively:
175200

176201
```typescript
177-
server.prompt(
202+
server.registerPrompt(
178203
"review-code",
179-
{ code: z.string() },
204+
{
205+
title: "Code Review",
206+
description: "Review code for best practices and potential issues",
207+
arguments: { code: z.string() }
208+
},
180209
({ code }) => ({
181210
messages: [{
182211
role: "user",
@@ -189,6 +218,44 @@ server.prompt(
189218
);
190219
```
191220

221+
### Display Names and Metadata
222+
223+
All resources, tools, and prompts support an optional `title` field for better UI presentation. The `title` is used as a display name, while `name` remains the unique identifier.
224+
225+
**Note:** The `register*` methods (`registerTool`, `registerPrompt`, `registerResource`) are the recommended approach for new code. The older methods (`tool`, `prompt`, `resource`) remain available for backwards compatibility.
226+
227+
#### Title Precedence for Tools
228+
229+
For tools specifically, there are two ways to specify a title:
230+
- `title` field in the tool configuration
231+
- `annotations.title` field (when using the older `tool()` method with annotations)
232+
233+
The precedence order is: `title``annotations.title``name`
234+
235+
```typescript
236+
// Using registerTool (recommended)
237+
server.registerTool("my_tool", {
238+
title: "My Tool", // This title takes precedence
239+
annotations: {
240+
title: "Annotation Title" // This is ignored if title is set
241+
}
242+
}, handler);
243+
244+
// Using tool with annotations (older API)
245+
server.tool("my_tool", "description", {
246+
title: "Annotation Title" // This is used as title
247+
}, handler);
248+
```
249+
250+
When building clients, use the provided utility to get the appropriate display name:
251+
252+
```typescript
253+
import { getDisplayName } from "@modelcontextprotocol/sdk/shared/metadataUtils.js";
254+
255+
// Automatically handles the precedence: title → annotations.title → name
256+
const displayName = getDisplayName(tool);
257+
```
258+
192259
## Running Your Server
193260

194261
MCP servers in TypeScript need to be connected to a transport to communicate with clients. How you start the server depends on the choice of transport:
@@ -401,13 +468,17 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mc
401468
import { z } from "zod";
402469

403470
const server = new McpServer({
404-
name: "Echo",
471+
name: "echo-server",
405472
version: "1.0.0"
406473
});
407474

408-
server.resource(
475+
server.registerResource(
409476
"echo",
410477
new ResourceTemplate("echo://{message}", { list: undefined }),
478+
{
479+
title: "Echo Resource",
480+
description: "Echoes back messages as resources"
481+
},
411482
async (uri, { message }) => ({
412483
contents: [{
413484
uri: uri.href,
@@ -416,17 +487,25 @@ server.resource(
416487
})
417488
);
418489

419-
server.tool(
490+
server.registerTool(
420491
"echo",
421-
{ message: z.string() },
492+
{
493+
title: "Echo Tool",
494+
description: "Echoes back the provided message",
495+
inputSchema: { message: z.string() }
496+
},
422497
async ({ message }) => ({
423498
content: [{ type: "text", text: `Tool echo: ${message}` }]
424499
})
425500
);
426501

427-
server.prompt(
502+
server.registerPrompt(
428503
"echo",
429-
{ message: z.string() },
504+
{
505+
title: "Echo Prompt",
506+
description: "Creates a prompt to process a message",
507+
argsSchema: { message: z.string() }
508+
},
430509
({ message }) => ({
431510
messages: [{
432511
role: "user",
@@ -450,7 +529,7 @@ import { promisify } from "util";
450529
import { z } from "zod";
451530

452531
const server = new McpServer({
453-
name: "SQLite Explorer",
532+
name: "sqlite-explorer",
454533
version: "1.0.0"
455534
});
456535

@@ -463,9 +542,14 @@ const getDb = () => {
463542
};
464543
};
465544

466-
server.resource(
545+
server.registerResource(
467546
"schema",
468547
"schema://main",
548+
{
549+
title: "Database Schema",
550+
description: "SQLite database schema",
551+
mimeType: "text/plain"
552+
},
469553
async (uri) => {
470554
const db = getDb();
471555
try {
@@ -484,9 +568,13 @@ server.resource(
484568
}
485569
);
486570

487-
server.tool(
571+
server.registerTool(
488572
"query",
489-
{ sql: z.string() },
573+
{
574+
title: "SQL Query",
575+
description: "Execute SQL queries on the database",
576+
inputSchema: { sql: z.string() }
577+
},
490578
async ({ sql }) => {
491579
const db = getDb();
492580
try {

src/examples/client/simpleStreamableHttp.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
LoggingMessageNotificationSchema,
1616
ResourceListChangedNotificationSchema,
1717
} from '../../types.js';
18+
import { getDisplayName } from '../../shared/metadataUtils.js';
1819

1920
// Create readline interface for user input
2021
const readline = createInterface({
@@ -317,7 +318,7 @@ async function listTools(): Promise<void> {
317318
console.log(' No tools available');
318319
} else {
319320
for (const tool of toolsResult.tools) {
320-
console.log(` - ${tool.name}: ${tool.description}`);
321+
console.log(` - id: ${tool.name}, name: ${getDisplayName(tool)}, description: ${tool.description}`);
321322
}
322323
}
323324
} catch (error) {
@@ -380,7 +381,7 @@ async function runNotificationsToolWithResumability(interval: number, count: num
380381
try {
381382
console.log(`Starting notification stream with resumability: interval=${interval}ms, count=${count || 'unlimited'}`);
382383
console.log(`Using resumption token: ${notificationsToolLastEventId || 'none'}`);
383-
384+
384385
const request: CallToolRequest = {
385386
method: 'tools/call',
386387
params: {
@@ -393,7 +394,7 @@ async function runNotificationsToolWithResumability(interval: number, count: num
393394
notificationsToolLastEventId = event;
394395
console.log(`Updated resumption token: ${event}`);
395396
};
396-
397+
397398
const result = await client.request(request, CallToolResultSchema, {
398399
resumptionToken: notificationsToolLastEventId,
399400
onresumptiontoken: onLastEventIdUpdate
@@ -429,7 +430,7 @@ async function listPrompts(): Promise<void> {
429430
console.log(' No prompts available');
430431
} else {
431432
for (const prompt of promptsResult.prompts) {
432-
console.log(` - ${prompt.name}: ${prompt.description}`);
433+
console.log(` - id: ${prompt.name}, name: ${getDisplayName(prompt)}, description: ${prompt.description}`);
433434
}
434435
}
435436
} catch (error) {
@@ -480,7 +481,7 @@ async function listResources(): Promise<void> {
480481
console.log(' No resources available');
481482
} else {
482483
for (const resource of resourcesResult.resources) {
483-
console.log(` - ${resource.name}: ${resource.uri}`);
484+
console.log(` - id: ${resource.name}, name: ${getDisplayName(resource)}, description: ${resource.uri}`);
484485
}
485486
}
486487
} catch (error) {

src/examples/server/simpleStreamableHttp.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@ const useOAuth = process.argv.includes('--oauth');
1717
const getServer = () => {
1818
const server = new McpServer({
1919
name: 'simple-streamable-http-server',
20-
version: '1.0.0',
20+
version: '1.0.0'
2121
}, { capabilities: { logging: {} } });
2222

2323
// Register a simple tool that returns a greeting
24-
server.tool(
24+
server.registerTool(
2525
'greet',
26-
'A simple greeting tool',
2726
{
28-
name: z.string().describe('Name to greet'),
27+
title: 'Greeting Tool', // Display name for UI
28+
description: 'A simple greeting tool',
29+
inputSchema: {
30+
name: z.string().describe('Name to greet'),
31+
},
2932
},
3033
async ({ name }): Promise<CallToolResult> => {
3134
return {
@@ -84,12 +87,15 @@ const getServer = () => {
8487
}
8588
);
8689

87-
// Register a simple prompt
88-
server.prompt(
90+
// Register a simple prompt with title
91+
server.registerPrompt(
8992
'greeting-template',
90-
'A simple greeting prompt template',
9193
{
92-
name: z.string().describe('Name to include in greeting'),
94+
title: 'Greeting Template', // Display name for UI
95+
description: 'A simple greeting prompt template',
96+
argsSchema: {
97+
name: z.string().describe('Name to include in greeting'),
98+
},
9399
},
94100
async ({ name }): Promise<GetPromptResult> => {
95101
return {
@@ -148,10 +154,14 @@ const getServer = () => {
148154
);
149155

150156
// Create a simple resource at a fixed URI
151-
server.resource(
157+
server.registerResource(
152158
'greeting-resource',
153159
'https://example.com/greetings/default',
154-
{ mimeType: 'text/plain' },
160+
{
161+
title: 'Default Greeting', // Display name for UI
162+
description: 'A simple greeting resource',
163+
mimeType: 'text/plain'
164+
},
155165
async (): Promise<ReadResourceResult> => {
156166
return {
157167
contents: [

0 commit comments

Comments
 (0)