From 25ddad2da3f416f843bc7c1fa257c8b7c5be953d Mon Sep 17 00:00:00 2001 From: SkinnnyJay Date: Fri, 26 Sep 2025 08:31:47 -0700 Subject: [PATCH] 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. --- .changeset/mcp-structured-content.md | 9 + packages/agents-core/src/mcp.ts | 86 +++- .../src/shims/mcp-server/browser.ts | 8 +- .../agents-core/src/shims/mcp-server/node.ts | 26 +- packages/agents-core/test/mcpCache.test.ts | 6 +- .../test/mcpStructuredContent.test.ts | 418 ++++++++++++++++++ .../test/mcpToolFilter.integration.test.ts | 16 +- 7 files changed, 533 insertions(+), 36 deletions(-) create mode 100644 .changeset/mcp-structured-content.md create mode 100644 packages/agents-core/test/mcpStructuredContent.test.ts diff --git a/.changeset/mcp-structured-content.md b/.changeset/mcp-structured-content.md new file mode 100644 index 00000000..ec3d9c3d --- /dev/null +++ b/.changeset/mcp-structured-content.md @@ -0,0 +1,9 @@ +--- +'@openai/agents-core': patch +--- + +feat(mcp): add structuredContent support behind `useStructuredContent`; return full CallToolResult from `callTool` + +- `MCPServer#callTool` now returns the full `CallToolResult` (was `content[]`), exposing optional `structuredContent`. +- Add `useStructuredContent` option to MCP servers (stdio/streamable-http/SSE), default `false` to avoid duplicate data by default. +- When enabled, function tool outputs return JSON strings for consistency with Python SDK implementation. diff --git a/packages/agents-core/src/mcp.ts b/packages/agents-core/src/mcp.ts index 1aeda9d3..e937338f 100644 --- a/packages/agents-core/src/mcp.ts +++ b/packages/agents-core/src/mcp.ts @@ -35,6 +35,11 @@ export const DEFAULT_SSE_MCP_CLIENT_LOGGER_NAME = export interface MCPServer { cacheToolsList: boolean; toolFilter?: MCPToolFilterCallable | MCPToolFilterStatic; + + /** + * Whether to include structuredContent in tool outputs when available. + */ + useStructuredContent?: boolean; connect(): Promise; readonly name: string; close(): Promise; @@ -42,7 +47,7 @@ export interface MCPServer { callTool( toolName: string, args: Record | null, - ): Promise; + ): Promise; invalidateToolsCache(): Promise; } @@ -50,6 +55,7 @@ export abstract class BaseMCPServerStdio implements MCPServer { public cacheToolsList: boolean; protected _cachedTools: any[] | undefined = undefined; public toolFilter?: MCPToolFilterCallable | MCPToolFilterStatic; + public useStructuredContent?: boolean; protected logger: Logger; constructor(options: MCPServerStdioOptions) { @@ -57,6 +63,7 @@ export abstract class BaseMCPServerStdio implements MCPServer { options.logger ?? getLogger(DEFAULT_STDIO_MCP_CLIENT_LOGGER_NAME); this.cacheToolsList = options.cacheToolsList ?? false; this.toolFilter = options.toolFilter; + this.useStructuredContent = options.useStructuredContent ?? false; } abstract get name(): string; @@ -66,7 +73,7 @@ export abstract class BaseMCPServerStdio implements MCPServer { abstract callTool( _toolName: string, _args: Record | null, - ): Promise; + ): Promise; abstract invalidateToolsCache(): Promise; /** @@ -85,6 +92,7 @@ export abstract class BaseMCPServerStreamableHttp implements MCPServer { public cacheToolsList: boolean; protected _cachedTools: any[] | undefined = undefined; public toolFilter?: MCPToolFilterCallable | MCPToolFilterStatic; + public useStructuredContent?: boolean; protected logger: Logger; constructor(options: MCPServerStreamableHttpOptions) { @@ -93,6 +101,7 @@ export abstract class BaseMCPServerStreamableHttp implements MCPServer { getLogger(DEFAULT_STREAMABLE_HTTP_MCP_CLIENT_LOGGER_NAME); this.cacheToolsList = options.cacheToolsList ?? false; this.toolFilter = options.toolFilter; + this.useStructuredContent = options.useStructuredContent ?? false; } abstract get name(): string; @@ -102,7 +111,7 @@ export abstract class BaseMCPServerStreamableHttp implements MCPServer { abstract callTool( _toolName: string, _args: Record | null, - ): Promise; + ): Promise; abstract invalidateToolsCache(): Promise; /** @@ -121,6 +130,7 @@ export abstract class BaseMCPServerSSE implements MCPServer { public cacheToolsList: boolean; protected _cachedTools: any[] | undefined = undefined; public toolFilter?: MCPToolFilterCallable | MCPToolFilterStatic; + public useStructuredContent?: boolean; protected logger: Logger; constructor(options: MCPServerSSEOptions) { @@ -128,6 +138,7 @@ export abstract class BaseMCPServerSSE implements MCPServer { options.logger ?? getLogger(DEFAULT_SSE_MCP_CLIENT_LOGGER_NAME); this.cacheToolsList = options.cacheToolsList ?? false; this.toolFilter = options.toolFilter; + this.useStructuredContent = options.useStructuredContent ?? false; } abstract get name(): string; @@ -137,7 +148,7 @@ export abstract class BaseMCPServerSSE implements MCPServer { abstract callTool( _toolName: string, _args: Record | null, - ): Promise; + ): Promise; abstract invalidateToolsCache(): Promise; /** @@ -201,7 +212,7 @@ export class MCPServerStdio extends BaseMCPServerStdio { callTool( toolName: string, args: Record | null, - ): Promise { + ): Promise { return this.underlying.callTool(toolName, args); } invalidateToolsCache(): Promise { @@ -237,7 +248,7 @@ export class MCPServerStreamableHttp extends BaseMCPServerStreamableHttp { callTool( toolName: string, args: Record | null, - ): Promise { + ): Promise { return this.underlying.callTool(toolName, args); } invalidateToolsCache(): Promise { @@ -273,7 +284,7 @@ export class MCPServerSSE extends BaseMCPServerSSE { callTool( toolName: string, args: Record | null, - ): Promise { + ): Promise { return this.underlying.callTool(toolName, args); } invalidateToolsCache(): Promise { @@ -446,6 +457,7 @@ export async function getAllMcpTools( /** * Converts an MCP tool definition to a function tool for the Agents SDK. + * When useStructuredContent is enabled, returns JSON strings for consistency with Python SDK. */ export function mcpToFunctionTool( mcpTool: MCPTool, @@ -463,8 +475,36 @@ export function mcpToFunctionTool( if (currentSpan) { currentSpan.spanData['mcp_data'] = { server: server.name }; } - const content = await server.callTool(mcpTool.name, args); - return content.length === 1 ? content[0] : content; + const result = await server.callTool(mcpTool.name, args); + + if (result.content && result.content.length === 1) { + if ( + server.useStructuredContent && + 'structuredContent' in result && + result.structuredContent !== undefined + ) { + return JSON.stringify([result.content[0], result.structuredContent]); + } + return result.content[0]; + } else if (result.content && result.content.length > 1) { + if ( + server.useStructuredContent && + 'structuredContent' in result && + result.structuredContent !== undefined + ) { + const outputs = [...result.content, result.structuredContent]; + return JSON.stringify(outputs); + } + return result.content; + } else if ( + server.useStructuredContent && + 'structuredContent' in result && + result.structuredContent !== undefined + ) { + return JSON.stringify(result.structuredContent); + } + // Preserve backward compatibility: return empty array when no content + return result.content || []; } const schema: JsonObjectSchema = { @@ -533,6 +573,11 @@ export interface BaseMCPServerStdioOptions { encodingErrorHandler?: 'strict' | 'ignore' | 'replace'; logger?: Logger; toolFilter?: MCPToolFilterCallable | MCPToolFilterStatic; + + /** + * Whether to include structuredContent in tool outputs when available. + */ + useStructuredContent?: boolean; timeout?: number; } export interface DefaultMCPServerStdioOptions @@ -555,6 +600,11 @@ export interface MCPServerStreamableHttpOptions { name?: string; logger?: Logger; toolFilter?: MCPToolFilterCallable | MCPToolFilterStatic; + + /** + * Whether to include structuredContent in tool outputs when available. + */ + useStructuredContent?: boolean; timeout?: number; // ---------------------------------------------------- @@ -579,6 +629,11 @@ export interface MCPServerSSEOptions { name?: string; logger?: Logger; toolFilter?: MCPToolFilterCallable | MCPToolFilterStatic; + + /** + * Whether to include structuredContent in tool outputs when available. + */ + useStructuredContent?: boolean; timeout?: number; // ---------------------------------------------------- @@ -621,9 +676,22 @@ export interface JsonRpcResponse { error?: any; } +/** + * Structured content that can be returned by MCP tools. + * Supports various data types including objects, arrays, primitives, and null. + */ +export type StructuredContent = + | Record + | unknown[] + | string + | number + | boolean + | null; + export interface CallToolResponse extends JsonRpcResponse { result: { content: { type: string; text: string }[]; + structuredContent?: StructuredContent; }; } export type CallToolResult = CallToolResponse['result']; diff --git a/packages/agents-core/src/shims/mcp-server/browser.ts b/packages/agents-core/src/shims/mcp-server/browser.ts index 51b475b7..7d809556 100644 --- a/packages/agents-core/src/shims/mcp-server/browser.ts +++ b/packages/agents-core/src/shims/mcp-server/browser.ts @@ -2,7 +2,7 @@ import { BaseMCPServerSSE, BaseMCPServerStdio, BaseMCPServerStreamableHttp, - CallToolResultContent, + CallToolResult, MCPServerSSEOptions, MCPServerStdioOptions, MCPServerStreamableHttpOptions, @@ -28,7 +28,7 @@ export class MCPServerStdio extends BaseMCPServerStdio { callTool( _toolName: string, _args: Record | null, - ): Promise { + ): Promise { throw new Error('Method not implemented.'); } invalidateToolsCache(): Promise { @@ -55,7 +55,7 @@ export class MCPServerStreamableHttp extends BaseMCPServerStreamableHttp { callTool( _toolName: string, _args: Record | null, - ): Promise { + ): Promise { throw new Error('Method not implemented.'); } invalidateToolsCache(): Promise { @@ -84,7 +84,7 @@ export class MCPServerSSE extends BaseMCPServerSSE { callTool( _toolName: string, _args: Record | null, - ): Promise { + ): Promise { throw new Error('Method not implemented.'); } diff --git a/packages/agents-core/src/shims/mcp-server/node.ts b/packages/agents-core/src/shims/mcp-server/node.ts index 00702690..73b4540d 100644 --- a/packages/agents-core/src/shims/mcp-server/node.ts +++ b/packages/agents-core/src/shims/mcp-server/node.ts @@ -5,7 +5,7 @@ import { BaseMCPServerStdio, BaseMCPServerStreamableHttp, BaseMCPServerSSE, - CallToolResultContent, + CallToolResult, DefaultMCPServerStdioOptions, InitializeResult, MCPServerStdioOptions, @@ -124,7 +124,7 @@ export class NodeMCPServerStdio extends BaseMCPServerStdio { async callTool( toolName: string, args: Record | null, - ): Promise { + ): Promise { const { CallToolResultSchema } = await import( '@modelcontextprotocol/sdk/types.js' ).catch(failedToImport); @@ -144,12 +144,12 @@ export class NodeMCPServerStdio extends BaseMCPServerStdio { }, ); const parsed = CallToolResultSchema.parse(response); - const result = parsed.content; + const result = parsed; this.debugLog( () => - `Called tool ${toolName} (args: ${JSON.stringify(args)}, result: ${JSON.stringify(result)})`, + `Called tool ${toolName} (args: ${JSON.stringify(args)}, result: ${JSON.stringify(result.content)})`, ); - return result as CallToolResultContent; + return result as CallToolResult; } get name() { @@ -245,7 +245,7 @@ export class NodeMCPServerSSE extends BaseMCPServerSSE { async callTool( toolName: string, args: Record | null, - ): Promise { + ): Promise { const { CallToolResultSchema } = await import( '@modelcontextprotocol/sdk/types.js' ).catch(failedToImport); @@ -265,12 +265,12 @@ export class NodeMCPServerSSE extends BaseMCPServerSSE { }, ); const parsed = CallToolResultSchema.parse(response); - const result = parsed.content; + const result = parsed; this.debugLog( () => - `Called tool ${toolName} (args: ${JSON.stringify(args)}, result: ${JSON.stringify(result)})`, + `Called tool ${toolName} (args: ${JSON.stringify(args)}, result: ${JSON.stringify(result.content)})`, ); - return result as CallToolResultContent; + return result as CallToolResult; } get name() { @@ -371,7 +371,7 @@ export class NodeMCPServerStreamableHttp extends BaseMCPServerStreamableHttp { async callTool( toolName: string, args: Record | null, - ): Promise { + ): Promise { const { CallToolResultSchema } = await import( '@modelcontextprotocol/sdk/types.js' ).catch(failedToImport); @@ -391,12 +391,12 @@ export class NodeMCPServerStreamableHttp extends BaseMCPServerStreamableHttp { }, ); const parsed = CallToolResultSchema.parse(response); - const result = parsed.content; + const result = parsed; this.debugLog( () => - `Called tool ${toolName} (args: ${JSON.stringify(args)}, result: ${JSON.stringify(result)})`, + `Called tool ${toolName} (args: ${JSON.stringify(args)}, result: ${JSON.stringify(result.content)})`, ); - return result as CallToolResultContent; + return result as CallToolResult; } get name() { diff --git a/packages/agents-core/test/mcpCache.test.ts b/packages/agents-core/test/mcpCache.test.ts index 42c83eb5..4636aa3c 100644 --- a/packages/agents-core/test/mcpCache.test.ts +++ b/packages/agents-core/test/mcpCache.test.ts @@ -3,7 +3,7 @@ import { getAllMcpTools } from '../src/mcp'; import type { FunctionTool } from '../src/tool'; import { withTrace } from '../src/tracing'; import { NodeMCPServerStdio } from '../src/shims/mcp-server/node'; -import type { CallToolResultContent } from '../src/mcp'; +import type { CallToolResult } from '../src/mcp'; import { RunContext } from '../src/runContext'; import { Agent } from '../src/agent'; @@ -27,8 +27,8 @@ class StubServer extends NodeMCPServerStdio { async callTool( _toolName: string, _args: Record | null, - ): Promise { - return []; + ): Promise { + return { content: [] }; } } diff --git a/packages/agents-core/test/mcpStructuredContent.test.ts b/packages/agents-core/test/mcpStructuredContent.test.ts new file mode 100644 index 00000000..35c61957 --- /dev/null +++ b/packages/agents-core/test/mcpStructuredContent.test.ts @@ -0,0 +1,418 @@ +import { describe, it, expect } from 'vitest'; +import { mcpToFunctionTool } from '../src/mcp'; +import { NodeMCPServerStdio } from '../src/shims/mcp-server/node'; +import type { CallToolResult } from '../src/mcp'; + +class StubServer extends NodeMCPServerStdio { + public toolList: any[]; + constructor(name: string, tools: any[], useStructuredContent?: boolean) { + super({ command: 'noop', name, useStructuredContent }); + this.toolList = tools; + this.cacheToolsList = false; + } + async connect(): Promise {} + async close(): Promise {} + async listTools(): Promise { + this._toolsList = this.toolList; + return this.toolList; + } + async callTool( + _toolName: string, + _args: Record | null, + ): Promise { + // default gets overridden in tests via monkey patching + return { content: [] } as CallToolResult; + } +} + +describe('MCP structuredContent handling', () => { + it('omits structuredContent by default and returns single item object', async () => { + const server = new StubServer( + 's', + [ + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + ], + false, + ); + // Patch callTool to return one content and structuredContent + (server as any).callTool = async () => ({ + content: [{ type: 'text', text: 'hello' }], + structuredContent: { foo: 1 }, + }); + + const tool = mcpToFunctionTool( + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + server, + false, + ); + + const out = await tool.invoke({} as any, '{}'); + // when not using structured content, return the single content object + expect(out).toEqual({ type: 'text', text: 'hello' }); + }); + + it('includes structuredContent when enabled: single content -> array with structuredContent appended', async () => { + const server = new StubServer( + 's', + [ + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + ], + true, + ); + (server as any).callTool = async () => ({ + content: [{ type: 'text', text: 'hello' }], + structuredContent: { foo: 1 }, + }); + + const tool = mcpToFunctionTool( + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + server, + false, + ); + + const out = await tool.invoke({} as any, '{}'); + expect(out).toEqual(JSON.stringify([{ type: 'text', text: 'hello' }, { foo: 1 }])); + }); + + it('includes structuredContent when enabled: no content -> structuredContent only', async () => { + const server = new StubServer( + 's', + [ + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + ], + true, + ); + (server as any).callTool = async () => ({ + content: [], + structuredContent: { foo: 1 }, + }); + + const tool = mcpToFunctionTool( + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + server, + false, + ); + + const out = await tool.invoke({} as any, '{}'); + expect(out).toEqual(JSON.stringify({ foo: 1 })); + }); + + it('includes structuredContent when enabled: multiple contents -> array with structuredContent appended', async () => { + const server = new StubServer( + 's', + [ + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + ], + true, + ); + (server as any).callTool = async () => ({ + content: [ + { type: 'text', text: 'a' }, + { type: 'text', text: 'b' }, + ], + structuredContent: { foo: 1 }, + }); + + const tool = mcpToFunctionTool( + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + server, + false, + ); + + const out = await tool.invoke({} as any, '{}'); + expect(out).toEqual(JSON.stringify([ + { type: 'text', text: 'a' }, + { type: 'text', text: 'b' }, + { foo: 1 }, + ])); + }); + + it('preserves falsy structuredContent values when enabled', async () => { + const server = new StubServer( + 's', + [ + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + ], + true, + ); + + // Test different falsy values + const falsyValues = [0, false, '', null]; + + for (const falsyValue of falsyValues) { + // Test with single content + falsy structuredContent + (server as any).callTool = async () => ({ + content: [{ type: 'text', text: 'hello' }], + structuredContent: falsyValue, + }); + + const tool = mcpToFunctionTool( + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + server, + false, + ); + + const out = await tool.invoke({} as any, '{}'); + expect(out).toEqual(JSON.stringify([{ type: 'text', text: 'hello' }, falsyValue])); + } + + // Test with no content + falsy structuredContent + for (const falsyValue of falsyValues) { + (server as any).callTool = async () => ({ + content: [], + structuredContent: falsyValue, + }); + + const tool = mcpToFunctionTool( + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + server, + false, + ); + + const out = await tool.invoke({} as any, '{}'); + expect(out).toEqual(JSON.stringify(falsyValue)); + } + }); + + it('handles undefined structuredContent gracefully', async () => { + const server = new StubServer( + 's', + [ + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + ], + true, // useStructuredContent enabled + ); + + // Test with structuredContent explicitly undefined + (server as any).callTool = async () => ({ + content: [{ type: 'text', text: 'hello' }], + structuredContent: undefined, + }); + + const tool = mcpToFunctionTool( + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + server, + false, + ); + + const out = await tool.invoke({} as any, '{}'); + // Should return just the content item since structuredContent is undefined + expect(out).toEqual({ type: 'text', text: 'hello' }); + }); + + it('handles mixed scenarios with some tools having structured content', async () => { + const server = new StubServer( + 's', + [ + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + ], + true, + ); + + // First call: has structured content + (server as any).callTool = async () => ({ + content: [{ type: 'text', text: 'with-structured' }], + structuredContent: { data: 'structured' }, + }); + + const tool = mcpToFunctionTool( + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + server, + false, + ); + + let out = await tool.invoke({} as any, '{}'); + expect(out).toEqual(JSON.stringify([ + { type: 'text', text: 'with-structured' }, + { data: 'structured' }, + ])); + + // Second call: no structured content + (server as any).callTool = async () => ({ + content: [{ type: 'text', text: 'without-structured' }], + }); + + out = await tool.invoke({} as any, '{}'); + // Should return just the content item when no structuredContent property + expect(out).toEqual({ type: 'text', text: 'without-structured' }); + }); + + it('handles empty content with useStructuredContent disabled', async () => { + const server = new StubServer( + 's', + [ + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + ], + false, // useStructuredContent disabled + ); + + // Tool returns empty content and structured content + (server as any).callTool = async () => ({ + content: [], + structuredContent: { important: 'data' }, + }); + + const tool = mcpToFunctionTool( + { + name: 't', + description: '', + inputSchema: { + type: 'object', + properties: {}, + required: [], + additionalProperties: false, + }, + }, + server, + false, + ); + + const out = await tool.invoke({} as any, '{}'); + // Should return empty array, ignoring structured content when disabled + expect(out).toEqual([]); + }); +}); diff --git a/packages/agents-core/test/mcpToolFilter.integration.test.ts b/packages/agents-core/test/mcpToolFilter.integration.test.ts index dfa37a2c..04baf817 100644 --- a/packages/agents-core/test/mcpToolFilter.integration.test.ts +++ b/packages/agents-core/test/mcpToolFilter.integration.test.ts @@ -60,23 +60,25 @@ class StubFilesystemServer extends NodeMCPServerStdio { async callTool(name: string, args: any) { const blocked = (this.toolFilter as any)?.blockedToolNames ?? []; if (blocked.includes(name)) { - return [ - { type: 'text', text: `Tool "${name}" is blocked by MCP filter` }, - ]; + return { + content: [ + { type: 'text', text: `Tool "${name}" is blocked by MCP filter` }, + ], + }; } if (name === 'list_directory') { const files = fs.readdirSync(this.dir); - return [{ type: 'text', text: files.join('\n') }]; + return { content: [{ type: 'text', text: files.join('\n') }] }; } if (name === 'read_file') { const text = fs.readFileSync(path.join(this.dir, args.path), 'utf8'); - return [{ type: 'text', text }]; + return { content: [{ type: 'text', text }] }; } if (name === 'write_file') { fs.writeFileSync(path.join(this.dir, args.path), args.text, 'utf8'); - return [{ type: 'text', text: 'ok' }]; + return { content: [{ type: 'text', text: 'ok' }] }; } - return []; + return { content: [] }; } }