Skip to main content

mcp-client-kit

Write your MCP server wrappers once — from the live server. Keep them as real Python source you can diff, review, and pin.

mcpgen turns an MCP server reachable over stdio or Streamable HTTP into a Python module: one async def per tool, typed inputs from the start, and typed returns once you've probed the real responses. No live server needed to read it.

mcpgen generates the Python wrapper layer for MCP code-mode architectures. Token savings occur when your agent or runtime executes these wrappers instead of loading the full MCP tool catalog into model context. mcpgen does not itself alter host tool registration and does not provide a sandbox.

Package / repo / plugin: mcp-client-kit · CLI command: mcpgen

Two artifacts, one repo: a CLI (mcpgen) you run anywhere, and a Claude Code plugin (generate-mcp-wrappers skill) that drives it for the parts that need judgment.

This repo ships two artifacts: the mcp-client-kit Python package (PyPI; git tags vX.Y.Z; installs the mcpgen command) and the mcp-client-kit Claude Code plugin — which provides the generate-mcp-wrappers skill (git tags plugin-vX.Y.Z).


The problem

MCP tool schemas eat your context window before the agent does any work.

Every tool definition costs 300–600 tokens for its name, description, and JSON schema. That adds up fast:

  • The GitHub MCP server alone burns ~55,000 tokens across its 93 tools.
  • One developer measured 66,000 tokens consumed at conversation start — a third of a 200k window, gone before the first query.
  • A SaaS server with 50+ endpoints can spend 30,000+ tokens just describing what it could do.

Anthropic's validated fix ("Code execution with MCP," Nov 2025): stop routing schemas through the model. Generate wrapper code and call the tools from code instead — an approach Anthropic measured shrinking one workflow from ~150,000 tokens to ~2,000 (98.7%), with independent benchmarks landing around 78–85% on less extreme workloads.

Those numbers describe the pattern, as reported by Anthropic and by third-party benchmarks — they are not a measurement of this CLI. mcpgen supplies one piece of that pattern: the wrapper layer. What you save depends on how your runtime uses it.

The catch for Python teams: nothing generated wrappers you could actually own — importable .py modules, free of a mandatory runtime sidecar, with return types grounded in what the server really sends. So everyone hand-writes jira.py, github.py, slack.py — slowly, inconsistently, and they silently rot when the server changes.

That's the gap mcpgen fills.


What you get

uv tool install mcp-client-kit          # puts `mcpgen` on your PATH
mcpgen login github                     # browser OAuth, tokens persisted
mcpgen codegen github --out github.py   # one async def per tool, typed inputs
import asyncio
from mcpgen import McpBridgeCaller
import github  # the file you just generated

async def main():
    caller = McpBridgeCaller(url="https://api.githubcopilot.com/mcp/")
    me = await github.get_me(caller)                                  # -> GitHubUser
    issues = await github.list_issues(caller, owner="octocat", repo="hello-world")
    print(me, issues)

asyncio.run(main())

The -> GitHubUser return type above comes from the full lifecycle — probe the tool, shape the response, regenerate. Straight out of codegen, get_me takes the same typed arguments but returns Any. See Lifecycle.

github.py is just Python. Open it in your IDE, review it in a PR, pin it to a commit, ship it. No runtime proxy, no framework lock-in, no live server required to read what your tools return.

One connection for a series of calls. Each call opens its own session by default. Wrap a run in connected() and every call to a server shares one initialized session — one initialize(), one stdio subprocess, one OAuth pre-flight refresh:

caller = McpBridgeCaller(url="https://api.githubcopilot.com/mcp/")

async with caller.connected():
    me = await github.get_me(caller)
    issues = await github.list_issues(caller, owner="octocat", repo="hello-world")

Outside the block nothing changes, so existing code keeps working untouched. Details — per-server sessions, concurrency, cleanup — in doc/USAGE.md.


Real generated output

The snippet above is an illustration. These are the actual committed files — generated end-to-end by the skill against live MCP servers, not written by hand:

Server Generated wrapper Transport
github github.py HTTP + bearer
filesystem filesystem.py stdio
huggingface huggingface.py HTTP, no auth
sqlite sqlite.py stdio
time time.py stdio

All thirteen servers: eval/. Each folder also holds the <server>.shapes.json sidecar that produced the typed returns, and a run.py sample runner.

These live on a separate eval branch because the harness that produces them is its own project, not part of the shipped package — see doc/EVALS.md.


Why developers pick it

Real source you own. Importable .py modules — not .pyi stubs (mcp2py), not a runtime proxy, not a generated CLI script that needs its generator installed (fastmcp generate-cli), not modules that require a tool server running to work (mcpygen). You can diff it, review it, pin it, and read it in your IDE without a server running.

