name is what the API calls the endpoint. events_list is
events_list whether you hand it to LangChain, publish it over MCP, or call it
directly. Charter does not prefix it.
That is not a gap. It is the convention every surface Charter targets already
follows: an MCP server publishes plain names and the host composes
mcp__<server>__<tool> from the server it was configured with; OpenAI
constrains a function name to [A-Za-z0-9_-]{1,64} and defines no namespace at
all; LangChain resolves a call against the list you gave it. Qualifying inside
the library would produce mcp__gcalendar_gcalendar_events_list the moment a
host did its job.
So the rule is that whoever assembles tools into one namespace owns the
names — and Charter ships the one convention they should use.
The collision
Two packs can declare the same tool name, because two APIs can.pack, and why it is not provider
Every tool carries three pieces of identity, answering three different
questions. All three are needed.
provider cannot stand in for pack: gmail, gcalendar, gsheets, gdocs, gdrive and
gforms all authenticate as google and deliberately share one refresh token. Naming by
provider would collapse six packs into one namespace.
base_url cannot either: gmail, calendar, sheets, docs and forms use five different
hosts (Drive shares Calendar’s), so the mapping runs the wrong way, and shopify’s base URL is not known until
configure() is called.
Declare it once, on the factory:
Qualifying
mcp__<server>__<tool> on top, and the MCP entry point
names its server charter, so the result reads
mcp__charter__gcalendar_events_list — each segment a different fact. It is
also a budget: the host takes <server> from the key it was configured under,
the whole name must fit 64, and one server holding every pack is what keeps the
pack out of that prefix.
All of them, or none of them. Neither function qualifies “just the
collisions”. A surface where stripe_products_list sits beside a bare
balance_retrieve makes the pack a substring of some names and not others, so
every filter over it — a +stripe in a tool search, a log grep, an allow-list —
silently misses the unqualified half. A filter that works on most names is worse
than none, because nothing tells you which half you got.
One underscore, and none in a pack. stripe_products_list is the shape
MCP servers already publish — Playwright’s browser_click, GitHub’s
actions_list — and it still splits back into its pack and its tool, because a
pack label may not contain an underscore: the first one is always the boundary.
A tool name may not contain __. Hosts join server and tool with it
(mcp__<server>__<tool>), and one that parses its names back cannot tell where
such a name splits: Grok Build drops the tool. Charter used __ as its own
separator until 0.2.6, for that reason the one choice that did not work
everywhere.
A tool with no pack cannot be qualified, and saying so is a DeclarationError
rather than a silent fall back to the bare name — which would produce exactly
the half-qualified surface above.