mcp-timeout-demo
# mcp-timeout-demo
A Claude Code MCP server that exposes a long-running tool with
**per-tool configurable timeouts** and periodic progress notifications,
so Claude Code never severs the request mid-flight.
If you have ever seen Claude Code kill an MCP tool call that was
otherwise healthy — because it went silent for longer than the client's
default idle window — this plugin shows the smallest possible fix and
ships it as a drop-in server you can install today.
Companion article:
<https://claudeplugins.nicedx.com/claude-code-mcp-tool-timeout-configurable-long-running/>
---
## What you get
- One MCP tool, `long_running_task`, with a `timeout_ms` parameter
validated against `[1, 600_000]` ms.
- Cooperative cancellation via `asyncio.wait_for` — a genuinely stuck
call fails at the ceiling, it does not hang forever.
- Progress notifications every 250 ms via `ctx.report_progress` — the
Claude Code client's idle timer never fires while the tool is
actually working.
- Per-tool timeout defaults from an env var **or** a JSON config file,
with env vars winning over the file.
- A ready-to-copy `.mcp.json` snippet at
[`examples/claude_code.mcp.json`](examples/claude_code.mcp.json).
- A Claude Code plugin manifest at
[`.claude-plugin/plugin.json`](.claude-plugin/plugin.json) so the
same server can install via a plugin marketplace.
---
## Install
The server ships as a standard Python distribution. Three install
paths, in order of preference:
### 1. `uv run` (recommended, zero pre-install)
`uv` bootstraps a venv on the fly, so nothing has to be pre-installed
globally. Drop this into your project's `.mcp.json`:
```json
{
"mcpServers": {
"mcp-timeout-demo": {
"command": "uv",
"args": ["run", "mcp-timeout-demo"],
"env": {
"MCP_TIMEOUT_DEMO_TIMEOUT_MS__LONG_RUNNING_TASK": "60000"
}
}
}
}
```
Then restart Claude Code in that project. That is it.
### 2. `pip install` + console script
```bash
pip install mcp-timeout-demo
```
Register with Claude Code:
```bash
claude mcp add mcp-timeout-demo \
--env MCP_TIMEOUT_DEMO_TIMEOUT_MS__LONG_RUNNING_TASK=60000 \
-- mcp-timeout-demo
```
### 3. From source
```bash
git clone https://github.com/vytharion/claude-code-mcp-tool-timeout-configurable-long-running.git
cd claude-code-mcp-tool-timeout-configurable-long-running
uv sync
uv run mcp-timeout-demo
```
### 4. As a Claude Code plugin
The repository ships a `.claude-plugin/plugin.json` manifest, so the
server can also be published as a plugin through a Claude Code plugin
marketplace. The `.claude-plugin/marketplace.json` in this repo is the
minimal example marketplace entry — point your marketplace tooling at
the repository URL and Claude Code will read both files.
---
## Configure
The tool has one default: `DEFAULT_TIMEOUT_MS = 5_000`. Two mechanisms
override that default, in this precedence (most-specific wins):
### Environment variable
```
MCP_TIMEOUT_DEMO_TIMEOUT_MS__<TOOL_NAME_UPPER>
```
Example — raise the ceiling for `long_running_task` to 60 seconds:
```bash
export MCP_TIMEOUT_DEMO_TIMEOUT_MS__LONG_RUNNING_TASK=60000
```
### JSON config file
Set `MCP_TIMEOUT_DEMO_CONFIG` to point at a JSON file with this shape:
```json
{
"tools": {
"long_running_task": {
"timeout_ms": 60000
}
}
}
```
Then:
```bash
export MCP_TIMEOUT_DEMO_CONFIG=/path/to/mcp-timeouts.json
```
### Per-call override
Clients can still pass `timeout_ms` on the individual `call_tool`
request. That per-call value always wins over the default — the
default only sets the ceiling the schema advertises.
---
## Verify
Once the server is registered, ask Claude Code to call the tool:
> "Call `long_running_task` with label 'demo', `timeout_ms=3000`, and
> `work_duration_ms=1500`."
You should see the tool return:
```
done:demo:worked=1500ms:timeout=3000ms
```
To see the timeout path fire, ask for work that exceeds the deadline:
> "Call `long_running_task` with label 'over', `timeout_ms=200`, and
> `work_duration_ms=5000`."
The tool returns cleanly with:
```
timeout:over:after=200ms
```
The client will also receive periodic progress notifications during
either call.
---
## Troubleshooting
### "Claude Code still says the tool timed out"
That is the *client*-side MCP timeout, not the tool's `timeout_ms`.
This plugin only fixes the *server-side* idle-timer story via progress
notifications. If Claude Code itself has a hard per-call ceiling, this
plugin cannot reach past it — file a follow-up config with the client.
### "The env var is not being picked up"
The env-var key encodes the tool name in **upper case with underscores
preserved**. `long_running_task` becomes
`MCP_TIMEOUT_DEMO_TIMEOUT_MS__LONG_RUNNING_TASK` (note the double
underscore between prefix and tool name). Print `env_key_for(tool_name)`
from the `mcp_timeout_demo.config` module if you are unsure.
### "The tool default advertised in `tools/list` is wrong"
The advertised default is captured at server startup. If you change
the env var or the config file, restart the MCP server (in Claude
Code: `/mcp` → restart the server, or just restart the client).
### "I get `FileNotFoundError: config file not found`"
`MCP_TIMEOUT_DEMO_CONFIG` must point at an existing file; a missing
path is treated as operator error, not silently ignored. Check the
path and the process's working directory.
### "I get `ValueError: timeout ... outside [1, 600000]`"
Timeout values are clamped to `[1, 600_000]` ms (10 minutes). A
malformed override fails loudly at startup rather than silently
degrading — fix the value in your env or config file.
---
## Upgrade notes
- Semantic Versioning applies. Minor releases may add tools or config
keys; patch releases are strictly bug fixes.
- The env-var **prefix** (`MCP_TIMEOUT_DEMO_`) and **key template**
(`MCP_TIMEOUT_DEMO_TIMEOUT_MS__<TOOL_NAME_UPPER>`) are part of the
public API. They will not change inside a major version.
- The JSON config file schema (`tools.<name>.timeout_ms`) is also
part of the public API for the current major.
See [CHANGELOG.md](CHANGELOG.md) for the full release history.
---
## Development
```bash
uv sync
uv run pytest -v
```
The suite drives the server through the SDK's in-memory transport, so
no subprocess or stdio hand-off is required to run it.
---
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 1 tool
Only one tool exists, so there is no possibility of confusion or overlap. Every available tool has a distinct purpose by virtue of being the sole tool.
With a single tool, naming consistency is trivially satisfied. The name 'long_running_task' is clear and follows a descriptive pattern, although no pattern can be established from one example.
A single tool feels thin for general use, but it is borderline acceptable for a server explicitly named 'mcp-timeout-demo' whose purpose appears to be demonstrating timeout behavior. One tool may be intentionally minimal for this narrow scope.
For a server dedicated to timeout demonstration, the 'long_running_task' tool fully covers the apparent purpose. There are no obvious gaps or dead ends given the focused domain.