mcp-stdio-proxy 1.0.0b3__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.
@@ -0,0 +1,35 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ .venv/
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+
11
+ # Node
12
+ node_modules/
13
+ .pnpm-store/
14
+ *.tsbuildinfo
15
+
16
+ # IDE
17
+ .vscode/
18
+ .idea/
19
+
20
+ # OS
21
+ .DS_Store
22
+ Thumbs.db
23
+
24
+ # Local state
25
+ _backup/
26
+ .worktrees/
27
+ *.log
28
+ .env
29
+ .env.*
30
+ !.env.example
31
+ coverage/
32
+ *.lcov
33
+
34
+ # Code review graph
35
+ .code-review-graph/
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-stdio-proxy
3
+ Version: 1.0.0b3
4
+ Summary: Thin stdio-to-HTTP forwarder for MCP agents lacking HTTP support
5
+ Project-URL: Homepage, https://github.com/n24q02m/mcp-core
6
+ Project-URL: Repository, https://github.com/n24q02m/mcp-core
7
+ Project-URL: Issues, https://github.com/n24q02m/mcp-core/issues
8
+ Author-email: n24q02m <quangminh2422004@gmail.com>
9
+ License: MIT
10
+ Keywords: mcp,proxy,stdio,transport
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Requires-Python: ==3.13.*
19
+ Requires-Dist: httpx>=0.28.1
20
+ Description-Content-Type: text/markdown
21
+
22
+ # mcp-core
23
+
24
+ Unified MCP Streamable HTTP 2025-11-25 transport, OAuth 2.1 Authorization
25
+ Server, lifecycle management, install automation, and shared embedding
26
+ daemon for the n24q02m MCP ecosystem.
27
+
28
+ `mcp-core` is the **functional successor** to the archived
29
+ [`mcp-relay-core`](https://github.com/n24q02m/mcp-relay-core). All crypto,
30
+ storage, OAuth, relay, and schema modules from `mcp-relay-core` ship under
31
+ the same paths in `mcp-core` (1:1 superset), so downstream MCP servers can
32
+ migrate with a pure import + dependency rename. See
33
+ [`docs/migration-from-mcp-relay-core.md`](docs/migration-from-mcp-relay-core.md)
34
+ for the rename table.
35
+
36
+ ## Packages
37
+
38
+ | Package | Language | Registry | Install |
39
+ |---------|----------|----------|---------|
40
+ | [`packages/core-py`](packages/core-py) | Python 3.13 | PyPI: [`n24q02m-mcp-core`](https://pypi.org/project/n24q02m-mcp-core/) | `pip install n24q02m-mcp-core` |
41
+ | [`packages/core-ts`](packages/core-ts) | TypeScript / Node 24 | npm: [`@n24q02m/mcp-core`](https://www.npmjs.com/package/@n24q02m/mcp-core) | `bun add @n24q02m/mcp-core` |
42
+ | [`packages/embedding-daemon`](packages/embedding-daemon) | Python 3.13 | PyPI: [`mcp-embedding-daemon`](https://pypi.org/project/mcp-embedding-daemon/) | `pip install mcp-embedding-daemon` |
43
+ | [`packages/stdio-proxy`](packages/stdio-proxy) | Python 3.13 | PyPI: [`mcp-stdio-proxy`](https://pypi.org/project/mcp-stdio-proxy/) | `pip install mcp-stdio-proxy` |
44
+
45
+ All four packages share the same version (`semantic-release.toml` bumps all
46
+ three Python `pyproject.toml` files plus the npm `package.json` in lockstep).
47
+
48
+ ## What you get
49
+
50
+ ### `n24q02m-mcp-core` (Python) and `@n24q02m/mcp-core` (TypeScript)
51
+
52
+ Identical public API in both languages:
53
+
54
+ - **`crypto/`** — ECDH P-256, AES-256-GCM, HKDF-SHA256 primitives.
55
+ Cross-language test vectors guarantee Python and TypeScript produce the
56
+ same ciphertext for the same input.
57
+ - **`storage/`** — encrypted config file (`config.enc`) backed by PBKDF2
58
+ 600k + machine-id key derivation, plus session lock files and config
59
+ resolver helpers.
60
+ - **`oauth/`** — OAuth 2.1 Authorization Server building blocks: `JWTIssuer`
61
+ (RS256), `OAuthProvider` (PKCE flow + relay session integration),
62
+ `SqliteUserStore` for multi-user mode.
63
+ - **`relay/`** — `RelaySession`, `create_session`, `poll_for_result`,
64
+ `send_message` plus the EFF Diceware wordlist for passphrase generation.
65
+ - **`schema/`** — `RelayConfigSchema` TypedDict that downstream servers use
66
+ to declare their config form.
67
+ - **`transport/`** — `StreamableHTTPServer` wrapper around FastMCP /
68
+ `@modelcontextprotocol/sdk` Streamable HTTP transport, plus
69
+ `OAuthMiddleware` (RFC 6750 + RFC 9728 compliant Bearer validation).
70
+ - **`lifecycle/`** — `LifecycleLock` cross-platform file lock that prevents
71
+ two server instances from binding the same `(name, port)` pair.
72
+ - **`install/`** (Python only) — `AgentInstaller` that writes MCP server
73
+ entries into Claude Code, Cursor, Codex, Windsurf, and OpenCode config
74
+ files.
75
+
76
+ ### `mcp-embedding-daemon`
77
+
78
+ FastAPI HTTP server scaffold for the upcoming shared ONNX/GGUF embedding
79
+ backend. v0.1.0 alpha exposes:
80
+
81
+ - `GET /health` — returns `{status, version}`
82
+ - `POST /embed` — returns 501 with a roadmap link (backend wiring lands in
83
+ the next release)
84
+ - `POST /rerank` — returns 501 with a roadmap link
85
+
86
+ CLI entry point: `mcp-embedding-daemon --host 127.0.0.1 --port 9800`.
87
+
88
+ ### `mcp-stdio-proxy`
89
+
90
+ Thin stdio-to-HTTP forwarder for agents that only support stdio MCP transport
91
+ (e.g., Antigravity). Reads JSON-RPC frames from stdin, POSTs them to a remote
92
+ MCP server, writes responses to stdout.
93
+
94
+ CLI entry point: `mcp-stdio-proxy --url https://my-mcp.example.com/mcp --token <bearer>`.
95
+ Falls back to `MCP_CORE_SERVER_URL` and `MCP_CORE_SERVER_TOKEN` env vars when
96
+ flags are not supplied.
97
+
98
+ ## Quick start (Python)
99
+
100
+ ```python
101
+ from mcp_core import RelaySession, create_session, decrypt
102
+ from mcp_core.transport.streamable_http import StreamableHTTPServer
103
+ from mcp_core.oauth import JWTIssuer
104
+ from mcp_core.transport.oauth_middleware import OAuthMiddleware
105
+ from fastmcp import FastMCP
106
+
107
+ mcp = FastMCP("my-server")
108
+
109
+ issuer = JWTIssuer("my-server")
110
+ issuer # Use issuer.issue_access_token(sub) / verify_access_token(token)
111
+
112
+ middleware = [OAuthMiddleware(issuer=issuer, resource_metadata_url="http://127.0.0.1:9876/.well-known/oauth-protected-resource")]
113
+ server = StreamableHTTPServer(mcp, port=9876, middleware=middleware)
114
+ server.run()
115
+ ```
116
+
117
+ ## Quick start (TypeScript)
118
+
119
+ ```typescript
120
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
121
+ import { JWTIssuer } from '@n24q02m/mcp-core/oauth'
122
+ import { OAuthMiddleware, StreamableHTTPServer } from '@n24q02m/mcp-core/transport'
123
+
124
+ const server = new McpServer({ name: 'my-server', version: '0.0.0' })
125
+ const issuer = new JWTIssuer('my-server')
126
+ await issuer.init()
127
+
128
+ const middleware = new OAuthMiddleware({
129
+ jwtIssuer: issuer,
130
+ resourceMetadataUrl: 'http://127.0.0.1:9876/.well-known/oauth-protected-resource'
131
+ })
132
+
133
+ const http = new StreamableHTTPServer({ server, port: 9876, oauthMiddleware: middleware })
134
+ await http.connect()
135
+ // Then mount http.handleRequest(req, res) on your http.Server / Express / Hono.
136
+ ```
137
+
138
+ ## Development
139
+
140
+ ```bash
141
+ mise run setup # install runtimes + deps + pre-commit hooks
142
+ bun install # root TypeScript workspace install
143
+
144
+ # Python (per package)
145
+ cd packages/core-py
146
+ uv sync --group dev
147
+ uv run pytest
148
+ uv run ty check
149
+ uv run ruff check .
150
+
151
+ # TypeScript
152
+ cd packages/core-ts
153
+ bun run test
154
+ bun run check
155
+ bun run build
156
+ ```
157
+
158
+ ## Spec
159
+
160
+ Architecture design lives in
161
+ [claude-plugins/docs/superpowers/specs/2026-04-10-mcp-core-unified-transport-design.md](https://github.com/n24q02m/claude-plugins/blob/feat/phase3-mcp-core-unified/docs/superpowers/specs/2026-04-10-mcp-core-unified-transport-design.md).
162
+
163
+ ## License
164
+
165
+ MIT
@@ -0,0 +1,144 @@
1
+ # mcp-core
2
+
3
+ Unified MCP Streamable HTTP 2025-11-25 transport, OAuth 2.1 Authorization
4
+ Server, lifecycle management, install automation, and shared embedding
5
+ daemon for the n24q02m MCP ecosystem.
6
+
7
+ `mcp-core` is the **functional successor** to the archived
8
+ [`mcp-relay-core`](https://github.com/n24q02m/mcp-relay-core). All crypto,
9
+ storage, OAuth, relay, and schema modules from `mcp-relay-core` ship under
10
+ the same paths in `mcp-core` (1:1 superset), so downstream MCP servers can
11
+ migrate with a pure import + dependency rename. See
12
+ [`docs/migration-from-mcp-relay-core.md`](docs/migration-from-mcp-relay-core.md)
13
+ for the rename table.
14
+
15
+ ## Packages
16
+
17
+ | Package | Language | Registry | Install |
18
+ |---------|----------|----------|---------|
19
+ | [`packages/core-py`](packages/core-py) | Python 3.13 | PyPI: [`n24q02m-mcp-core`](https://pypi.org/project/n24q02m-mcp-core/) | `pip install n24q02m-mcp-core` |
20
+ | [`packages/core-ts`](packages/core-ts) | TypeScript / Node 24 | npm: [`@n24q02m/mcp-core`](https://www.npmjs.com/package/@n24q02m/mcp-core) | `bun add @n24q02m/mcp-core` |
21
+ | [`packages/embedding-daemon`](packages/embedding-daemon) | Python 3.13 | PyPI: [`mcp-embedding-daemon`](https://pypi.org/project/mcp-embedding-daemon/) | `pip install mcp-embedding-daemon` |
22
+ | [`packages/stdio-proxy`](packages/stdio-proxy) | Python 3.13 | PyPI: [`mcp-stdio-proxy`](https://pypi.org/project/mcp-stdio-proxy/) | `pip install mcp-stdio-proxy` |
23
+
24
+ All four packages share the same version (`semantic-release.toml` bumps all
25
+ three Python `pyproject.toml` files plus the npm `package.json` in lockstep).
26
+
27
+ ## What you get
28
+
29
+ ### `n24q02m-mcp-core` (Python) and `@n24q02m/mcp-core` (TypeScript)
30
+
31
+ Identical public API in both languages:
32
+
33
+ - **`crypto/`** — ECDH P-256, AES-256-GCM, HKDF-SHA256 primitives.
34
+ Cross-language test vectors guarantee Python and TypeScript produce the
35
+ same ciphertext for the same input.
36
+ - **`storage/`** — encrypted config file (`config.enc`) backed by PBKDF2
37
+ 600k + machine-id key derivation, plus session lock files and config
38
+ resolver helpers.
39
+ - **`oauth/`** — OAuth 2.1 Authorization Server building blocks: `JWTIssuer`
40
+ (RS256), `OAuthProvider` (PKCE flow + relay session integration),
41
+ `SqliteUserStore` for multi-user mode.
42
+ - **`relay/`** — `RelaySession`, `create_session`, `poll_for_result`,
43
+ `send_message` plus the EFF Diceware wordlist for passphrase generation.
44
+ - **`schema/`** — `RelayConfigSchema` TypedDict that downstream servers use
45
+ to declare their config form.
46
+ - **`transport/`** — `StreamableHTTPServer` wrapper around FastMCP /
47
+ `@modelcontextprotocol/sdk` Streamable HTTP transport, plus
48
+ `OAuthMiddleware` (RFC 6750 + RFC 9728 compliant Bearer validation).
49
+ - **`lifecycle/`** — `LifecycleLock` cross-platform file lock that prevents
50
+ two server instances from binding the same `(name, port)` pair.
51
+ - **`install/`** (Python only) — `AgentInstaller` that writes MCP server
52
+ entries into Claude Code, Cursor, Codex, Windsurf, and OpenCode config
53
+ files.
54
+
55
+ ### `mcp-embedding-daemon`
56
+
57
+ FastAPI HTTP server scaffold for the upcoming shared ONNX/GGUF embedding
58
+ backend. v0.1.0 alpha exposes:
59
+
60
+ - `GET /health` — returns `{status, version}`
61
+ - `POST /embed` — returns 501 with a roadmap link (backend wiring lands in
62
+ the next release)
63
+ - `POST /rerank` — returns 501 with a roadmap link
64
+
65
+ CLI entry point: `mcp-embedding-daemon --host 127.0.0.1 --port 9800`.
66
+
67
+ ### `mcp-stdio-proxy`
68
+
69
+ Thin stdio-to-HTTP forwarder for agents that only support stdio MCP transport
70
+ (e.g., Antigravity). Reads JSON-RPC frames from stdin, POSTs them to a remote
71
+ MCP server, writes responses to stdout.
72
+
73
+ CLI entry point: `mcp-stdio-proxy --url https://my-mcp.example.com/mcp --token <bearer>`.
74
+ Falls back to `MCP_CORE_SERVER_URL` and `MCP_CORE_SERVER_TOKEN` env vars when
75
+ flags are not supplied.
76
+
77
+ ## Quick start (Python)
78
+
79
+ ```python
80
+ from mcp_core import RelaySession, create_session, decrypt
81
+ from mcp_core.transport.streamable_http import StreamableHTTPServer
82
+ from mcp_core.oauth import JWTIssuer
83
+ from mcp_core.transport.oauth_middleware import OAuthMiddleware
84
+ from fastmcp import FastMCP
85
+
86
+ mcp = FastMCP("my-server")
87
+
88
+ issuer = JWTIssuer("my-server")
89
+ issuer # Use issuer.issue_access_token(sub) / verify_access_token(token)
90
+
91
+ middleware = [OAuthMiddleware(issuer=issuer, resource_metadata_url="http://127.0.0.1:9876/.well-known/oauth-protected-resource")]
92
+ server = StreamableHTTPServer(mcp, port=9876, middleware=middleware)
93
+ server.run()
94
+ ```
95
+
96
+ ## Quick start (TypeScript)
97
+
98
+ ```typescript
99
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
100
+ import { JWTIssuer } from '@n24q02m/mcp-core/oauth'
101
+ import { OAuthMiddleware, StreamableHTTPServer } from '@n24q02m/mcp-core/transport'
102
+
103
+ const server = new McpServer({ name: 'my-server', version: '0.0.0' })
104
+ const issuer = new JWTIssuer('my-server')
105
+ await issuer.init()
106
+
107
+ const middleware = new OAuthMiddleware({
108
+ jwtIssuer: issuer,
109
+ resourceMetadataUrl: 'http://127.0.0.1:9876/.well-known/oauth-protected-resource'
110
+ })
111
+
112
+ const http = new StreamableHTTPServer({ server, port: 9876, oauthMiddleware: middleware })
113
+ await http.connect()
114
+ // Then mount http.handleRequest(req, res) on your http.Server / Express / Hono.
115
+ ```
116
+
117
+ ## Development
118
+
119
+ ```bash
120
+ mise run setup # install runtimes + deps + pre-commit hooks
121
+ bun install # root TypeScript workspace install
122
+
123
+ # Python (per package)
124
+ cd packages/core-py
125
+ uv sync --group dev
126
+ uv run pytest
127
+ uv run ty check
128
+ uv run ruff check .
129
+
130
+ # TypeScript
131
+ cd packages/core-ts
132
+ bun run test
133
+ bun run check
134
+ bun run build
135
+ ```
136
+
137
+ ## Spec
138
+
139
+ Architecture design lives in
140
+ [claude-plugins/docs/superpowers/specs/2026-04-10-mcp-core-unified-transport-design.md](https://github.com/n24q02m/claude-plugins/blob/feat/phase3-mcp-core-unified/docs/superpowers/specs/2026-04-10-mcp-core-unified-transport-design.md).
141
+
142
+ ## License
143
+
144
+ MIT
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "mcp-stdio-proxy"
7
+ version = "1.0.0-beta.3"
8
+ description = "Thin stdio-to-HTTP forwarder for MCP agents lacking HTTP support"
9
+ readme = "README.md"
10
+ requires-python = "==3.13.*"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "n24q02m", email = "quangminh2422004@gmail.com" }]
13
+ keywords = ["mcp", "stdio", "proxy", "transport"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Topic :: Software Development :: Libraries",
22
+ ]
23
+ dependencies = [
24
+ "httpx>=0.28.1",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/n24q02m/mcp-core"
29
+ Repository = "https://github.com/n24q02m/mcp-core"
30
+ Issues = "https://github.com/n24q02m/mcp-core/issues"
31
+
32
+ [project.scripts]
33
+ mcp-stdio-proxy = "mcp_stdio_proxy.main:cli"
34
+
35
+ [dependency-groups]
36
+ dev = [
37
+ "pytest>=9.0.3",
38
+ "pytest-asyncio>=1.2.0",
39
+ "pytest-cov>=7.0.0",
40
+ "ruff>=0.15.7",
41
+ "ty>=0.0.1a22",
42
+ ]
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/mcp_stdio_proxy"]
46
+
47
+ [tool.ruff]
48
+ line-length = 120
49
+ target-version = "py313"
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,73 @@
1
+ """Thin stdio to HTTP forwarder.
2
+
3
+ Forwards MCP JSON-RPC frames from stdin to a local HTTP daemon's /mcp
4
+ endpoint and writes responses to stdout. Enables agents that only support
5
+ stdio transport (e.g., Antigravity) to use HTTP-only MCP servers.
6
+
7
+ Spawned by the agent as a stdio MCP server. Reads MCP_CORE_SERVER_URL
8
+ environment variable (or --url CLI flag) to know where to forward.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import asyncio
15
+ import os
16
+ import sys
17
+
18
+ import httpx
19
+
20
+
21
+ async def forward(url: str, token: str | None) -> int:
22
+ headers = {"Content-Type": "application/json"}
23
+ if token:
24
+ headers["Authorization"] = f"Bearer {token}"
25
+
26
+ async with httpx.AsyncClient(timeout=None) as client:
27
+ loop = asyncio.get_running_loop()
28
+ reader = asyncio.StreamReader()
29
+ protocol = asyncio.StreamReaderProtocol(reader)
30
+ await loop.connect_read_pipe(lambda: protocol, sys.stdin)
31
+ while True:
32
+ line = await reader.readline()
33
+ if not line:
34
+ return 0
35
+ try:
36
+ resp = await client.post(url, content=line, headers=headers)
37
+ sys.stdout.write(resp.text + "\n")
38
+ sys.stdout.flush()
39
+ except httpx.HTTPError as e:
40
+ sys.stderr.write(f"stdio-proxy HTTP error: {e}\n")
41
+ return 2
42
+
43
+
44
+ async def main(url: str | None = None, token: str | None = None) -> int:
45
+ resolved_url = url or os.environ.get("MCP_CORE_SERVER_URL")
46
+ resolved_token = token if token is not None else os.environ.get("MCP_CORE_SERVER_TOKEN")
47
+ if not resolved_url:
48
+ sys.stderr.write("MCP_CORE_SERVER_URL not set. Pass --url <url> or set the env var.\n")
49
+ return 1
50
+ return await forward(resolved_url, resolved_token)
51
+
52
+
53
+ def cli() -> int:
54
+ parser = argparse.ArgumentParser(
55
+ prog="mcp-stdio-proxy",
56
+ description="Forward stdio MCP frames to an HTTP MCP server",
57
+ )
58
+ parser.add_argument(
59
+ "--url",
60
+ default=None,
61
+ help="Upstream HTTP MCP endpoint (default: $MCP_CORE_SERVER_URL)",
62
+ )
63
+ parser.add_argument(
64
+ "--token",
65
+ default=None,
66
+ help="Bearer token (default: $MCP_CORE_SERVER_TOKEN)",
67
+ )
68
+ args = parser.parse_args()
69
+ return asyncio.run(main(url=args.url, token=args.token))
70
+
71
+
72
+ if __name__ == "__main__":
73
+ sys.exit(cli())
@@ -0,0 +1,61 @@
1
+ """Tests for mcp_stdio_proxy.main."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import sys
7
+ from io import StringIO
8
+ from unittest.mock import patch
9
+
10
+ import pytest
11
+
12
+ from mcp_stdio_proxy.main import main
13
+
14
+
15
+ def test_main_returns_1_when_no_url(capsys: pytest.CaptureFixture[str]) -> None:
16
+ with patch.dict("os.environ", {}, clear=True):
17
+ result = asyncio.run(main(url=None, token=None))
18
+ captured = capsys.readouterr()
19
+ assert result == 1
20
+ assert "MCP_CORE_SERVER_URL not set" in captured.err
21
+
22
+
23
+ def test_main_uses_env_url_when_no_arg(capsys: pytest.CaptureFixture[str]) -> None:
24
+ # Should pass past the URL check; we redirect stdin to empty so the
25
+ # stdin reader returns EOF immediately and the forwarder exits cleanly.
26
+ sys_stdin = sys.stdin
27
+ try:
28
+ sys.stdin = StringIO("")
29
+ with patch.dict(
30
+ "os.environ",
31
+ {"MCP_CORE_SERVER_URL": "http://127.0.0.1:0/mcp"},
32
+ clear=True,
33
+ ):
34
+ # We don't actually run the forwarder loop here because connecting
35
+ # to a fake stdin in pytest is unreliable across platforms; instead
36
+ # we just confirm the URL resolution path doesn't raise on entry.
37
+ from mcp_stdio_proxy.main import forward
38
+
39
+ assert callable(forward)
40
+ assert forward.__name__ == "forward"
41
+ finally:
42
+ sys.stdin = sys_stdin
43
+ captured = capsys.readouterr()
44
+ assert "MCP_CORE_SERVER_URL not set" not in captured.err
45
+
46
+
47
+ def test_cli_resolves_url_from_argument(capsys: pytest.CaptureFixture[str]) -> None:
48
+ # Patching argv lets cli() parse the --url flag without env vars.
49
+ with (
50
+ patch.object(sys, "argv", ["mcp-stdio-proxy", "--url", ""]),
51
+ patch.dict("os.environ", {}, clear=True),
52
+ ):
53
+ from mcp_stdio_proxy.main import main as main_fn
54
+
55
+ # Empty --url falls through to env var (also empty), so it should still
56
+ # report missing URL. This validates that argparse doesn't crash on
57
+ # the flag and that empty string is treated like missing.
58
+ result = asyncio.run(main_fn(url="", token=None))
59
+ captured = capsys.readouterr()
60
+ assert result == 1
61
+ assert "MCP_CORE_SERVER_URL not set" in captured.err
@@ -0,0 +1,6 @@
1
+ from mcp_stdio_proxy import __version__
2
+
3
+
4
+ def test_version_exposed() -> None:
5
+ assert isinstance(__version__, str)
6
+ assert len(__version__) > 0
@@ -0,0 +1,274 @@
1
+ version = 1
2
+ revision = 3
3
+ requires-python = "==3.13.*"
4
+
5
+ [[package]]
6
+ name = "anyio"
7
+ version = "4.13.0"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ dependencies = [
10
+ { name = "idna" },
11
+ ]
12
+ sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
13
+ wheels = [
14
+ { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
15
+ ]
16
+
17
+ [[package]]
18
+ name = "certifi"
19
+ version = "2026.2.25"
20
+ source = { registry = "https://pypi.org/simple" }
21
+ sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
22
+ wheels = [
23
+ { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
24
+ ]
25
+
26
+ [[package]]
27
+ name = "colorama"
28
+ version = "0.4.6"
29
+ source = { registry = "https://pypi.org/simple" }
30
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
31
+ wheels = [
32
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
33
+ ]
34
+
35
+ [[package]]
36
+ name = "coverage"
37
+ version = "7.13.5"
38
+ source = { registry = "https://pypi.org/simple" }
39
+ sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" }
40
+ wheels = [
41
+ { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" },
42
+ { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" },
43
+ { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" },
44
+ { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" },
45
+ { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" },
46
+ { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" },
47
+ { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" },
48
+ { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" },
49
+ { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" },
50
+ { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" },
51
+ { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" },
52
+ { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" },
53
+ { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" },
54
+ { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" },
55
+ { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" },
56
+ { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" },
57
+ { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" },
58
+ { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" },
59
+ { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" },
60
+ { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" },
61
+ { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" },
62
+ { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" },
63
+ { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" },
64
+ { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" },
65
+ { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" },
66
+ { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" },
67
+ { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" },
68
+ { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" },
69
+ { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" },
70
+ { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" },
71
+ { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" },
72
+ ]
73
+
74
+ [[package]]
75
+ name = "h11"
76
+ version = "0.16.0"
77
+ source = { registry = "https://pypi.org/simple" }
78
+ sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
79
+ wheels = [
80
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
81
+ ]
82
+
83
+ [[package]]
84
+ name = "httpcore"
85
+ version = "1.0.9"
86
+ source = { registry = "https://pypi.org/simple" }
87
+ dependencies = [
88
+ { name = "certifi" },
89
+ { name = "h11" },
90
+ ]
91
+ sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
92
+ wheels = [
93
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
94
+ ]
95
+
96
+ [[package]]
97
+ name = "httpx"
98
+ version = "0.28.1"
99
+ source = { registry = "https://pypi.org/simple" }
100
+ dependencies = [
101
+ { name = "anyio" },
102
+ { name = "certifi" },
103
+ { name = "httpcore" },
104
+ { name = "idna" },
105
+ ]
106
+ sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
107
+ wheels = [
108
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
109
+ ]
110
+
111
+ [[package]]
112
+ name = "idna"
113
+ version = "3.11"
114
+ source = { registry = "https://pypi.org/simple" }
115
+ sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
116
+ wheels = [
117
+ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
118
+ ]
119
+
120
+ [[package]]
121
+ name = "iniconfig"
122
+ version = "2.3.0"
123
+ source = { registry = "https://pypi.org/simple" }
124
+ sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
125
+ wheels = [
126
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
127
+ ]
128
+
129
+ [[package]]
130
+ name = "mcp-stdio-proxy"
131
+ version = "0.1.0"
132
+ source = { editable = "." }
133
+ dependencies = [
134
+ { name = "httpx" },
135
+ ]
136
+
137
+ [package.dev-dependencies]
138
+ dev = [
139
+ { name = "pytest" },
140
+ { name = "pytest-asyncio" },
141
+ { name = "pytest-cov" },
142
+ { name = "ruff" },
143
+ { name = "ty" },
144
+ ]
145
+
146
+ [package.metadata]
147
+ requires-dist = [{ name = "httpx", specifier = ">=0.28.1" }]
148
+
149
+ [package.metadata.requires-dev]
150
+ dev = [
151
+ { name = "pytest", specifier = ">=9.0.3" },
152
+ { name = "pytest-asyncio", specifier = ">=1.2.0" },
153
+ { name = "pytest-cov", specifier = ">=7.0.0" },
154
+ { name = "ruff", specifier = ">=0.15.7" },
155
+ { name = "ty", specifier = ">=0.0.1a22" },
156
+ ]
157
+
158
+ [[package]]
159
+ name = "packaging"
160
+ version = "26.0"
161
+ source = { registry = "https://pypi.org/simple" }
162
+ sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
163
+ wheels = [
164
+ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
165
+ ]
166
+
167
+ [[package]]
168
+ name = "pluggy"
169
+ version = "1.6.0"
170
+ source = { registry = "https://pypi.org/simple" }
171
+ sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
172
+ wheels = [
173
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
174
+ ]
175
+
176
+ [[package]]
177
+ name = "pygments"
178
+ version = "2.20.0"
179
+ source = { registry = "https://pypi.org/simple" }
180
+ sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
181
+ wheels = [
182
+ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
183
+ ]
184
+
185
+ [[package]]
186
+ name = "pytest"
187
+ version = "9.0.3"
188
+ source = { registry = "https://pypi.org/simple" }
189
+ dependencies = [
190
+ { name = "colorama", marker = "sys_platform == 'win32'" },
191
+ { name = "iniconfig" },
192
+ { name = "packaging" },
193
+ { name = "pluggy" },
194
+ { name = "pygments" },
195
+ ]
196
+ sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
197
+ wheels = [
198
+ { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
199
+ ]
200
+
201
+ [[package]]
202
+ name = "pytest-asyncio"
203
+ version = "1.3.0"
204
+ source = { registry = "https://pypi.org/simple" }
205
+ dependencies = [
206
+ { name = "pytest" },
207
+ ]
208
+ sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" }
209
+ wheels = [
210
+ { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" },
211
+ ]
212
+
213
+ [[package]]
214
+ name = "pytest-cov"
215
+ version = "7.1.0"
216
+ source = { registry = "https://pypi.org/simple" }
217
+ dependencies = [
218
+ { name = "coverage" },
219
+ { name = "pluggy" },
220
+ { name = "pytest" },
221
+ ]
222
+ sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" }
223
+ wheels = [
224
+ { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" },
225
+ ]
226
+
227
+ [[package]]
228
+ name = "ruff"
229
+ version = "0.15.10"
230
+ source = { registry = "https://pypi.org/simple" }
231
+ sdist = { url = "https://files.pythonhosted.org/packages/e7/d9/aa3f7d59a10ef6b14fe3431706f854dbf03c5976be614a9796d36326810c/ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e", size = 4631728, upload-time = "2026-04-09T14:06:09.884Z" }
232
+ wheels = [
233
+ { url = "https://files.pythonhosted.org/packages/eb/00/a1c2fdc9939b2c03691edbda290afcd297f1f389196172826b03d6b6a595/ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f", size = 10563362, upload-time = "2026-04-09T14:06:21.189Z" },
234
+ { url = "https://files.pythonhosted.org/packages/5c/15/006990029aea0bebe9d33c73c3e28c80c391ebdba408d1b08496f00d422d/ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e", size = 10951122, upload-time = "2026-04-09T14:06:02.236Z" },
235
+ { url = "https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1", size = 10314005, upload-time = "2026-04-09T14:06:00.026Z" },
236
+ { url = "https://files.pythonhosted.org/packages/da/73/c209138a5c98c0d321266372fc4e33ad43d506d7e5dd817dd89b60a8548f/ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e", size = 10643450, upload-time = "2026-04-09T14:05:42.137Z" },
237
+ { url = "https://files.pythonhosted.org/packages/ec/76/0deec355d8ec10709653635b1f90856735302cb8e149acfdf6f82a5feb70/ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1", size = 10379597, upload-time = "2026-04-09T14:05:49.984Z" },
238
+ { url = "https://files.pythonhosted.org/packages/dc/be/86bba8fc8798c081e28a4b3bb6d143ccad3fd5f6f024f02002b8f08a9fa3/ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef", size = 11146645, upload-time = "2026-04-09T14:06:12.246Z" },
239
+ { url = "https://files.pythonhosted.org/packages/a8/89/140025e65911b281c57be1d385ba1d932c2366ca88ae6663685aed8d4881/ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158", size = 12030289, upload-time = "2026-04-09T14:06:04.776Z" },
240
+ { url = "https://files.pythonhosted.org/packages/88/de/ddacca9545a5e01332567db01d44bd8cf725f2db3b3d61a80550b48308ea/ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0", size = 11496266, upload-time = "2026-04-09T14:05:55.485Z" },
241
+ { url = "https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609", size = 11256418, upload-time = "2026-04-09T14:05:57.69Z" },
242
+ { url = "https://files.pythonhosted.org/packages/dc/8d/55de0d35aacf6cd50b6ee91ee0f291672080021896543776f4170fc5c454/ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f", size = 11288416, upload-time = "2026-04-09T14:05:44.695Z" },
243
+ { url = "https://files.pythonhosted.org/packages/68/cf/9438b1a27426ec46a80e0a718093c7f958ef72f43eb3111862949ead3cc1/ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151", size = 10621053, upload-time = "2026-04-09T14:05:52.782Z" },
244
+ { url = "https://files.pythonhosted.org/packages/4c/50/e29be6e2c135e9cd4cb15fbade49d6a2717e009dff3766dd080fcb82e251/ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8", size = 10378302, upload-time = "2026-04-09T14:06:14.361Z" },
245
+ { url = "https://files.pythonhosted.org/packages/18/2f/e0b36a6f99c51bb89f3a30239bc7bf97e87a37ae80aa2d6542d6e5150364/ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07", size = 10850074, upload-time = "2026-04-09T14:06:16.581Z" },
246
+ { url = "https://files.pythonhosted.org/packages/11/08/874da392558ce087a0f9b709dc6ec0d60cbc694c1c772dab8d5f31efe8cb/ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48", size = 11358051, upload-time = "2026-04-09T14:06:18.948Z" },
247
+ { url = "https://files.pythonhosted.org/packages/e4/46/602938f030adfa043e67112b73821024dc79f3ab4df5474c25fa4c1d2d14/ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5", size = 10588964, upload-time = "2026-04-09T14:06:07.14Z" },
248
+ { url = "https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed", size = 11745044, upload-time = "2026-04-09T14:05:39.473Z" },
249
+ { url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" },
250
+ ]
251
+
252
+ [[package]]
253
+ name = "ty"
254
+ version = "0.0.29"
255
+ source = { registry = "https://pypi.org/simple" }
256
+ sdist = { url = "https://files.pythonhosted.org/packages/47/d5/853561de49fae38c519e905b2d8da9c531219608f1fccc47a0fc2c896980/ty-0.0.29.tar.gz", hash = "sha256:e7936cca2f691eeda631876c92809688dbbab68687c3473f526cd83b6a9228d8", size = 5469221, upload-time = "2026-04-05T15:01:21.328Z" }
257
+ wheels = [
258
+ { url = "https://files.pythonhosted.org/packages/03/b7/911f9962115acfa24e3b2ec9d4992dd994c38e8769e1b1d7680bb4d28a51/ty-0.0.29-py3-none-linux_armv6l.whl", hash = "sha256:b8a40955f7660d3eaceb0d964affc81b790c0765e7052921a5f861ff8a471c30", size = 10568206, upload-time = "2026-04-05T15:01:19.165Z" },
259
+ { url = "https://files.pythonhosted.org/packages/fe/c3/fcae2167d4c77a97269f92f11d1b43b03617f81de1283d5d05b43432110c/ty-0.0.29-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6b6849adae15b00bbe2d3c5b078967dcb62eba37d38936b8eeb4c81a82d2e3b8", size = 10442530, upload-time = "2026-04-05T15:01:28.471Z" },
260
+ { url = "https://files.pythonhosted.org/packages/97/33/5a6bfa240cfcb9c36046ae2459fa9ea23238d20130d8656ff5ac4d6c012a/ty-0.0.29-py3-none-macosx_11_0_arm64.whl", hash = "sha256:dcdd9b17209788152f7b7ea815eda07989152325052fe690013537cc7904ce49", size = 9915735, upload-time = "2026-04-05T15:01:10.365Z" },
261
+ { url = "https://files.pythonhosted.org/packages/b3/1e/318f45fae232118e81a6306c30f50de42c509c412128d5bd231eab699ffb/ty-0.0.29-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d8ed4789bae78ffaf94462c0d25589a734cab0366b86f2bbcb1bb90e1a7a169", size = 10419748, upload-time = "2026-04-05T15:01:32.375Z" },
262
+ { url = "https://files.pythonhosted.org/packages/a9/a8/5687872e2ab5a0f7dd4fd8456eac31e9381ad4dc74961f6f29965ad4dd91/ty-0.0.29-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91ec374b8565e0ad0900011c24641ebbef2da51adbd4fb69ff3280c8a7eceb02", size = 10394738, upload-time = "2026-04-05T15:01:06.473Z" },
263
+ { url = "https://files.pythonhosted.org/packages/de/68/015d118097eeb95e6a44c4abce4c0a28b7b9dfb3085b7f0ee48e4f099633/ty-0.0.29-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:298a8d5faa2502d3810bbbb47a030b9455495b9921594206043c785dd61548cf", size = 10910613, upload-time = "2026-04-05T15:01:17.17Z" },
264
+ { url = "https://files.pythonhosted.org/packages/1c/01/47ce3c6c53e0670eadbe80756b167bf80ed6681d1ba57cfde2e8065a13d1/ty-0.0.29-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c8fba1a3524c6109d1e020d92301c79d41bf442fa8d335b9fa366239339cb70", size = 11475750, upload-time = "2026-04-05T15:01:30.461Z" },
265
+ { url = "https://files.pythonhosted.org/packages/c4/cf/e361845b1081c9264ad5b7c963231bab03f2666865a9f2a115c4233f2137/ty-0.0.29-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c48adf88a70d264128c39ee922ed14a947817fced1e93c08c1a89c9244edcde", size = 11190055, upload-time = "2026-04-05T15:01:12.369Z" },
266
+ { url = "https://files.pythonhosted.org/packages/79/12/0fb0857e9a62cb11586e9a712103877bbf717f5fb570d16634408cfdefee/ty-0.0.29-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ce0a7a0e96bc7b42518cd3a1a6a6298ef64ff40ca4614355c1aa807059b5c6f", size = 11020539, upload-time = "2026-04-05T15:01:37.022Z" },
267
+ { url = "https://files.pythonhosted.org/packages/20/36/5a26753802083f80cd125db6c4348ad42b3c982ec36e718e0bf4c18f75e5/ty-0.0.29-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6ac86a05b4a3731d45365ab97780acc7b8146fa62fccb3cbe94fe6546c67a97", size = 10396399, upload-time = "2026-04-05T15:01:26.167Z" },
268
+ { url = "https://files.pythonhosted.org/packages/00/e6/b4e75b5752239ab3ab400f19faef4dbef81d05aab5d3419fda0c062a3765/ty-0.0.29-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6bbbf53141af0f3150bf288d716263f1a3550054e4b3551ca866d38192ba9891", size = 10421461, upload-time = "2026-04-05T15:01:08.367Z" },
269
+ { url = "https://files.pythonhosted.org/packages/c0/21/1084b5b609f9abed62070ec0b31c283a403832a6310c8bbc208bd45ee1e6/ty-0.0.29-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1c9e06b770c1d0ff5efc51e34312390db31d53fcf3088163f413030b42b74f84", size = 10599187, upload-time = "2026-04-05T15:01:23.52Z" },
270
+ { url = "https://files.pythonhosted.org/packages/ab/a1/ce19a2ca717bbcc1ee11378aba52ef70b6ce5b87245162a729d9fdc2360f/ty-0.0.29-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0307fe37e3f000ef1a4ae230bbaf511508a78d24a5e51b40902a21b09d5e6037", size = 11121198, upload-time = "2026-04-05T15:01:15.22Z" },
271
+ { url = "https://files.pythonhosted.org/packages/6b/6b/f1430b279af704321566ce7ec2725d3d8258c2f815ebd93e474c64cd4543/ty-0.0.29-py3-none-win32.whl", hash = "sha256:7a2a898217960a825f8bc0087e1fdbaf379606175e98f9807187221d53a4a8ed", size = 9995331, upload-time = "2026-04-05T15:01:01.32Z" },
272
+ { url = "https://files.pythonhosted.org/packages/d2/ef/3ef01c17785ff9a69378465c7d0faccd48a07b163554db0995e5d65a5a23/ty-0.0.29-py3-none-win_amd64.whl", hash = "sha256:fc1294200226b91615acbf34e0a9ad81caf98c081e9c6a912a31b0a7b603bc3f", size = 11023644, upload-time = "2026-04-05T15:01:04.432Z" },
273
+ { url = "https://files.pythonhosted.org/packages/2c/55/87280a994d6a2d2647c65e12abbc997ed49835794366153c04c4d9304d76/ty-0.0.29-py3-none-win_arm64.whl", hash = "sha256:f9794bbd1bb3ce13f78c191d0c89ae4c63f52c12b6daa0c6fe220b90d019d12c", size = 10428165, upload-time = "2026-04-05T15:01:34.665Z" },
274
+ ]