From 5c1949e90fadd1c2afb1ffcae500bb1ec651c266 Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Fri, 18 Apr 2025 23:08:40 -0400 Subject: [PATCH 1/4] add Tool.outputSchema --- docs/specification/draft/server/tools.mdx | 71 +++++++++++++++++++++++ schema/draft/schema.json | 6 ++ schema/draft/schema.ts | 14 +++++ 3 files changed, 91 insertions(+) diff --git a/docs/specification/draft/server/tools.mdx b/docs/specification/draft/server/tools.mdx index 075550b2a..4458d6698 100644 --- a/docs/specification/draft/server/tools.mdx +++ b/docs/specification/draft/server/tools.mdx @@ -181,6 +181,7 @@ A tool definition includes: - `name`: Unique identifier for the tool - `description`: Human-readable description of functionality - `inputSchema`: JSON Schema defining expected parameters +- `outputSchema`: Optional JSON Schema defining the expected output structure - `annotations`: optional properties describing tool behavior For trust & safety and security, clients **MUST** consider @@ -235,6 +236,76 @@ or data, behind a URI that can be subscribed to or fetched again by the client l } ``` +### Output Schema + +The optional `outputSchema` property provides a JSON Schema that describes the expected structure of the tool's output. This schema applies to the `text` property of the TextContent object returned by the tool. + +When a tool specifies an `outputSchema`: + +1. Clients **SHOULD** validate that the tool's result: + - Contains exactly one content item + - This item is a `TextContent` object + - The `text` property of this object validates against the schema + +2. Servers **SHOULD**: + - Provide responses that conform to their declared schema + - Use proper JSON or structured formats in their responses when the schema specifies structured data + +Example tool with output schema: + +```json +{ + "name": "search_database", + "description": "Search a database for records", + "inputSchema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Search query" + } + }, + "required": ["query"] + }, + "outputSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "title": { "type": "string" }, + "url": { "type": "string" } + }, + "required": ["id", "title"] + } + } +} +``` + +Example valid response for this tool: + +```json +{ + "jsonrpc": "2.0", + "id": 5, + "result": { + "content": [ + { + "type": "text", + "text": "[{\"id\":\"doc-1\",\"title\":\"Introduction to MCP\",\"url\":\"https://example.com/docs/1\"},{\"id\":\"doc-2\",\"title\":\"Tool Usage Guide\",\"url\":\"https://example.com/docs/2\"}]" + } + ] + } +} +``` + +The `outputSchema` helps clients and LLMs understand and properly handle tool outputs by: + +- Enabling schema validation of responses +- Providing type information for better integration with programming languages +- Guiding LLMs to properly parse and utilize the returned data +- Supporting better documentation and developer experience + ## Error Handling Tools use two error reporting mechanisms: diff --git a/schema/draft/schema.json b/schema/draft/schema.json index a1e3f2679..12e55df23 100644 --- a/schema/draft/schema.json +++ b/schema/draft/schema.json @@ -2049,6 +2049,12 @@ "name": { "description": "The name of the tool.", "type": "string" + }, + "outputSchema": { + "additionalProperties": true, + "description": "A JSON Schema object defining the structure of the tool's output.\n\nIf set, a client SHOULD validate a CallToolResult for this Tool as follows:\n1. check that the length of the result's `content` property == 1\n2. check that this single item is a `TextContent` object\n3. validate this object's `text` property against this schema.\n\nIn other words, for a CallToolResult to be valid with respect to this schema,\nit must first satisfy the precondition that its payload be a single TextContent \nobject.", + "properties": {}, + "type": "object" } }, "required": [ diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index 1f6c3b1e2..d43b9e349 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -803,6 +803,20 @@ export interface Tool { required?: string[]; }; + /** + * A JSON Schema object defining the structure of the tool's output. + * + * If set, a client SHOULD validate a CallToolResult for this Tool as follows: + * 1. check that the length of the result's `content` property == 1 + * 2. check that this single item is a `TextContent` object + * 3. validate this object's `text` property against this schema. + * + * In other words, for a CallToolResult to be valid with respect to this schema, + * it must first satisfy the precondition that its payload be a single TextContent + * object. + */ + outputSchema?: object; + /** * Optional additional tool information. */ From 9876ab9ac9c26318330aced0b360192f8129e241 Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Mon, 5 May 2025 11:46:50 -0400 Subject: [PATCH 2/4] add `CallToolResult.structuredContent` --- docs/specification/draft/server/tools.mdx | 29 +++++++++-------------- schema/draft/schema.json | 10 ++++---- schema/draft/schema.ts | 26 ++++++++++++-------- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/docs/specification/draft/server/tools.mdx b/docs/specification/draft/server/tools.mdx index 4458d6698..c561569ff 100644 --- a/docs/specification/draft/server/tools.mdx +++ b/docs/specification/draft/server/tools.mdx @@ -181,7 +181,7 @@ A tool definition includes: - `name`: Unique identifier for the tool - `description`: Human-readable description of functionality - `inputSchema`: JSON Schema defining expected parameters -- `outputSchema`: Optional JSON Schema defining the expected output structure +- `outputSchema`: Optional JSON Schema defining expected output structure - `annotations`: optional properties describing tool behavior For trust & safety and security, clients **MUST** consider @@ -238,18 +238,13 @@ or data, behind a URI that can be subscribed to or fetched again by the client l ### Output Schema -The optional `outputSchema` property provides a JSON Schema that describes the expected structure of the tool's output. This schema applies to the `text` property of the TextContent object returned by the tool. +The optional `outputSchema` property provides a JSON Schema that describes the expected structure of the `structuredContent` property of a CallToolResult. When a tool specifies an `outputSchema`: -1. Clients **SHOULD** validate that the tool's result: - - Contains exactly one content item - - This item is a `TextContent` object - - The `text` property of this object validates against the schema +1. Clients **MUST** validate that the tool result contains a `structuredContent` field whose contents validate against the declared `outputSchema`. -2. Servers **SHOULD**: - - Provide responses that conform to their declared schema - - Use proper JSON or structured formats in their responses when the schema specifies structured data +2. Servers **MUST** provide tool results that conform to their declared `outputSchema`s. Example tool with output schema: @@ -289,21 +284,19 @@ Example valid response for this tool: "jsonrpc": "2.0", "id": 5, "result": { - "content": [ - { - "type": "text", - "text": "[{\"id\":\"doc-1\",\"title\":\"Introduction to MCP\",\"url\":\"https://example.com/docs/1\"},{\"id\":\"doc-2\",\"title\":\"Tool Usage Guide\",\"url\":\"https://example.com/docs/2\"}]" - } - ] + "structuredContent": { + "type": "text", + "text": "[{\"id\":\"doc-1\",\"title\":\"Introduction to MCP\",\"url\":\"https://example.com/docs/1\"},{\"id\":\"doc-2\",\"title\":\"Tool Usage Guide\",\"url\":\"https://example.com/docs/2\"}]" + } } } ``` -The `outputSchema` helps clients and LLMs understand and properly handle tool outputs by: +The `outputSchema` helps clients and LLMs understand and properly handle structured tool outputs by: -- Enabling schema validation of responses +- Enabling strict schema validation of responses - Providing type information for better integration with programming languages -- Guiding LLMs to properly parse and utilize the returned data +- Guiding clients and LLMs to properly parse and utilize the returned data - Supporting better documentation and developer experience ## Error Handling diff --git a/schema/draft/schema.json b/schema/draft/schema.json index 12e55df23..dfdd5c46f 100644 --- a/schema/draft/schema.json +++ b/schema/draft/schema.json @@ -109,6 +109,7 @@ "type": "object" }, "content": { + "description": "A list of content objects that represent the result of the tool call.", "items": { "anyOf": [ { @@ -130,11 +131,12 @@ "isError": { "description": "Whether the tool call ended in an error.\n\nIf not set, this is assumed to be false (the call was successful).", "type": "boolean" + }, + "structuredContent": { + "description": "If the Tool defines an outputSchema, this field MUST contain a serialized JSON object that matches the schema.", + "type": "string" } }, - "required": [ - "content" - ], "type": "object" }, "CancelledNotification": { @@ -2052,7 +2054,7 @@ }, "outputSchema": { "additionalProperties": true, - "description": "A JSON Schema object defining the structure of the tool's output.\n\nIf set, a client SHOULD validate a CallToolResult for this Tool as follows:\n1. check that the length of the result's `content` property == 1\n2. check that this single item is a `TextContent` object\n3. validate this object's `text` property against this schema.\n\nIn other words, for a CallToolResult to be valid with respect to this schema,\nit must first satisfy the precondition that its payload be a single TextContent \nobject.", + "description": "A JSON Schema object defining the structure of the tool's output.\n\nIf set, a CallToolResult for this Tool MUST contain a structuredContent field whose contents validate against this schema.", "properties": {}, "type": "object" } diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index d43b9e349..656a27836 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -696,7 +696,19 @@ export interface ListToolsResult extends PaginatedResult { * should be reported as an MCP error response. */ export interface CallToolResult extends Result { - content: (TextContent | ImageContent | AudioContent | EmbeddedResource)[]; + /** + * A list of content objects that represent the result of the tool call. + * + * If the Tool does not define an outputSchema, this field MUST be present in the result. + */ + content?: (TextContent | ImageContent | AudioContent | EmbeddedResource)[]; + + /** + * A string containing structured tool output. + * + * If the Tool defines an outputSchema, this field MUST be present in the result, and contain a serialized JSON object that matches the schema. + */ + structuredContent?: string; /** * Whether the tool call ended in an error. @@ -804,16 +816,10 @@ export interface Tool { }; /** - * A JSON Schema object defining the structure of the tool's output. - * - * If set, a client SHOULD validate a CallToolResult for this Tool as follows: - * 1. check that the length of the result's `content` property == 1 - * 2. check that this single item is a `TextContent` object - * 3. validate this object's `text` property against this schema. + * An optional JSON Schema object defining the structure of the tool's output. * - * In other words, for a CallToolResult to be valid with respect to this schema, - * it must first satisfy the precondition that its payload be a single TextContent - * object. + * If set, a CallToolResult for this Tool MUST contain a structuredContent field whose contents validate against this schema. + * If not set, a CallToolResult for this Tool MUST contain a content field. */ outputSchema?: object; From 405fc6412e0a34571b09aa18aad4269e88bf2787 Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Tue, 6 May 2025 16:33:25 -0400 Subject: [PATCH 3/4] address comments: - `structuredOutput` typed as object rather than string - tighten CallToolResult to codify at-least-one constraint and explicitly allow structured results to contain `content` (but not the reverse) - update docs --- docs/specification/draft/server/tools.mdx | 125 ++++++++++++++++++---- schema/draft/schema.json | 87 +++++++++++++-- schema/draft/schema.ts | 42 +++++++- 3 files changed, 223 insertions(+), 31 deletions(-) diff --git a/docs/specification/draft/server/tools.mdx b/docs/specification/draft/server/tools.mdx index c561569ff..de2d66332 100644 --- a/docs/specification/draft/server/tools.mdx +++ b/docs/specification/draft/server/tools.mdx @@ -189,7 +189,11 @@ tool annotations to be untrusted unless they come from trusted servers. +For backwards compatibility, a tool that declares an `outputSchema` may also return unstructured results in the `content` field. +* If present, the unstructured result should be functionally equivalent to the structured result. +* Clients that support `structuredContent` should ignore the `content` field if present. + Example tool with output schema: ```json { - "name": "search_database", - "description": "Search a database for records", + "name": "get_weather_data", + "description": "Get current weather conditions and forecast data for a location", "inputSchema": { "type": "object", "properties": { - "query": { + "location": { + "type": "string", + "description": "City name or zip code" + }, + "units": { "type": "string", - "description": "Search query" + "enum": ["celsius", "fahrenheit"], + "default": "celsius", + "description": "Temperature unit" } }, - "required": ["query"] + "required": ["location"] }, "outputSchema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { "type": "string" }, - "title": { "type": "string" }, - "url": { "type": "string" } + "type": "object", + "properties": { + "current": { + "type": "object", + "properties": { + "temperature": { "type": "number" }, + "humidity": { "type": "number" }, + "conditions": { "type": "string" }, + "wind": { + "type": "object", + "properties": { + "speed": { "type": "number" }, + "direction": { "type": "string" } + }, + "required": ["speed", "direction"] + } + }, + "required": ["temperature", "humidity", "conditions", "wind"] }, - "required": ["id", "title"] - } + "forecast": { + "type": "array", + "items": { + "type": "object", + "properties": { + "date": { "type": "string", "format": "date" }, + "high": { "type": "number" }, + "low": { "type": "number" }, + "conditions": { "type": "string" } + }, + "required": ["date", "high", "low", "conditions"] + } + }, + "location": { + "type": "object", + "properties": { + "city": { "type": "string" }, + "country": { "type": "string" }, + "coordinates": { + "type": "object", + "properties": { + "latitude": { "type": "number" }, + "longitude": { "type": "number" } + }, + "required": ["latitude", "longitude"] + } + }, + "required": ["city", "country", "coordinates"] + } + }, + "required": ["current", "forecast", "location"] } } ``` @@ -285,8 +341,37 @@ Example valid response for this tool: "id": 5, "result": { "structuredContent": { - "type": "text", - "text": "[{\"id\":\"doc-1\",\"title\":\"Introduction to MCP\",\"url\":\"https://example.com/docs/1\"},{\"id\":\"doc-2\",\"title\":\"Tool Usage Guide\",\"url\":\"https://example.com/docs/2\"}]" + "current": { + "temperature": 22.5, + "humidity": 65, + "conditions": "Partly cloudy", + "wind": { + "speed": 12, + "direction": "NW" + } + }, + "forecast": [ + { + "date": "2024-03-28", + "high": 25, + "low": 18, + "conditions": "Sunny" + }, + { + "date": "2024-03-29", + "high": 23, + "low": 17, + "conditions": "Cloudy" + } + ], + "location": { + "city": "San Francisco", + "country": "US", + "coordinates": { + "latitude": 37.7749, + "longitude": -122.4194 + } + } } } } diff --git a/schema/draft/schema.json b/schema/draft/schema.json index dfdd5c46f..ad03810d7 100644 --- a/schema/draft/schema.json +++ b/schema/draft/schema.json @@ -101,7 +101,18 @@ "type": "object" }, "CallToolResult": { - "description": "The server's response to a tool call.\n\nAny errors that originate from the tool SHOULD be reported inside the result\nobject, with `isError` set to true, _not_ as an MCP protocol-level error\nresponse. Otherwise, the LLM would not be able to see that an error occurred\nand self-correct.\n\nHowever, any errors in _finding_ the tool, an error indicating that the\nserver does not support tool calls, or any other exceptional conditions,\nshould be reported as an MCP error response.", + "anyOf": [ + { + "$ref": "#/definitions/CallToolUnstructuredResult" + }, + { + "$ref": "#/definitions/CallToolStructuredResult" + } + ], + "description": "The server's response to a tool call.\n\nAny errors that originate from the tool SHOULD be reported inside the result\nobject, with `isError` set to true, _not_ as an MCP protocol-level error\nresponse. Otherwise, the LLM would not be able to see that an error occurred\nand self-correct.\n\nHowever, any errors in _finding_ the tool, an error indicating that the\nserver does not support tool calls, or any other exceptional conditions,\nshould be reported as an MCP error response." + }, + "CallToolStructuredResult": { + "description": "Tool result for tools that do declare an outputSchema.", "properties": { "_meta": { "additionalProperties": {}, @@ -109,7 +120,7 @@ "type": "object" }, "content": { - "description": "A list of content objects that represent the result of the tool call.", + "description": "If the Tool defines an outputSchema, this field MAY be present in the result.\nTools should use this field to provide compatibility with older clients that do not support structured content.\nClients that support structured content should ignore this field.", "items": { "anyOf": [ { @@ -133,10 +144,52 @@ "type": "boolean" }, "structuredContent": { - "description": "If the Tool defines an outputSchema, this field MUST contain a serialized JSON object that matches the schema.", - "type": "string" + "additionalProperties": {}, + "description": "An object containing structured tool output.\n\nIf the Tool defines an outputSchema, this field MUST be present in the result, and contain a JSON object that matches the schema.", + "type": "object" + } + }, + "required": [ + "structuredContent" + ], + "type": "object" + }, + "CallToolUnstructuredResult": { + "description": "Tool result for tools that do not declare an outputSchema.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.", + "type": "object" + }, + "content": { + "description": "A list of content objects that represent the result of the tool call.\n\nIf the Tool does not define an outputSchema, this field MUST be present in the result.", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "type": "array" + }, + "isError": { + "description": "Whether the tool call ended in an error.\n\nIf not set, this is assumed to be false (the call was successful).", + "type": "boolean" } }, + "required": [ + "content" + ], "type": "object" }, "CancelledNotification": { @@ -360,6 +413,25 @@ ], "type": "object" }, + "ContentList": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/TextContent" + }, + { + "$ref": "#/definitions/ImageContent" + }, + { + "$ref": "#/definitions/AudioContent" + }, + { + "$ref": "#/definitions/EmbeddedResource" + } + ] + }, + "type": "array" + }, "CreateMessageRequest": { "description": "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.", "properties": { @@ -1906,7 +1978,10 @@ "$ref": "#/definitions/ListToolsResult" }, { - "$ref": "#/definitions/CallToolResult" + "$ref": "#/definitions/CallToolUnstructuredResult" + }, + { + "$ref": "#/definitions/CallToolStructuredResult" }, { "$ref": "#/definitions/CompleteResult" @@ -2054,7 +2129,7 @@ }, "outputSchema": { "additionalProperties": true, - "description": "A JSON Schema object defining the structure of the tool's output.\n\nIf set, a CallToolResult for this Tool MUST contain a structuredContent field whose contents validate against this schema.", + "description": "An optional JSON Schema object defining the structure of the tool's output.\n\nIf set, a CallToolResult for this Tool MUST contain a structuredContent field whose contents validate against this schema.\nIf not set, a CallToolResult for this Tool MUST contain a content field.", "properties": {}, "type": "object" } diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index 656a27836..61de0de46 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -695,20 +695,51 @@ export interface ListToolsResult extends PaginatedResult { * server does not support tool calls, or any other exceptional conditions, * should be reported as an MCP error response. */ -export interface CallToolResult extends Result { +export type CallToolResult = CallToolUnstructuredResult | CallToolStructuredResult; + +export type ContentList = (TextContent | ImageContent | AudioContent | EmbeddedResource)[]; + +/** + * Tool result for tools that do not declare an outputSchema. + */ +export interface CallToolUnstructuredResult extends Result { /** * A list of content objects that represent the result of the tool call. * * If the Tool does not define an outputSchema, this field MUST be present in the result. */ - content?: (TextContent | ImageContent | AudioContent | EmbeddedResource)[]; + content: ContentList; /** - * A string containing structured tool output. + * Structured output must not be provided in an unstructured tool result. + */ + structuredContent: never; + + /** + * Whether the tool call ended in an error. * - * If the Tool defines an outputSchema, this field MUST be present in the result, and contain a serialized JSON object that matches the schema. + * If not set, this is assumed to be false (the call was successful). */ - structuredContent?: string; + isError?: boolean; +} + +/** + * Tool result for tools that do declare an outputSchema. + */ +export interface CallToolStructuredResult extends Result { + /** + * An object containing structured tool output. + * + * If the Tool defines an outputSchema, this field MUST be present in the result, and contain a JSON object that matches the schema. + */ + structuredContent: { [key: string]: unknown }; + + /** + * If the Tool defines an outputSchema, this field MAY be present in the result. + * Tools should use this field to provide compatibility with older clients that do not support structured content. + * Clients that support structured content should ignore this field. + */ + content?: ContentList; /** * Whether the tool call ended in an error. @@ -718,6 +749,7 @@ export interface CallToolResult extends Result { isError?: boolean; } + /** * Used by the client to invoke a tool provided by the server. */ From 1365d4e06f181905283760cd91cbeb3d27b21a38 Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Tue, 6 May 2025 23:24:33 -0400 Subject: [PATCH 4/4] add serialized JSON suggestion in docs --- docs/specification/draft/server/tools.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/specification/draft/server/tools.mdx b/docs/specification/draft/server/tools.mdx index de2d66332..c5cf1c59b 100644 --- a/docs/specification/draft/server/tools.mdx +++ b/docs/specification/draft/server/tools.mdx @@ -242,7 +242,7 @@ or data, behind a URI that can be subscribed to or fetched again by the client l ### Output Schema -Tools that produce structured results can use the `outputSchema` property to provide a JSON Schema describing the expected structure of their output. +Tools that produce structured results can use the `outputSchema` property to provide a JSON Schema describing the expected structure of their output. When a tool specifies an `outputSchema`: @@ -252,7 +252,7 @@ When a tool specifies an `outputSchema`: For backwards compatibility, a tool that declares an `outputSchema` may also return unstructured results in the `content` field. -* If present, the unstructured result should be functionally equivalent to the structured result. +* If present, the unstructured result should be functionally equivalent to the structured result. (For example, serialized JSON can be returned in a `TextContent` block.) * Clients that support `structuredContent` should ignore the `content` field if present.