mcp-client-kit 0.3.0__tar.gz → 0.5.0__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_client_kit-0.5.0/CHANGELOG.md +188 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/PKG-INFO +5 -2
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/README.md +4 -1
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/USAGE.md +69 -3
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/mcpgen/__init__.py +4 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/mcpgen/_bridge.py +182 -20
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/mcpgen/cli.py +89 -2
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/pyproject.toml +1 -1
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_bridge.py +660 -2
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/uv.lock +1 -1
- mcp_client_kit-0.3.0/CHANGELOG.md +0 -102
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/.claude/skills/releasing-a-version/SKILL.md +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/.claude-plugin/marketplace.json +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/.claude-plugin/plugin.json +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/.github/workflows/ci.yml +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/.github/workflows/publish.yml +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/.gitignore +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/CLAUDE.md +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/LICENSE +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/CODEGEN_SKILL_IDEA.md +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/DISTRIBUTION.md +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/EXTRACTION_ANALYSIS.md +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/LANDSCAPE.md +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/MARKET_RESEARCH.md +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/RUNNING_LOCALLY.md +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/VERDICT.md +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/mcpgen/codegen.py +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/mcpgen/discovery.py +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/mcpgen/seam.py +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/servers.example.json +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/skills/generate-mcp-runner/SKILL.md +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/skills/generate-mcp-runner/runner_templates/http_bearer.py +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/skills/generate-mcp-runner/runner_templates/http_oauth.py +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/skills/generate-mcp-runner/runner_templates/http_public.py +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/skills/generate-mcp-runner/runner_templates/stdio.py +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/skills/generate-mcp-wrappers/SKILL.md +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_cli_call.py +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_cli_env.py +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_cli_list.py +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_cli_merge.py +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_cli_probe.py +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_codegen.py +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_config.py +0 -0
- {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_discovery.py +0 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased] — 0.6.0
|
|
4
|
+
|
|
5
|
+
## [0.5.0] — 2026-08-09
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **A caller-supplied credentials path was silently ignored once past login, so tokens were
|
|
10
|
+
written to one file and read from another.** `login()` and `ensure_login()` accepted
|
|
11
|
+
`creds_path` and honoured it, but the call path never did: `_http_session()` built its
|
|
12
|
+
`FileTokenStorage` on the default `~/.mcpgen/credentials.json`, and neither `session()` nor
|
|
13
|
+
`McpBridgeCaller` even accepted the parameter, so there was no way to route a call to the file
|
|
14
|
+
the login had just written. Anyone keeping tokens outside the shared store — a project-local
|
|
15
|
+
credentials file, two identities side by side, a test fixture — logged in successfully and then
|
|
16
|
+
got `ReauthenticationRequired` on the very next call, in a loop, with both files on disk looking
|
|
17
|
+
plausible. `creds_path` now flows through `_http_session()`, `session()`, and
|
|
18
|
+
`McpBridgeCaller`, defaulting to `DEFAULT_CREDS_PATH` when omitted, so existing behaviour is
|
|
19
|
+
unchanged. The stdio, bearer, static-header, and raw-URL transports do not take it: they store
|
|
20
|
+
no credentials at all, and a parameter that silently does nothing is worse than its absence.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **`--creds PATH`** on every command that touches stored credentials — `codegen`, `list`,
|
|
25
|
+
`probe`, `call`, `login`, and the management trio `list-creds`, `delete-creds`,
|
|
26
|
+
`migrate-creds` — the CLI surface for the fix above. `mcpgen login` previously dropped the
|
|
27
|
+
path entirely, having no flag to drop it from; it now logs in to exactly the file the
|
|
28
|
+
subsequent calls will read. The management commands were the other half of the same gap: all
|
|
29
|
+
three already took a `credentials_path` internally but exposed no way to set it, so after a
|
|
30
|
+
`--creds` login `list-creds` reported an empty store, `delete-creds` deleted nothing while
|
|
31
|
+
saying so, and `migrate-creds` migrated the wrong file. A flag that exists on half the
|
|
32
|
+
commands that read a file is worse than no flag at all — it makes the other half look broken.
|
|
33
|
+
|
|
34
|
+
- **`DEFAULT_CREDS_PATH` is exported from `mcpgen`** — consumers that thread a credentials path
|
|
35
|
+
through their own CLI need the default as a value. Re-deriving
|
|
36
|
+
`Path.home() / ".mcpgen" / "credentials.json"` downstream would silently drift the day mcpgen
|
|
37
|
+
changes it.
|
|
38
|
+
|
|
39
|
+
## [0.4.0] — 2026-08-09
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **Headless OAuth login — `mcpgen login <server> --headless`** — logging in no longer requires
|
|
44
|
+
a browser on the machine running the command. Instead of opening a browser and binding a local
|
|
45
|
+
callback server, mcpgen prints the authorization URL to stderr, you authorize on any device,
|
|
46
|
+
and paste the resulting callback URL back on stdin. This is the only workable flow inside
|
|
47
|
+
containers, over SSH, and in CI, where `webbrowser.open()` silently does nothing and the
|
|
48
|
+
callback port is unreachable from wherever the browser actually runs. The redirect URI
|
|
49
|
+
registered in headless mode is port-less (`http://localhost/callback`) — nothing ever fetches
|
|
50
|
+
it, and keeping it free of an ephemeral port makes the registered value stable across runs for
|
|
51
|
+
servers that pin `redirect_uris`. Without the flag mcpgen auto-detects: macOS and Windows are
|
|
52
|
+
always treated as interactive, other platforms are headless when neither `DISPLAY` nor
|
|
53
|
+
`WAYLAND_DISPLAY` is set. `MCPGEN_HEADLESS=1`/`0` overrides the detection in either direction
|
|
54
|
+
for environments that guess wrong; an explicit `--headless`/`--no-headless` outranks both.
|
|
55
|
+
|
|
56
|
+
- **`mcpgen login --callback-timeout SECONDS`** — the 300-second bound on the browser redirect
|
|
57
|
+
is a guess about how long a consent screen plus MFA takes, and guesses about human latency are
|
|
58
|
+
wrong for someone: a hardware-token or approve-on-phone flow can outlast it, while a scripted
|
|
59
|
+
login wants to fail fast. The flag sets that bound per invocation, `0` restores the original
|
|
60
|
+
unbounded wait as an escape hatch, and anything negative or non-numeric is rejected by argparse
|
|
61
|
+
with a usage message rather than surfacing as a traceback from inside asyncio. The same value
|
|
62
|
+
is available in code as `callback_timeout=` on `login()`, `ensure_login()`, and
|
|
63
|
+
`ensure_login_all()`. Headless logins ignore it — the pasted-URL prompt is never bounded.
|
|
64
|
+
|
|
65
|
+
- **`ensure_login_all(servers)`** — pre-flight refresh-or-login across several servers in one
|
|
66
|
+
call, for pipelines that talk to more than one MCP server and want every token settled before
|
|
67
|
+
work starts. Deliberately sequential: concurrent logins would open several browser tabs at
|
|
68
|
+
once and, in headless mode, race each other for stdin. Servers with a valid cached token stay
|
|
69
|
+
silent, so the steady-state cost is zero.
|
|
70
|
+
|
|
71
|
+
### Fixed
|
|
72
|
+
|
|
73
|
+
- **An authorization denial in the browser flow now fails with the same clear error as the
|
|
74
|
+
headless flow.** The local callback server extracted only `code` and `state` and ignored
|
|
75
|
+
`error`/`error_description`, so declining consent resolved the callback to `(None, None)` and
|
|
76
|
+
surfaced later as an opaque downstream failure. Both callback paths now share one parser,
|
|
77
|
+
which raises `ValueError: OAuth authorization failed: <error> — <description>` at the point of
|
|
78
|
+
failure.
|
|
79
|
+
|
|
80
|
+
- **`mcpgen login` no longer hangs forever when the browser never comes back.** The error-redirect
|
|
81
|
+
fix above only helps when the authorization server actually redirects; real ones often don't —
|
|
82
|
+
cancel the consent screen and the tab simply closes, so no request ever reaches the local
|
|
83
|
+
callback server and the wait never ends. The interactive flow now bounds that wait at 300s
|
|
84
|
+
(`_CALLBACK_TIMEOUT`) and raises `TimeoutError` explaining that the browser never returned,
|
|
85
|
+
with a pointer to `--headless` for pasting the redirect URL by hand. The timeout cancels the
|
|
86
|
+
pending callback future, so the background server task shuts down cleanly, and it unwinds
|
|
87
|
+
through the same restore path as any other login failure — a timed-out attempt leaves a
|
|
88
|
+
previously-working credential intact. Headless logins are deliberately unbounded: a human
|
|
89
|
+
pasting a URL may take arbitrarily long.
|
|
90
|
+
|
|
91
|
+
## [0.3.0] — 2026-07-14
|
|
92
|
+
|
|
93
|
+
### Fixed
|
|
94
|
+
|
|
95
|
+
- **`mcpgen login` — OAuth token exchange failed with `400 invalid_request`** on authorization
|
|
96
|
+
servers that strictly enforce RFC 6749 §2.3 (`"Client must not use multiple authentication
|
|
97
|
+
methods"`). Dynamic client registration omitted `token_endpoint_auth_method`, so the server
|
|
98
|
+
defaulted us to `client_secret_basic` and issued a `client_secret`; the MCP SDK then sent an
|
|
99
|
+
`Authorization: Basic` header *and* `client_id` in the form body — two client authentication
|
|
100
|
+
methods in one request. mcpgen now registers as a public client
|
|
101
|
+
(`token_endpoint_auth_method: "none"`), which is the correct posture for a distributed CLI
|
|
102
|
+
(RFC 8252 §8.4) and is fully secured by the PKCE (S256) the SDK already performs on every
|
|
103
|
+
authorization code grant. No security regression: the discarded `client_secret` was issued
|
|
104
|
+
per-install by dynamic registration and stored in the same `credentials.json` as the refresh
|
|
105
|
+
token, so anyone who could read the secret could already read the token — it never added a
|
|
106
|
+
layer of defense.
|
|
107
|
+
|
|
108
|
+
- **Discriminated-union codegen mistyped `string` discriminators with numeric-looking
|
|
109
|
+
enum values as `int`.** Discriminator type was inferred solely by sniffing whether
|
|
110
|
+
shape-spec variant keys looked numeric (e.g. `"1"`, `"2"`), so a genuinely
|
|
111
|
+
`type: "string"` discriminator with those enum values got a wrong `int` `Literal`
|
|
112
|
+
in the generated `@overload` stubs. Discriminator type is now resolved from an
|
|
113
|
+
explicit `input_overrides` entry, then the tool's declared `inputSchema` type,
|
|
114
|
+
and only falls back to key-sniffing when neither is available.
|
|
115
|
+
|
|
116
|
+
- **`mcpgen call`/`probe` crashed on `resource_link` content items** — real MCP SDK
|
|
117
|
+
`ResourceLink.uri` is a pydantic `AnyUrl`, not `str`; `json.dumps` on the summarized
|
|
118
|
+
content raised `TypeError`. The summary now coerces `uri` to `str`.
|
|
119
|
+
|
|
120
|
+
- **`mcpgen probe` reported inflated payload sizes for non-ASCII content** — size was
|
|
121
|
+
measured as the character count of `json.dumps`'s default `ensure_ascii=True`
|
|
122
|
+
output, which escapes every non-ASCII character to a `\uXXXX` sequence. Size is
|
|
123
|
+
now measured as real UTF-8 byte length.
|
|
124
|
+
|
|
125
|
+
## [0.2.0] — 2026-06-20
|
|
126
|
+
|
|
127
|
+
### Added
|
|
128
|
+
|
|
129
|
+
- **`mcpgen list --schema`** — include raw `inputSchema` JSON per tool in list output.
|
|
130
|
+
Useful for inspecting required params and enum constraints without a separate probe.
|
|
131
|
+
|
|
132
|
+
- **`mcpgen codegen --embed-schema`** — embed `fn.__schema__ = {<inputSchema>}` on each
|
|
133
|
+
generated function, plus an Args docstring section (per-param description, enum values,
|
|
134
|
+
default). Enables introspection at runtime (`mod.get_issue.__schema__`) and richer IDE
|
|
135
|
+
hover docs.
|
|
136
|
+
|
|
137
|
+
- **Enum params → `Literal[...]`** (default, no flag) — `py_type()` now maps JSON Schema
|
|
138
|
+
`enum` arrays to `Literal[v1, v2, ...]` instead of bare `str`/`int`. Applies to direct
|
|
139
|
+
enum params and array items (`list[Literal[...]]`). Static analysis and call-site type
|
|
140
|
+
narrowing work without any extra configuration.
|
|
141
|
+
|
|
142
|
+
## [0.1.0] — initial release
|
|
143
|
+
|
|
144
|
+
### Added
|
|
145
|
+
|
|
146
|
+
- **`mcpgen codegen`** — generate typed async Python wrappers from a live MCP server's
|
|
147
|
+
`tools/list`. Every tool becomes an `async def` typed from `inputSchema`; returns `Any`
|
|
148
|
+
by default (shape-spec refines to `TypedDict`).
|
|
149
|
+
|
|
150
|
+
- **Shape-spec sidecar** (`<server>.shapes.json`) — hand-editable file driving typed
|
|
151
|
+
return models. Fields: `unwrap` (key path), `return_model` (TypedDict name), `fields`,
|
|
152
|
+
`input_overrides`, `overloads`. Intermediate probe parts land in `.parts/` until `merge`.
|
|
153
|
+
|
|
154
|
+
- **Discriminator detection + overloads** — `mcpgen list` / `probe` emit a stderr advisory
|
|
155
|
+
when a param is shared across ≥2 tools (polymorphic-suspect). Codegen emits one
|
|
156
|
+
`@overload` per discriminator variant (`Literal[<val>]`) plus a union impl.
|
|
157
|
+
|
|
158
|
+
- **`mcpgen list`** — print all tools on a server as JSON `[{name, description}]`.
|
|
159
|
+
Includes discriminator advisory on stderr.
|
|
160
|
+
|
|
161
|
+
- **`mcpgen probe`** — live call(s) → response-shape skeleton. Writes per-probe
|
|
162
|
+
intermediates to `.parts/` for later merge.
|
|
163
|
+
|
|
164
|
+
- **`mcpgen merge`** — consolidate `.parts/` into `<server>.shapes.json`; emits a
|
|
165
|
+
gitignored `verify.json` sidecar with pre-scrub `probed_args`.
|
|
166
|
+
|
|
167
|
+
- **`mcpgen call`** — single live call, raw payload written to disk; useful for
|
|
168
|
+
bootstrapping ids and inspecting raw output.
|
|
169
|
+
|
|
170
|
+
- **`mcpgen discover`** — enumerate MCP servers configured in installed agent hosts
|
|
171
|
+
(reads Claude Code CLI / `~/.claude.json`).
|
|
172
|
+
|
|
173
|
+
- **`mcpgen login`** — browser OAuth login; tokens stored at
|
|
174
|
+
`~/.mcpgen/credentials.json` (chmod 0600) or OS keychain via `--cred-backend keyring`.
|
|
175
|
+
|
|
176
|
+
- **`mcpgen migrate-creds`** — move stored OAuth tokens between `file` / `keyring`
|
|
177
|
+
backends.
|
|
178
|
+
|
|
179
|
+
- **`mcpgen list-creds` / `delete-creds`** — inspect and remove stored credentials.
|
|
180
|
+
|
|
181
|
+
- **Transport flags** — `--stdio` (launch command), `--url` (HTTP), `--bearer` (PAT),
|
|
182
|
+
`--config` (config-file server resolution); shared across `codegen`/`list`/`probe`/`call`/`login`.
|
|
183
|
+
|
|
184
|
+
- **`generate-mcp-wrappers` plugin skill** — agent skill that drives the full
|
|
185
|
+
probe → shape-spec → codegen workflow interactively.
|
|
186
|
+
|
|
187
|
+
- **`generate-mcp-runner` plugin skill** — agent skill for building typed runner
|
|
188
|
+
scripts on top of generated wrappers.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-client-kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: mcp-client-kit — generate typed Python wrappers for any MCP server (codegen skill + CLI).
|
|
5
5
|
Project-URL: Homepage, https://github.com/svd/mcp-client-kit
|
|
6
6
|
Project-URL: Source, https://github.com/svd/mcp-client-kit
|
|
@@ -168,11 +168,14 @@ Full workflow and flags: [`doc/USAGE.md`](doc/USAGE.md).
|
|
|
168
168
|
|
|
169
169
|
```bash
|
|
170
170
|
mcpgen login <server> # OAuth (most servers)
|
|
171
|
+
mcpgen login <server> --headless # no browser: print URL, paste callback URL back
|
|
171
172
|
mcpgen codegen <server> --bearer "$TOKEN" --out s.py # PAT / bearer
|
|
172
173
|
mcpgen codegen <server> --stdio "python server.py" --out s.py # local stdio, no auth
|
|
173
174
|
```
|
|
174
175
|
|
|
175
|
-
Tokens persist in `~/.mcpgen/credentials.json` (chmod 0600) or your OS keystore via `--cred-backend keyring`. In code, `ensure_login(server, url=...)` refreshes silently and only opens a browser when a real login is required.
|
|
176
|
+
Tokens persist in `~/.mcpgen/credentials.json` (chmod 0600) or your OS keystore via `--cred-backend keyring`. In code, `ensure_login(server, url=...)` refreshes silently and only opens a browser when a real login is required; `ensure_login_all([...])` does the same for several servers, one at a time.
|
|
177
|
+
|
|
178
|
+
In containers, over SSH, or in CI there is no browser to open. `--headless` prints the authorization URL, you authorize on any device, and paste the callback URL back on stdin. It is auto-detected when neither `DISPLAY` nor `WAYLAND_DISPLAY` is set (never on macOS or Windows); `MCPGEN_HEADLESS=1`/`0` overrides the detection, and `--headless`/`--no-headless` overrides both.
|
|
176
179
|
|
|
177
180
|
---
|
|
178
181
|
|
|
@@ -144,11 +144,14 @@ Full workflow and flags: [`doc/USAGE.md`](doc/USAGE.md).
|
|
|
144
144
|
|
|
145
145
|
```bash
|
|
146
146
|
mcpgen login <server> # OAuth (most servers)
|
|
147
|
+
mcpgen login <server> --headless # no browser: print URL, paste callback URL back
|
|
147
148
|
mcpgen codegen <server> --bearer "$TOKEN" --out s.py # PAT / bearer
|
|
148
149
|
mcpgen codegen <server> --stdio "python server.py" --out s.py # local stdio, no auth
|
|
149
150
|
```
|
|
150
151
|
|
|
151
|
-
Tokens persist in `~/.mcpgen/credentials.json` (chmod 0600) or your OS keystore via `--cred-backend keyring`. In code, `ensure_login(server, url=...)` refreshes silently and only opens a browser when a real login is required.
|
|
152
|
+
Tokens persist in `~/.mcpgen/credentials.json` (chmod 0600) or your OS keystore via `--cred-backend keyring`. In code, `ensure_login(server, url=...)` refreshes silently and only opens a browser when a real login is required; `ensure_login_all([...])` does the same for several servers, one at a time.
|
|
153
|
+
|
|
154
|
+
In containers, over SSH, or in CI there is no browser to open. `--headless` prints the authorization URL, you authorize on any device, and paste the callback URL back on stdin. It is auto-detected when neither `DISPLAY` nor `WAYLAND_DISPLAY` is set (never on macOS or Windows); `MCPGEN_HEADLESS=1`/`0` overrides the detection, and `--headless`/`--no-headless` overrides both.
|
|
152
155
|
|
|
153
156
|
---
|
|
154
157
|
|
|
@@ -79,14 +79,18 @@ uv add mcp-client-kit # or: pip install mcp-client-kit
|
|
|
79
79
|
| `probe <server> <tool>` | Live call(s) → shape skeleton | `--args` (repeatable), `--emit-shape <path>` (writes `.parts/`) |
|
|
80
80
|
| `call <server> <tool>` | One live call, raw payload to disk — bootstrap ids / inspect output | `--out <path>` (required) |
|
|
81
81
|
| `merge <server>` | Consolidate `.parts/` → `<server>.shapes.json`; emit gitignored `verify.json` | `--out <path>` |
|
|
82
|
-
| `login <server>` | Browser OAuth login | connection flags below |
|
|
83
|
-
| `migrate-creds` | Copy stored OAuth tokens between `file`/`keyring` backends | `--from`, `--to`, `--servers`, `--purge`, `--set-default
|
|
82
|
+
| `login <server>` | Browser OAuth login | `--headless` / `--no-headless`, `--callback-timeout <seconds>`, connection flags below |
|
|
83
|
+
| `migrate-creds` | Copy stored OAuth tokens between `file`/`keyring` backends | `--from`, `--to`, `--servers`, `--purge`, `--set-default`, `--creds <path>` |
|
|
84
84
|
| `discover` | List servers from agent hosts | `--host <id>` (repeatable), `--json` |
|
|
85
85
|
|
|
86
86
|
Connection flags shared by `codegen`/`list`/`probe`/`call`/`login`: `--url`,
|
|
87
|
-
`--bearer`, `--stdio`, `--config`, `--client-name`, `--cred-backend`
|
|
87
|
+
`--bearer`, `--stdio`, `--config`, `--client-name`, `--cred-backend`, `--creds`
|
|
88
88
|
(see [§ Authenticate](#authenticate)).
|
|
89
89
|
|
|
90
|
+
`--creds` is also accepted by the credential-management commands — `list-creds`,
|
|
91
|
+
`delete-creds`, `migrate-creds` — so a non-default store stays addressable from every
|
|
92
|
+
command that reads or writes it.
|
|
93
|
+
|
|
90
94
|
> **PII:** `call` writes the raw, unscrubbed payload. Name the file `*.probe-raw.json`
|
|
91
95
|
> (gitignored) and never commit it.
|
|
92
96
|
|
|
@@ -138,6 +142,47 @@ mcpgen login myserver # opens browser; token stored at ~/.mcpgen/credentials.j
|
|
|
138
142
|
|
|
139
143
|
Re-run when you see `ReauthenticationRequired`.
|
|
140
144
|
|
|
145
|
+
The browser flow waits up to 300 seconds for the redirect. If you cancel on the consent
|
|
146
|
+
screen, many authorization servers just close the tab without redirecting back, so mcpgen
|
|
147
|
+
never hears anything — the wait then ends with `TimeoutError` instead of hanging. Your
|
|
148
|
+
existing credential is left untouched by a failed or timed-out attempt.
|
|
149
|
+
|
|
150
|
+
Adjust the bound when the default doesn't fit — a hardware token or an approve-on-phone
|
|
151
|
+
step can outlast it, and a scripted login may want to fail sooner:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
mcpgen login myserver --callback-timeout 900 # 15 minutes
|
|
155
|
+
mcpgen login myserver --callback-timeout 0 # wait forever (no bound)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Negative or non-numeric values are rejected outright. Headless logins ignore the flag —
|
|
159
|
+
the pasted-URL prompt is never bounded. In code, the same value is `callback_timeout=` on
|
|
160
|
+
`login()`, `ensure_login()`, and `ensure_login_all()`.
|
|
161
|
+
|
|
162
|
+
**No browser available (container, SSH, CI):**
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
mcpgen login myserver --headless
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
mcpgen prints the authorization URL instead of opening it, and waits:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
Open this URL in your browser:
|
|
172
|
+
|
|
173
|
+
https://auth.example.com/authorize?response_type=code&client_id=…
|
|
174
|
+
|
|
175
|
+
After authorizing, paste the full callback URL here (http://localhost.../callback?code=...):
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Authorize on any device, then copy the URL from the browser's address bar — the page
|
|
179
|
+
itself will fail to load, which is expected, nothing is listening on that address — and
|
|
180
|
+
paste it at the prompt.
|
|
181
|
+
|
|
182
|
+
Headless mode is auto-detected when neither `DISPLAY` nor `WAYLAND_DISPLAY` is set (macOS
|
|
183
|
+
and Windows are always treated as interactive). Set `MCPGEN_HEADLESS=1` or `0` to override
|
|
184
|
+
the detection; an explicit `--headless` / `--no-headless` overrides both.
|
|
185
|
+
|
|
141
186
|
**PAT / Bearer token:**
|
|
142
187
|
|
|
143
188
|
```bash
|
|
@@ -169,6 +214,23 @@ by `--cred-backend`, then `$MCPGEN_CRED_BACKEND`, then `~/.mcpgen/config.json`
|
|
|
169
214
|
mcpgen login myserver --cred-backend keyring
|
|
170
215
|
```
|
|
171
216
|
|
|
217
|
+
**Credential file location:** `--creds PATH` moves the `file` backend off
|
|
218
|
+
`~/.mcpgen/credentials.json` — useful to keep one project's tokens out of the shared
|
|
219
|
+
store, or to run two identities side by side. Pass the same path to every command
|
|
220
|
+
that touches those tokens; a login written to one file is invisible to a call reading
|
|
221
|
+
another:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
mcpgen login myserver --creds ./.mcp-creds.json
|
|
225
|
+
mcpgen call myserver whoami --creds ./.mcp-creds.json --out out.json
|
|
226
|
+
mcpgen list-creds --creds ./.mcp-creds.json
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The flag has no effect with `--bearer` or `--stdio`, which store no credentials. In
|
|
230
|
+
code the same value is `creds_path=` on `login()`, `ensure_login()`,
|
|
231
|
+
`ensure_login_all()`, `session()`, and `McpBridgeCaller(...)`; `mcpgen.DEFAULT_CREDS_PATH`
|
|
232
|
+
is exported as the default.
|
|
233
|
+
|
|
172
234
|
**Validate keyring storage** — confirm the token landed in the OS keystore, not the
|
|
173
235
|
fallback file (service `mcpgen`, username `credentials`):
|
|
174
236
|
|
|
@@ -323,6 +385,10 @@ How it works:
|
|
|
323
385
|
absent), it falls back to a full browser login — the in-code equivalent of
|
|
324
386
|
`mcpgen login <server>`. Credentials are persisted at
|
|
325
387
|
`~/.mcpgen/credentials.json`.
|
|
388
|
+
- **Several servers at once** — `await ensure_login_all(["a", "b"])` runs the same
|
|
389
|
+
refresh-or-login for each name in turn. Sequential on purpose: parallel logins would
|
|
390
|
+
open several browser tabs at once and race for stdin in headless mode. Both functions
|
|
391
|
+
accept `headless=True`/`False` to force the paste-the-URL or browser flow explicitly.
|
|
326
392
|
- **Lower-level alternative** — skip `ensure_login` and catch
|
|
327
393
|
`ReauthenticationRequired` from the first failing `.call()`, then call
|
|
328
394
|
`login(SERVER, url=URL)` and retry. `ReauthenticationRequired` and `login` are
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""mcpgen: typed Python wrapper generator for MCP servers."""
|
|
2
2
|
|
|
3
3
|
from mcpgen._bridge import (
|
|
4
|
+
DEFAULT_CREDS_PATH,
|
|
4
5
|
McpBridgeCaller,
|
|
5
6
|
ReauthenticationRequired,
|
|
6
7
|
delete_cred,
|
|
7
8
|
ensure_login,
|
|
9
|
+
ensure_login_all,
|
|
8
10
|
list_creds,
|
|
9
11
|
login,
|
|
10
12
|
migrate_creds,
|
|
@@ -12,11 +14,13 @@ from mcpgen._bridge import (
|
|
|
12
14
|
from mcpgen.seam import McpCaller
|
|
13
15
|
|
|
14
16
|
__all__ = [
|
|
17
|
+
"DEFAULT_CREDS_PATH",
|
|
15
18
|
"McpBridgeCaller",
|
|
16
19
|
"McpCaller",
|
|
17
20
|
"ReauthenticationRequired",
|
|
18
21
|
"delete_cred",
|
|
19
22
|
"ensure_login",
|
|
23
|
+
"ensure_login_all",
|
|
20
24
|
"list_creds",
|
|
21
25
|
"login",
|
|
22
26
|
"migrate_creds",
|