Tool names on MCP.
Some model APIs accept only [a-zA-Z0-9_-] in tool names, so MCP shows
every tool with _ where the name has a . (catalogue.set_prices is
catalogue_set_prices). REST and OpenAPI keep the dotted name, and
tools/call accepts both.
Two tools of one audience can map to the same MCP name (a.b_c and
a_b.c). PhoenixKit.API.ToolLint.check_tools/1 reports that in a
module's tests; at run time the first one in registry order wins, in
dedupe/1 and in resolve/2. dedupe/1 works on the list the caller may
see and resolve/2 ignores scopes, so when the two tools need different
scopes a key that holds only the second one sees the second tool in
tools/list, while tools/call with the underscore name reaches the first
and is refused. The dotted name always reaches its own tool.
Summary
Functions
Drops every tool whose MCP name an earlier tool of the list already has.
The dotted name for name, which may be given in either form.
The MCP form of a tool name.
Functions
@spec dedupe([PhoenixKit.API.Tool.t()]) :: [PhoenixKit.API.Tool.t()]
Drops every tool whose MCP name an earlier tool of the list already has.
@spec resolve(String.t(), PhoenixKit.API.Tool.audience()) :: String.t()
The dotted name for name, which may be given in either form.
A name that has a . is returned as it is. An underscore name is mapped to
the dotted name of a tool of audience: a live one, or one whose module
is switched off (so that PhoenixKit.API.Executor can say module_disabled
instead of not_found). That is decided by PhoenixKit.API.Registry.find/2,
which looks at the audience, so the answer for a tool of the other audience,
a tool hidden by api_disabled_tools, a tool of a module switched off in
api_disabled_modules and a name nobody has is the same: the name comes
back unchanged. An anonymous caller of the public endpoint can
therefore not learn the dotted name of an admin tool, or that it exists.
Whether the tool is visible to the caller, permitted or enabled is the executor's answer.
The MCP form of a tool name.