claude-code - 💡(How to fix) Fix MCP client drops stdio transport on unknown progressToken instead of ignoring it [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#47765Fetched 2026-04-15 06:42:59
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
0
Author
Timeline (top)
labeled ×4commented ×1

When a stdio MCP server emits a notifications/progress message whose progressToken is no longer in the client's in-flight request map, Claude Code CLI's MCP client treats the unknown-token notification as a fatal stdio transport error, logs Closing transport (stdio transport error: Error), tears down the pipe, and respawns the server process. This happens at least once per tool call against any server that emits a terminal progress marker (progress >= total) after dispatching its response, because terminal progress races the tool-call response on the wire.

The MCP spec progress notifications section allows progress notifications to arrive at any time during a long-running request, but it does not specify what a client should do when a progress notification arrives for a token it does not recognize. The pragmatic behavior — adopted by most MCP SDK implementations — is to drop the notification with a debug/warning log and continue, because unknown tokens are an expected race condition and cannot by themselves indicate a transport corruption.

Claude Code's current behavior is strictly over-eager and causes observable user-facing failures.

Error Message

{"debug":"Calling MCP tool: fmp_sec_filings_sec_filings_8k","timestamp":"2026-04-14T05:41:34.761Z"} {"debug":"STDIO connection dropped after 139s uptime","timestamp":"2026-04-14T05:41:34.880Z"} {"debug":"Connection error: Received a progress notification for an unknown token: {"method":"notifications/progress","params":{"_meta":{"requestId":"5"},"progress":1,"total":1,"message":"Completed fmp.sec_filings.sec_filings_8k","progressToken":5}}","timestamp":"2026-04-14T05:41:34.880Z"} {"debug":"Closing transport (stdio transport error: Error)"} {"debug":"Tool 'fmp_sec_filings_sec_filings_8k' completed successfully in 119ms"} {"debug":"Starting connection with timeout of 30000ms"}

Root Cause

When a stdio MCP server emits a notifications/progress message whose progressToken is no longer in the client's in-flight request map, Claude Code CLI's MCP client treats the unknown-token notification as a fatal stdio transport error, logs Closing transport (stdio transport error: Error), tears down the pipe, and respawns the server process. This happens at least once per tool call against any server that emits a terminal progress marker (progress >= total) after dispatching its response, because terminal progress races the tool-call response on the wire.

Fix Action

Fix / Workaround

When a stdio MCP server emits a notifications/progress message whose progressToken is no longer in the client's in-flight request map, Claude Code CLI's MCP client treats the unknown-token notification as a fatal stdio transport error, logs Closing transport (stdio transport error: Error), tears down the pipe, and respawns the server process. This happens at least once per tool call against any server that emits a terminal progress marker (progress >= total) after dispatching its response, because terminal progress races the tool-call response on the wire.

  1. Run any stdio MCP server that emits a terminal progress notification after dispatching its response.
  2. From Claude Code, issue a tool call against that server carrying a _meta.progressToken in the request.
  3. The server's response arrives and is consumed.
  4. The terminal progress notification (same token) arrives next.
  5. Claude Code closes the transport and respawns the server. If a second tool call is in flight to the same server, it dies with "MCP server disconnected" mid-response.

In the stdio-transport MCP client's message dispatcher, when a notifications/progress arrives and progressToken is not present in the active-request map, swallow the notification instead of raising. Example (pseudocode):

Code Example

{"debug":"Calling MCP tool: fmp_sec_filings_sec_filings_8k","timestamp":"2026-04-14T05:41:34.761Z"}
{"debug":"STDIO connection dropped after 139s uptime","timestamp":"2026-04-14T05:41:34.880Z"}
{"debug":"Connection error: Received a progress notification for an unknown token: {\"method\":\"notifications/progress\",\"params\":{\"_meta\":{\"requestId\":\"5\"},\"progress\":1,\"total\":1,\"message\":\"Completed fmp.sec_filings.sec_filings_8k\",\"progressToken\":5}}","timestamp":"2026-04-14T05:41:34.880Z"}
{"debug":"Closing transport (stdio transport error: Error)"}
{"debug":"Tool 'fmp_sec_filings_sec_filings_8k' completed successfully in 119ms"}
{"debug":"Starting connection with timeout of 30000ms"}

---

case "notifications/progress": {
  const token = msg.params?.progressToken;
  const handler = this.progressHandlers.get(token);
  if (!handler) {
    this.log.debug(
      `Received progress notification for unknown token ${token}; ` +
      `dropping (token may have completed already).`
    );
    return; // do NOT throw; do NOT close the transport
  }
  handler(msg.params);
  return;
}

---

gh issue create \
  --repo anthropics/claude-code \
  --title "MCP client drops stdio transport on unknown progressToken instead of ignoring it" \
  --body-file /Users/yaleleber/Code/ai-toolkit/docs/claude-code-mcp-progress-token-issue.md
RAW_BUFFERClick to expand / collapse

Draft: Claude Code CLI MCP client tears down stdio transport on unknown progressToken

Target repo: anthropics/claude-code Draft author: Yale (bajman) — drafted 2026-04-14


Title

MCP client drops stdio transport when receiving a notifications/progress for an unknown token, instead of ignoring it

Body

Summary

When a stdio MCP server emits a notifications/progress message whose progressToken is no longer in the client's in-flight request map, Claude Code CLI's MCP client treats the unknown-token notification as a fatal stdio transport error, logs Closing transport (stdio transport error: Error), tears down the pipe, and respawns the server process. This happens at least once per tool call against any server that emits a terminal progress marker (progress >= total) after dispatching its response, because terminal progress races the tool-call response on the wire.

The MCP spec progress notifications section allows progress notifications to arrive at any time during a long-running request, but it does not specify what a client should do when a progress notification arrives for a token it does not recognize. The pragmatic behavior — adopted by most MCP SDK implementations — is to drop the notification with a debug/warning log and continue, because unknown tokens are an expected race condition and cannot by themselves indicate a transport corruption.

Claude Code's current behavior is strictly over-eager and causes observable user-facing failures.

Environment

  • Claude Code CLI version: (fill in — claude --version)
  • OS: macOS Darwin 25.5.0
  • Node: (fill in)
  • MCP transport: stdio
  • Affected servers (observed): fmp-modeling-prep (custom Python server, ai-toolkit); likely any server that emits notifications/progress with progress == total.

Repro

  1. Run any stdio MCP server that emits a terminal progress notification after dispatching its response.
  2. From Claude Code, issue a tool call against that server carrying a _meta.progressToken in the request.
  3. The server's response arrives and is consumed.
  4. The terminal progress notification (same token) arrives next.
  5. Claude Code closes the transport and respawns the server. If a second tool call is in flight to the same server, it dies with "MCP server disconnected" mid-response.

Observed log (from ~/Library/Caches/claude-cli-nodejs/-Users-yaleleber/mcp-logs-fmp-modeling-prep/2026-04-14T05-39-09-548Z.jsonl)

{"debug":"Calling MCP tool: fmp_sec_filings_sec_filings_8k","timestamp":"2026-04-14T05:41:34.761Z"}
{"debug":"STDIO connection dropped after 139s uptime","timestamp":"2026-04-14T05:41:34.880Z"}
{"debug":"Connection error: Received a progress notification for an unknown token: {\"method\":\"notifications/progress\",\"params\":{\"_meta\":{\"requestId\":\"5\"},\"progress\":1,\"total\":1,\"message\":\"Completed fmp.sec_filings.sec_filings_8k\",\"progressToken\":5}}","timestamp":"2026-04-14T05:41:34.880Z"}
{"debug":"Closing transport (stdio transport error: Error)"}
{"debug":"Tool 'fmp_sec_filings_sec_filings_8k' completed successfully in 119ms"}
{"debug":"Starting connection with timeout of 30000ms"}

Note that the tool call did complete successfully — the response is in the client. The transport is nonetheless torn down purely because of the trailing progress notification.

Expected behavior

When the MCP client receives a notifications/progress message for a progressToken that is not in its in-flight map, it should:

  1. Log the event at debug level with the token and message, and
  2. Drop the notification, and
  3. Leave the transport open.

This matches the behavior of most reference MCP SDK implementations and follows the general MCP design principle that notifications are best-effort and must not be load-bearing for transport health.

Proposed fix

In the stdio-transport MCP client's message dispatcher, when a notifications/progress arrives and progressToken is not present in the active-request map, swallow the notification instead of raising. Example (pseudocode):

case "notifications/progress": {
  const token = msg.params?.progressToken;
  const handler = this.progressHandlers.get(token);
  if (!handler) {
    this.log.debug(
      `Received progress notification for unknown token ${token}; ` +
      `dropping (token may have completed already).`
    );
    return; // do NOT throw; do NOT close the transport
  }
  handler(msg.params);
  return;
}

Related context

This was triggered on my side by a bug in my own custom MCP server (it was emitting a terminal progress == total notification after the response — that is a server bug and I have fixed it on my end). But the CLI's reaction — tearing down the entire transport — means a single malformed notification from any third-party MCP server can silently break a user's tool-call pipeline. The client-side fix is defense-in-depth against a wide class of well-meaning-but-buggy MCP servers.

Happy to submit a PR if you point me at the dispatcher file.


Ready to file

If this looks right, post with:

gh issue create \
  --repo anthropics/claude-code \
  --title "MCP client drops stdio transport on unknown progressToken instead of ignoring it" \
  --body-file /Users/yaleleber/Code/ai-toolkit/docs/claude-code-mcp-progress-token-issue.md

extent analysis

TL;DR

The MCP client should be modified to ignore notifications/progress messages with unknown progressToken instead of tearing down the stdio transport.

Guidance

  • Review the MCP client's message dispatcher to ensure it handles notifications/progress messages correctly, specifically when the progressToken is not present in the active-request map.
  • Update the dispatcher to log a debug message and drop the notification when an unknown progressToken is encountered, rather than raising an error and closing the transport.
  • Verify that the client can handle terminal progress notifications with progress == total after dispatching a response without tearing down the transport.
  • Test the updated client with various MCP servers, including those that emit malformed notifications, to ensure the fix is robust.

Example

The proposed fix in pseudocode is provided in the issue body:

case "notifications/progress": {
  const token = msg.params?.progressToken;
  const handler = this.progressHandlers.get(token);
  if (!handler) {
    this.log.debug(
      `Received progress notification for unknown token ${token}; ` +
      `dropping (token may have completed already).`
    );
    return; // do NOT throw; do NOT close the transport
  }
  handler(msg.params);
  return;
}

This code snippet demonstrates how to handle unknown progressToken notifications by logging a debug message and dropping the notification.

Notes

The fix assumes that the MCP client's message dispatcher is the correct place to handle notifications/progress messages. If the issue lies elsewhere in the codebase, additional debugging may be necessary.

Recommendation

Apply the proposed workaround by updating the MCP client's message dispatcher to ignore notifications/progress messages with unknown progressToken. This fix provides defense-in-depth against buggy MCP servers and prevents unnecessary transport teardowns.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

When the MCP client receives a notifications/progress message for a progressToken that is not in its in-flight map, it should:

  1. Log the event at debug level with the token and message, and
  2. Drop the notification, and
  3. Leave the transport open.

This matches the behavior of most reference MCP SDK implementations and follows the general MCP design principle that notifications are best-effort and must not be load-bearing for transport health.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix MCP client drops stdio transport on unknown progressToken instead of ignoring it [1 comments, 2 participants]