stacklok / stacklok/toolhive

uvx:// protocol-scheme container for a Python/FastMCP stdio server restarts in a loop (exit 0), while npx:// containers work fine

Open
#6,634 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Go
Stars
2.2k
Forks
300
Avg merge
1d 15h
Merged PRs (30d)
184

Description

Bug description

A container built via thv run uvx://<package> for a Python-based MCP server using the MCP Python SDK's stdio transport (mcp.server.fastmcp) exits cleanly (exit code 0) almost immediately after starting, then Docker restarts it, in an endless loop. Two other MCP servers running in the same ToolHive instance, also built via uvx:///npx:// but using Node/TypeScript-based SDKs, run correctly for hours with zero restarts under the identical launch mechanism.

Steps to reproduce

  1. thv run uvx://agentcairn --name agentcairn --group <group> -- --vault /path/to/vault
  2. docker ps shows the container in a Restarting (0) loop, roughly every 10-15 seconds
  3. docker logs <container> shows no output at all — no error, no traceback, nothing printed before exit
  4. For comparison, two other servers running via the identical thv run uvx:// / npx:// mechanism (different packages, Node/TypeScript-based) have run continuously for 1+ hour with zero restarts

Expected behavior

The container should start once and stay running, with ToolHive's proxy keeping a live connection to the container's stdin/stdout for the lifetime of the workload — matching the behavior seen with the two working servers.

Actual behavior

The container starts, the Python process appears to receive an immediate stdin EOF, and exits cleanly (exit 0) before ever becoming ready. ToolHive's health check reports a persistent HTTP 502 while waiting, then the container restarts and the cycle repeats indefinitely.

Confirmed manually: running the exact same built image directly with docker run -i --rm <image> --vault /path (explicit interactive stdin) works correctly and the server stays running as expected. This strongly suggests the container's stdin is not being kept open at the point the Python process starts reading from it, whereas it evidently is for the Node-based servers.

Suspected underlying cause: the MCP Python SDK's stdio transport (mcp.server.fastmcp / mcp.server.stdio) treats stdin EOF as an immediate, clean shutdown signal by design (see modelcontextprotocol/python-sdk #2678 and #2815 for related discussion of this exact behavior class). If ToolHive's container launch doesn't guarantee stdin is attached and open before the Python process begins its read loop, this SDK behavior would produce exactly the exit-0 restart loop observed here.

Environment

  • OS/version: macOS, Apple Silicon, OrbStack v2.2.3 (not Docker Desktop)
  • ToolHive version: v0.46.0 (commit c6c425a924fac51c86cbade15d0e720e29a600ab)

Additional context

Happy to provide the exact generated Dockerfile (from thv run's build output) if useful — it wraps the entrypoint as sh -c "exec 'agentcairn' \"$@\"", which may be a relevant detail if the extra shell layer affects stdin attachment timing.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the issue with the exact thv run uvx://agentcairn command and inspect the generated Dockerfile and container launch path, especially the sh -c entrypoint and stdin attachment. Compare it with the working npx:// path and verify that the Python process keeps stdin open, remains running, and no longer produces the HTTP 502/restart loop.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, go, python
Domain
backend, devops, infrastructure
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.