Types that match reality. A tool's inputSchema describes its inputs — it tells you nothing about the output shape, so codegen alone returns Any. mcpgen's probe makes a live call and records the actual response; you (or the skill) turn that observation into an output model, and regeneration bakes it in. Return types then reflect what the server really sends, not a guess. No other generator does this.

OAuth that survives restarts. Pre-flight token refresh means a fresh process renews a near-expired token silently from the refresh token — no surprise browser pop-up at cold start. (The official SDK's canonical example is in-memory only; every restart re-authenticates.)

Swap auth without regenerating. Every wrapper takes an McpCaller as its first argument. Change transports or auth backends — bearer, OAuth, stdio, a fake for tests — without touching the generated code.

Built for production teams. Works with MCP servers reachable over stdio or Streamable HTTP (OAuth, bearer/PAT, or no auth). Generated code lives in git like any other module, so it survives code review, audits, and pinning.


What mcpgen does not do

  • It does not remove tool schemas from Claude Code or Cursor context — host tool registration is not mcpgen's to change.
  • It does not sandbox generated code. Generated wrappers are ordinary Python and run with your process's privileges.
  • It does not infer trustworthy output types from input schemas alone. Output types come from live probes plus a judgment pass (see the lifecycle below).
  • It does not speak SSE. SSE servers are surfaced by mcpgen discover but marked unsupported by this version.
  • It does not guarantee a remote server will stay stable. That is what mcpgen check is for.

Lifecycle

Step Command Result
1. Generate mcpgen codegen <server> --out <server>.py One async def per tool: typed inputs, Any return by default.
2. Probe mcpgen probe <server> <tool> --args '{}' --emit-shape <server>.shapes.json Observed response-shape skeleton, written as a per-tool part under <server>.shapes.json.parts/.
3. Merge mcpgen merge <server> --out <server>.shapes.json Consolidates the parts into the committed, hand-editable shape-spec.
4. Edit the shape-spec (or run the skill) edit <server>.shapes.json Output model and unwrap decisions — the judgment pass.
5. Regenerate mcpgen codegen <server> --out <server>.py --shapes <server>.shapes.json TypedDict returns, unions, lists, overloads.

Step 3 is not optional: --emit-shape writes only into <server>.shapes.json.parts/, so <server>.shapes.json does not exist until you merge.

Typed input wrappers by default; empirically shaped output types after live probes. Steps 2–5 are optional as a group — skip them and you still get working wrappers, just with Any returns. Shaping is also per-tool: tools you never probe keep their Any return.

Polymorphic tools — ones that return different shapes depending on an input (entityType=1Person, =2Position) — get typed @overloads, so your type checker narrows the return at every call site.

The full reference, including the shape-spec format and credential backends, is in doc/USAGE.md.


Install

The PyPI package is mcp-client-kit; the command it installs is mcpgen.

CLI on your PATH:

uv tool install mcp-client-kit

One-off, no install:

uvx --from mcp-client-kit mcpgen codegen <server> --out <server>.py

As a project dependency:

uv add mcp-client-kit      # or: pip install mcp-client-kit

Requires Python 3.11+.


Claude Code plugin

The plugin bundles the generate-mcp-wrappers skill, which drives the CLI through the 20% that needs judgment — curating which tools matter, probing live responses, and editing the shape-spec — then regenerates and verifies the module.

The CLI is not bundled with the plugin — install it separately (uv add mcp-client-kit, see Install above). The skill requires mcpgen >= 0.1.0 and checks this before running; a local editable install (uv pip install -e .) satisfies it for development. This is a version floor, not an exact pin, so the skill and CLI can be upgraded independently as long as the CLI stays at or above the floor.

/plugin marketplace add svd/mcp-client-kit
/mcp-client-kit:generate-mcp-wrappers

A companion skill, generate-mcp-runner, writes a standalone smoke-test run.py that exercises the generated wrappers end-to-end.


Command reference

Command What it does
codegen <server> Emit wrappers; --shapes applies the shape-spec, --probe records a response shape inline, --embed-schema embeds fn.__schema__ + Args docstring per function. Also writes <out-stem>.mcpgen.json for check.
check <server> Compare the server's live tool inventory against the stored manifest. Exit 0 no drift, 1 drift, 2 operational error.
list <server> Print a server's tools as JSON; --schema adds raw inputSchema per tool.
probe <server> <tool> Live call(s) → response-shape skeleton.
call <server> <tool> --out <p> One live call, raw payload to disk — bootstrap ids or inspect output.
merge <server> Consolidate probe parts into <server>.shapes.json.
login <server> Browser OAuth login; tokens stored at ~/.mcpgen/credentials.json.
migrate-creds Move stored OAuth tokens between file / keyring backends.
discover List MCP servers configured in Claude Code.

discover reads Claude Code's configuration — the only host provider implemented today. discovery.PROVIDERS is the extension point where more hosts get added.

Full workflow and flags: doc/USAGE.md.


Drift detection

codegen writes a deterministic, non-secret snapshot of the server's tool inventory beside the generated module:

mcpgen codegen demo --stdio 'python server.py' --out gen/demo.py
# writes gen/demo.py and gen/demo.mcpgen.json

Commit both. In CI, verify the server still matches what you generated against:

mcpgen check demo --stdio 'python server.py' --manifest gen/demo.mcpgen.json
Exit Meaning
0 No drift. Description-only changes print as advisories and do not fail.
1 Drift: a tool was added, removed, or its input schema or annotations changed.
2 Operational error: manifest missing or unreadable, transport failure, auth failure.

Exit 2 is never drift, so a broken runner or an expired token cannot be misread as a changed tool contract. --json emits a structured report. check never calls a tool and never touches <server>.shapes.json.

To accept the new inventory, ask for it explicitly — check never writes on its own:

mcpgen check demo --stdio 'python server.py' --manifest gen/demo.mcpgen.json --update

GitHub Actions:

- name: Check MCP tool drift
  run: mcpgen check demo --config .mcp.json --manifest gen/demo.mcpgen.json --json

Authentication

mcpgen login <server>                              # OAuth (most servers)
mcpgen login <server> --headless                   # no browser: print URL, paste callback URL back
mcpgen codegen <server> --bearer "$TOKEN" --out s.py  # PAT / bearer
mcpgen codegen <server> --stdio "python server.py" --out s.py  # local stdio, no auth

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.

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.


Who it's for

Python developers building AI agent pipelines on MCP servers — especially Claude Code users who've already hand-written at least one <server>.py wrapper and felt the pain. And platform teams running multi-server MCP environments where token cost and auth reliability are production concerns.

If you write your agent logic in Python and want generated tool wrappers you can actually own — review, pin, and keep in git — this is built for you.


Docs

  • doc/USAGE.md — full end-user guide: install paths, server config, auth, the shape-spec, and calling generated wrappers.
  • doc/RUNNING_LOCALLY.md — run from a local clone without installing.
  • doc/EVALS.md — generated wrappers for thirteen real MCP servers, plus the evaluation harness and report, on the eval branch.

Status

Early access (v0.x). The codegen engine, OAuth persistence, live-probe shaping, tool-inventory drift detection (mcpgen check), and both Claude Code skills are working today. Transport support is stdio and Streamable HTTP; SSE is discovered but not implemented.

MCP SDK compatibility. mcpgen currently pins the official Python SDK to mcp>=1.27,<2. The OAuth pre-flight refresh in _bridge.py depends on specific FileTokenStorage and auth-flow behavior in the 1.x line, and the SDK's 2.x rework for the 2026-07-28 spec changes that surface. The pin is deliberate, not neglect — lifting it means re-validating the token lifecycle against 2.x, which is tracked as the next significant piece of work. Generated wrappers themselves are unaffected: they import only seam.McpCaller, so a wrapper generated today keeps working against whatever bridge implementation you supply.

License

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcp_client_kit-0.8.0.tar.gz (354.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcp_client_kit-0.8.0-py3-none-any.whl (84.7 kB view details)

Uploaded Python 3

File details

Details for the file mcp_client_kit-0.8.0.tar.gz.

File metadata

  • Download URL: mcp_client_kit-0.8.0.tar.gz
  • Upload date:
  • Size: 354.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for mcp_client_kit-0.8.0.tar.gz
Algorithm Hash digest
SHA256 fce0132894dc09b8c7c8cb5c4226bc7f1b2e7427d84c41bdb283f7eaf6cc32bb
MD5 d5d5c44e5ed4b4916e98b7370b144616
BLAKE2b-256 5298045093370a283773b3877734d06feaa5a5c0cce731d08512c9d02567be4e

See more details on using hashes here.

File details

Details for the file mcp_client_kit-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: mcp_client_kit-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 84.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for mcp_client_kit-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2c8634d681fb21be0b3df19fbe17d66942318c736cfefcad7caf2280940bbd85
MD5 19bb119a7699b91467a6f8833f719fc0
BLAKE2b-256 0c6435cc6dc0e6334d91f0f82b45106795fd2fa4afb215464daea84010023818

See more details on using hashes here.

Release history Release notifications | RSS feed

0.9.0

2 files

This release

0.8.0 This release

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.0.4

2 files

0.0.3

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page