Skip to content

Commit d522a91

Browse files
committed
make content nonoptional, add structured output creation shim
1 parent 3975c1a commit d522a91

File tree

5 files changed

+66
-44
lines changed

5 files changed

+66
-44
lines changed

src/client/index.test.ts

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import { Transport } from "../shared/transport.js";
2121
import { Server } from "../server/index.js";
2222
import { InMemoryTransport } from "../inMemory.js";
23+
import { createCallToolStructuredResult } from "../server/mcp.js";
2324

2425
/***
2526
* Test: Initialize with Matching Protocol Version
@@ -841,9 +842,7 @@ describe('outputSchema validation', () => {
841842

842843
server.setRequestHandler(CallToolRequestSchema, async (request) => {
843844
if (request.params.name === 'test-tool') {
844-
return {
845-
structuredContent: { result: 'success', count: 42 },
846-
};
845+
return createCallToolStructuredResult({ result: 'success', count: 42 });
847846
}
848847
throw new Error('Unknown tool');
849848
});
@@ -916,9 +915,7 @@ describe('outputSchema validation', () => {
916915
server.setRequestHandler(CallToolRequestSchema, async (request) => {
917916
if (request.params.name === 'test-tool') {
918917
// Return invalid structured content (count is string instead of number)
919-
return {
920-
structuredContent: { result: 'success', count: 'not a number' },
921-
};
918+
return createCallToolStructuredResult({ result: 'success', count: 'not a number' });
922919
}
923920
throw new Error('Unknown tool');
924921
});
@@ -1145,17 +1142,15 @@ describe('outputSchema validation', () => {
11451142

11461143
server.setRequestHandler(CallToolRequestSchema, async (request) => {
11471144
if (request.params.name === 'complex-tool') {
1148-
return {
1149-
structuredContent: {
1150-
name: 'John Doe',
1151-
age: 30,
1152-
active: true,
1153-
tags: ['user', 'admin'],
1154-
metadata: {
1155-
created: '2023-01-01T00:00:00Z',
1156-
},
1145+
return createCallToolStructuredResult({
1146+
name: 'John Doe',
1147+
age: 30,
1148+
active: true,
1149+
tags: ['user', 'admin'],
1150+
metadata: {
1151+
created: '2023-01-01T00:00:00Z',
11571152
},
1158-
};
1153+
});
11591154
}
11601155
throw new Error('Unknown tool');
11611156
});
@@ -1230,12 +1225,10 @@ describe('outputSchema validation', () => {
12301225
server.setRequestHandler(CallToolRequestSchema, async (request) => {
12311226
if (request.params.name === 'strict-tool') {
12321227
// Return structured content with extra property
1233-
return {
1234-
structuredContent: {
1228+
return createCallToolStructuredResult({
12351229
name: 'John',
12361230
extraField: 'not allowed',
1237-
},
1238-
};
1231+
});
12391232
}
12401233
throw new Error('Unknown tool');
12411234
});

src/examples/server/mcpServerOutputSchema.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This demonstrates how to easily create tools with structured output
55
*/
66

7-
import { McpServer } from "../../server/mcp.js";
7+
import { createCallToolStructuredResult, McpServer } from "../../server/mcp.js";
88
import { StdioServerTransport } from "../../server/stdio.js";
99
import { z } from "zod";
1010

@@ -45,20 +45,18 @@ server.registerTool(
4545
const temp_c = Math.round((Math.random() * 35 - 5) * 10) / 10;
4646
const conditions = ["sunny", "cloudy", "rainy", "stormy", "snowy"][Math.floor(Math.random() * 5)];
4747

48-
return {
49-
structuredContent: {
50-
temperature: {
51-
celsius: temp_c,
52-
fahrenheit: Math.round((temp_c * 9/5 + 32) * 10) / 10
53-
},
54-
conditions,
55-
humidity: Math.round(Math.random() * 100),
56-
wind: {
57-
speed_kmh: Math.round(Math.random() * 50),
58-
direction: ["N", "NE", "E", "SE", "S", "SW", "W", "NW"][Math.floor(Math.random() * 8)]
59-
}
48+
return createCallToolStructuredResult({
49+
temperature: {
50+
celsius: temp_c,
51+
fahrenheit: Math.round((temp_c * 9/5 + 32) * 10) / 10
52+
},
53+
conditions,
54+
humidity: Math.round(Math.random() * 100),
55+
wind: {
56+
speed_kmh: Math.round(Math.random() * 50),
57+
direction: ["N", "NE", "E", "SE", "S", "SW", "W", "NW"][Math.floor(Math.random() * 8)]
6058
}
61-
};
59+
})
6260
}
6361
);
6462

