mcp-stdio 0.3.2__tar.gz → 0.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,13 +8,27 @@ on:
8
8
  jobs:
9
9
  test:
10
10
  runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
11
14
  steps:
12
15
  - uses: actions/checkout@v6
13
16
 
14
- - name: Set up Python
17
+ - name: Verify __version__ matches tag
18
+ if: matrix.python-version == '3.12'
19
+ run: |
20
+ TAG="${GITHUB_REF_NAME#v}"
21
+ PKG_VERSION=$(python3 -c "import re; print(re.search(r'__version__ = \"([^\"]+)\"', open('src/mcp_stdio/__init__.py').read()).group(1))")
22
+ if [ "$TAG" != "$PKG_VERSION" ]; then
23
+ echo "::error::Tag v$TAG does not match __version__ $PKG_VERSION in src/mcp_stdio/__init__.py"
24
+ exit 1
25
+ fi
26
+ echo "Verified: tag v$TAG matches __version__ $PKG_VERSION"
27
+
28
+ - name: Set up Python ${{ matrix.python-version }}
15
29
  uses: actions/setup-python@v6
16
30
  with:
17
- python-version: "3.12"
31
+ python-version: ${{ matrix.python-version }}
18
32
 
19
33
  - name: Install dependencies
20
34
  run: pip install -e ".[dev]"
@@ -10,7 +10,7 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
  strategy:
12
12
  matrix:
13
- python-version: ["3.10", "3.12", "3.14"]
13
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
14
14
  steps:
15
15
  - uses: actions/checkout@v6
16
16
 
@@ -1,20 +1,22 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-stdio
3
- Version: 0.3.2
4
- Summary: Stdio-to-HTTP relay for MCP servers
3
+ Version: 0.3.4
4
+ Summary: Stdio-to-HTTP relay bridges MCP clients to remote HTTP MCP servers
5
5
  Project-URL: Homepage, https://github.com/shigechika/mcp-stdio
6
6
  Project-URL: Issues, https://github.com/shigechika/mcp-stdio/issues
7
7
  Author: Shigechika AIKAWA
8
8
  License-Expression: MIT
9
9
  License-File: LICENSE
10
- Keywords: claude,mcp,proxy,relay,stdio
10
+ Keywords: bridge,claude,mcp,oauth,proxy,relay,stdio
11
11
  Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Environment :: Console
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
17
18
  Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
18
20
  Classifier: Programming Language :: Python :: 3.14
19
21
  Classifier: Topic :: Software Development :: Libraries
20
22
  Requires-Python: >=3.10
@@ -30,11 +32,11 @@ Description-Content-Type: text/markdown
30
32
 
31
33
  English | [日本語](README.ja.md)
32
34
 
33
- Stdio-to-HTTP relay for MCP servers — bridges Claude Desktop/Code to remote Streamable HTTP endpoints.
35
+ Stdio-to-HTTP relay — bridges MCP clients to remote HTTP MCP servers.
34
36
 
35
- ## Why?
37
+ ## Overview
36
38
 
