How to offer tools from your ACP client
This guide shows how a Go program that drives mm -acp gives the agent its own tools: functions of your program that the model calls like its built-in tools (an editor's open buffers, an application's data, a to-do list…). It assumes your program is already an ACP client of mm, written with github.com/coder/acp-go-sdk (v0.13.5, the version mm uses). The exact protocol is in the client tools specification.
Steps
1. Describe your tools
One description per tool: a name, a description for the model, the extension method mm will call (it must start with _), and the JSON schema of the arguments.
type toolSpec struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Method string `json:"method"`
InputSchema map[string]any `json:"inputSchema,omitempty"`
}
var tools = []toolSpec{{
Name: "add_todo",
Description: "Add one item to the user's to-do list.",
Method: "_com.example/todo/add",
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{"item": map[string]any{"type": "string"}},
"required": []any{"item"},
},
}}
The model will see the tool as client_add_todo.
2. Advertise them in initialize
Put the list in clientCapabilities._meta, under org.mini-me/tools:
_, err := conn.Initialize(ctx, sdk.InitializeRequest{
ProtocolVersion: sdk.ProtocolVersionNumber,
ClientInfo: &sdk.Implementation{Name: "my-client", Version: "1.0.0"},
ClientCapabilities: sdk.ClientCapabilities{
Meta: map[string]any{"org.mini-me/tools": tools},
},
})
That is all mm needs with the default configuration (acp.clientTools.discovery: meta): every session opened afterwards has the tools.
3. Answer the calls
Your client type (the one passed to sdk.NewClientSideConnection) implements sdk.ExtensionMethodHandler. The SDK hands it every request whose method starts with _:
var _ sdk.ExtensionMethodHandler = (*myClient)(nil)
func (c *myClient) HandleExtensionMethod(ctx context.Context, method string, params json.RawMessage) (any, error) {
switch method {
case "_com.example/todo/add":
var args struct {
Item string `json:"item"`
Meta struct {
SessionID string `json:"org.mini-me/sessionId"`
} `json:"_meta"`
}
if err := json.Unmarshal(params, &args); err != nil {
return nil, sdk.NewInvalidParams(map[string]any{"error": err.Error()})
}
c.todos = append(c.todos, args.Item)
return textResult(fmt.Sprintf("added %q", args.Item), false), nil
}
return nil, sdk.NewMethodNotFound(method)
}
// textResult is the answer shape mm reads first.
func textResult(text string, isError bool) map[string]any {
return map[string]any{
"content": []map[string]any{{"type": "text", "text": text}},
"isError": isError,
}
}
- The model's arguments are at the top level of
params;_meta["org.mini-me/sessionId"]says which session called. - Answer a failure with
isError: trueand a text the model can act on, rather than a JSON-RPC error: both reach the model, but the text is yours. - Keep the handler safe for concurrent use: the SDK runs each request in its own goroutine.
4. Check that mm sees them
Start mm and read its stderr. After initialize it prints:
[acp] client tools (meta): client_add_todo
An entry it could not use is reported on its own line ([acp] client tools: entry 0 skipped: …), with the reason.
Variants
-
The list depends on the session (its
cwd, a user): setdiscovery: requestin the agent file, advertise only"org.mini-me/tools": true, and answer_org.mini-me/tools/listinHandleExtensionMethodwithmap[string]any{"tools": tools}. mm sends it duringsession/new,session/loadandsession/resume, before answering them; the SDK lets you answer while yourNewSessioncall waits.acp: clientTools: discovery: request -
Every call must be confirmed: set
acp.clientTools.permissions: ask. mm then sendssession/request_permissionbefore each call, titledclient: <name>; answer it in yourRequestPermissionmethod. With the defaultauto, this happens only when the agent file saysyolo: false. -
Slow tools: raise
acp.clientTools.timeout(default30s). Past it, the model reads an error and the turn goes on. -
Using the
minimepackage ofdemo.acp.sdk/go: steps 1 to 3 areminime.Options{Tools: []minime.Tool{…}}, eachToolwith aHandlefunction;ToolDiscovery: minime.DiscoveryRequestselects the second mode.go run ./examples/clienttoolsruns a complete example.
See also
- Client tools specification — the exact JSON, the validation of a list, the errors
- Configuration file —
acpkeys - How to drive mini-me from a program
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
|