Skip to main content
A tool’s name is what the API calls the endpoint, and Charter never changes it. Qualification belongs to whoever assembles tools into one namespace; these are the functions that do it, so every assembler uses one convention. Two APIs really do declare the same tool name — products_list exists in both Stripe and Shopify — so a surface holding both has to disambiguate them. See Tool names across packs for why the library does not qualify its own names, and why pack is not provider.

qualified_names

Every tool under <pack>_<tool>, keyed by the qualified name. This is what the three shipped adapters call, so a pack is named the same way inline, through LangChain, and over MCP. Qualified whatever else is loaded. A name is a property of the tool, not of its neighbours: qualifying only once a second pack appeared would rename every tool in the first the day someone added one, breaking saved prompts, allow-lists and logged traces in silence. MCP servers publish the same names whatever else is installed, and a host composes mcp__<server>__<tool> on top — the MCP entry point names its server charter, so the result reads mcp__charter__gcalendar_events_list. The host builds <server> from the key the server was configured under, and the whole composed name has to fit the 64 characters OpenAI allows a function name. One server holding every pack peaks at 60; a server named after the pack it serves spends eight more on saying the pack a second time and puts two names over. Which is why --pack takes a list.
Neither function qualifies only the colliding names. A surface where stripe_products_list sits beside a bare balance_retrieve makes the pack a substring of some names and not others, so any filter over it silently misses the unqualified half. Raises DeclarationError if a tool declares no pack, or if two tools qualify to the same name.

qualified_name

One tool’s qualified name.
Raises DeclarationError if the tool declares no pack. Falling back to the bare name would produce the half-qualified surface above, silently.

report_key

The row a tool gets in an audit artifact.
What egress_map and conflict_map key their dicts by. The same qualification as above wherever the tool declares a pack, and the bare name where it does not, because a report is not a tool surface: nothing dispatches on these keys, each row carries its pack and name beside the data, and refusing a pack-less tool would put the artifact out of reach of a tool built by hand. Both maps raise DeclarationError rather than let two tools share a row. That guard and this key replace keying on the bare name, under which twenty-four colliding names across the seventeen shipped packs turned one assembled surface into 659 rows for 687 tools, naming none of the twenty-eight it dropped.

Routing a tool call back

MCP and LangChain resolve a tool call by name for you. The OpenAI APIs hand it back to you to execute, so you need the same mapping you published with — qualified_names is it.
Calling a Tool directly in Python never involves a name at all — tool.name stays events_list, and await gcalendar.events_list.ainvoke(...) is a Python reference. Qualification exists only where tools are published to a model.