Skip to content

Migrating from stdio to Streamable HTTP

This guide walks through migrating your axm-mcp setup from the default stdio transport to the persistent Streamable HTTP server.

Why migrate?

stdio HTTP
Processes One per conversation Single shared server
Startup time One uvx resolve per conversation (cached, fast) Instant (already running)
AST cache Lost between conversations Shared across all calls
Protocol sessions Per-conversation only Persistent
CPU usage Risk of zombie processes Single managed process

Concurrency model (what "shared" really means)

The HTTP server serves many conversations from one process, so it is explicitly designed not to let one slow call stall the others:

  • Sync tools run off the event loop. In HTTP mode every tool's synchronous body is offloaded to a worker thread (asyncio.to_thread), so a multi-minute verify cannot freeze /health, keep-alives, or other conversations.
  • Per-key serialization. Calls that mutate the same resource are serialized on purpose: git_* tools by (normalized) repo path, protocol_* tools by session_id. Two conversations committing to the same repo take turns; unrelated repos run in parallel.
  • Lock timeout is graceful. If a lock cannot be acquired within its timeout (30 s), the call returns a structured {success: false, error: "… busy, retry"} rather than a raw protocol error.

This holds whether a tool is called directly or through axm_call — both go through the same execution path.

Prerequisites

  • A recent axm-mcp with the serve/status/stop subcommands (run axm-mcp --help to confirm they are present)
  • macOS (launchd integration) or any OS (manual serve)

Step 1 — Start the server

Bash
axm-mcp install

This generates a launchd plist at ~/Library/LaunchAgents/io.axm.mcp-server.plist, loads it via launchctl, and starts the server automatically. The service restarts on crash and starts on login.

Option B: manual

Bash
axm-mcp serve

Starts the server in the foreground on 127.0.0.1:9427.

To use a different port:

Bash
axm-mcp serve --port 8080
# or
AXM_MCP_PORT=8080 axm-mcp serve

Step 2 — Verify the server is running

Bash
axm-mcp status

Expected output:

Text Only
Server running on 127.0.0.1:9427 (42 tools)

You can also hit the health endpoint directly:

Bash
curl http://localhost:9427/health
# {"status": "ok", "tools_count": 42}

Step 3 — Update .mcp.json

Replace the stdio config with the HTTP config.

Before (stdio — the default setup from the Quick Start):

JSON
{
  "mcpServers": {
    "axm-mcp": {
      "command": "uvx",
      "args": ["--python", "3.12", "--from", "axm-mcp[all]@latest", "axm-mcp"]
    }
  }
}

After (HTTP):

JSON
{
  "mcpServers": {
    "axm-mcp": {
      "type": "url",
      "url": "http://localhost:9427/mcp"
    }
  }
}

This file lives at ~/.claude.json (global, applies to all projects) or .mcp.json at a project root (project-scoped — takes precedence over the global file when present).

Step 4 — Restart Claude Code

Restart your Claude Code session so it picks up the new .mcp.json config. The MCP client will now connect to the HTTP server instead of forking a stdio process.

Rolling back to stdio

If you need to revert:

  1. Restore the stdio .mcp.json config (the "Before" block above)
  2. Stop the HTTP server:
Bash
axm-mcp stop
  1. If you installed the launchd service:
Bash
axm-mcp uninstall
  1. Restart Claude Code

Troubleshooting

Server not running

Text Only
$ axm-mcp status
Server not running

Fix: Start the server with axm-mcp serve or reinstall the service with axm-mcp install.

If using launchd, check the logs:

Bash
cat ~/Library/Logs/axm-mcp/stderr.log

Port conflict

Text Only
Error: [Errno 48] Address already in use

Fix: Another process is using port 9427. Either stop that process or use a different port:

Bash
axm-mcp serve --port 9428

Update your .mcp.json URL to match the new port.

You can also set the port via environment variable:

Bash
export AXM_MCP_PORT=9428

Zombie processes (100% CPU)

This typically happens with leftover stdio processes from before the migration.

Fix: Stop the HTTP server and restart it cleanly:

Bash
axm-mcp stop
axm-mcp serve

If using launchd:

Bash
axm-mcp uninstall
axm-mcp install

Tools not responding after migration

All path-dependent tools require explicit path arguments in HTTP mode (the server has no per-conversation working directory). If a tool returns an error about missing paths, ensure you are passing absolute paths.

PermissionError — Full Disk Access (macOS)

Text Only
PermissionError: [Errno 1] Operation not permitted

Found in ~/Library/Logs/axm-mcp/stderr.log.

Cause: macOS blocks launchd background services from accessing ~/Documents, ~/Desktop, and ~/Downloads without Full Disk Access granted to the binary. Because the launchd plist points to a binary inside a uv cache or project virtualenv, the OS sandbox denies access to protected directories.

Fix options (in order of preference):

  1. Install the binary in ~/.local/bin/ (recommended) — this path is outside the protected locations and is not subject to the same FDA restrictions:
Bash
uv tool install axm-mcp
axm-mcp install

uv tool install places the binary at ~/.local/bin/axm-mcp, which axm-mcp install will detect and use automatically.

  1. Specify the binary path explicitly — if the binary already lives at an FDA-exempt path, pass it directly:
Bash
axm-mcp install --binary ~/.local/bin/axm-mcp
  1. Grant Full Disk Access — if you need to keep the current binary location, grant Full Disk Access to your terminal application or directly to the axm-mcp binary in System Settings > Privacy & Security > Full Disk Access.