add title

This commit is contained in:
ihrpr 2025-06-15 20:42:25 +01:00
commit 4d03b243e3
No known key found for this signature in database
7 changed files with 599 additions and 171 deletions

138
README.md
View file

@ -54,26 +54,35 @@ import { z } from "zod";
// Create an MCP server
const server = new McpServer({
name: "Demo",
version: "1.0.0"
name: "demo-server",
version: "1.0.0",
title: "Demo Server" // Optional display name
});
// Add an addition tool
server.tool("add",
{ a: z.number(), b: z.number() },
server.registerTool("add",
{
title: "Addition Tool",
description: "Add two numbers",
inputSchema: { a: z.number(), b: z.number() }
},
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }]
})
);
// Add a dynamic greeting resource
server.resource(
server.registerResource(
"greeting",
new ResourceTemplate("greeting://{name}", { list: undefined }),
async (uri, { name }) => ({
{
title: "Greeting Resource", // Display name for UI
description: "Dynamic greeting generator"
},
async (uri, params) => ({
contents: [{
uri: uri.href,
text: `Hello, ${name}!`
text: `Hello, ${params.name}!`
}]
})
);
@ -100,8 +109,9 @@ The McpServer is your core interface to the MCP protocol. It handles connection
```typescript
const server = new McpServer({
name: "My App",
version: "1.0.0"
name: "my-app", // Unique identifier for your server
version: "1.0.0", // Server version
title: "My Application" // Optional display name for UI
});
```
@ -111,9 +121,14 @@ Resources are how you expose data to LLMs. They're similar to GET endpoints in a
```typescript
// Static resource
server.resource(
server.registerResource(
"config",
"config://app",
{
title: "Application Config",
description: "Application configuration data",
mimeType: "text/plain"
},
async (uri) => ({
contents: [{
uri: uri.href,
@ -123,13 +138,17 @@ server.resource(
);
// Dynamic resource with parameters
server.resource(
server.registerResource(
"user-profile",
new ResourceTemplate("users://{userId}/profile", { list: undefined }),
async (uri, { userId }) => ({
{
title: "User Profile",
description: "User profile information"
},
async (uri, params) => ({
contents: [{
uri: uri.href,
text: `Profile data for user ${userId}`
text: `Profile data for user ${params.userId}`
}]
})
);
@ -141,11 +160,15 @@ Tools let LLMs take actions through your server. Unlike resources, tools are exp
```typescript
// Simple tool with parameters
server.tool(
server.registerTool(
"calculate-bmi",
{
weightKg: z.number(),
heightM: z.number()
title: "BMI Calculator",
description: "Calculate Body Mass Index",
inputSchema: {
weightKg: z.number(),
heightM: z.number()
}
},
async ({ weightKg, heightM }) => ({
content: [{
@ -156,9 +179,13 @@ server.tool(
);
// Async tool with external API call
server.tool(
server.registerTool(
"fetch-weather",
{ city: z.string() },
{
title: "Weather Fetcher",
description: "Get weather data for a city",
inputSchema: { city: z.string() }
},
async ({ city }) => {
const response = await fetch(`https://api.weather.com/${city}`);
const data = await response.text();
@ -174,9 +201,13 @@ server.tool(
Prompts are reusable templates that help LLMs interact with your server effectively:
```typescript
server.prompt(
server.registerPrompt(
"review-code",
{ code: z.string() },
{
title: "Code Review",
description: "Review code for best practices and potential issues",
arguments: { code: z.string() }
},
({ code }) => ({
messages: [{
role: "user",
@ -189,6 +220,22 @@ server.prompt(
);
```
### Display Names and Metadata
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.
**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.
When building clients, use the provided utility to get the appropriate display name:
```typescript
import { getDisplayName } from "@modelcontextprotocol/sdk/shared/metadataUtils.js";
// Falls back to 'name' if 'title' is not provided
const displayName = getDisplayName(tool); // Returns title if available, otherwise name
```
## Running Your Server
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,32 +448,45 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mc
import { z } from "zod";
const server = new McpServer({
name: "Echo",
version: "1.0.0"
name: "echo-server",
version: "1.0.0",
title: "Echo Server"
});
server.resource(
server.registerResource(
"echo",
new ResourceTemplate("echo://{message}", { list: undefined }),
async (uri, { message }) => ({
{
title: "Echo Resource",
description: "Echoes back messages as resources"
},
async (uri, params) => ({
contents: [{
uri: uri.href,
text: `Resource echo: ${message}`
text: `Resource echo: ${params.message}`
}]
})
);
server.tool(
server.registerTool(
"echo",
{ message: z.string() },
{
title: "Echo Tool",
description: "Echoes back the provided message",
inputSchema: { message: z.string() }
},
async ({ message }) => ({
content: [{ type: "text", text: `Tool echo: ${message}` }]
})
);
server.prompt(
server.registerPrompt(
"echo",
{ message: z.string() },
{
title: "Echo Prompt",
description: "Creates a prompt to process a message",
arguments: { message: z.string() }
},
({ message }) => ({
messages: [{
role: "user",
@ -450,8 +510,9 @@ import { promisify } from "util";
import { z } from "zod";
const server = new McpServer({
name: "SQLite Explorer",
version: "1.0.0"
name: "sqlite-explorer",
version: "1.0.0",
title: "SQLite Explorer"
});
// Helper to create DB connection
@ -463,9 +524,14 @@ const getDb = () => {
};
};
server.resource(
server.registerResource(
"schema",
"schema://main",
{
title: "Database Schema",
description: "SQLite database schema",
mimeType: "text/plain"
},
async (uri) => {
const db = getDb();
try {
@ -484,9 +550,13 @@ server.resource(
}
);
server.tool(
server.registerTool(
"query",
{ sql: z.string() },
{
title: "SQL Query",
description: "Execute SQL queries on the database",
inputSchema: { sql: z.string() }
},
async ({ sql }) => {
const db = getDb();
try {

View file

@ -15,6 +15,7 @@ import {
LoggingMessageNotificationSchema,
ResourceListChangedNotificationSchema,
} from '../../types.js';
import { getDisplayName } from '../../shared/metadataUtils.js';
// Create readline interface for user input
const readline = createInterface({
@ -317,7 +318,7 @@ async function listTools(): Promise<void> {
console.log(' No tools available');
} else {
for (const tool of toolsResult.tools) {
console.log(` - ${tool.name}: ${tool.description}`);
console.log(` - ${getDisplayName(tool)}: ${tool.description}`);
}
}
} catch (error) {
@ -429,7 +430,7 @@ async function listPrompts(): Promise<void> {
console.log(' No prompts available');
} else {
for (const prompt of promptsResult.prompts) {
console.log(` - ${prompt.name}: ${prompt.description}`);
console.log(` - ${getDisplayName(prompt)}: ${prompt.description}`);
}
}
} catch (error) {
@ -480,7 +481,7 @@ async function listResources(): Promise<void> {
console.log(' No resources available');
} else {
for (const resource of resourcesResult.resources) {
console.log(` - ${resource.name}: ${resource.uri}`);
console.log(` - ${getDisplayName(resource)}: ${resource.uri}`);
}
}
} catch (error) {

View file

@ -18,14 +18,18 @@ const getServer = () => {
const server = new McpServer({
name: 'simple-streamable-http-server',
version: '1.0.0',
title: 'Simple Streamable HTTP Server', // Display name for UI
}, { capabilities: { logging: {} } });
// Register a simple tool that returns a greeting
server.tool(
server.registerTool(
'greet',
'A simple greeting tool',
{
name: z.string().describe('Name to greet'),
title: 'Greeting Tool', // Display name for UI
description: 'A simple greeting tool',
inputSchema: {
name: z.string().describe('Name to greet'),
},
},
async ({ name }): Promise<CallToolResult> => {
return {
@ -84,12 +88,15 @@ const getServer = () => {
}
);
// Register a simple prompt
server.prompt(
// Register a simple prompt with title
server.registerPrompt(
'greeting-template',
'A simple greeting prompt template',
{
name: z.string().describe('Name to include in greeting'),
title: 'Greeting Template', // Display name for UI
description: 'A simple greeting prompt template',
arguments: {
name: z.string().describe('Name to include in greeting'),
},
},
async ({ name }): Promise<GetPromptResult> => {
return {
@ -148,10 +155,14 @@ const getServer = () => {
);
// Create a simple resource at a fixed URI
server.resource(
server.registerResource(
'greeting-resource',
'https://example.com/greetings/default',
{ mimeType: 'text/plain' },
{
title: 'Default Greeting', // Display name for UI
description: 'A simple greeting resource',
mimeType: 'text/plain'
},
async (): Promise<ReadResourceResult> => {
return {
contents: [

View file

@ -122,6 +122,10 @@ export class McpServer {
annotations: tool.annotations,
};
if (tool.title !== undefined) {
toolDefinition.title = tool.title;
}
if (tool.outputSchema) {
toolDefinition.outputSchema = zodToJsonSchema(
tool.outputSchema,
@ -467,13 +471,19 @@ export class McpServer {
([, prompt]) => prompt.enabled,
).map(
([name, prompt]): Prompt => {
return {
const promptDefinition: Prompt = {
name,
description: prompt.description,
arguments: prompt.argsSchema
? promptArgumentsFromSchema(prompt.argsSchema)
: undefined,
};
if (prompt.title !== undefined) {
promptDefinition.title = prompt.title;
}
return promptDefinition;
},
),
}),
@ -634,6 +644,83 @@ export class McpServer {
}
}
/**
* Registers a resource with a config object and callback.
* For static resources, use a URI string. For dynamic resources, use a ResourceTemplate.
*/
registerResource(
name: string,
uriOrTemplate: string | ResourceTemplate,
config: ResourceMetadata & {
title?: string;
description?: string;
mimeType?: string;
},
readCallback: ReadResourceCallback | ReadResourceTemplateCallback
): RegisteredResource | RegisteredResourceTemplate {
if (typeof uriOrTemplate === "string") {
if (this._registeredResources[uriOrTemplate]) {
throw new Error(`Resource ${uriOrTemplate} is already registered`);
}
const registeredResource: RegisteredResource = {
name,
metadata: config,
readCallback: readCallback as ReadResourceCallback,
enabled: true,
disable: () => registeredResource.update({ enabled: false }),
enable: () => registeredResource.update({ enabled: true }),
remove: () => registeredResource.update({ uri: null }),
update: (updates) => {
if (typeof updates.uri !== "undefined" && updates.uri !== uriOrTemplate) {
delete this._registeredResources[uriOrTemplate]
if (updates.uri) this._registeredResources[updates.uri] = registeredResource
}
if (typeof updates.name !== "undefined") registeredResource.name = updates.name
if (typeof updates.metadata !== "undefined") registeredResource.metadata = updates.metadata
if (typeof updates.callback !== "undefined") registeredResource.readCallback = updates.callback
if (typeof updates.enabled !== "undefined") registeredResource.enabled = updates.enabled
this.sendResourceListChanged()
},
};
this._registeredResources[uriOrTemplate] = registeredResource;
this.setResourceRequestHandlers();
this.sendResourceListChanged();
return registeredResource;
} else {
if (this._registeredResourceTemplates[name]) {
throw new Error(`Resource template ${name} is already registered`);
}
const registeredResourceTemplate: RegisteredResourceTemplate = {
resourceTemplate: uriOrTemplate,
metadata: config,
readCallback: readCallback as ReadResourceTemplateCallback,
enabled: true,
disable: () => registeredResourceTemplate.update({ enabled: false }),
enable: () => registeredResourceTemplate.update({ enabled: true }),
remove: () => registeredResourceTemplate.update({ name: null }),
update: (updates) => {
if (typeof updates.name !== "undefined" && updates.name !== name) {
delete this._registeredResourceTemplates[name]
if (updates.name) this._registeredResourceTemplates[updates.name] = registeredResourceTemplate
}
if (typeof updates.template !== "undefined") registeredResourceTemplate.resourceTemplate = updates.template
if (typeof updates.metadata !== "undefined") registeredResourceTemplate.metadata = updates.metadata
if (typeof updates.callback !== "undefined") registeredResourceTemplate.readCallback = updates.callback
if (typeof updates.enabled !== "undefined") registeredResourceTemplate.enabled = updates.enabled
this.sendResourceListChanged()
},
};
this._registeredResourceTemplates[name] = registeredResourceTemplate;
this.setResourceRequestHandlers();
this.sendResourceListChanged();
return registeredResourceTemplate;
}
}
private _createRegisteredTool(
name: string,
description: string | undefined,
@ -659,6 +746,7 @@ export class McpServer {
delete this._registeredTools[name]
if (updates.name) this._registeredTools[updates.name] = registeredTool
}
if (typeof updates.title !== "undefined") registeredTool.title = updates.title
if (typeof updates.description !== "undefined") registeredTool.description = updates.description
if (typeof updates.paramsSchema !== "undefined") registeredTool.inputSchema = z.object(updates.paramsSchema)
if (typeof updates.callback !== "undefined") registeredTool.callback = updates.callback
@ -789,6 +877,7 @@ export class McpServer {
registerTool<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape>(
name: string,
config: {
title?: string;
description?: string;
inputSchema?: InputArgs;
outputSchema?: OutputArgs;
@ -800,16 +889,23 @@ export class McpServer {
throw new Error(`Tool ${name} is already registered`);
}
const { description, inputSchema, outputSchema, annotations } = config;
const { title, description, inputSchema, outputSchema, annotations } = config;
return this._createRegisteredTool(
const registeredTool = this._createRegisteredTool(
name,
description,
inputSchema,
outputSchema,
annotations,
cb as ToolCallback<ZodRawShape | undefined>
)
);
// Set title if provided
if (title !== undefined) {
registeredTool.title = title;
}
return registeredTool;
}
/**
@ -870,6 +966,7 @@ export class McpServer {
delete this._registeredPrompts[name]
if (updates.name) this._registeredPrompts[updates.name] = registeredPrompt
}
if (typeof updates.title !== "undefined") registeredPrompt.title = updates.title
if (typeof updates.description !== "undefined") registeredPrompt.description = updates.description
if (typeof updates.argsSchema !== "undefined") registeredPrompt.argsSchema = z.object(updates.argsSchema)
if (typeof updates.callback !== "undefined") registeredPrompt.callback = updates.callback
@ -885,6 +982,54 @@ export class McpServer {
return registeredPrompt
}
/**
* Registers a prompt with a config object and callback.
*/
registerPrompt<Args extends PromptArgsRawShape>(
name: string,
config: {
title?: string;
description?: string;
arguments?: Args;
},
cb: PromptCallback<Args>
): RegisteredPrompt {
if (this._registeredPrompts[name]) {
throw new Error(`Prompt ${name} is already registered`);
}
const { title, description, arguments: argsSchema } = config;
const registeredPrompt: RegisteredPrompt = {
title,
description,
argsSchema: argsSchema === undefined ? undefined : z.object(argsSchema),
callback: cb as PromptCallback<PromptArgsRawShape | undefined>,
enabled: true,
disable: () => registeredPrompt.update({ enabled: false }),
enable: () => registeredPrompt.update({ enabled: true }),
remove: () => registeredPrompt.update({ name: null }),
update: (updates) => {
if (typeof updates.name !== "undefined" && updates.name !== name) {
delete this._registeredPrompts[name]
if (updates.name) this._registeredPrompts[updates.name] = registeredPrompt
}
if (typeof updates.title !== "undefined") registeredPrompt.title = updates.title
if (typeof updates.description !== "undefined") registeredPrompt.description = updates.description
if (typeof updates.argsSchema !== "undefined") registeredPrompt.argsSchema = z.object(updates.argsSchema)
if (typeof updates.callback !== "undefined") registeredPrompt.callback = updates.callback
if (typeof updates.enabled !== "undefined") registeredPrompt.enabled = updates.enabled
this.sendPromptListChanged()
},
};
this._registeredPrompts[name] = registeredPrompt;
this.setPromptRequestHandlers();
this.sendPromptListChanged()
return registeredPrompt;
}
/**
* Checks if the server is connected to a transport.
* @returns True if the server is connected
@ -1000,6 +1145,7 @@ export type ToolCallback<Args extends undefined | ZodRawShape = undefined> =
: (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => CallToolResult | Promise<CallToolResult>;
export type RegisteredTool = {
title?: string;
description?: string;
inputSchema?: AnyZodObject;
outputSchema?: AnyZodObject;
@ -1011,6 +1157,7 @@ export type RegisteredTool = {
update<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape>(
updates: {
name?: string | null,
title?: string,
description?: string,
paramsSchema?: InputArgs,
outputSchema?: OutputArgs,
@ -1110,13 +1257,14 @@ export type PromptCallback<
: (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => GetPromptResult | Promise<GetPromptResult>;
export type RegisteredPrompt = {
title?: string;
description?: string;
argsSchema?: ZodObject<PromptArgsRawShape>;
callback: PromptCallback<undefined | PromptArgsRawShape>;
enabled: boolean;
enable(): void;
disable(): void;
update<Args extends PromptArgsRawShape>(updates: { name?: string | null, description?: string, argsSchema?: Args, callback?: PromptCallback<Args>, enabled?: boolean }): void
update<Args extends PromptArgsRawShape>(updates: { name?: string | null, title?: string, description?: string, argsSchema?: Args, callback?: PromptCallback<Args>, enabled?: boolean }): void
remove(): void
};

194
src/server/title.test.ts Normal file
View file

@ -0,0 +1,194 @@
import { Server } from "./index.js";
import { Client } from "../client/index.js";
import { InMemoryTransport } from "../inMemory.js";
import { z } from "zod";
import { McpServer } from "./mcp.js";
describe("Title field backwards compatibility", () => {
it("should work with tools that have title", async () => {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const server = new McpServer(
{ name: "test-server", version: "1.0.0" },
{ capabilities: {} }
);
// Register tool with title
server.registerTool(
"test-tool",
{
title: "Test Tool Display Name",
description: "A test tool",
inputSchema: {
value: z.string()
}
},
async () => ({ content: [{ type: "text", text: "result" }] })
);
const client = new Client({ name: "test-client", version: "1.0.0" });
await server.server.connect(serverTransport);
await client.connect(clientTransport);
const tools = await client.listTools();
expect(tools.tools).toHaveLength(1);
expect(tools.tools[0].name).toBe("test-tool");
expect(tools.tools[0].title).toBe("Test Tool Display Name");
expect(tools.tools[0].description).toBe("A test tool");
});
it("should work with tools without title", async () => {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const server = new McpServer(
{ name: "test-server", version: "1.0.0" },
{ capabilities: {} }
);
// Register tool without title
server.tool(
"test-tool",
"A test tool",
{ value: z.string() },
async () => ({ content: [{ type: "text", text: "result" }] })
);
const client = new Client({ name: "test-client", version: "1.0.0" });
await server.server.connect(serverTransport);
await client.connect(clientTransport);
const tools = await client.listTools();
expect(tools.tools).toHaveLength(1);
expect(tools.tools[0].name).toBe("test-tool");
expect(tools.tools[0].title).toBeUndefined();
expect(tools.tools[0].description).toBe("A test tool");
});
it("should work with prompts that have title using update", async () => {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const server = new McpServer(
{ name: "test-server", version: "1.0.0" },
{ capabilities: {} }
);
// Register prompt with title by updating after creation
const prompt = server.prompt(
"test-prompt",
"A test prompt",
async () => ({ messages: [{ role: "user", content: { type: "text", text: "test" } }] })
);
prompt.update({ title: "Test Prompt Display Name" });
const client = new Client({ name: "test-client", version: "1.0.0" });
await server.server.connect(serverTransport);
await client.connect(clientTransport);
const prompts = await client.listPrompts();
expect(prompts.prompts).toHaveLength(1);
expect(prompts.prompts[0].name).toBe("test-prompt");
expect(prompts.prompts[0].title).toBe("Test Prompt Display Name");
expect(prompts.prompts[0].description).toBe("A test prompt");
});
it("should work with prompts using registerPrompt", async () => {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const server = new McpServer(
{ name: "test-server", version: "1.0.0" },
{ capabilities: {} }
);
// Register prompt with title using registerPrompt
server.registerPrompt(
"test-prompt",
{
title: "Test Prompt Display Name",
description: "A test prompt",
arguments: { input: z.string() }
},
async ({ input }) => ({
messages: [{
role: "user",
content: { type: "text", text: `test: ${input}` }
}]
})
);
const client = new Client({ name: "test-client", version: "1.0.0" });
await server.server.connect(serverTransport);
await client.connect(clientTransport);
const prompts = await client.listPrompts();
expect(prompts.prompts).toHaveLength(1);
expect(prompts.prompts[0].name).toBe("test-prompt");
expect(prompts.prompts[0].title).toBe("Test Prompt Display Name");
expect(prompts.prompts[0].description).toBe("A test prompt");
expect(prompts.prompts[0].arguments).toHaveLength(1);
});
it("should work with resources using registerResource", async () => {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const server = new McpServer(
{ name: "test-server", version: "1.0.0" },
{ capabilities: {} }
);
// Register resource with title using registerResource
server.registerResource(
"test-resource",
"https://example.com/test",
{
title: "Test Resource Display Name",
description: "A test resource",
mimeType: "text/plain"
},
async () => ({
contents: [{
uri: "https://example.com/test",
text: "test content"
}]
})
);
const client = new Client({ name: "test-client", version: "1.0.0" });
await server.server.connect(serverTransport);
await client.connect(clientTransport);
const resources = await client.listResources();
expect(resources.resources).toHaveLength(1);
expect(resources.resources[0].name).toBe("test-resource");
expect(resources.resources[0].title).toBe("Test Resource Display Name");
expect(resources.resources[0].description).toBe("A test resource");
expect(resources.resources[0].mimeType).toBe("text/plain");
});
it("should support serverInfo with title", async () => {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const server = new Server(
{
name: "test-server",
version: "1.0.0",
title: "Test Server Display Name"
},
{ capabilities: {} }
);
const client = new Client({ name: "test-client", version: "1.0.0" });
await server.connect(serverTransport);
await client.connect(clientTransport);
const serverInfo = client.getServerVersion();
expect(serverInfo?.name).toBe("test-server");
expect(serverInfo?.version).toBe("1.0.0");
expect(serverInfo?.title).toBe("Test Server Display Name");
});
});

View file

@ -0,0 +1,21 @@
import { BaseMetadata } from "../types.js";
/**
* Utilities for working with BaseMetadata objects.
*/
/**
* Gets the display name for an object with BaseMetadata.
* Returns the title if available, otherwise falls back to name.
* This implements the spec requirement: "if no title is provided, name should be used for display purposes"
*/
export function getDisplayName(metadata: BaseMetadata): string {
return metadata.title ?? metadata.name;
}
/**
* Checks if an object has a custom title different from its name.
*/
export function hasCustomTitle(metadata: BaseMetadata): boolean {
return metadata.title !== undefined && metadata.title !== metadata.name;
}

View file

@ -195,16 +195,26 @@ export const CancelledNotificationSchema = NotificationSchema.extend({
}),
});
/* Base Metadata */
/**
* Base metadata interface for common properties across resources, tools, prompts, and implementations.
*/
export const BaseMetadataSchema = z
.object({
/** Intended for programmatic or logical use, but used as a display name in past specs or fallback */
name: z.string(),
/** Intended for UI and end-user contexts — optimized to be human-readable */
title: z.optional(z.string()),
})
.passthrough();
/* Initialization */
/**
* Describes the name and version of an MCP implementation.
*/
export const ImplementationSchema = z
.object({
name: z.string(),
version: z.string(),
})
.passthrough();
export const ImplementationSchema = BaseMetadataSchema.extend({
version: z.string(),
});
/**
* Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.
@ -438,74 +448,56 @@ export const BlobResourceContentsSchema = ResourceContentsSchema.extend({
/**
* A known resource that the server is capable of reading.
*/
export const ResourceSchema = z
.object({
/**
* The URI of this resource.
*/
uri: z.string(),
export const ResourceSchema = BaseMetadataSchema.extend({
/**
* The URI of this resource.
*/
uri: z.string(),
/**
* A human-readable name for this resource.
*
* This can be used by clients to populate UI elements.
*/
name: z.string(),
/**
* A description of what this resource represents.
*
* This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.
*/
description: z.optional(z.string()),
/**
* A description of what this resource represents.
*
* This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.
*/
description: z.optional(z.string()),
/**
* The MIME type of this resource, if known.
*/
mimeType: z.optional(z.string()),
/**
* The MIME type of this resource, if known.
*/
mimeType: z.optional(z.string()),
/**
* Reserved by MCP for protocol-level metadata; implementations must not make assumptions about its contents.
*/
_meta: z.optional(z.object({}).passthrough()),
})
.passthrough();
/**
* Reserved by MCP for protocol-level metadata; implementations must not make assumptions about its contents.
*/
_meta: z.optional(z.object({}).passthrough()),
});
/**
* A template description for resources available on the server.
*/
export const ResourceTemplateSchema = z
.object({
/**
* A URI template (according to RFC 6570) that can be used to construct resource URIs.
*/
uriTemplate: z.string(),
export const ResourceTemplateSchema = BaseMetadataSchema.extend({
/**
* A URI template (according to RFC 6570) that can be used to construct resource URIs.
*/
uriTemplate: z.string(),
/**
* A human-readable name for the type of resource this template refers to.
*
* This can be used by clients to populate UI elements.
*/
name: z.string(),
/**
* A description of what this template is for.
*
* This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.
*/
description: z.optional(z.string()),
/**
* A description of what this template is for.
*
* This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.
*/
description: z.optional(z.string()),
/**
* The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type.
*/
mimeType: z.optional(z.string()),
/**
* The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type.
*/
mimeType: z.optional(z.string()),
/**
* Reserved by MCP for protocol-level metadata; implementations must not make assumptions about its contents.
*/
_meta: z.optional(z.object({}).passthrough()),
})
.passthrough();
/**
* Reserved by MCP for protocol-level metadata; implementations must not make assumptions about its contents.
*/
_meta: z.optional(z.object({}).passthrough()),
});
/**
* Sent from the client to request a list of resources the server has.
@ -629,22 +621,16 @@ export const PromptArgumentSchema = z
/**
* A prompt or prompt template that the server offers.
*/
export const PromptSchema = z
.object({
/**
* The name of the prompt or prompt template.
*/
name: z.string(),
/**
* An optional description of what this prompt provides
*/
description: z.optional(z.string()),
/**
* A list of arguments to use for templating the prompt.
*/
arguments: z.optional(z.array(PromptArgumentSchema)),
})
.passthrough();
export const PromptSchema = BaseMetadataSchema.extend({
/**
* An optional description of what this prompt provides
*/
description: z.optional(z.string()),
/**
* A list of arguments to use for templating the prompt.
*/
arguments: z.optional(z.array(PromptArgumentSchema)),
});
/**
* Sent from the client to request a list of prompts and prompt templates the server has.
@ -842,49 +828,43 @@ export const ToolAnnotationsSchema = z
/**
* Definition for a tool the client can call.
*/
export const ToolSchema = z
.object({
/**
* The name of the tool.
*/
name: z.string(),
/**
* A human-readable description of the tool.
*/
description: z.optional(z.string()),
/**
* A JSON Schema object defining the expected parameters for the tool.
*/
inputSchema: z
.object({
type: z.literal("object"),
properties: z.optional(z.object({}).passthrough()),
required: z.optional(z.array(z.string())),
})
.passthrough(),
/**
* An optional JSON Schema object defining the structure of the tool's output returned in
* the structuredContent field of a CallToolResult.
*/
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.
*/
annotations: z.optional(ToolAnnotationsSchema),
export const ToolSchema = BaseMetadataSchema.extend({
/**
* A human-readable description of the tool.
*/
description: z.optional(z.string()),
/**
* A JSON Schema object defining the expected parameters for the tool.
*/
inputSchema: z
.object({
type: z.literal("object"),
properties: z.optional(z.object({}).passthrough()),
required: z.optional(z.array(z.string())),
})
.passthrough(),
/**
* An optional JSON Schema object defining the structure of the tool's output returned in
* the structuredContent field of a CallToolResult.
*/
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.
*/
annotations: z.optional(ToolAnnotationsSchema),
/**
* Reserved by MCP for protocol-level metadata; implementations must not make assumptions about its contents.
*/
_meta: z.optional(z.object({}).passthrough()),
})
.passthrough();
/**
* Reserved by MCP for protocol-level metadata; implementations must not make assumptions about its contents.
*/
_meta: z.optional(z.object({}).passthrough()),
});
/**
* Sent from the client to request a list of tools the server has.
@ -1347,6 +1327,9 @@ export type EmptyResult = Infer<typeof EmptyResultSchema>;
/* Cancellation */
export type CancelledNotification = Infer<typeof CancelledNotificationSchema>;
/* Base Metadata */
export type BaseMetadata = Infer<typeof BaseMetadataSchema>;
/* Initialization */
export type Implementation = Infer<typeof ImplementationSchema>;
export type ClientCapabilities = Infer<typeof ClientCapabilitiesSchema>;