configure()
Every shipped pack builds its tools at import, so from charter.packs.gmail import TOOLS works before anything is configured — schemas, llm_schema() and
to_json_schema() need no credential. The credential arrives afterwards,
through the pack’s configure().
A credential is resolved per request, not captured when the tool is built. That
is why
configure() reaches tools that already exist, and why a reference taken
before configuration keeps working after it. Calling a tool on a pack that has
none raises CredentialError naming both
the configure() call and the environment variable it would have accepted. It
fails locally, before the request.Bearer packs
gmail, gcalendar, gsheets, gdocs, gdrive, gforms, slack, github, notion, outlook.
CredentialProvider: a
StaticTokenProvider for a script, an
OAuth2Client for a process that stays up, a
SubjectProvider wrapping one of those per end user.
API-key packs
stripe, linear, firecrawl, granola, tavily, luma. Exactly one of the two:
api_key is one account for the whole process, and credential_provider is
read on every call, so a SubjectProvider serves each
of your users their own key or OAuth token. Both, or neither, raises
TypeError; an empty key raises
CredentialError.
Authorization: Bearer <key>, except on Linear, which
sends a personal key (lin_api_...) bare and anything else — an OAuth access
token — as Bearer. Connect your users’ accounts has each
pack’s per-user setup.
Shopify
shop is the subdomain — "my-store" for
my-store.myshopify.com — and the full domain or URL is accepted too; left out,
it comes from $SHOPIFY_SHOP. The credential is at most one of client_id with
client_secret, a Dev Dashboard app’s pair, which the pack trades for a 24-hour
token and renews; access_token, held as given; or credential_provider. Two
at once raise TypeError, and so does moving to another store while holding an
access_token given for the previous one — a token belongs to the store that
issued it, while the client ID and secret mint per store. Nothing changes when
a call raises. The token is sent in X-Shopify-Access-Token.
shop may also be a function returning the store, called on every request,
for an application whose merchants each connect their own. Pair it with a
credential_provider that answers per user too; with an access_token it
raises TypeError, since that token belongs to one store.
Environment fallbacks
A pack with noconfigure() call falls back to a documented variable, read on
first use.
Every variable is read per call, so one set after import is still found.
The Google packs take a renewable grant. A raw access token expires in about
an hour and nothing renews it, so the six Google packs also look for a grant,
tried first:
GOOGLE_TOKEN_FILE— the path of an authorized-user JSON file: thetoken.jsongoogle-auth writes, Hermes Agent’sgoogle_token.json, or gcloud’sapplication_default_credentials.json. Onlyclient_id,client_secretandrefresh_tokenare read, and~is expanded.GOOGLE_REFRESH_TOKENwithGOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRET— the three values Set up Google ends with.GOOGLE_ACCESS_TOKEN.
OAuth2Client shared
by all six packs, so a call to Gmail and one to Calendar spend one refresh. The
file is read per call, and a different grant in it is picked up without a
restart. A grant that is named but unusable — a file that cannot be read, an
OAuth client file in place of a token, a refresh token without its client —
raises a CredentialError saying which,
rather than falling through to GOOGLE_ACCESS_TOKEN.
The token file holds a refresh token: keep it readable by its owner alone
(chmod 600). Charter warns once per process when other users can read it, and
never changes it.
This is the path the MCP entry point uses, which is why it needs
no configuration code. It is one account per process — the shape the MCP
spec prescribes for a stdio server, which reads its credentials from the
environment. Serving many users means a grant per user, through a
SubjectProvider over your own store; persisting a
rotated refresh token is an OAuth2Client with
on_refresh, in your own process.
Settings that tune behaviour
These change how a request is made rather than what it contains. All are factory parameters unless noted, and each can be overridden per tool.int
default:"20"
Request timeout in seconds. Per tool:
timeout_override.int
default:"10"
How dead a bearer token must be before the runtime refuses to send it. On
oauth_tool_factory only. This is a guard, not a refresh policy: keeping a
token fresh is the provider’s job, and OAuth2Client renews 90 seconds early
by default. Ten seconds covers clock skew and the request itself — refusing a
token the server still calls valid for a minute would be the runtime
overruling the server.Optional[Iterable[int]]
default:"{401}"
Which statuses raise
CredentialError rather than APIError. Declare
{401, 403} for an API that means “your token” by 403.Optional[CallSink]
default:"None"
Where finished
ToolCall records go. Declared on
the factory because observability belongs to the deployment, not to one
endpoint. Per tool: on_call_override.Optional[Dict[str, str]]
default:"None"
Headers sent verbatim on every request — an Azure
api-version, a
Notion-Version. Not case-converted, absent from the LLM schema, applied
after tool input so a schema field cannot overwrite them.Optional[Dict[str, Any]]
default:"None"
Query parameters sent verbatim on every request. Same three properties.
Optional[Dict[str, Any]]
default:"None"
Body keys sent verbatim. Per tool only: a constant in the body belongs to one
operation, the GraphQL query document being the case that motivated it.
KeyCase
default:"\"camel\", \"snake\", \"snake\""
How keys are spelled on the wire. Per tool:
body_case_override,
query_case_override, path_case_override. Per field:
Case. See
the cascade."json" | "form"
default:"\"json\""
"form" for application/x-www-form-urlencoded. Per tool:
body_format_override."repeat" | "bracket"
default:"\"repeat\""
How structured query values serialise. Factory-wide; there is no per-tool
override.
Logging
The library attaches aNullHandler to the charter logger and configures
nothing else for its host: emitting a record is a no-op until the application
opts in.
Enabling INFO also switches on payload measurement, because something is then
listening for the sizes.
Related
- MCP server — the entry point these variables feed
- Packs — what each pack configures
- Authorization servers — declaring the server behind an
OAuth2Client