src/server/mcp.test.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { ResourceTemplate } from "./mcp.js";
1919
import { completable } from "./completable.js";
2020
import { UriTemplate } from "../shared/uriTemplate.js";
21+
import { createCallToolStructuredResult } from "./mcp.js";
2122

2223
describe("McpServer", () => {
2324
/***
@@ -1082,12 +1083,10 @@ describe("tool()", () => {
10821083
timestamp: z.string()
10831084
},
10841085
},
1085-
async ({ input }) => ({
1086-
structuredContent: {
1086+
async ({ input }) => createCallToolStructuredResult({
10871087
processedInput: input,
10881088
resultType: "structured",
10891089
timestamp: "2023-01-01T00:00:00Z"
1090-
},
10911090
})
10921091
);
10931092

@@ -1185,13 +1184,11 @@ describe("tool()", () => {
11851184
timestamp: z.string()
11861185
},
11871186
},
1188-
async ({ input }) => ({
1189-
structuredContent: {
1187+
async ({ input }) => createCallToolStructuredResult({
11901188
processedInput: input,
11911189
resultType: "structured",
11921190
// Missing required 'timestamp' field
11931191
someExtraField: "unexpected" // Extra field not in schema
1194-
},
11951192
})
11961193
);
11971194

src/server/mcp.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,3 +1161,37 @@ const EMPTY_COMPLETION_RESULT: CompleteResult = {
11611161
hasMore: false,
11621162
},
11631163
};
1164+
1165+
/**
1166+
* The spec encourages structured tools to return a `content` field
1167+
* containing a stringified version of the structured content for backward
1168+
* compatibility.
1169+
*
1170+
* Use this function to create a CallToolStructuredResult with a content
1171+
* field from your structured content, like so:
1172+
*
1173+
* async ({ input }) => createCallToolStructuredResult({
1174+
* firstProp: "first",
1175+
* secondProp: 2,
1176+
* // ...
1177+
* })
1178+
*
1179+
* Note: in SDK versions 1.11.*, the content field is required for all tool
1180+
* call results, in the interest of backward compatibility. Later versions
1181+
* of the SDK will allow tools to omit the content field if they return
1182+
* structured content.
1183+
*/
1184+
export function createCallToolStructuredResult(
1185+
structuredContent: CallToolResult["structuredContent"]
1186+
): CallToolResult {
1187+
return {
1188+
structuredContent,
1189+
content: [
1190+
{
1191+
type: "text",
1192+
text: JSON.stringify(structuredContent, null, 2),
1193+
},
1194+
],
1195+
};
1196+
}
1197+

src/types.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -924,14 +924,14 @@ export const CallToolStructuredResultSchema = ResultSchema.extend({
924924
/**
925925
* A list of content objects that represent the result of the tool call.
926926
*
927-
* If the Tool defines an outputSchema, this field MAY be present in the result.
927+
* Per the spec, if the Tool defines an outputSchema, this field MAY be present in the result.
928928
*
929-
* Tools may use this field to provide compatibility with older clients that
930-
* do not support structured content.
929+
* In this SDK we automatically generate backwards-compatible `content` for older clients,
930+
* so this field can be defined as non-optional.
931931
*
932932
* Clients that support structured content should ignore this field.
933933
*/
934-
content: z.optional(ContentListSchema),
934+
content: ContentListSchema,
935935

936936
/**
937937
* Whether the tool call ended in an error.

0 commit comments

Comments
 (0)