37
- [MCP](https://modelcontextprotocol.io/) 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 over Streamable HTTP:
39
+ [MCP](https://modelcontextprotocol.io/) 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:
38
40
 
39
41
  ```mermaid
40
42
  flowchart BT
@@ -47,6 +49,25 @@ flowchart BT
47
49
 
48
50
  Bearer tokens, custom headers, and OAuth 2.1 credentials are forwarded to the remote server.
49
51
 
52
+ ## Features
53
+
54
+ - **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:
55
+ - [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) Protected Resource Metadata discovery
56
+ - [RFC 8414](https://www.rfc-editor.org/rfc/rfc8414) Authorization Server Metadata discovery
57
+ - [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707) Resource Indicators for audience binding
58
+ - [RFC 7636](https://www.rfc-editor.org/rfc/rfc7636) PKCE with S256 challenge method
59
+ - [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591) Dynamic Client Registration
60
+ - [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750) Bearer Token usage
61
+ - **Retry with backoff** — retries up to 3 times on connection errors
62
+ - **Streaming resilience** — streams SSE responses in real time; retries on mid-stream disconnect
63
+ - **Session recovery** — resets MCP session ID on 404 and retries
64
+ - **Token refresh on 401** — automatically refreshes expired OAuth tokens mid-session
65
+ - **Bearer token auth** — via `--bearer-token` flag or `MCP_BEARER_TOKEN` env var
66
+ - **Custom headers** — pass any header with `-H`
67
+ - **Graceful shutdown** — handles SIGTERM/SIGINT
68
+ - **Proxy support** — respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` env vars via [httpx](https://www.python-httpx.org/)
69
+ - **Minimal dependencies** — only [httpx](https://www.python-httpx.org/); OAuth uses stdlib only
70
+
50
71
  ## Install
51
72
 
52
73
  ```bash
@@ -90,7 +111,7 @@ mcp-stdio https://your-server.example.com:8080/mcp --bearer-token YOUR_TOKEN
90
111
  With custom headers:
91
112
 
92
113
  ```bash
93
- mcp-stdio https://your-server.example.com:8080/mcp -H "X-API-Key: YOUR_KEY"
114
+ mcp-stdio https://your-server.example.com:8080/mcp --header "X-API-Key: YOUR_KEY"
94
115
  ```
95
116
 
96
117
  With OAuth 2.1 authentication (for servers that require it):
@@ -102,6 +123,12 @@ mcp-stdio --oauth https://your-server.example.com:8080/mcp
102
123
  mcp-stdio --oauth --client-id YOUR_CLIENT_ID https://your-server.example.com:8080/mcp
103
124
  ```
104
125
 
126
+ Test connectivity before use:
127
+
128
+ ```bash
129
+ mcp-stdio --test https://your-server.example.com:8080/mcp
130
+ ```
131
+
105
132
  ## Claude Desktop Configuration
106
133
 
107
134
  Add to `claude_desktop_config.json`:
@@ -146,7 +173,7 @@ Options:
146
173
  --oauth Enable OAuth 2.1 authentication
147
174
  --client-id ID Pre-registered OAuth client ID (or set MCP_OAUTH_CLIENT_ID)
148
175
  --oauth-scope SCOPE OAuth scope to request
149
- -H 'Key: Value' Custom header (can be repeated)
176
+ -H, --header 'Key: Value' Custom header (can be repeated)
150
177
  --timeout-connect SEC Connection timeout (default: 10)
151
178
  --timeout-read SEC Read timeout (default: 120)
152
179
  --test Test connection and exit
@@ -154,7 +181,7 @@ Options:
154
181
  -h, --help Show help
155
182
  ```
156
183
 
157
- ## Use Cases
184
+ ## Workarounds
158
185
 
159
186
  Works around known issues in Claude Code's HTTP transport:
160
187
 
@@ -162,27 +189,9 @@ Works around known issues in Claude Code's HTTP transport:
162
189
  - **Missing Accept header** — servers return 406, misinterpreted as auth failure ([#42470](https://github.com/anthropics/claude-code/issues/42470))
163
190
  - **OAuth fallback loop** — Claude Code enters OAuth discovery even when not needed ([#34008](https://github.com/anthropics/claude-code/issues/34008), [#39271](https://github.com/anthropics/claude-code/issues/39271))
164
191
  - **Session lost after disconnect** — mcp-stdio recovers MCP sessions automatically on 404 ([#34498](https://github.com/anthropics/claude-code/issues/34498), [#38631](https://github.com/anthropics/claude-code/issues/38631))
192
+ - **OAuth scope omitted** — Claude Code sends no `scope` parameter in authorization requests, causing strict OAuth servers to reject the flow ([#4540](https://github.com/anthropics/claude-code/issues/4540)); mcp-stdio sends scopes via `--oauth-scope`
165
193
  - **Proxy settings ignored** — Claude Code does not respect `NO_PROXY` ([#34804](https://github.com/anthropics/claude-code/issues/34804)); mcp-stdio inherits proxy settings from httpx
166
194
 
167
- ## Features
168
-
169
- - **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:
170
- - [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) Protected Resource Metadata discovery
171
- - [RFC 8414](https://www.rfc-editor.org/rfc/rfc8414) Authorization Server Metadata discovery
172
- - [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707) Resource Indicators for audience binding
173
- - [RFC 7636](https://www.rfc-editor.org/rfc/rfc7636) PKCE with S256 challenge method
174
- - [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591) Dynamic Client Registration
175
- - [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750) Bearer Token usage
176
- - **Retry with backoff** — retries up to 3 times on connection errors
177
- - **Streaming resilience** — streams SSE responses in real time; retries on mid-stream disconnect
178
- - **Session recovery** — resets MCP session ID on 404 and retries
179
- - **Token refresh on 401** — automatically refreshes expired OAuth tokens mid-session
180
- - **Bearer token auth** — via `--bearer-token` flag or `MCP_BEARER_TOKEN` env var
181
- - **Custom headers** — pass any header with `-H`
182
- - **Graceful shutdown** — handles SIGTERM/SIGINT
183
- - **Proxy support** — respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` env vars via [httpx](https://www.python-httpx.org/)
184
- - **Minimal dependencies** — only [httpx](https://www.python-httpx.org/); OAuth uses stdlib only
185
-
186
195
  ## How It Works
187
196
 
188
197
  1. If `--oauth` is set, obtains an access token (cached → refresh → browser flow)
@@ -2,11 +2,11 @@
2
2
 
3
3
  [English](README.md) | 日本語
4
4
 
5
- MCP サーバー向け stdio-to-HTTP リレー — Claude Desktop/Code とリモート Streamable HTTP エンドポイントを橋渡しします。
5
+ Stdio-to-HTTP リレー — MCP クライアントとリモート HTTP MCP サーバーを橋渡しします。
6
6
 
7
- ## なぜ必要?
7
+ ## 概要
8
8
 
9
- [MCP](https://modelcontextprotocol.io/) クライアント(Claude Desktop, Claude Code)に対してローカルで稼働するセルフホスト MCP サーバのように振る舞いつつ、リモート MCP サーバへの Streamable HTTP 接続を橋渡しします:
9
+ [MCP](https://modelcontextprotocol.io/) クライアント(Claude Desktop, Claude Code)に対してローカルで稼働するセルフホスト MCP サーバのように振る舞いつつ、各種認証でリモート MCP サーバーへの接続を橋渡しします:
10
10
 
11
11
  ```mermaid
12
12
  flowchart BT
@@ -19,6 +19,25 @@ flowchart BT
19
19
 
20
20
  Bearer token、カスタムヘッダー、OAuth 2.1 認証情報をリモートサーバーへ転送します。
21
21
 
22
+ ## 特徴
23
+
24
+ - **OAuth 2.1 クライアント** — 認可コードフロー(PKCE)、動的クライアント登録、トークンリフレッシュ、安全なトークン永続化を内蔵。MCP 認可仕様の関連 RFC に完全対応:
25
+ - [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) Protected Resource Metadata の検出
26
+ - [RFC 8414](https://www.rfc-editor.org/rfc/rfc8414) Authorization Server Metadata の検出
27
+ - [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707) Resource Indicators によるオーディエンス指定
28
+ - [RFC 7636](https://www.rfc-editor.org/rfc/rfc7636) PKCE(S256 チャレンジメソッド)
29
+ - [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591) Dynamic Client Registration
30
+ - [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750) Bearer Token の利用
31
+ - **バックオフ付きリトライ** — 接続エラー時に最大3回リトライ
32
+ - **ストリーミング耐性** — SSE レスポンスをリアルタイムで転送、切断時にリトライ
33
+ - **セッション回復** — 404 でセッション ID をリセットして再試行
34
+ - **401 時の自動トークンリフレッシュ** — セッション中に OAuth トークンが失効しても自動更新
35
+ - **Bearer token 認証** — `--bearer-token` フラグまたは `MCP_BEARER_TOKEN` 環境変数
36
+ - **カスタムヘッダー** — `-H` で任意のヘッダーを送信
37
+ - **グレースフルシャットダウン** — SIGTERM/SIGINT ハンドリング
38
+ - **プロキシ対応** — `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` 環境変数を [httpx](https://www.python-httpx.org/) 経由でサポート
39
+ - **最小依存** — [httpx](https://www.python-httpx.org/) のみ; OAuth は stdlib のみ使用
40
+
22
41
  ## インストール
23
42
 
24
43
  ```bash
@@ -62,7 +81,7 @@ mcp-stdio https://your-server.example.com:8080/mcp --bearer-token YOUR_TOKEN
62
81
  カスタムヘッダー付き:
63
82
 
64
83
  ```bash
65
- mcp-stdio https://your-server.example.com:8080/mcp -H "X-API-Key: YOUR_KEY"
84
+ mcp-stdio https://your-server.example.com:8080/mcp --header "X-API-Key: YOUR_KEY"
66
85
  ```
67
86
 
68
87
  OAuth 2.1 認証付き(OAuth 必須のサーバー向け):
@@ -74,6 +93,12 @@ mcp-stdio --oauth https://your-server.example.com:8080/mcp
74
93
  mcp-stdio --oauth --client-id YOUR_CLIENT_ID https://your-server.example.com:8080/mcp
75
94
  ```
76
95
 
96
+ 接続テスト:
97
+
98
+ ```bash
99
+ mcp-stdio --test https://your-server.example.com:8080/mcp
100
+ ```
101
+
77
102
  ## Claude Desktop の設定
78
103
 
79
104
  `claude_desktop_config.json` に追加:
@@ -118,7 +143,7 @@ mcp-stdio [OPTIONS] URL
118
143
  --oauth OAuth 2.1 認証を有効化
119
144
  --client-id ID 事前登録済み OAuth クライアント ID(MCP_OAUTH_CLIENT_ID 環境変数でも指定可)
120
145
  --oauth-scope SCOPE 要求する OAuth スコープ
121
- -H 'Key: Value' カスタムヘッダー(複数指定可)
146
+ -H, --header 'Key: Value' カスタムヘッダー(複数指定可)
122
147
  --timeout-connect SEC 接続タイムアウト(デフォルト: 10秒)
123
148
  --timeout-read SEC 読み取りタイムアウト(デフォルト: 120秒)
124
149
  --test 接続テストして終了
@@ -126,7 +151,7 @@ mcp-stdio [OPTIONS] URL
126
151
  -h, --help ヘルプ表示
127
152
  ```
128
153
 
129
- ## ユースケース
154
+ ## ワークアラウンド
130
155
 
131
156
  Claude Code の HTTP transport の既知の問題を回避できます:
132
157
 
@@ -134,26 +159,8 @@ Claude Code の HTTP transport の既知の問題を回避できます:
134
159
  - **Accept ヘッダーの欠落** — サーバーが 406 を返し、認証エラーと誤認される([#42470](https://github.com/anthropics/claude-code/issues/42470))
135
160
  - **OAuth フォールバックループ** — OAuth 不要なサーバーでも OAuth 検出が走る([#34008](https://github.com/anthropics/claude-code/issues/34008), [#39271](https://github.com/anthropics/claude-code/issues/39271))
136
161
  - **切断後にセッションが失われる** — mcp-stdio は 404 で MCP セッションを自動回復([#34498](https://github.com/anthropics/claude-code/issues/34498), [#38631](https://github.com/anthropics/claude-code/issues/38631))
137
- - **プロキシ設定が無視される** Claude Code `NO_PROXY` を尊重しない([#34804](https://github.com/anthropics/claude-code/issues/34804)); mcp-stdio は httpx 経由でプロキシ設定を��承
138
-
139
- ## 機能
140
-
141
- - **OAuth 2.1 クライアント** — 認可コードフロー(PKCE)、動的クライアント登録、トークンリフレッシュ、安全なトークン永続化を内蔵。MCP 認可仕様の関連 RFC に完全対応:
142
- - [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) Protected Resource Metadata の検出
143
- - [RFC 8414](https://www.rfc-editor.org/rfc/rfc8414) Authorization Server Metadata の検出
144
- - [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707) Resource Indicators によるオーディエンス指定
145
- - [RFC 7636](https://www.rfc-editor.org/rfc/rfc7636) PKCE(S256 チャレンジメソッド)
146
- - [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591) Dynamic Client Registration
147
- - [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750) Bearer Token の利用
148
- - **バックオフ付きリトライ** — 接続エラー時に最大3回リトライ
149
- - **ストリーミング耐性** — SSE レスポンスをリアルタイムで転送、切断時にリトライ
150
- - **セッション回復** — 404 でセッション ID をリセットして再試行
151
- - **401 時の自動トークンリフレッシュ** — セッション中に OAuth トークンが失効しても自動更新
152
- - **Bearer token 認証** — `--bearer-token` フラグまたは `MCP_BEARER_TOKEN` 環境変数
153
- - **カスタムヘッダー** — `-H` で任意のヘッダーを送信
154
- - **グレースフルシャットダウン** — SIGTERM/SIGINT ハンドリング
155
- - **プロキシ対応** — `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` 環境変数を [httpx](https://www.python-httpx.org/) 経由でサポート
156
- - **最小依存** — [httpx](https://www.python-httpx.org/) のみ; OAuth は stdlib のみ使用
162
+ - **OAuth scope が送信されない** 認可リクエストに `scope` パラメータが含まれず、厳格な OAuth サーバーがフローを拒否する([#4540](https://github.com/anthropics/claude-code/issues/4540)); mcp-stdio は `--oauth-scope` でスコープを送信
163
+ - **プロキシ設定が無視される** — Claude Code が `NO_PROXY` を尊重しない([#34804](https://github.com/anthropics/claude-code/issues/34804)); mcp-stdio は httpx 経由でプロキシ設定を継承
157
164
 
158
165
  ## 仕組み
159
166
 
@@ -4,11 +4,11 @@
4
4
 
5
5
  English | [日本語](README.ja.md)
6
6
 
7
- Stdio-to-HTTP relay for MCP servers — bridges Claude Desktop/Code to remote Streamable HTTP endpoints.
7
+ Stdio-to-HTTP relay — bridges MCP clients to remote HTTP MCP servers.
8
8
 
9
- ## Why?
9
+ ## Overview
10
10
 
11
- [MCP](https://modelcontextprotocol.io/) 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 over Streamable HTTP:
11
+ [MCP](https://modelcontextprotocol.io/) 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:
12
12
 
13
13
  ```mermaid
14
14
  flowchart BT
@@ -21,6 +21,25 @@ flowchart BT
21
21
 
22
22
  Bearer tokens, custom headers, and OAuth 2.1 credentials are forwarded to the remote server.
23
23
 
24
+ ## Features
25
+
26
+ - **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:
27
+ - [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) Protected Resource Metadata discovery
28
+ - [RFC 8414](https://www.rfc-editor.org/rfc/rfc8414) Authorization Server Metadata discovery
29
+ - [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707) Resource Indicators for audience binding
30
+ - [RFC 7636](https://www.rfc-editor.org/rfc/rfc7636) PKCE with S256 challenge method
31
+ - [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591) Dynamic Client Registration
32
+ - [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750) Bearer Token usage
33
+ - **Retry with backoff** — retries up to 3 times on connection errors
34
+ - **Streaming resilience** — streams SSE responses in real time; retries on mid-stream disconnect
35
+ - **Session recovery** — resets MCP session ID on 404 and retries
36
+ - **Token refresh on 401** — automatically refreshes expired OAuth tokens mid-session
37
+ - **Bearer token auth** — via `--bearer-token` flag or `MCP_BEARER_TOKEN` env var
38
+ - **Custom headers** — pass any header with `-H`
39
+ - **Graceful shutdown** — handles SIGTERM/SIGINT
40
+ - **Proxy support** — respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` env vars via [httpx](https://www.python-httpx.org/)
41
+ - **Minimal dependencies** — only [httpx](https://www.python-httpx.org/); OAuth uses stdlib only
42
+
24
43
  ## Install
25
44
 
26
45
  ```bash
@@ -64,7 +83,7 @@ mcp-stdio https://your-server.example.com:8080/mcp --bearer-token YOUR_TOKEN
64
83
  With custom headers:
65
84
 
66
85
  ```bash
67
- mcp-stdio https://your-server.example.com:8080/mcp -H "X-API-Key: YOUR_KEY"
86
+ mcp-stdio https://your-server.example.com:8080/mcp --header "X-API-Key: YOUR_KEY"
68
87
  ```
69
88
 
70
89
  With OAuth 2.1 authentication (for servers that require it):
@@ -76,6 +95,12 @@ mcp-stdio --oauth https://your-server.example.com:8080/mcp
76
95
  mcp-stdio --oauth --client-id YOUR_CLIENT_ID https://your-server.example.com:8080/mcp
77
96
  ```
78
97
 
98
+ Test connectivity before use:
99
+
100
+ ```bash
101
+ mcp-stdio --test https://your-server.example.com:8080/mcp
102
+ ```
103
+
79
104
  ## Claude Desktop Configuration
80
105
 
81
106
  Add to `claude_desktop_config.json`:
@@ -120,7 +145,7 @@ Options:
120
145
  --oauth Enable OAuth 2.1 authentication
121
146
  --client-id ID Pre-registered OAuth client ID (or set MCP_OAUTH_CLIENT_ID)
122
147
  --oauth-scope SCOPE OAuth scope to request
123
- -H 'Key: Value' Custom header (can be repeated)
148
+ -H, --header 'Key: Value' Custom header (can be repeated)
124
149
  --timeout-connect SEC Connection timeout (default: 10)
125
150
  --timeout-read SEC Read timeout (default: 120)
126
151
  --test Test connection and exit
@@ -128,7 +153,7 @@ Options:
128
153
  -h, --help Show help
129
154
  ```
130
155
 
131
- ## Use Cases
156
+ ## Workarounds
132
157
 
133
158
  Works around known issues in Claude Code's HTTP transport:
134
159
 
@@ -136,27 +161,9 @@ Works around known issues in Claude Code's HTTP transport:
136
161
  - **Missing Accept header** — servers return 406, misinterpreted as auth failure ([#42470](https://github.com/anthropics/claude-code/issues/42470))
137
162
  - **OAuth fallback loop** — Claude Code enters OAuth discovery even when not needed ([#34008](https://github.com/anthropics/claude-code/issues/34008), [#39271](https://github.com/anthropics/claude-code/issues/39271))
138
163
  - **Session lost after disconnect** — mcp-stdio recovers MCP sessions automatically on 404 ([#34498](https://github.com/anthropics/claude-code/issues/34498), [#38631](https://github.com/anthropics/claude-code/issues/38631))
164
+ - **OAuth scope omitted** — Claude Code sends no `scope` parameter in authorization requests, causing strict OAuth servers to reject the flow ([#4540](https://github.com/anthropics/claude-code/issues/4540)); mcp-stdio sends scopes via `--oauth-scope`
139
165
  - **Proxy settings ignored** — Claude Code does not respect `NO_PROXY` ([#34804](https://github.com/anthropics/claude-code/issues/34804)); mcp-stdio inherits proxy settings from httpx
140
166
 
141
- ## Features
142
-
143
- - **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:
144
- - [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) Protected Resource Metadata discovery
145
- - [RFC 8414](https://www.rfc-editor.org/rfc/rfc8414) Authorization Server Metadata discovery
146
- - [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707) Resource Indicators for audience binding
147
- - [RFC 7636](https://www.rfc-editor.org/rfc/rfc7636) PKCE with S256 challenge method
148
- - [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591) Dynamic Client Registration
149
- - [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750) Bearer Token usage
150
- - **Retry with backoff** — retries up to 3 times on connection errors
151
- - **Streaming resilience** — streams SSE responses in real time; retries on mid-stream disconnect
152
- - **Session recovery** — resets MCP session ID on 404 and retries
153
- - **Token refresh on 401** — automatically refreshes expired OAuth tokens mid-session
154
- - **Bearer token auth** — via `--bearer-token` flag or `MCP_BEARER_TOKEN` env var
155
- - **Custom headers** — pass any header with `-H`
156
- - **Graceful shutdown** — handles SIGTERM/SIGINT
157
- - **Proxy support** — respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` env vars via [httpx](https://www.python-httpx.org/)
158
- - **Minimal dependencies** — only [httpx](https://www.python-httpx.org/); OAuth uses stdlib only
159
-
160
167
  ## How It Works
161
168
 
162
169
  1. If `--oauth` is set, obtains an access token (cached → refresh → browser flow)
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "mcp-stdio"
3
3
  dynamic = ["version"]
4
- description = "Stdio-to-HTTP relay for MCP servers"
4
+ description = "Stdio-to-HTTP relay bridges MCP clients to remote HTTP MCP servers"
5
5
  readme = "README.md"
6
6
  license = "MIT"
7
7
  requires-python = ">=3.10"
8
8
  authors = [{ name = "Shigechika AIKAWA" }]
9
- keywords = ["mcp", "stdio", "proxy", "relay", "claude"]
9
+ keywords = ["mcp", "stdio", "proxy", "relay", "claude", "bridge", "oauth"]
10
10
  classifiers = [
11
11
  "Development Status :: 4 - Beta",
12
12
  "Environment :: Console",
@@ -14,7 +14,9 @@ classifiers = [
14
14
  "License :: OSI Approved :: MIT License",
15
15
  "Programming Language :: Python :: 3",
16
16
  "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
17
18
  "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
18
20
  "Programming Language :: Python :: 3.14",
19
21
  "Topic :: Software Development :: Libraries",
20
22
  ]
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.shigechika/mcp-stdio",
4
4
  "title": "MCP Stdio",
5
- "description": "Stdio-to-HTTP relay for MCP servers",
5
+ "description": "Stdio-to-HTTP relay bridges MCP clients to remote HTTP MCP servers",
6
6
  "repository": {
7
7
  "url": "https://github.com/shigechika/mcp-stdio",
8
8
  "source": "github"
@@ -1,3 +1,3 @@
1
1
  """mcp-stdio: Stdio-to-HTTP relay for MCP servers."""
2
2
 
3
- __version__ = "0.3.2"
3
+ __version__ = "0.3.4"
@@ -11,7 +11,7 @@ from typing import Callable
11
11
  import httpx
12
12
 
13
13
  from . import __version__
14
- from .relay import log, run, test_connection
14
+ from .relay import check_connection, log, run
15
15
 
16
16
 
17
17
  def _parse_header(header: str) -> tuple[str, str]:
@@ -194,7 +194,7 @@ def main() -> None:
194
194
  client.close()
195
195
 
196
196
  if args.test:
197
- ok = test_connection(
197
+ ok = check_connection(
198
198
  url=args.url,
199
199
  headers=headers,
200
200
  timeout_connect=args.timeout_connect,
@@ -10,6 +10,8 @@ from typing import Any
10
10
 
11
11
  import httpx
12
12
 
13
+ from mcp_stdio import __version__
14
+
13
15
  MAX_RETRIES = 3
14
16
  RETRY_DELAY = 1 # seconds
15
17
 
@@ -99,14 +101,74 @@ def _post_and_stream(
99
101
  return None
100
102
 
101
103
 
102
- def test_connection(
104
+ def _reinitialize(
105
+ client: httpx.Client,
106
+ url: str,
107
+ headers: dict[str, str],
108
+ ) -> str | None:
109
+ """Send an initialize handshake to establish a new MCP session.
110
+
111
+ Used to recover after a session expires (server returns 404 on the
112
+ next request). Performs the full MCP initialize handshake:
113
+
114
+ 1. POST an ``initialize`` request to get a new session ID
115
+ 2. POST a ``notifications/initialized`` notification to signal
116
+ readiness (required by the MCP spec before any other requests)
117
+
118
+ Returns the new session ID on success, or None on failure. The
119
+ initialize response payload is discarded — the caller only needs
120
+ the session ID for subsequent requests.
121
+ """
122
+ initialize_msg = json.dumps(
123
+ {
124
+ "jsonrpc": "2.0",
125
+ "method": "initialize",
126
+ "id": 0,
127
+ "params": {
128
+ "protocolVersion": "2024-11-05",
129
+ "capabilities": {},
130
+ "clientInfo": {"name": "mcp-stdio", "version": __version__},
131
+ },
132
+ }
133
+ )
134
+ try:
135
+ resp = client.post(url, content=initialize_msg, headers=headers)
136
+ except httpx.HTTPError as e:
137
+ log(f"re-initialize request failed: {e}")
138
+ return None
139
+ if resp.status_code != 200:
140
+ log(f"re-initialize returned HTTP {resp.status_code}")
141
+ return None
142
+ new_session_id = resp.headers.get("mcp-session-id")
143
+ if not new_session_id:
144
+ log("re-initialize response missing mcp-session-id header")
145
+ return None
146
+
147
+ # MCP spec: send notifications/initialized before any other requests
148
+ initialized_msg = json.dumps(
149
+ {"jsonrpc": "2.0", "method": "notifications/initialized"}
150
+ )
151
+ initialized_headers = dict(headers)
152
+ initialized_headers["Mcp-Session-Id"] = new_session_id
153
+ try:
154
+ resp = client.post(url, content=initialized_msg, headers=initialized_headers)
155
+ except httpx.HTTPError as e:
156
+ log(f"notifications/initialized failed: {e}")
157
+ return None
158
+ if resp.status_code not in (200, 202):
159
+ log(f"notifications/initialized returned HTTP {resp.status_code}")
160
+ return None
161
+ return new_session_id
162
+
163
+
164
+ def check_connection(
103
165
  url: str,
104
166
  headers: dict[str, str],
105
167
  *,
106
168
  timeout_connect: float = 10,
107
169
  timeout_read: float = 120,
108
170
  ) -> bool:
109
- """Test MCP server connectivity by sending an initialize request.
171
+ """Check MCP server connectivity by sending an initialize request.
110
172
 
111
173
  Returns True if the server responds successfully.
112
174
  """
@@ -118,15 +180,13 @@ def test_connection(
118
180
  "params": {
119
181
  "protocolVersion": "2024-11-05",
120
182
  "capabilities": {},
121
- "clientInfo": {"name": "mcp-stdio", "version": "test"},
183
+ "clientInfo": {"name": "mcp-stdio", "version": __version__},
122
184
  },
123
185
  }
124
186
  )
125
187
 
126
188
  client = httpx.Client(
127
- timeout=httpx.Timeout(
128
- connect=timeout_connect, read=timeout_read, write=30, pool=10
129
- )
189
+ timeout=httpx.Timeout(connect=timeout_connect, read=timeout_read, write=30, pool=10)
130
190
  )
131
191
 
132
192
  try:
@@ -169,9 +229,7 @@ def test_connection(
169
229
  tools = "yes" if caps.get("tools") else "no"
170
230
  resources = "yes" if caps.get("resources") else "no"
171
231
  prompts = "yes" if caps.get("prompts") else "no"
172
- log(
173
- f"✓ Capabilities: tools={tools}, resources={resources}, prompts={prompts}"
174
- )
232
+ log(f"✓ Capabilities: tools={tools}, resources={resources}, prompts={prompts}")
175
233
  elif result_data and "error" in result_data:
176
234
  err = result_data["error"]
177
235
  log(f"✗ MCP error: {err.get('message', err)}")
@@ -273,11 +331,18 @@ def run(
273
331
  )
274
332
  continue
275
333
 
276
- # Session expired (404) — reset and retry once
334
+ # Session expired (404) — reset, re-initialize, then retry
277
335
  if result.status_code == 404 and session_id:
278
- log("session expired, resetting and retrying")
336
+ log("session expired, re-initializing and retrying")
279
337
  session_id = None
338
+ new_session_id = _reinitialize(client, url, dict(headers))
339
+ if new_session_id is None:
340
+ log("re-initialize failed, dropping request")
341
+ print(_error_response("session lost", req_id), flush=True)
342
+ continue
343
+ session_id = new_session_id
280
344
  req_headers = dict(headers)
345
+ req_headers["Mcp-Session-Id"] = session_id
281
346
  result = _post_and_stream(client, url, line, req_headers, req_id)
282
347
  if result is None:
283
348
  continue