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.
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/.github/workflows/release.yml +16 -2
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/.github/workflows/test.yml +1 -1
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/PKG-INFO +37 -28
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/README.ja.md +33 -26
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/README.md +32 -25
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/pyproject.toml +4 -2
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/server.json +1 -1
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/src/mcp_stdio/__init__.py +1 -1
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/src/mcp_stdio/cli.py +2 -2
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/src/mcp_stdio/relay.py +76 -11
- mcp_stdio-0.3.4/tests/test_relay.py +498 -0
- mcp_stdio-0.3.2/tests/test_relay.py +0 -283
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/.gitignore +0 -0
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/CLAUDE.md +0 -0
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/LICENSE +0 -0
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/src/mcp_stdio/__main__.py +0 -0
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/src/mcp_stdio/oauth.py +0 -0
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/src/mcp_stdio/token_store.py +0 -0
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/tests/__init__.py +0 -0
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/tests/test_cli.py +0 -0
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/tests/test_oauth.py +0 -0
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/tests/test_token_store.py +0 -0
- {mcp_stdio-0.3.2 → mcp_stdio-0.3.4}/uv.lock +0 -0
|
@@ -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:
|
|
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:
|
|
31
|
+
python-version: ${{ matrix.python-version }}
|
|
18
32
|
|
|
19
33
|
- name: Install dependencies
|
|
20
34
|
run: pip install -e ".[dev]"
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-stdio
|
|
3
|
-
Version: 0.3.
|
|
4
|
-
Summary: Stdio-to-HTTP relay
|
|
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
|
|
35
|
+
Stdio-to-HTTP relay — bridges MCP clients to remote HTTP MCP servers.
|
|
34
36
|
|
|
35
|
-
##
|
|
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
|
|
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
|
|
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'
|
|
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
|
-
##
|
|
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
|
-
|
|
5
|
+
Stdio-to-HTTP リレー — MCP クライアントとリモート HTTP MCP サーバーを橋渡しします。
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 概要
|
|
8
8
|
|
|
9
|
-
[MCP](https://modelcontextprotocol.io/) クライアント(Claude Desktop, Claude Code)に対してローカルで稼働するセルフホスト MCP
|
|
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
|
|
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
|
-
-
|
|
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
|
|
7
|
+
Stdio-to-HTTP relay — bridges MCP clients to remote HTTP MCP servers.
|
|
8
8
|
|
|
9
|
-
##
|
|
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
|
|
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
|
|
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'
|
|
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
|
-
##
|
|
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
|
|
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
|
|
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"
|
|
@@ -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
|
|
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 =
|
|
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
|
|
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
|
-
"""
|
|
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":
|
|
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
|
|
334
|
+
# Session expired (404) — reset, re-initialize, then retry
|
|
277
335
|
if result.status_code == 404 and session_id:
|
|
278
|
-
log("session expired,
|
|
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
|