SIGN IN SIGN UP

fix(mcp): echo the client's progressToken instead of fabricating one (#24582)

Fixes #24559

## Problem

`POST /mcp` invented its own progress token, `tool-call-<jsonrpc id>`,
and emitted a `notifications/progress` for every tool call over SSE,
whether or not the client had asked for progress.

The spec requires progress notifications to reference only tokens that
"were provided in an active request"
([Progress](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/progress)),
so `params._meta.progressToken` is the only token a client can
recognise. A fabricated one can never match on the reference TypeScript
SDK, for two independent reasons: the SDK overwrites
`_meta.progressToken` with the numeric JSON-RPC message id and registers
the handler under that number, and its lookup is
`Number(progressToken)`, so `"tool-call-4"` resolves to `NaN`.

The reporter saw 157 client-side protocol errors in one session from
Cursor, after which the client marked the transport failed and left the
server permanently red in its MCP list. Other clients tear the transport
down instead.

## Fix

Read `params._meta.progressToken` and echo it back. Send nothing when
the client supplied no token, which the spec explicitly permits
("Servers receiving a request with a progress token MAY choose not to
send any progress notifications").

A zod schema does the extraction, so a token that is not a string or an
integer is treated as absent rather than echoed. The generated schema
types `ProgressToken` as `["string", "integer"]`, so echoing a
fractional number would emit a notification a schema-validating client
rejects, and echoing some other type produces one no client can match.
Falling back to no notification is the conformant option.
`TOOL_CALL_PROGRESS_TOKEN_PREFIX` is now unused and removed.

## Verification

`_meta` surviving validation is the linchpin, so the full HTTP path was
exercised in process (real `ValidationPipe`, controller, and SSE
writer): the DTO validates `params` with a bare `@IsObject()` and no
`@ValidateNested`, so class-validator's `whitelist` does not recurse
into it and nothing strips `_meta` on the way in. With
`_meta.progressToken: 4` the stream now opens with:

```
data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":4,"progress":0,"total":1}}
data: {"id":4,"jsonrpc":"2.0","result":{...}}
```

which is the exact token the SDK's `Number(progressToken)` lookup
resolves. Without `_meta`, only the result event is written.

## Tests

- `mcp.controller.integration-spec.ts`: the existing SSE test asserted
the buggy `tool-call-sse-tool-1` token and now asserts the client's; a
second case covers a tool call with no token requested, and fails
against the pre-fix service
- `get-progress-token.util.spec.ts`: token extraction, including
`progressToken: 0` and negative integers (valid tokens a truthiness
check would drop), and non-conformant tokens: wrong JSON types plus
fractional, `NaN`, and `Infinity` numbers


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/24582?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
M
MD. EBRAHIM ALI committed
e27f01abecaa18fbf8b960a9c13ff1301ece7ba0
Parent: f099cfe
Committed by GitHub <noreply@github.com> on 8/25/2026, 12:18:57 PM