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.
Files changed (44) hide show
  1. mcp_client_kit-0.5.0/CHANGELOG.md +188 -0
  2. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/PKG-INFO +5 -2
  3. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/README.md +4 -1
  4. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/USAGE.md +69 -3
  5. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/mcpgen/__init__.py +4 -0
  6. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/mcpgen/_bridge.py +182 -20
  7. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/mcpgen/cli.py +89 -2
  8. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/pyproject.toml +1 -1
  9. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_bridge.py +660 -2
  10. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/uv.lock +1 -1
  11. mcp_client_kit-0.3.0/CHANGELOG.md +0 -102
  12. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/.claude/skills/releasing-a-version/SKILL.md +0 -0
  13. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/.claude-plugin/marketplace.json +0 -0
  14. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/.claude-plugin/plugin.json +0 -0
  15. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/.github/workflows/ci.yml +0 -0
  16. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/.github/workflows/publish.yml +0 -0
  17. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/.gitignore +0 -0
  18. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/CLAUDE.md +0 -0
  19. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/LICENSE +0 -0
  20. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/CODEGEN_SKILL_IDEA.md +0 -0
  21. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/DISTRIBUTION.md +0 -0
  22. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/EXTRACTION_ANALYSIS.md +0 -0
  23. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/LANDSCAPE.md +0 -0
  24. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/MARKET_RESEARCH.md +0 -0
  25. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/RUNNING_LOCALLY.md +0 -0
  26. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/doc/VERDICT.md +0 -0
  27. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/mcpgen/codegen.py +0 -0
  28. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/mcpgen/discovery.py +0 -0
  29. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/mcpgen/seam.py +0 -0
  30. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/servers.example.json +0 -0
  31. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/skills/generate-mcp-runner/SKILL.md +0 -0
  32. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/skills/generate-mcp-runner/runner_templates/http_bearer.py +0 -0
  33. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/skills/generate-mcp-runner/runner_templates/http_oauth.py +0 -0
  34. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/skills/generate-mcp-runner/runner_templates/http_public.py +0 -0
  35. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/skills/generate-mcp-runner/runner_templates/stdio.py +0 -0
  36. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/skills/generate-mcp-wrappers/SKILL.md +0 -0
  37. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_cli_call.py +0 -0
  38. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_cli_env.py +0 -0
  39. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_cli_list.py +0 -0
  40. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_cli_merge.py +0 -0
  41. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_cli_probe.py +0 -0
  42. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_codegen.py +0 -0
  43. {mcp_client_kit-0.3.0 → mcp_client_kit-0.5.0}/tests/test_config.py +0 -0
  44. {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.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",