Skip to content

Commit be686e9

Browse files
authored
feat(mcp): add structuredContent support and fix CallToolResult type issues (#471)
1 parent 926bc13 commit be686e9

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)