Skip to main content
A tool’s 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.
Published bare, that surface lists 23 tools and can route only 21. The model sees a name twice and its call reaches whichever tool was built last.

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

The three shipped adapters call it, so a pack is named the same way inline, through LangChain, and over MCP. Three properties are worth knowing. A name never depends on its neighbours. Qualifying only once a second pack appeared would rename every tool in the first the day someone added one, and break saved prompts, allow-lists, logged traces and eval fixtures without a word. An MCP server publishes the same names whatever else is installed; so does this. A host composes mcp__<server>__<tool> on top, and charter-mcp names its server charter, so the result reads mcp__charter__gcalendar__events_list — each segment a different fact. 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. The separator is doubled. Tool names contain single underscores, so stripe_products_list cannot be split back into its pack and its tool while stripe__products_list can. MCP doubles it for the same reason. 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.