ACP client tools — explanation
What is this about?
An editor, or any program that drives mm -acp, often knows things the agent cannot reach: the buffers open on screen, the selection, an application's data, a device. ACP client tools let that program lend the agent functions of its own. The model calls them like bash or read_file; mm forwards each call to the client as an ACP request and gives the answer back to the model. The exact protocol is in the reference; this page is about why it looks the way it does.
Why mm needed its own convention
ACP has an extension mechanism: either side may define methods whose name starts with _, and may advertise them in the _meta object of the capabilities exchanged at initialize. That is enough to call a method, but not to make it a tool: a model needs a name, a description and a schema of the arguments, and ACP defines no way to describe any of them. So mm defines one, under its own namespace (org.mini-me), using only what ACP already provides — _meta for the advertisement, extension methods for the calls. A client that does not know about it sends nothing, and nothing changes.
The tool description deliberately copies MCP's (name, description, inputSchema) and so does the answer (content blocks, isError). Anyone who has written an MCP tool already knows both shapes, and mm already knew how to present an MCP schema to a small model (the same cleaning of required and enum applies, for the same reasons: see MCP decisions).
Two discovery modes, and why meta is the default
meta: the list travels ininitialize. One message, nothing to answer, and the list is known before any session exists. Most clients have a fixed set of tools, and for them this is the least code to write. Hence the default.request: the client only says it has tools, and mm asks for the list each time a session is opened, loaded or resumed. This is for a client whose tools depend on the session — its working directory, the user behind it — or change over time.
The request mode has one constraint worth knowing: mm sends the list request while the client is still waiting for its session/new response. A client that answered requests one at a time would wait for mm while mm waits for it. The ACP SDKs (Go, TypeScript) run each incoming request on its own, so this is free for them; it is written in the specification for clients that roll their own JSON-RPC.
Why the session travels in _meta, and the arguments at the top level
The call's params are exactly the model's arguments. This keeps an existing extension method usable as is: a client that already answers _com.example/workspace/buffers {"language": "go"} can expose it as a tool without an adapter. The session id is not an argument the model chose, so it goes where ACP puts out-of-band data — _meta — and the model cannot forge it: a _meta it invented is replaced.
Why the tools are built per session and never registered
Genkit keeps a registry of named tools, and registering the same name twice is an error. Client tools change per connection (and per session in request mode), so mm builds them as dynamic tools (ai.NewTool), which Genkit registers in a child registry for the length of one generation. Two sessions can then hold client_open_buffers with different definitions, and nothing has to be unregistered when a session ends.
Why errors are answers
A client that does not answer, answers Method not found, or reports a failure gives the model a [client tool error: …] text, and the turn goes on. This is mm's general rule (see Architecture): the model is the one that must react, so it is the one that must read what went wrong. The same rule governs a bad list: an entry that cannot be used is skipped with a line on stderr, and the session opens with the others. A client's mistake must not cost the user the conversation.
Permissions
Client tools follow the rule of the tools that come from elsewhere, with their own key (acp.clientTools.permissions): ask always asks, auto asks only when yolo is off. The default is auto, where MCP's is ask, because the trust is not the same: an MCP server is third-party code, a client tool is a function of the very program the user started to talk to the agent. See Yolo mode and permissions.
Rejected alternatives
- Using the
mcpServersofsession/new. ACP lets a client hand the agent MCP servers to connect to. That would have meant the client running an MCP server — a second protocol, a second transport — to expose a function it already holds in the same process, at the other end of the pipe it already shares with mm. - One generic method,
_org.mini-me/tools/call {name, arguments}. Simpler to dispatch, but it would force every client to adopt mm's method name; amethodper tool lets a client expose extension methods it already has. - Declaring the tools in
agent.yaml. The agent file does not know which client will start it, and the same agent can be driven by an editor today and by a script tomorrow. The tools belong to the client, so the client announces them. - Only one discovery mode.
metaalone cannot serve tools that depend on the session;requestalone costs every client a request handler and a round trip for a list that is usually fixed.
How it relates to the rest
- The tools exist only in ACP mode: the terminal and the window have no client to lend them.
- Once declared, they are ordinary tools for the rest of mm: tool routing can leave them out of a turn when one of its groups matches their names (
^client_), hooks see their calls, the loop detector counts them. demo.acp.sdk/go(Options.Tools) anddemo.acp.sdk/client-tools-nodejsare two client implementations; How to offer tools from your ACP client walks through one.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|