Skip to content

Commit 25ddad2

Browse files
committed
feat(mcp): add structuredContent support and fix CallToolResult type issues
- Add useStructuredContent option to MCP servers (stdio/streamable-http/SSE) - Return full CallToolResult from callTool() instead of just content array - Enable structured content support with JSON string returns for Python SDK consistency - Fix TypeScript compilation errors (TS2352) in MCP server shims - Add comprehensive tests for structuredContent feature - Update test mocks to use proper CallToolResult interface - Add changeset documenting the breaking change Breaking Change: MCPServer.callTool() now returns CallToolResult (full object) instead of CallToolResultContent (content array only). This enables access to optional structuredContent while maintaining backward compatibility through the content property. Resolves compilation errors and adds structured content capabilities.
1 parent 3984db8 commit 25ddad2

File tree

7 files changed

+533
-36
lines changed

7 files changed

+533
-36
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@openai/agents-core': patch
3+
---
4+
5+
feat(mcp): add structuredContent support behind `useStructuredContent`; return full CallToolResult from `callTool`
6+
7+
- `MCPServer#callTool` now returns the full `CallToolResult` (was `content[]`), exposing optional `structuredContent`.
8+
- Add `useStructuredContent` option to MCP servers (stdio/streamable-http/SSE), default `false` to avoid duplicate data by default.
9+
- When enabled, function tool outputs return JSON strings for consistency with Python SDK implementation.

packages/agents-core/src/mcp.ts

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,28 +35,35 @@ export const DEFAULT_SSE_MCP_CLIENT_LOGGER_NAME =
3535
export interface MCPServer {
3636
cacheToolsList: boolean;
3737
toolFilter?: MCPToolFilterCallable | MCPToolFilterStatic;
38+
39+
/**
40+
* Whether to include structuredContent in tool outputs when available.
41+
*/
42+
useStructuredContent?: boolean;
3843
connect(): Promise<void>;
3944
readonly name: string;
4045
close(): Promise<void>;
4146
listTools(): Promise<MCPTool[]>;
4247
callTool(
4348
toolName: string,
4449
args: Record<string, unknown> | null,
45-
): Promise<CallToolResultContent>;
50+
): Promise<CallToolResult>;
4651
invalidateToolsCache(): Promise<void>;
4752
}
4853

