Stdio-to-HTTP gateway — connects MCP clients to remote HTTP MCP servers
Project description
mcp-stdio
English | 日本語
Stdio-to-HTTP gateway — connects MCP clients to remote HTTP MCP servers.
Overview
MCP clients like Claude Desktop and Claude Code see mcp-stdio as a locally running self-hosted MCP server, while it relays all requests to a remote MCP server with support for various authentication methods:
flowchart BT
A[Claude<br>Desktop/Code] <-- stdio --> B(mcp-stdio)
B <== "<b>HTTPS</b><br>Streamable HTTP / SSE<br>Bearer Token<br>Header<br>OAuth" ==> C[Remote<br>MCP Server]
B -. "OAuth 2.1<br>(PKCE)" .-> D[Authorization<br>Server]
D -. callback .-> B
style B fill:#4a5,stroke:#333,color:#fff
Bearer tokens, custom headers, and OAuth 2.1 credentials are forwarded to the remote server.
Features
- Both MCP transports supported — Streamable HTTP (current spec, default) and SSE (MCP 2024-11-05 legacy), selectable with
--transport. SSE parser follows the WHATWG Server-Sent Events spec. - OAuth 2.1 client — built-in authorization code flow with PKCE, dynamic client registration, token refresh, and secure token persistence. Implements the full MCP authorization spec at the section level:
- RFC 9728 Protected Resource Metadata
- §3 discovery of authorization servers via
/.well-known/oauth-protected-resource - §3.1 path-aware well-known URL construction for path-based reverse-proxy deployments, with host-root fallback; preserves the resource URL's query component on the constructed metadata URL
- §3.3
resourcefield validation — warn on mismatch, continue - §5.1
WWW-Authenticate: Bearer resource_metadata=hint — probes the server before discovery so servers that publish PRM at a non-standard URL are found without well-known path guessing
- §3 discovery of authorization servers via
- RFC 8414 Authorization Server Metadata
- §3.1 well-known URL construction, including path insertion for issuers with path components
- §3.3
issuervalidation — reject a cross-origin issuer (AS mix-up guard), warn on a same-origin mismatch (trailing slash / path / case) and continue
- RFC 8707 Resource Indicators
- §2
resourceparameter in authorization, token exchange, and refresh requests
- §2
- RFC 7636 PKCE
- §4.1–4.2 S256
code_challenge_methodwith an 86-charcode_verifier
- §4.1–4.2 S256
- RFC 8628 Device Authorization Grant
- §3.1 device authorization request with
resourceindicator (RFC 8707) - §3.4–3.5 token polling with
authorization_pending/slow_down(interval +=5 s) /expired_token/access_deniedhandling - DCR registers
urn:ietf:params:oauth:grant-type:device_codeingrant_types(RFC 7591 §2)
- §3.1 device authorization request with
- RFC 7591 Dynamic Client Registration
- §3 client registration request;
token_endpoint_auth_methodchosen fromtoken_endpoint_auth_methods_supportedin AS metadata (prefersnone→client_secret_post→client_secret_basic) - §3.2.1
client_secret_expires_athandling — auto re-register on expiry
- §3 client registration request;
- RFC 6749 OAuth 2.0
- §2.3.1
client_secret_basic:Authorization: Basicheader with percent-encoded credentials (applied to code exchange, token refresh, and Device Authorization Grant polling)
- §2.3.1
- RFC 6750 Bearer Token usage
- §2.1
Authorization: Bearer <token>request header
- §2.1
- RFC 9728 Protected Resource Metadata
- Retry with backoff — retries up to 3 times on connection errors
- HTTP 429 / 503 handling — honours
Retry-After(delta-seconds or HTTP-date) up to a 60-second cap on both 429 (Too Many Requests) and 503 (Service Unavailable) — the two spec-sanctioned Retry-After carriers (RFC 9110 §10.2.3) — then surfaces the status so the client can decide (cf. modelcontextprotocol/typescript-sdk#1892) - Auto-pagination (Streamable HTTP transport) — transparently follows
nextCursorfortools/list/resources/list/resources/templates/list/prompts/listand merges the pages into one response, so clients that drop pages beyond the first still see the full list (cf. anthropics/claude-code#39586) - Streaming resilience — streams SSE responses in real time; auto-reconnects on mid-stream disconnect
- Line-separator safety — escapes raw
U+2028/U+2029(legal in JSON, but JavaScript line terminators) in upstream responses so clients that treat them as line breaks cannot mis-frame the output; lossless (cf. modelcontextprotocol/typescript-sdk#2155) - Argument normalization — rewrites a
tools/callrequest whoseargumentsisnullto{}so strict servers that reject the null form accept the call; on by default, opt out with--no-normalize-arguments(cf. modelcontextprotocol/typescript-sdk#2012) - Cancellation-aware filtering — tracks request ids cancelled via
notifications/cancelledon stdin and drops any late upstream response carrying one of those ids before it reaches the client, per the MCP cancellation spec; on by default (60 s TTL), opt out with--no-cancel-filter(cf. anthropics/claude-code#51073) - Session recovery — resets MCP session ID on 404 and retries
- Protocol version header — captures the negotiated
protocolVersionfrom theinitializeresponse and injectsMCP-Protocol-Versionon every subsequent Streamable HTTP request (MCP spec rev 2025-06-18); servers that enforce the header would otherwise reject post-initialize requests with400 Bad Request - Token refresh on 401 — automatically refreshes expired OAuth tokens mid-session (OAuth mode only)
- Step-up authorization on 403 — on a
Bearer error="insufficient_scope"challenge, re-authorizes for the union of the granted and required scopes (RFC 9470 / MCP step-up; cf. anthropics/claude-code#44652) - Bearer token auth — via
--bearer-tokenflag orMCP_BEARER_TOKENenv var - Custom headers — pass any header with
-H/--header - Graceful shutdown — handles SIGTERM/SIGINT
- Proxy support — respects
HTTP_PROXY,HTTPS_PROXY,NO_PROXYenv vars via httpx - Minimal dependencies — only httpx; OAuth uses stdlib only
Install
pip install mcp-stdio
Or with uv:
uv tool install mcp-stdio
Or run directly without installing:
uvx mcp-stdio https://your-server.example.com:8080/mcp
Or with Homebrew:
brew install shigechika/tap/mcp-stdio
Quick Start
mcp-stdio https://your-server.example.com:8080/mcp
With Bearer token authentication:
# Recommended: use env var (token is hidden from `ps`)
MCP_BEARER_TOKEN=YOUR_TOKEN mcp-stdio https://your-server.example.com:8080/mcp
# Or pass directly (token is visible in `ps` output)
mcp-stdio https://your-server.example.com:8080/mcp --bearer-token YOUR_TOKEN
With custom headers:
mcp-stdio https://your-server.example.com:8080/mcp --header "X-API-Key: YOUR_KEY"
With OAuth 2.1 authentication (for servers that require it):
mcp-stdio --oauth https://your-server.example.com:8080/mcp
# With a pre-registered client ID (skips dynamic registration)
mcp-stdio --oauth --client-id YOUR_CLIENT_ID https://your-server.example.com:8080/mcp
With OAuth 2.1 Device Authorization Grant (RFC 8628, for headless/SSH environments):
mcp-stdio --oauth-device https://your-server.example.com:8080/mcp
For legacy MCP servers using the 2024-11-05 SSE transport:
mcp-stdio --transport sse https://your-server.example.com:8080/sse
Check connectivity before use:
mcp-stdio --check https://your-server.example.com:8080/mcp
# For an SSE server, pass --transport sse so --check runs the legacy
# GET/endpoint/POST handshake instead of a Streamable HTTP probe:
mcp-stdio --check --transport sse https://your-server.example.com:8080/sse
Claude Desktop Configuration
Add to claude_desktop_config.json:
{
"mcpServers": {
"my-remote-server": {
"command": "mcp-stdio",
"args": ["https://your-server.example.com:8080/mcp"],
"env": {
"MCP_BEARER_TOKEN": "YOUR_TOKEN"
}
}
}
}
Config file locations:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
Claude Code Configuration
claude mcp add my-remote-server \
-e MCP_BEARER_TOKEN=YOUR_TOKEN \
-- mcp-stdio https://your-server.example.com:8080/mcp
Usage
mcp-stdio [OPTIONS] URL
Arguments:
URL Remote MCP server URL
Options:
--bearer-token TOKEN Bearer token (or set MCP_BEARER_TOKEN env var)
--oauth Enable OAuth 2.1 authentication (browser flow)
--oauth-device Enable OAuth 2.1 Device Authorization Grant (RFC 8628, headless)
--client-id ID Pre-registered OAuth client ID (or set MCP_OAUTH_CLIENT_ID)
--oauth-scope SCOPE OAuth scope to request
--oauth-refresh-leeway SECONDS
Proactively refresh tokens this many seconds before
expiry (default: 60, or MCP_OAUTH_REFRESH_LEEWAY)
--oauth-timeout SECONDS
Seconds to wait for the interactive OAuth flow (browser
callback / device-code confirmation) before giving up
(default: 120; OAuth only)
--no-resource-indicator
Omit the RFC 8707 resource parameter from all OAuth
requests. Required for AS that reject it, such as
Microsoft Entra ID v2 with api:// scopes (AADSTS9010010).
Persisted in the token store so proactive refreshes
and step-up flows stay consistent
-H, --header 'Key: Value' Custom header (can be repeated)
--transport {streamable-http,sse}
Transport type (default: streamable-http)
--timeout-connect SEC Connection timeout (default: 10)
--timeout-read SEC Read timeout (default: 120)
--sse-read-timeout SEC Idle read timeout on the SSE GET stream
(default: 300; 0 disables; SSE transport only)
--no-tcp-keepalive Disable TCP keepalive on the HTTP socket
--no-cancel-filter Disable the cancel-aware response filter (drops late
responses for ids cancelled via notifications/cancelled)
--no-normalize-arguments
Disable rewriting a tools/call request's
arguments:null to {} before forwarding
--check Check connection and exit
-V, --version Show version
-h, --help Show help
Run mcp-stdio --help for the full per-flag detail (platform notes and issue references are more verbose than this table).
Reverse gateway: serve mode
The default mode bridges stdio → HTTP (client side). The serve subcommand
is the mirror image — HTTP → stdio — exposing a local stdio MCP server as a
Streamable HTTP MCP endpoint so clients that cannot spawn it locally can reach
it over the network:
flowchart BT
A["MCP client<br>Claude Code / Desktop<br>(or mcp-stdio --oauth)"]
B("mcp-stdio serve<br><b>HTTP → stdio</b> gateway<br>auth: none / static token /<br>embedded OAuth 2.1 AS")
C["local stdio<br>MCP server"]
A <== "Streamable HTTP<br>Bearer / OAuth 2.1 (PKCE)" ==> B
B <-- "stdio (spawned child)" --> C
This is the mirror of the client-side diagram at the top: there mcp-stdio is stdio → HTTP; here it is HTTP → stdio.
mcp-stdio serve --port 8080 -- python -m my_mcp_server
Then point any MCP client (including mcp-stdio itself) at it:
mcp-stdio --check http://127.0.0.1:8080/mcp
- Stdlib only (
http.server) — adds no runtime dependency. - Implements the Streamable HTTP request/response and notification semantics plus a GET SSE channel for server-initiated messages.
- Authentication is optional and layered:
- No token — the endpoint is open (run it behind a TLS-terminating proxy).
- Static token (
--auth-token/MCP_STDIO_SERVE_TOKEN) — acts as an OAuth Resource Server: MCP requests requireAuthorization: Bearer <token>, and a 401 advertises RFC 9728 Protected Resource Metadata at/.well-known/oauth-protected-resource. - Embedded OAuth AS (
--enable-oauth) — a minimal OAuth 2.1 Authorization Server (PKCE auth-code, RFC 7591 dynamic client registration, refresh, opaque in-memory tokens, stdlib only). The mcp-stdio client's--oauthflow then works against the gateway.
- Single-client assumption for now: JSON-RPC ids pass through verbatim.
Static-token example (token via env so it is not visible in ps):
MCP_STDIO_SERVE_TOKEN=your-secret mcp-stdio serve --port 8080 -- python -m my_mcp_server
mcp-stdio --bearer-token your-secret --check http://127.0.0.1:8080/mcp
Embedded-OAuth example. User authentication is delegated to a fronting
reverse proxy that asserts the logged-in user via a header
(--trusted-user-header, only trusted behind a proxy that strips client copies).
--dev-user is an insecure loopback-only shortcut for local testing:
mcp-stdio serve --enable-oauth --public-url http://127.0.0.1:8080 \
--dev-user alice --port 8080 -- python -m my_mcp_server
mcp-stdio --oauth http://127.0.0.1:8080/mcp
Options: --host (default 127.0.0.1), --port (default 8080), --path
(default /mcp), --auth-token TOKEN (or MCP_STDIO_SERVE_TOKEN, preferred);
and for the embedded AS: --enable-oauth, --public-url URL (pins the issuer;
recommended behind a proxy), --trusted-user-header HEADER, --dev-user USER
(insecure, testing only), --access-token-ttl SECONDS. In-memory tokens mean a
restart invalidates issued tokens (the client re-runs --oauth). The backend
command follows the options (an optional -- separator is supported).
Workarounds
See WORKAROUNDS.md for known issues in Claude Code, mcp-remote, the MCP SDKs, and Windows that mcp-stdio addresses.
How It Works
- If
--oauth(browser) or--oauth-device(headless, RFC 8628) is set, obtains an access token (cached → refresh → browser/device flow) - Reads JSON-RPC messages from stdin (sent by Claude Desktop/Code)
- Relays them over HTTPS to the remote MCP server
- Parses responses and writes them to stdout
- On 401 (OAuth mode only), refreshes the access token and retries; with static
--bearer-token/-Hauth the 401 is surfaced to the client
Transport details:
- Streamable HTTP (default) — each stdin message is a single POST; session state is tracked via the
Mcp-Session-Idheader and re-initialized automatically on 404. The negotiatedMCP-Protocol-Versionheader is sent on every post-initialize request (spec rev 2025-06-18). - SSE (MCP 2024-11-05 legacy) — a persistent
GETstream delivers responses and the initialendpointevent containing the POST URL; the stream auto-reconnects on disconnect.
OAuth tokens are stored in ~/.config/mcp-stdio/tokens.json (permissions 0600).
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_stdio-0.16.0.tar.gz.
File metadata
- Download URL: mcp_stdio-0.16.0.tar.gz
- Upload date:
- Size: 279.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d6e1edf0884cdb98b8bcff752cf9963be1a7f0688e09e4d6c591fdc17de02d44
|
|
| MD5 |
3e4704cc1488e3a7a5c5bc267418b080
|
|
| BLAKE2b-256 |
054ae1661a5c9ba870c2504fb5eaa027cf257d90fd87a77bdf1b7c40ade66563
|
Provenance
The following attestation bundles were made for mcp_stdio-0.16.0.tar.gz:
Publisher:
release.yml on shigechika/mcp-stdio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_stdio-0.16.0.tar.gz -
Subject digest:
d6e1edf0884cdb98b8bcff752cf9963be1a7f0688e09e4d6c591fdc17de02d44 - Sigstore transparency entry: 1708645700
- Sigstore integration time:
-
Permalink:
shigechika/mcp-stdio@f3ac5476447fe5f7b5103507633e7f6e3f0c456c -
Branch / Tag:
refs/tags/v0.16.0 - Owner: https://github.com/shigechika
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f3ac5476447fe5f7b5103507633e7f6e3f0c456c -
Trigger Event:
release
-
Statement type:
File details
Details for the file mcp_stdio-0.16.0-py3-none-any.whl.
File metadata
- Download URL: mcp_stdio-0.16.0-py3-none-any.whl
- Upload date:
- Size: 115.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fd480640e28241a60d3d082afc1b6a77851ed7c57f4fae2a77821e03a2c3c159
|
|
| MD5 |
c46d8badac650894235e8ed13b913891
|
|
| BLAKE2b-256 |
13ecc313323ca1c180f51363787122bc55fc426d4fb8719bc186dc38df87d422
|
Provenance
The following attestation bundles were made for mcp_stdio-0.16.0-py3-none-any.whl:
Publisher:
release.yml on shigechika/mcp-stdio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_stdio-0.16.0-py3-none-any.whl -
Subject digest:
fd480640e28241a60d3d082afc1b6a77851ed7c57f4fae2a77821e03a2c3c159 - Sigstore transparency entry: 1708645710
- Sigstore integration time:
-
Permalink:
shigechika/mcp-stdio@f3ac5476447fe5f7b5103507633e7f6e3f0c456c -
Branch / Tag:
refs/tags/v0.16.0 - Owner: https://github.com/shigechika
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f3ac5476447fe5f7b5103507633e7f6e3f0c456c -
Trigger Event:
release
-
Statement type: