diff --git a/src/examples/server/toolWithSampleServer.ts b/src/examples/server/toolWithSampleServer.ts index 4fa13c78a..c198dc0ec 100644 --- a/src/examples/server/toolWithSampleServer.ts +++ b/src/examples/server/toolWithSampleServer.ts @@ -33,13 +33,12 @@ mcpServer.registerTool( maxTokens: 500 }); + const contents = Array.isArray(response.content) ? response.content : [response.content]; return { - content: [ - { - type: 'text', - text: response.content.type === 'text' ? response.content.text : 'Unable to generate summary' - } - ] + content: contents.map(content => ({ + type: 'text', + text: content.type === 'text' ? content.text : 'Unable to generate summary' + })) }; } ); diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index 059ebf00d..1c0b6ab5d 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -360,11 +360,11 @@ const sdkTypeChecks = { sdk = spec; spec = sdk; }, - SamplingMessage: (sdk: SDKTypes.SamplingMessage, spec: SpecTypes.SamplingMessage) => { + SamplingMessage: (sdk: RemovePassthrough, spec: SpecTypes.SamplingMessage) => { sdk = spec; spec = sdk; }, - CreateMessageResult: (sdk: SDKTypes.CreateMessageResult, spec: SpecTypes.CreateMessageResult) => { + CreateMessageResult: (sdk: RemovePassthrough, spec: SpecTypes.CreateMessageResult) => { sdk = spec; spec = sdk; }, @@ -614,6 +614,25 @@ const sdkTypeChecks = { ModelPreferences: (sdk: SDKTypes.ModelPreferences, spec: SpecTypes.ModelPreferences) => { sdk = spec; spec = sdk; + }, + ToolChoice: (sdk: SDKTypes.ToolChoice, spec: SpecTypes.ToolChoice) => { + sdk = spec; + spec = sdk; + }, + ToolUseContent: (sdk: RemovePassthrough, spec: SpecTypes.ToolUseContent) => { + sdk = spec; + spec = sdk; + }, + ToolResultContent: (sdk: RemovePassthrough, spec: SpecTypes.ToolResultContent) => { + sdk = spec; + spec = sdk; + }, + SamplingMessageContentBlock: ( + sdk: RemovePassthrough, + spec: SpecTypes.SamplingMessageContentBlock + ) => { + sdk = spec; + spec = sdk; } }; @@ -643,7 +662,7 @@ describe('Spec Types', () => { it('should define some expected types', () => { expect(specTypes).toContain('JSONRPCNotification'); expect(specTypes).toContain('ElicitResult'); - expect(specTypes).toHaveLength(123); + expect(specTypes).toHaveLength(127); }); it('should have up to date list of missing sdk types', () => { diff --git a/src/spec.types.ts b/src/spec.types.ts index 307884fa0..6ce24059e 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -307,7 +307,17 @@ export interface ClientCapabilities { /** * Present if the client supports sampling from an LLM. */ - sampling?: object; + sampling?: { + /** + * Whether the client supports context inclusion via includeContext parameter. + * If not declared, servers SHOULD only use `includeContext: "none"` (or omit it). + */ + context?: object; + /** + * Whether the client supports tool use via tools and toolChoice parameters. + */ + tools?: object; + }; /** * Present if the client supports elicitation from the server. */ @@ -1255,7 +1265,11 @@ export interface CreateMessageRequestParams extends RequestParams { */ systemPrompt?: string; /** - * A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The client MAY ignore this request. + * A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. + * The client MAY ignore this request. + * + * Default is "none". Values "thisServer" and "allServers" are soft-deprecated. Servers SHOULD only use these values if the client + * declares ClientCapabilities.sampling.context. These values may be removed in future spec releases. */ includeContext?: "none" | "thisServer" | "allServers"; /** @@ -1273,6 +1287,32 @@ export interface CreateMessageRequestParams extends RequestParams { * Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. */ metadata?: object; + /** + * Tools that the model may use during generation. + * The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + */ + tools?: Tool[]; + /** + * Controls how the model uses tools. + * The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + * Default is `{ mode: "auto" }`. + */ + toolChoice?: ToolChoice; +} + +/** + * Controls tool selection behavior for sampling requests. + * + * @category `sampling/createMessage` + */ +export interface ToolChoice { + /** + * Controls the tool use ability of the model: + * - "auto": Model decides whether to use tools (default) + * - "required": Model MUST use at least one tool before completing + * - "none": Model MUST NOT use any tools + */ + mode?: "auto" | "required" | "none"; } /** @@ -1295,10 +1335,19 @@ export interface CreateMessageResult extends Result, SamplingMessage { * The name of the model that generated the message. */ model: string; + /** * The reason why sampling stopped, if known. + * + * Standard values: + * - "endTurn": Natural end of the assistant's turn + * - "stopSequence": A stop sequence was encountered + * - "maxTokens": Maximum token limit was reached + * - "toolUse": The model wants to use one or more tools + * + * This field is an open string to allow for provider-specific stop reasons. */ - stopReason?: "endTurn" | "stopSequence" | "maxTokens" | string; + stopReason?: "endTurn" | "stopSequence" | "maxTokens" | "toolUse" | string; } /** @@ -1308,8 +1357,18 @@ export interface CreateMessageResult extends Result, SamplingMessage { */ export interface SamplingMessage { role: Role; - content: TextContent | ImageContent | AudioContent; + content: SamplingMessageContentBlock | SamplingMessageContentBlock[]; + /** + * See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. + */ + _meta?: { [key: string]: unknown }; } +export type SamplingMessageContentBlock = + | TextContent + | ImageContent + | AudioContent + | ToolUseContent + | ToolResultContent; /** * Optional annotations for the client. The client can use annotations to inform how objects are used or displayed @@ -1444,6 +1503,87 @@ export interface AudioContent { _meta?: { [key: string]: unknown }; } +/** + * A request from the assistant to call a tool. + * + * @category `sampling/createMessage` + */ +export interface ToolUseContent { + type: "tool_use"; + + /** + * A unique identifier for this tool use. + * + * This ID is used to match tool results to their corresponding tool uses. + */ + id: string; + + /** + * The name of the tool to call. + */ + name: string; + + /** + * The arguments to pass to the tool, conforming to the tool's input schema. + */ + input: { [key: string]: unknown }; + + /** + * Optional metadata about the tool use. Clients SHOULD preserve this field when + * including tool uses in subsequent sampling requests to enable caching optimizations. + * + * See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. + */ + _meta?: { [key: string]: unknown }; +} + +/** + * The result of a tool use, provided by the user back to the assistant. + * + * @category `sampling/createMessage` + */ +export interface ToolResultContent { + type: "tool_result"; + + /** + * The ID of the tool use this result corresponds to. + * + * This MUST match the ID from a previous ToolUseContent. + */ + toolUseId: string; + + /** + * The unstructured result content of the tool use. + * + * This has the same format as CallToolResult.content and can include text, images, + * audio, resource links, and embedded resources. + */ + content: ContentBlock[]; + + /** + * An optional structured result object. + * + * If the tool defined an outputSchema, this SHOULD conform to that schema. + */ + structuredContent?: { [key: string]: unknown }; + + /** + * Whether the tool use resulted in an error. + * + * If true, the content typically describes the error that occurred. + * Default: false + */ + isError?: boolean; + + /** + * Optional metadata about the tool result. Clients SHOULD preserve this field when + * including tool results in subsequent sampling requests to enable caching optimizations. + * + * See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. + */ + _meta?: { [key: string]: unknown }; +} + /** * The server's preferences for model selection, requested of the client during sampling. * @@ -1762,7 +1902,6 @@ export interface ElicitRequest extends JSONRPCRequest { params: ElicitRequestParams; } -/** /** * Restricted schema definitions that only allow primitive types * without nested objects or arrays. diff --git a/src/types.test.ts b/src/types.test.ts index cd8cc0711..3f6f83a14 100644 --- a/src/types.test.ts +++ b/src/types.test.ts @@ -5,7 +5,14 @@ import { ContentBlockSchema, PromptMessageSchema, CallToolResultSchema, - CompleteRequestSchema + CompleteRequestSchema, + ToolUseContentSchema, + ToolResultContentSchema, + ToolChoiceSchema, + SamplingMessageSchema, + CreateMessageRequestSchema, + CreateMessageResultSchema, + ClientCapabilitiesSchema } from './types.js'; describe('Types', () => { @@ -311,4 +318,475 @@ describe('Types', () => { } }); }); + + describe('ToolUseContent', () => { + test('should validate a tool call content', () => { + const toolCall = { + type: 'tool_use', + id: 'call_123', + name: 'get_weather', + input: { city: 'San Francisco', units: 'celsius' } + }; + + const result = ToolUseContentSchema.safeParse(toolCall); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.type).toBe('tool_use'); + expect(result.data.id).toBe('call_123'); + expect(result.data.name).toBe('get_weather'); + expect(result.data.input).toEqual({ city: 'San Francisco', units: 'celsius' }); + } + }); + + test('should validate tool call with _meta', () => { + const toolCall = { + type: 'tool_use', + id: 'call_456', + name: 'search', + input: { query: 'test' }, + _meta: { custom: 'data' } + }; + + const result = ToolUseContentSchema.safeParse(toolCall); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data._meta).toEqual({ custom: 'data' }); + } + }); + + test('should fail validation for missing required fields', () => { + const invalidToolCall = { + type: 'tool_use', + name: 'test' + // missing id and input + }; + + const result = ToolUseContentSchema.safeParse(invalidToolCall); + expect(result.success).toBe(false); + }); + }); + + describe('ToolResultContent', () => { + test('should validate a tool result content', () => { + const toolResult = { + type: 'tool_result', + toolUseId: 'call_123', + structuredContent: { temperature: 72, condition: 'sunny' } + }; + + const result = ToolResultContentSchema.safeParse(toolResult); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.type).toBe('tool_result'); + expect(result.data.toolUseId).toBe('call_123'); + expect(result.data.structuredContent).toEqual({ temperature: 72, condition: 'sunny' }); + } + }); + + test('should validate tool result with error in content', () => { + const toolResult = { + type: 'tool_result', + toolUseId: 'call_456', + structuredContent: { error: 'API_ERROR', message: 'Service unavailable' }, + isError: true + }; + + const result = ToolResultContentSchema.safeParse(toolResult); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.structuredContent).toEqual({ error: 'API_ERROR', message: 'Service unavailable' }); + expect(result.data.isError).toBe(true); + } + }); + + test('should fail validation for missing required fields', () => { + const invalidToolResult = { + type: 'tool_result', + content: { data: 'test' } + // missing toolUseId + }; + + const result = ToolResultContentSchema.safeParse(invalidToolResult); + expect(result.success).toBe(false); + }); + }); + + describe('ToolChoice', () => { + test('should validate tool choice with mode auto', () => { + const toolChoice = { + mode: 'auto' + }; + + const result = ToolChoiceSchema.safeParse(toolChoice); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.mode).toBe('auto'); + } + }); + + test('should validate tool choice with mode required', () => { + const toolChoice = { + mode: 'required' + }; + + const result = ToolChoiceSchema.safeParse(toolChoice); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.mode).toBe('required'); + } + }); + + test('should validate empty tool choice', () => { + const toolChoice = {}; + + const result = ToolChoiceSchema.safeParse(toolChoice); + expect(result.success).toBe(true); + }); + + test('should fail validation for invalid mode', () => { + const invalidToolChoice = { + mode: 'invalid' + }; + + const result = ToolChoiceSchema.safeParse(invalidToolChoice); + expect(result.success).toBe(false); + }); + }); + + describe('SamplingMessage content types', () => { + test('should validate user message with text', () => { + const userMessage = { + role: 'user', + content: { type: 'text', text: "What's the weather?" } + }; + + const result = SamplingMessageSchema.safeParse(userMessage); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.role).toBe('user'); + if (!Array.isArray(result.data.content)) { + expect(result.data.content.type).toBe('text'); + } + } + }); + + test('should validate user message with tool result', () => { + const userMessage = { + role: 'user', + content: { + type: 'tool_result', + toolUseId: 'call_123', + content: [] + } + }; + + const result = SamplingMessageSchema.safeParse(userMessage); + expect(result.success).toBe(true); + if (result.success && !Array.isArray(result.data.content)) { + expect(result.data.content.type).toBe('tool_result'); + } + }); + + test('should validate assistant message with text', () => { + const assistantMessage = { + role: 'assistant', + content: { type: 'text', text: "I'll check the weather for you." } + }; + + const result = SamplingMessageSchema.safeParse(assistantMessage); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.role).toBe('assistant'); + } + }); + + test('should validate assistant message with tool call', () => { + const assistantMessage = { + role: 'assistant', + content: { + type: 'tool_use', + id: 'call_123', + name: 'get_weather', + input: { city: 'SF' } + } + }; + + const result = SamplingMessageSchema.safeParse(assistantMessage); + expect(result.success).toBe(true); + if (result.success && !Array.isArray(result.data.content)) { + expect(result.data.content.type).toBe('tool_use'); + } + }); + + test('should validate any content type for any role', () => { + // The simplified schema allows any content type for any role + const assistantWithToolResult = { + role: 'assistant', + content: { + type: 'tool_result', + toolUseId: 'call_123', + content: [] + } + }; + + const result1 = SamplingMessageSchema.safeParse(assistantWithToolResult); + expect(result1.success).toBe(true); + + const userWithToolUse = { + role: 'user', + content: { + type: 'tool_use', + id: 'call_123', + name: 'test', + input: {} + } + }; + + const result2 = SamplingMessageSchema.safeParse(userWithToolUse); + expect(result2.success).toBe(true); + }); + }); + + describe('SamplingMessage', () => { + test('should validate user message via discriminated union', () => { + const message = { + role: 'user', + content: { type: 'text', text: 'Hello' } + }; + + const result = SamplingMessageSchema.safeParse(message); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.role).toBe('user'); + } + }); + + test('should validate assistant message via discriminated union', () => { + const message = { + role: 'assistant', + content: { type: 'text', text: 'Hi there!' } + }; + + const result = SamplingMessageSchema.safeParse(message); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.role).toBe('assistant'); + } + }); + }); + + describe('CreateMessageRequest', () => { + test('should validate request without tools', () => { + const request = { + method: 'sampling/createMessage', + params: { + messages: [{ role: 'user', content: { type: 'text', text: 'Hello' } }], + maxTokens: 1000 + } + }; + + const result = CreateMessageRequestSchema.safeParse(request); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.params.tools).toBeUndefined(); + } + }); + + test('should validate request with tools', () => { + const request = { + method: 'sampling/createMessage', + params: { + messages: [{ role: 'user', content: { type: 'text', text: "What's the weather?" } }], + maxTokens: 1000, + tools: [ + { + name: 'get_weather', + description: 'Get weather for a location', + inputSchema: { + type: 'object', + properties: { + location: { type: 'string' } + }, + required: ['location'] + } + } + ], + toolChoice: { + mode: 'auto' + } + } + }; + + const result = CreateMessageRequestSchema.safeParse(request); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.params.tools).toHaveLength(1); + expect(result.data.params.toolChoice?.mode).toBe('auto'); + } + }); + + test('should validate request with includeContext (soft-deprecated)', () => { + const request = { + method: 'sampling/createMessage', + params: { + messages: [{ role: 'user', content: { type: 'text', text: 'Help' } }], + maxTokens: 1000, + includeContext: 'thisServer' + } + }; + + const result = CreateMessageRequestSchema.safeParse(request); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.params.includeContext).toBe('thisServer'); + } + }); + }); + + describe('CreateMessageResult', () => { + test('should validate result with text content', () => { + const result = { + model: 'claude-3-5-sonnet-20241022', + role: 'assistant', + content: { type: 'text', text: "Here's the answer." }, + stopReason: 'endTurn' + }; + + const parseResult = CreateMessageResultSchema.safeParse(result); + expect(parseResult.success).toBe(true); + if (parseResult.success) { + expect(parseResult.data.role).toBe('assistant'); + expect(parseResult.data.stopReason).toBe('endTurn'); + } + }); + + test('should validate result with tool call', () => { + const result = { + model: 'claude-3-5-sonnet-20241022', + role: 'assistant', + content: { + type: 'tool_use', + id: 'call_123', + name: 'get_weather', + input: { city: 'SF' } + }, + stopReason: 'toolUse' + }; + + const parseResult = CreateMessageResultSchema.safeParse(result); + expect(parseResult.success).toBe(true); + if (parseResult.success) { + expect(parseResult.data.stopReason).toBe('toolUse'); + const content = parseResult.data.content; + expect(Array.isArray(content)).toBe(false); + if (!Array.isArray(content)) { + expect(content.type).toBe('tool_use'); + } + } + }); + + test('should validate result with array content', () => { + const result = { + model: 'claude-3-5-sonnet-20241022', + role: 'assistant', + content: [ + { type: 'text', text: 'Let me check the weather.' }, + { + type: 'tool_use', + id: 'call_123', + name: 'get_weather', + input: { city: 'SF' } + } + ], + stopReason: 'toolUse' + }; + + const parseResult = CreateMessageResultSchema.safeParse(result); + expect(parseResult.success).toBe(true); + if (parseResult.success) { + expect(parseResult.data.stopReason).toBe('toolUse'); + const content = parseResult.data.content; + expect(Array.isArray(content)).toBe(true); + if (Array.isArray(content)) { + expect(content).toHaveLength(2); + expect(content[0].type).toBe('text'); + expect(content[1].type).toBe('tool_use'); + } + } + }); + + test('should validate all new stop reasons', () => { + const stopReasons = ['endTurn', 'stopSequence', 'maxTokens', 'toolUse', 'refusal', 'other']; + + stopReasons.forEach(stopReason => { + const result = { + model: 'test', + role: 'assistant', + content: { type: 'text', text: 'test' }, + stopReason + }; + + const parseResult = CreateMessageResultSchema.safeParse(result); + expect(parseResult.success).toBe(true); + }); + }); + + test('should allow custom stop reason string', () => { + const result = { + model: 'test', + role: 'assistant', + content: { type: 'text', text: 'test' }, + stopReason: 'custom_provider_reason' + }; + + const parseResult = CreateMessageResultSchema.safeParse(result); + expect(parseResult.success).toBe(true); + }); + }); + + describe('ClientCapabilities with sampling', () => { + test('should validate capabilities with sampling.tools', () => { + const capabilities = { + sampling: { + tools: {} + } + }; + + const result = ClientCapabilitiesSchema.safeParse(capabilities); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.sampling?.tools).toBeDefined(); + } + }); + + test('should validate capabilities with sampling.context', () => { + const capabilities = { + sampling: { + context: {} + } + }; + + const result = ClientCapabilitiesSchema.safeParse(capabilities); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.sampling?.context).toBeDefined(); + } + }); + + test('should validate capabilities with both', () => { + const capabilities = { + sampling: { + context: {}, + tools: {} + } + }; + + const result = ClientCapabilitiesSchema.safeParse(capabilities); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.sampling?.context).toBeDefined(); + expect(result.data.sampling?.tools).toBeDefined(); + } + }); + }); }); diff --git a/src/types.ts b/src/types.ts index d44f7ca42..64153094d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -302,7 +302,19 @@ export const ClientCapabilitiesSchema = z.object({ /** * Present if the client supports sampling from an LLM. */ - sampling: AssertObjectSchema.optional(), + sampling: z + .object({ + /** + * Present if the client supports context inclusion via includeContext parameter. + * If not declared, servers SHOULD only use `includeContext: "none"` (or omit it). + */ + context: AssertObjectSchema.optional(), + /** + * Present if the client supports tool use via tools and toolChoice parameters. + */ + tools: AssertObjectSchema.optional() + }) + .optional(), /** * Present if the client supports eliciting user input. */ @@ -832,6 +844,36 @@ export const AudioContentSchema = z.object({ _meta: z.record(z.string(), z.unknown()).optional() }); +/** + * A tool call request from an assistant (LLM). + * Represents the assistant's request to use a tool. + */ +export const ToolUseContentSchema = z + .object({ + type: z.literal('tool_use'), + /** + * The name of the tool to invoke. + * Must match a tool name from the request's tools array. + */ + name: z.string(), + /** + * Unique identifier for this tool call. + * Used to correlate with ToolResultContent in subsequent messages. + */ + id: z.string(), + /** + * Arguments to pass to the tool. + * Must conform to the tool's inputSchema. + */ + input: z.object({}).passthrough(), + /** + * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + * for notes on _meta usage. + */ + _meta: z.optional(z.object({}).passthrough()) + }) + .passthrough(); + /** * The contents of a resource, embedded into a prompt or tool call result. */ @@ -1158,13 +1200,65 @@ export const ModelPreferencesSchema = z.object({ }); /** - * Describes a message issued to or received from an LLM API. + * Controls tool usage behavior in sampling requests. */ -export const SamplingMessageSchema = z.object({ - role: z.enum(['user', 'assistant']), - content: z.union([TextContentSchema, ImageContentSchema, AudioContentSchema]) +export const ToolChoiceSchema = z.object({ + /** + * Controls when tools are used: + * - "auto": Model decides whether to use tools (default) + * - "required": Model MUST use at least one tool before completing + * - "none": Model MUST NOT use any tools + */ + mode: z.optional(z.enum(['auto', 'required', 'none'])) }); +/** + * The result of a tool execution, provided by the user (server). + * Represents the outcome of invoking a tool requested via ToolUseContent. + */ +export const ToolResultContentSchema = z + .object({ + type: z.literal('tool_result'), + toolUseId: z.string().describe('The unique identifier for the corresponding tool call.'), + content: z.array(ContentBlockSchema).default([]), + structuredContent: z.object({}).passthrough().optional(), + isError: z.optional(z.boolean()), + + /** + * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + * for notes on _meta usage. + */ + _meta: z.optional(z.object({}).passthrough()) + }) + .passthrough(); + +/** + * Content block types allowed in sampling messages. + * This includes text, image, audio, tool use requests, and tool results. + */ +export const SamplingMessageContentBlockSchema = z.discriminatedUnion('type', [ + TextContentSchema, + ImageContentSchema, + AudioContentSchema, + ToolUseContentSchema, + ToolResultContentSchema +]); + +/** + * Describes a message issued to or received from an LLM API. + */ +export const SamplingMessageSchema = z + .object({ + role: z.enum(['user', 'assistant']), + content: z.union([SamplingMessageContentBlockSchema, z.array(SamplingMessageContentBlockSchema)]), + /** + * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + * for notes on _meta usage. + */ + _meta: z.optional(z.object({}).passthrough()) + }) + .passthrough(); + /** * Parameters for a `sampling/createMessage` request. */ @@ -1179,7 +1273,11 @@ export const CreateMessageRequestParamsSchema = BaseRequestParamsSchema.extend({ */ systemPrompt: z.string().optional(), /** - * A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The client MAY ignore this request. + * A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. + * The client MAY ignore this request. + * + * Default is "none". Values "thisServer" and "allServers" are soft-deprecated. Servers SHOULD only use these values if the client + * declares ClientCapabilities.sampling.context. These values may be removed in future spec releases. */ includeContext: z.enum(['none', 'thisServer', 'allServers']).optional(), temperature: z.number().optional(), @@ -1193,7 +1291,18 @@ export const CreateMessageRequestParamsSchema = BaseRequestParamsSchema.extend({ /** * Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. */ - metadata: AssertObjectSchema.optional() + metadata: AssertObjectSchema.optional(), + /** + * Tools that the model may use during generation. + * The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + */ + tools: z.optional(z.array(ToolSchema)), + /** + * Controls how the model uses tools. + * The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + * Default is `{ mode: "auto" }`. + */ + toolChoice: z.optional(ToolChoiceSchema) }); /** * A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it. @@ -1212,11 +1321,22 @@ export const CreateMessageResultSchema = ResultSchema.extend({ */ model: z.string(), /** - * The reason why sampling stopped. + * The reason why sampling stopped, if known. + * + * Standard values: + * - "endTurn": Natural end of the assistant's turn + * - "stopSequence": A stop sequence was encountered + * - "maxTokens": Maximum token limit was reached + * - "toolUse": The model wants to use one or more tools + * + * This field is an open string to allow for provider-specific stop reasons. */ - stopReason: z.optional(z.enum(['endTurn', 'stopSequence', 'maxTokens']).or(z.string())), + stopReason: z.optional(z.enum(['endTurn', 'stopSequence', 'maxTokens', 'toolUse']).or(z.string())), role: z.enum(['user', 'assistant']), - content: z.discriminatedUnion('type', [TextContentSchema, ImageContentSchema, AudioContentSchema]) + /** + * Response content. May be ToolUseContent if stopReason is "toolUse". + */ + content: z.union([SamplingMessageContentBlockSchema, z.array(SamplingMessageContentBlockSchema)]) }); /* Elicitation */ @@ -1812,6 +1932,8 @@ export type GetPromptRequest = Infer; export type TextContent = Infer; export type ImageContent = Infer; export type AudioContent = Infer; +export type ToolUseContent = Infer; +export type ToolResultContent = Infer; export type EmbeddedResource = Infer; export type ResourceLink = Infer; export type ContentBlock = Infer; @@ -1838,8 +1960,10 @@ export type LoggingMessageNotificationParams = Infer; /* Sampling */ +export type ToolChoice = Infer; export type ModelHint = Infer; export type ModelPreferences = Infer; +export type SamplingMessageContentBlock = Infer; export type SamplingMessage = Infer; export type CreateMessageRequestParams = Infer; export type CreateMessageRequest = Infer;