4954
export abstract class BaseMCPServerStdio implements MCPServer {
5055
public cacheToolsList: boolean;
5156
protected _cachedTools: any[] | undefined = undefined;
5257
public toolFilter?: MCPToolFilterCallable | MCPToolFilterStatic;
58+
public useStructuredContent?: boolean;
5359

5460
protected logger: Logger;
5561
constructor(options: MCPServerStdioOptions) {
5662
this.logger =
5763
options.logger ?? getLogger(DEFAULT_STDIO_MCP_CLIENT_LOGGER_NAME);
5864
this.cacheToolsList = options.cacheToolsList ?? false;
5965
this.toolFilter = options.toolFilter;
66+
this.useStructuredContent = options.useStructuredContent ?? false;
6067
}
6168

6269
abstract get name(): string;
@@ -66,7 +73,7 @@ export abstract class BaseMCPServerStdio implements MCPServer {
6673
abstract callTool(
6774
_toolName: string,
6875
_args: Record<string, unknown> | null,
69-
): Promise<CallToolResultContent>;
76+
): Promise<CallToolResult>;
7077
abstract invalidateToolsCache(): Promise<void>;
7178

7279
/**
@@ -85,6 +92,7 @@ export abstract class BaseMCPServerStreamableHttp implements MCPServer {
8592
public cacheToolsList: boolean;
8693
protected _cachedTools: any[] | undefined = undefined;
8794
public toolFilter?: MCPToolFilterCallable | MCPToolFilterStatic;
95+
public useStructuredContent?: boolean;
8896

8997
protected logger: Logger;
9098
constructor(options: MCPServerStreamableHttpOptions) {
@@ -93,6 +101,7 @@ export abstract class BaseMCPServerStreamableHttp implements MCPServer {
93101
getLogger(DEFAULT_STREAMABLE_HTTP_MCP_CLIENT_LOGGER_NAME);
94102
this.cacheToolsList = options.cacheToolsList ?? false;
95103
this.toolFilter = options.toolFilter;
104+
this.useStructuredContent = options.useStructuredContent ?? false;
96105
}
97106

98107
abstract get name(): string;
@@ -102,7 +111,7 @@ export abstract class BaseMCPServerStreamableHttp implements MCPServer {
102111
abstract callTool(
103112
_toolName: string,
104113
_args: Record<string, unknown> | null,
105-
): Promise<CallToolResultContent>;
114+
): Promise<CallToolResult>;
106115
abstract invalidateToolsCache(): Promise<void>;
107116

108117
/**
@@ -121,13 +130,15 @@ export abstract class BaseMCPServerSSE implements MCPServer {
121130
public cacheToolsList: boolean;
122131
protected _cachedTools: any[] | undefined = undefined;
123132
public toolFilter?: MCPToolFilterCallable | MCPToolFilterStatic;
133+
public useStructuredContent?: boolean;
124134

125135
protected logger: Logger;
126136
constructor(options: MCPServerSSEOptions) {
127137
this.logger =
128138
options.logger ?? getLogger(DEFAULT_SSE_MCP_CLIENT_LOGGER_NAME);
129139
this.cacheToolsList = options.cacheToolsList ?? false;
130140
this.toolFilter = options.toolFilter;
141+
this.useStructuredContent = options.useStructuredContent ?? false;
131142
}
132143

133144
abstract get name(): string;
@@ -137,7 +148,7 @@ export abstract class BaseMCPServerSSE implements MCPServer {
137148
abstract callTool(
138149
_toolName: string,
139150
_args: Record<string, unknown> | null,
140-
): Promise<CallToolResultContent>;
151+
): Promise<CallToolResult>;
141152
abstract invalidateToolsCache(): Promise<void>;
142153

143154
/**
@@ -201,7 +212,7 @@ export class MCPServerStdio extends BaseMCPServerStdio {
201212
callTool(
202213
toolName: string,
203214
args: Record<string, unknown> | null,
204-
): Promise<CallToolResultContent> {
215+
): Promise<CallToolResult> {
205216
return this.underlying.callTool(toolName, args);
206217
}
207218
invalidateToolsCache(): Promise<void> {
@@ -237,7 +248,7 @@ export class MCPServerStreamableHttp extends BaseMCPServerStreamableHttp {
237248
callTool(
238249
toolName: string,
239250
args: Record<string, unknown> | null,
240-
): Promise<CallToolResultContent> {
251+
): Promise<CallToolResult> {
241252
return this.underlying.callTool(toolName, args);
242253
}
243254
invalidateToolsCache(): Promise<void> {
@@ -273,7 +284,7 @@ export class MCPServerSSE extends BaseMCPServerSSE {
273284
callTool(
274285
toolName: string,
275286
args: Record<string, unknown> | null,
276-
): Promise<CallToolResultContent> {
287+
): Promise<CallToolResult> {
277288
return this.underlying.callTool(toolName, args);
278289
}
279290
invalidateToolsCache(): Promise<void> {
@@ -446,6 +457,7 @@ export async function getAllMcpTools<TContext = UnknownContext>(
446457

447458
/**
448459
* Converts an MCP tool definition to a function tool for the Agents SDK.
460+
* When useStructuredContent is enabled, returns JSON strings for consistency with Python SDK.
449461
*/
450462
export function mcpToFunctionTool(
451463
mcpTool: MCPTool,
@@ -463,8 +475,36 @@ export function mcpToFunctionTool(
463475
if (currentSpan) {
464476
currentSpan.spanData['mcp_data'] = { server: server.name };
465477
}
466-
const content = await server.callTool(mcpTool.name, args);
467-
return content.length === 1 ? content[0] : content;
478+
const result = await server.callTool(mcpTool.name, args);
479+
480+
if (result.content && result.content.length === 1) {
481+
if (
482+
server.useStructuredContent &&
483+
'structuredContent' in result &&
484+
result.structuredContent !== undefined
485+
) {
486+
return JSON.stringify([result.content[0], result.structuredContent]);
487+
}
488+
return result.content[0];
489+
} else if (result.content && result.content.length > 1) {
490+
if (
491+
server.useStructuredContent &&
492+
'structuredContent' in result &&
493+
result.structuredContent !== undefined
494+
) {
495+
const outputs = [...result.content, result.structuredContent];
496+
return JSON.stringify(outputs);
497+
}
498+
return result.content;
499+
} else if (
500+
server.useStructuredContent &&
501+
'structuredContent' in result &&
502+
result.structuredContent !== undefined
503+
) {
504+
return JSON.stringify(result.structuredContent);
505+
}
506+
// Preserve backward compatibility: return empty array when no content
507+
return result.content || [];
468508
}
469509

470510
const schema: JsonObjectSchema<any> = {
@@ -533,6 +573,11 @@ export interface BaseMCPServerStdioOptions {
533573
encodingErrorHandler?: 'strict' | 'ignore' | 'replace';
534574
logger?: Logger;
535575
toolFilter?: MCPToolFilterCallable | MCPToolFilterStatic;
576+
577+
/**
578+
* Whether to include structuredContent in tool outputs when available.
579+
*/
580+
useStructuredContent?: boolean;
536581
timeout?: number;
537582
}
538583
export interface DefaultMCPServerStdioOptions
@@ -555,6 +600,11 @@ export interface MCPServerStreamableHttpOptions {
555600
name?: string;
556601
logger?: Logger;
557602
toolFilter?: MCPToolFilterCallable | MCPToolFilterStatic;
603+
604+
/**
605+
* Whether to include structuredContent in tool outputs when available.
606+
*/
607+
useStructuredContent?: boolean;
558608
timeout?: number;
559609

560610
// ----------------------------------------------------
@@ -579,6 +629,11 @@ export interface MCPServerSSEOptions {
579629
name?: string;
580630
logger?: Logger;
581631
toolFilter?: MCPToolFilterCallable | MCPToolFilterStatic;
632+
633+
/**
634+
* Whether to include structuredContent in tool outputs when available.
635+
*/
636+
useStructuredContent?: boolean;
582637
timeout?: number;
583638

584639
// ----------------------------------------------------
@@ -621,9 +676,22 @@ export interface JsonRpcResponse {
621676
error?: any;
622677
}
623678

679+
/**
680+
* Structured content that can be returned by MCP tools.
681+
* Supports various data types including objects, arrays, primitives, and null.
682+
*/
683+
export type StructuredContent =
684+
| Record<string, unknown>
685+
| unknown[]
686+
| string
687+
| number
688+
| boolean
689+
| null;
690+
624691
export interface CallToolResponse extends JsonRpcResponse {
625692
result: {
626693
content: { type: string; text: string }[];
694+
structuredContent?: StructuredContent;
627695
};
628696
}
629697
export type CallToolResult = CallToolResponse['result'];

packages/agents-core/src/shims/mcp-server/browser.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
BaseMCPServerSSE,
33
BaseMCPServerStdio,
44
BaseMCPServerStreamableHttp,
5-
CallToolResultContent,
5+
CallToolResult,
66
MCPServerSSEOptions,
77
MCPServerStdioOptions,
88
MCPServerStreamableHttpOptions,
@@ -28,7 +28,7 @@ export class MCPServerStdio extends BaseMCPServerStdio {
2828
callTool(
2929
_toolName: string,
3030
_args: Record<string, unknown> | null,
31-
): Promise<CallToolResultContent> {
31+
): Promise<CallToolResult> {
3232
throw new Error('Method not implemented.');
3333
}
3434
invalidateToolsCache(): Promise<void> {
@@ -55,7 +55,7 @@ export class MCPServerStreamableHttp extends BaseMCPServerStreamableHttp {
5555
callTool(
5656
_toolName: string,
5757
_args: Record<string, unknown> | null,
58-
): Promise<CallToolResultContent> {
58+
): Promise<CallToolResult> {
5959
throw new Error('Method not implemented.');
6060
}
6161
invalidateToolsCache(): Promise<void> {
@@ -84,7 +84,7 @@ export class MCPServerSSE extends BaseMCPServerSSE {
8484
callTool(
8585
_toolName: string,
8686
_args: Record<string, unknown> | null,
87-
): Promise<CallToolResultContent> {
87+
): Promise<CallToolResult> {
8888
throw new Error('Method not implemented.');
8989
}
9090

packages/agents-core/src/shims/mcp-server/node.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
BaseMCPServerStdio,
66
BaseMCPServerStreamableHttp,
77
BaseMCPServerSSE,
8-
CallToolResultContent,
8+
CallToolResult,
99
DefaultMCPServerStdioOptions,
1010
InitializeResult,
1111
MCPServerStdioOptions,
@@ -124,7 +124,7 @@ export class NodeMCPServerStdio extends BaseMCPServerStdio {
124124
async callTool(
125125
toolName: string,
126126
args: Record<string, unknown> | null,
127-
): Promise<CallToolResultContent> {
127+
): Promise<CallToolResult> {
128128
const { CallToolResultSchema } = await import(
129129
'@modelcontextprotocol/sdk/types.js'
130130
).catch(failedToImport);
@@ -144,12 +144,12 @@ export class NodeMCPServerStdio extends BaseMCPServerStdio {
144144
},
145145
);
146146
const parsed = CallToolResultSchema.parse(response);
147-
const result = parsed.content;
147+
const result = parsed;
148148
this.debugLog(
149149
() =>
150-
`Called tool ${toolName} (args: ${JSON.stringify(args)}, result: ${JSON.stringify(result)})`,
150+
`Called tool ${toolName} (args: ${JSON.stringify(args)}, result: ${JSON.stringify(result.content)})`,
151151
);
152-
return result as CallToolResultContent;
152+
return result as CallToolResult;
153153
}
154154

155155
get name() {
@@ -245,7 +245,7 @@ export class NodeMCPServerSSE extends BaseMCPServerSSE {
245245
async callTool(
246246
toolName: string,
247247
args: Record<string, unknown> | null,
248-
): Promise<CallToolResultContent> {
248+
): Promise<CallToolResult> {
249249
const { CallToolResultSchema } = await import(
250250
'@modelcontextprotocol/sdk/types.js'
251251
).catch(failedToImport);
@@ -265,12 +265,12 @@ export class NodeMCPServerSSE extends BaseMCPServerSSE {
265265
},
266266
);
267267
const parsed = CallToolResultSchema.parse(response);
268-
const result = parsed.content;
268+
const result = parsed;
269269
this.debugLog(
270270
() =>
271-
`Called tool ${toolName} (args: ${JSON.stringify(args)}, result: ${JSON.stringify(result)})`,
271+
`Called tool ${toolName} (args: ${JSON.stringify(args)}, result: ${JSON.stringify(result.content)})`,
272272
);
273-
return result as CallToolResultContent;
273+
return result as CallToolResult;
274274
}
275275

276276
get name() {
@@ -371,7 +371,7 @@ export class NodeMCPServerStreamableHttp extends BaseMCPServerStreamableHttp {
371371
async callTool(
372372
toolName: string,
373373
args: Record<string, unknown> | null,
374-
): Promise<CallToolResultContent> {
374+
): Promise<CallToolResult> {
375375
const { CallToolResultSchema } = await import(
376376
'@modelcontextprotocol/sdk/types.js'
377377
).catch(failedToImport);
@@ -391,12 +391,12 @@ export class NodeMCPServerStreamableHttp extends BaseMCPServerStreamableHttp {
391391
},
392392
);
393393
const parsed = CallToolResultSchema.parse(response);
394-
const result = parsed.content;
394+
const result = parsed;
395395
this.debugLog(
396396
() =>
397-
`Called tool ${toolName} (args: ${JSON.stringify(args)}, result: ${JSON.stringify(result)})`,
397+
`Called tool ${toolName} (args: ${JSON.stringify(args)}, result: ${JSON.stringify(result.content)})`,
398398
);
399-
return result as CallToolResultContent;
399+
return result as CallToolResult;
400400
}
401401

402402
get name() {

packages/agents-core/test/mcpCache.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getAllMcpTools } from '../src/mcp';
33
import type { FunctionTool } from '../src/tool';
44
import { withTrace } from '../src/tracing';
55
import { NodeMCPServerStdio } from '../src/shims/mcp-server/node';
6-
import type { CallToolResultContent } from '../src/mcp';
6+
import type { CallToolResult } from '../src/mcp';
77
import { RunContext } from '../src/runContext';
88
import { Agent } from '../src/agent';
99

@@ -27,8 +27,8 @@ class StubServer extends NodeMCPServerStdio {
2727
async callTool(
2828
_toolName: string,
2929
_args: Record<string, unknown> | null,
30-
): Promise<CallToolResultContent> {
31-
return [];
30+
): Promise<CallToolResult> {
31+
return { content: [] };
3232
}
3333
}
3434

0 commit comments

Comments
 (0)