charter-mcp command, so a client config never depends on which python the client finds. With pip, pip install 'charter-ai[mcp]' and then charter-mcp --pack gmail.
--pack takes one pack name or several, repeated or comma-separated; --name overrides the server name, which defaults to charter and not to the pack, so the name a host composes reads mcp__charter__gmail_messages_send rather than spelling the pack twice. Every flag is listed under charter-mcp.
One command, every platform — including the three where it used to need a second one. The extra reaches
cryptography three levels down, mcp → pyjwt[crypto] → cryptography, and that is a Rust extension whose wheels have been retired platform by platform. Where a wheel is missing, pip and uv fall through to a source build that needs a Rust toolchain, and the install fails in cargo metadata before Charter is reached — with an error that names maturin and Cargo.lock and reads like nothing to do with an MCP server.So the extra caps cryptography on exactly the platforms that need it, at the newest release upstream still publishes a wheel for:Nothing in Charter imports
cryptography — it reaches mcp only for OAuth verification on HTTP transports, and Charter serves stdio — so the older version costs those platforms no behaviour. The core install and every pack are unaffected, and no other extra pulls it.tools/list from the first request, and nothing depends on how a client reacts to a list that changes. Checked against the real clients below, each running a model that read a live Gmail inbox through the server: Claude Code 2.1, Codex 0.160, Grok Build 1.0, OpenClaw 2026.9, Hermes Agent 0.21 and Google ADK 2.10.
The cost is the schemas, so serve the packs you need rather than all seventeen:
A projection is how a pack gets smaller without losing a tool you use. Claude Code defers MCP schemas with its own tool search once they grow large, so there the size costs little.
Loading on demand
--progressive sends a single ToolSearch instead, whose description lists every tool by name, and adds a tool to tools/list when the model asks for it, announcing each load with notifications/tools/list_changed. It only works in a client that re-lists on that notification and hands the new tools to the model straight away, which most do not:
So leave it off unless the client is Hermes and the packs are large.
Add it to your client
The server speaks stdio, so a client runs it as a command rather than connecting to a URL. Put the credential in the environment, not in a config file you commit.charter and give --pack a list. This is worth getting right, because the name your host composes is built from the key you write here, not from anything the server reports. Under charter a tool arrives as mcp__charter__gsheets_values_get; under a key named after the pack, charter-gsheets, it arrives as mcp__charter-gsheets__gsheets_values_get, which says gsheets twice and spends eight more of the 64 characters a function name is allowed. On the widest pack shipped that is the difference between 60 characters and 68, and two gsheets tools stop being callable.
Tool names carry one underscore. gmail_messages_list, never gmail__messages_list: hosts join server and tool with __, and Grok Build drops any tool whose own name contains it. See naming.
Reads run without asking. Each tool is annotated from its method: a GET is marked read-only, a DELETE destructive, and anything else as not read-only. Codex runs a read-only tool without an approval prompt and asks before the rest; codex exec, which cannot ask, refuses the rest unless you set default_tools_approval_mode on the server.
A pack you have no credential for is inert rather than broken: the tool is listed, and calling it raises a CredentialError naming the variable to set.
Restart the client after adding the server.
Credentials
The entry point reads them from the environment — the same environment fallbacks every pack honours.
Shopify always needs
SHOPIFY_SHOP, since the host is a property of the store.
Give it the app’s client ID and secret rather than a token: a token minted from
them lasts 24 hours, and from the pair the server mints and renews its own. The
pair wins when both are set. A store’s older shpat_ token does not expire and
works as SHOPIFY_ACCESS_TOKEN.
Give a Google server a grant, not a token. GOOGLE_ACCESS_TOKEN expires in about an hour and nothing renews it, so a server started from one fails every Google call an hour in. GOOGLE_TOKEN_FILE points at an authorized-user JSON file — the token.json Google’s quickstarts write, Hermes Agent’s google_token.json, or the file gcloud auth application-default login writes — and the server renews from it for as long as it runs. The three GOOGLE_* variables are the same grant spelled out; Set up Google ends with them. Either one wins over GOOGLE_ACCESS_TOKEN. Two servers side by side, one on each, both read the inbox at minute zero; at minute 62 the access-token server got a 401 and the token-file server read it again.
The token file holds a refresh token, so keep it readable by you alone — chmod 600 /path/to/token.json. The server warns once, on stderr, when other users on the machine can read it, and never changes the file itself.
One account per process. Credentials from the environment are what the MCP spec prescribes for a stdio server, and they are one person’s: the server runs on your machine, for you, and holds one grant. To serve many users from one process, give each their own grant through a SubjectProvider over your own store — set these variables on a shared server and every user acts as this one account.
What the client sees
The tools a pack exports, with the schema the model is allowed to see — not the full API surface. A field markedMode("response_only") is absent from the tool definition the MCP client receives, because the LLM view is computed before anything is serialised. The boundary is the same one every other caller crosses.
Related
charter-mcp— the flags, the client configuration, and using it as a function- Environment fallbacks — the variable each pack reads, and the order they are tried in
Mode— what the client is not shown, and why