Skip to content

Commit 405fc64

Browse files
committed
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
1 parent 9876ab9 commit 405fc64

File tree

3 files changed

+223
-31
lines changed

3 files changed

+223
-31
lines changed

docs/specification/draft/server/tools.mdx

Lines changed: 105 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,11 @@ tool annotations to be untrusted unless they come from trusted servers.</Warning
189189

190190
### Tool Result
191191

192-
Tool results can contain multiple content items of different types:
192+
Tool results may be **structured** or **unstructured**, depending on whether the tool definition specifies an [output schema](#output-schema).
193+
194+
**Structured** tool results are JSON objects that are valid with respect to the tool's output schema.
195+
196+
**Unstructured** tool results can contain multiple content items of different types:
193197

194198
#### Text Content
195199

@@ -238,41 +242,93 @@ or data, behind a URI that can be subscribed to or fetched again by the client l
238242

239243
### Output Schema
240244

241-
The optional `outputSchema` property provides a JSON Schema that describes the expected structure of the `structuredContent` property of a CallToolResult.
245+
Tools that produce structured results can use the `outputSchema` property to provide a JSON Schema describing the expected structure of their output.
242246

243247
When a tool specifies an `outputSchema`:
244248

245-
1. Clients **MUST** validate that the tool result contains a `structuredContent` field whose contents validate against the declared `outputSchema`.
249+
1. Clients **MUST** validate that results from that tool contain a `structuredContent` field whose contents validate against the declared `outputSchema`.
250+
251+
2. Servers **MUST** provide structured results in `structuredContent` that conform to the declared `outputSchema` of the tool.
246252

247-
2. Servers **MUST** provide tool results that conform to their declared `outputSchema`s.
253+
<Info>
254+
For backwards compatibility, a tool that declares an `outputSchema` may also return unstructured results in the `content` field.
255+
* If present, the unstructured result should be functionally equivalent to the structured result.
256+
* Clients that support `structuredContent` should ignore the `content` field if present.
257+
</Info>
248258

249259
Example tool with output schema:
250260

251261
```json
252262
{
253-
"name": "search_database",
254-
"description": "Search a database for records",
263+
"name": "get_weather_data",
264+
"description": "Get current weather conditions and forecast data for a location",
255265
"inputSchema": {
256266
"type": "object",
257267
"properties": {
258-
"query": {
268+
"location": {
269+
"type": "string",
270+
"description": "City name or zip code"
271+
},
272+
"units": {
259273
"type": "string",
260-
"description": "Search query"
274+
"enum": ["celsius", "fahrenheit"],
275+
"default": "celsius",
276+
"description": "Temperature unit"
261277
}
262278
},
263-
"required": ["query"]
279+
"required": ["location"]
264280
},
265281
"outputSchema": {
266-
"type": "array",
267-
"items": {
268-
"type": "object",
269-
"properties": {
270-
"id": { "type": "string" },
271-
"title": { "type": "string" },
272-
"url": { "type": "string" }
282+
"type": "object",
283+
"properties": {
284+
"current": {
285+
"type": "object",
286+
"properties": {
287+
"temperature": { "type": "number" },
288+
"humidity": { "type": "number" },
289+
"conditions": { "type": "string" },
290+
"wind": {
291+
"type": "object",
292+
"properties": {
293+
"speed": { "type": "number" },
294+
"direction": { "type": "string" }
295+
},
296+
"required": ["speed", "direction"]
297+
}
298+
},
299+
"required": ["temperature", "humidity", "conditions", "wind"]
273300
},
274-
"required": ["id", "title"]
275-
}
301+
"forecast": {
302+
"type": "array",
303+
"items": {
304+
"type": "object",
305+
"properties": {
306+
"date": { "type": "string", "format": "date" },
307+
"high": { "type": "number" },
308+
"low": { "type": "number" },
309+
"conditions": { "type": "string" }
310+
},
311+
"required": ["date", "high", "low", "conditions"]
312+
}
313+
},
314+
"location": {
315+
"type": "object",
316+
"properties": {
317+
"city": { "type": "string" },
318+
"country": { "type": "string" },
319+
"coordinates": {
320+
"type": "object",
321+
"properties": {
322+
"latitude": { "type": "number" },
323+
"longitude": { "type": "number" }
324+
},
325+
"required": ["latitude", "longitude"]
326+
}
327+
},
328+
"required": ["city", "country", "coordinates"]
329+
}
330+
},
331+
"required": ["current", "forecast", "location"]
276332
}
277333
}
278334
```
@@ -285,8 +341,37 @@ Example valid response for this tool:
285341
"id": 5,
286342
"result": {
287343
"structuredContent": {
288-
"type": "text",
289-
"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\"}]"
344+
"current": {
345+
"temperature": 22.5,
346+
"humidity": 65,
347+
"conditions": "Partly cloudy",
348+
"wind": {
349+
"speed": 12,
350+
"direction": "NW"
351+
}
352+
},
353+
"forecast": [
354+
{
355+
"date": "2024-03-28",
356+
"high": 25,
357+
"low": 18,
358+
"conditions": "Sunny"
359+
},
360+
{
361+
"date": "2024-03-29",
362+
"high": 23,
363+
"low": 17,
364+
"conditions": "Cloudy"
365+
}
366+
],
367+
"location": {
368+
"city": "San Francisco",
369+
"country": "US",
370+
"coordinates": {
371+
"latitude": 37.7749,
372+
"longitude": -122.4194
373+
}
374+
}
290375
}
291376
}
292377
}

