Skip to content

MCP: hardcoded 30s timeout on every request makes long-running tools impossible (same root cause as #617) #802

Description

@tic-top

Summary

Every MCP JSON-RPC request shares one hardcoded 30s timeout, including tools/call. There is no flag, config key, or mcp.json field to raise it, so any tool that legitimately takes longer than 30s can never succeed.

crates/jcode-base/src/mcp/client.rs:46 (at 02439b4):

let response = tokio::time::timeout(std::time::Duration::from_secs(30), rx)
    .await
    .context("Request timeout")?

request() is the single path used by initialize (client.rs:301), tools/list (client.rs:103) and tools/call (client.rs:71), so the same 30s covers server startup and long-running tool work alike.

I believe this is also the root cause of #617 — a slow-starting server blows the same budget during initialize.

Reproduction

Minimal MCP server (slow_mcp.py, needs pip install mcp):

import time
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("slowsrv")

@mcp.tool()
def slow_ping(seconds: int = 45) -> str:
    """Sleep then return a token."""
    time.sleep(seconds)
    return "SLOW-TOKEN-OK"

mcp.run()

.jcode/mcp.json:

{"mcpServers": {"slow": {"command": "python", "args": ["/abs/path/slow_mcp.py"], "env": {}}}}
jcode run 'Call slow_ping with seconds=45 exactly once, then report what it returned.'

Expected: the tool returns SLOW-TOKEN-OK after 45s.
Actual (0.68.0): the call fails at exactly 30s with Error: Request timeout, and the model reports the timeout instead of the result. I also tried timeout, timeout_ms, timeout_secs and tool_timeout_ms keys on the server entry in mcp.json — none are read.

Impact

I maintain a benchmark harness that drives Microsoft Word through an MCP server, where a single document edit routinely runs longer than 30s (the first call on a busy pool especially). Under jcode those calls come back as Request timeout and the model retries them; the work still completes, but every run burns turns and tokens on retries, and a slow enough backend can never finish at all. Anything else with real I/O behind it — builds, test suites, deploys, browser automation, remote APIs — has the same ceiling.

Suggestion

Two things would fix it, in rough priority order:

  1. Make the timeout configurable, and use a longer default for tools/call than for initialize/tools/list. Comparable clients all expose this: Claude Code has MCP_TIMEOUT / MCP_TOOL_TIMEOUT, VS Code and Cursor take a per-server timeout in mcp.json, goose and kimi-cli both have their own knobs. A per-server field in mcp.json plus a global default in config.toml would match what people already expect.
  2. Optionally, reset the deadline on MCP progress notifications — the spec allows implementations to do this so well-behaved long-running tools stay alive.

Happy to test a patch against the real workload if that helps. Environment: jcode 0.68.0 (installed via jcode.sh/install), Linux x86_64.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    autonomous: noNeeds your brain: a product/design decision is required before anyone acts.bugSomething isn't workingtriage: needs-decisionNeeds maintainer decision/design thought

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions