fix: return method-specific MCP responses (#18671)

## Summary

Fixes #18524 

Fixes the MCP response contract for non-`initialize` methods.

Previously, `/mcp` returned initialize-style metadata for methods like
`tools/list`, which caused strict MCP clients to reject the response
shape. The endpoint also returned `201 Created` for RPC calls even
though no resource was being created.

## Changes

- return only method-specific payloads for MCP list methods
  - `tools/list` -> `{ tools: [...] }`
  - `prompts/list` -> `{ prompts: [] }`
  - `resources/list` -> `{ resources: [] }`
- keep MCP server metadata only on `initialize`
- make `/mcp` return `200 OK` instead of `201 Created`
- add regression tests for:
  - `tools/list` response shape
  - `prompts/list` response shape
  - `resources/list` response shape

## Why

Strict MCP clients expect:
- standard RPC transport semantics over HTTP
- method-specific JSON-RPC result payloads

Returning initialize metadata for non-`initialize` methods breaks that
expectation and can cause client deserialization or protocol validation
failures.

## Verification

- reproduced the issue locally against `/mcp`
- verified `tools/list` was previously returning initialize-style fields
- verified `tools/list` now returns only `result.tools`
- verified `/mcp` now returns `200 OK`
- ran targeted Jest tests:

```bash
cd /Users/apple/MyProjects/OpenSource/twenty/packages/twenty-server
npx jest --runInBand src/engine/api/mcp/services/__tests__/mcp-protocol.service.spec.ts src/engine/api/mcp/services/__tests__/mcp-tool-executor.service.spec.ts

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Sri Hari Haran Sharma
2026-03-16 17:16:35 +05:30
committed by GitHub
parent 6e36ad9fa2
commit a07337fea0
14 changed files with 468 additions and 148 deletions
@@ -1,28 +1,12 @@
import { MCP_SERVER_METADATA } from 'src/engine/api/mcp/constants/mcp.const';
export const wrapJsonRpcResponse = (
id: string | number,
payload:
| Record<'result', Record<string, unknown>>
| Record<'error', Record<string, unknown>>,
omitMetadata = false,
) => {
const body =
'result' in payload
? {
result: omitMetadata
? payload.result
: { ...payload.result, ...MCP_SERVER_METADATA },
}
: {
error: omitMetadata
? payload.error
: { ...payload.error, ...MCP_SERVER_METADATA },
};
return {
id,
jsonrpc: '2.0',
...body,
...payload,
};
};