schema/draft/schema.json

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,26 @@
101101
"type": "object"
102102
},
103103
"CallToolResult": {
104-
"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.",
104+
"anyOf": [
105+
{
106+
"$ref": "#/definitions/CallToolUnstructuredResult"
107+
},
108+
{
109+
"$ref": "#/definitions/CallToolStructuredResult"
110+
}
111+
],
112+
"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."
113+
},
114+
"CallToolStructuredResult": {
115+
"description": "Tool result for tools that do declare an outputSchema.",
105116
"properties": {
106117
"_meta": {
107118
"additionalProperties": {},
108119
"description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.",
109120
"type": "object"
110121
},
111122
"content": {
112-
"description": "A list of content objects that represent the result of the tool call.",
123+
"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.",
113124
"items": {
114125
"anyOf": [
115126
{
@@ -133,10 +144,52 @@
133144
"type": "boolean"
134145
},
135146
"structuredContent": {
136-
"description": "If the Tool defines an outputSchema, this field MUST contain a serialized JSON object that matches the schema.",
137-
"type": "string"
147+
"additionalProperties": {},
148+
"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.",
149+
"type": "object"
150+
}
151+
},
152+
"required": [
153+
"structuredContent"
154+
],
155+
"type": "object"
156+
},
157+
"CallToolUnstructuredResult": {
158+
"description": "Tool result for tools that do not declare an outputSchema.",
159+
"properties": {
160+
"_meta": {
161+
"additionalProperties": {},
162+
"description": "This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses.",
163+
"type": "object"
164+
},
165+
"content": {
166+
"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.",
167+
"items": {
168+
"anyOf": [
169+
{
170+
"$ref": "#/definitions/TextContent"
171+
},
172+
{
173+
"$ref": "#/definitions/ImageContent"
174+
},
175+
{
176+
"$ref": "#/definitions/AudioContent"
177+
},
178+
{
179+
"$ref": "#/definitions/EmbeddedResource"
180+
}
181+
]
182+
},
183+
"type": "array"
184+
},
185+
"isError": {
186+
"description": "Whether the tool call ended in an error.\n\nIf not set, this is assumed to be false (the call was successful).",
187+
"type": "boolean"
138188
}
139189
},
190+
"required": [
191+
"content"
192+
],
140193
"type": "object"
141194
},
142195
"CancelledNotification": {
@@ -360,6 +413,25 @@
360413
],
361414
"type": "object"
362415
},
416+
"ContentList": {
417+
"items": {
418+
"anyOf": [
419+
{
420+
"$ref": "#/definitions/TextContent"
421+
},
422+
{
423+
"$ref": "#/definitions/ImageContent"
424+
},
425+
{
426+
"$ref": "#/definitions/AudioContent"
427+
},
428+
{
429+
"$ref": "#/definitions/EmbeddedResource"
430+
}
431+
]
432+
},
433+
"type": "array"
434+
},
363435
"CreateMessageRequest": {
364436
"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.",
365437
"properties": {
@@ -1906,7 +1978,10 @@
19061978
"$ref": "#/definitions/ListToolsResult"
19071979
},
19081980
{
1909-
"$ref": "#/definitions/CallToolResult"
1981+
"$ref": "#/definitions/CallToolUnstructuredResult"
1982+
},
1983+
{
1984+
"$ref": "#/definitions/CallToolStructuredResult"
19101985
},
19111986
{
19121987
"$ref": "#/definitions/CompleteResult"
@@ -2054,7 +2129,7 @@
20542129
},
20552130
"outputSchema": {
20562131
"additionalProperties": true,
2057-
"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.",
2132+
"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.",
20582133
"properties": {},
20592134
"type": "object"
20602135
}

schema/draft/schema.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -695,20 +695,51 @@ export interface ListToolsResult extends PaginatedResult {
695695
* server does not support tool calls, or any other exceptional conditions,
696696
* should be reported as an MCP error response.
697697
*/
698-
export interface CallToolResult extends Result {
698+
export type CallToolResult = CallToolUnstructuredResult | CallToolStructuredResult;
699+
700+
export type ContentList = (TextContent | ImageContent | AudioContent | EmbeddedResource)[];
701+
702+
/**
703+
* Tool result for tools that do not declare an outputSchema.
704+
*/
705+
export interface CallToolUnstructuredResult extends Result {
699706
/**
700707
* A list of content objects that represent the result of the tool call.
701708
*
702709
* If the Tool does not define an outputSchema, this field MUST be present in the result.
703710
*/
704-
content?: (TextContent | ImageContent | AudioContent | EmbeddedResource)[];
711+
content: ContentList;
705712

706713
/**
707-
* A string containing structured tool output.
714+
* Structured output must not be provided in an unstructured tool result.
715+
*/
716+
structuredContent: never;
717+
718+
/**
719+
* Whether the tool call ended in an error.
708720
*
709-
* If the Tool defines an outputSchema, this field MUST be present in the result, and contain a serialized JSON object that matches the schema.
721+
* If not set, this is assumed to be false (the call was successful).
710722
*/
711-
structuredContent?: string;
723+
isError?: boolean;
724+
}
725+
726+
/**
727+
* Tool result for tools that do declare an outputSchema.
728+
*/
729+
export interface CallToolStructuredResult extends Result {
730+
/**
731+
* An object containing structured tool output.
732+
*
733+
* If the Tool defines an outputSchema, this field MUST be present in the result, and contain a JSON object that matches the schema.
734+
*/
735+
structuredContent: { [key: string]: unknown };
736+
737+
/**
738+
* If the Tool defines an outputSchema, this field MAY be present in the result.
739+
* Tools should use this field to provide compatibility with older clients that do not support structured content.
740+
* Clients that support structured content should ignore this field.
741+
*/
742+
content?: ContentList;
712743

713744
/**
714745
* Whether the tool call ended in an error.
@@ -718,6 +749,7 @@ export interface CallToolResult extends Result {
718749
isError?: boolean;
719750
}
720751

752+
721753
/**
722754
* Used by the client to invoke a tool provided by the server.
723755
*/

0 commit comments

Comments
 (0)