diff --git a/.changeset/mcp-actions-backend-md-result-fix.md b/.changeset/mcp-actions-backend-md-result-fix.md new file mode 100644 index 0000000000..63b651bf34 --- /dev/null +++ b/.changeset/mcp-actions-backend-md-result-fix.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-mcp-actions-backend': patch +--- + +Fixed an issue where actions returned Markdown-formatted JSON instead of plain JSON and a `structuredContent` field for model context protocol responses. diff --git a/docs-ui/src/app/components/accordion/page.mdx b/docs-ui/src/app/components/accordion/page.mdx index 90c573b087..dc9b87b55d 100644 --- a/docs-ui/src/app/components/accordion/page.mdx +++ b/docs-ui/src/app/components/accordion/page.mdx @@ -140,7 +140,7 @@ Allows multiple panels to be open simultaneously. } code={groupMultipleOpenSnippet} /> diff --git a/plugins/mcp-actions-backend/README.md b/plugins/mcp-actions-backend/README.md index d496cf0f03..b3f6895078 100644 --- a/plugins/mcp-actions-backend/README.md +++ b/plugins/mcp-actions-backend/README.md @@ -135,6 +135,10 @@ When errors are thrown from MCP actions, the backend will handle and surface err See [Backstage Errors](https://backstage.io/docs/reference/errors/) for a full list of supported errors. +### Response Format + +Tool execution results are returned in a format compliant with the MCP specification, including both a plain-text representation in the `content` array and the raw JSON result in the `structuredContent` field. This ensures that AI clients can process the data either as text or as structured data for more precise tool use. + When writing MCP tools, use the appropriate error from `@backstage/errors` when applicable: ```ts diff --git a/plugins/mcp-actions-backend/src/services/McpService.test.ts b/plugins/mcp-actions-backend/src/services/McpService.test.ts index d96e9c7285..8aa1bcb22f 100644 --- a/plugins/mcp-actions-backend/src/services/McpService.test.ts +++ b/plugins/mcp-actions-backend/src/services/McpService.test.ts @@ -216,13 +216,10 @@ describe('McpService', () => { expect(result.content).toEqual([ { type: 'text', - text: [ - '```json', - JSON.stringify({ output: 'test' }, null, 2), - '```', - ].join('\n'), + text: JSON.stringify({ output: 'test' }), }, ]); + expect((result as any).structuredContent).toEqual({ output: 'test' }); const histogram = mockMetrics.createHistogram.mock.results[0]?.value; expect(histogram.record).toHaveBeenCalledTimes(1); diff --git a/plugins/mcp-actions-backend/src/services/McpService.ts b/plugins/mcp-actions-backend/src/services/McpService.ts index 9d907f8258..735bce6f55 100644 --- a/plugins/mcp-actions-backend/src/services/McpService.ts +++ b/plugins/mcp-actions-backend/src/services/McpService.ts @@ -135,7 +135,6 @@ export class McpService { const server = new McpServer( { name: serverConfig?.name ?? 'backstage', - // TODO: this version will most likely change in the future. version, ...(serverConfig?.description && { description: serverConfig.description, @@ -159,9 +158,6 @@ export class McpService { return { tools: actions.map(action => ({ inputSchema: action.schema.input, - // todo(blam): this is unfortunately not supported by most clients yet. - // When this is provided you need to provide structuredContent instead. - // outputSchema: action.schema.output, name: this.getToolName(action), description: action.description, annotations: { @@ -238,9 +234,6 @@ export class McpService { credentials, }); - // Record the structured action output directly rather than the - // CallToolResult envelope below, which wraps an already- - // stringified markdown-fenced JSON block. if (this.captureToolPayloads) { span.setAttribute( 'gen_ai.tool.call.result', @@ -249,19 +242,13 @@ export class McpService { } return { - // todo(blam): unfortunately structuredContent is not supported by most clients yet. - // so the validation for the output happens in the default actions registry - // and we return it as json text instead for now. content: [ { type: 'text', - text: [ - '```json', - JSON.stringify(output, null, 2), - '```', - ].join('\n'), + text: JSON.stringify(output), }, ], + structuredContent: output, }; });