bots-garden/mini-mepublic Fork
⑂ v1.0.7
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
🛟 Updated. bfca5aa · on v1.0.7 · k33g · 3d ago
offer-tools-from-an-acp-client.md · 115 lines · 4.9 KBmarkdown
Blame HistoryOpen raw

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: true and 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): set discovery: request in the agent file, advertise only "org.mini-me/tools": true, and answer _org.mini-me/tools/list in HandleExtensionMethod with map[string]any{"tools": tools}. mm sends it during session/new, session/load and session/resume, before answering them; the SDK lets you answer while your NewSession call waits.

    acp:
      clientTools:
        discovery: request
    
  • Every call must be confirmed: set acp.clientTools.permissions: ask. mm then sends session/request_permission before each call, titled client: <name>; answer it in your RequestPermission method. With the default auto, this happens only when the agent file says yolo: false.

  • Slow tools: raise acp.clientTools.timeout (default 30s). Past it, the model reads an error and the turn goes on.

  • Using the minime package of demo.acp.sdk/go: steps 1 to 3 are minime.Options{Tools: []minime.Tool{…}}, each Tool with a Handle function; ToolDiscovery: minime.DiscoveryRequest selects the second mode. go run ./examples/clienttools runs a complete example.

See also

  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
# 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`](https://github.com/coder/acp-go-sdk) (v0.13.5, the version mm uses). The exact protocol is in the [client tools specification](../reference/acp-client-tools.md).

## 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.

```go
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`:

```go
_, 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 `_`:

```go
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: true` and 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): set `discovery: request` in the agent file, advertise only `"org.mini-me/tools": true`, and answer `_org.mini-me/tools/list` in `HandleExtensionMethod` with `map[string]any{"tools": tools}`. mm sends it during `session/new`, `session/load` and `session/resume`, before answering them; the SDK lets you answer while your `NewSession` call waits.

  ```yaml
  acp:
    clientTools:
      discovery: request
  ```

- **Every call must be confirmed**: set `acp.clientTools.permissions: ask`. mm then sends `session/request_permission` before each call, titled `client: <name>`; answer it in your `RequestPermission` method. With the default `auto`, this happens only when the agent file says `yolo: false`.
- **Slow tools**: raise `acp.clientTools.timeout` (default `30s`). Past it, the model reads an error and the turn goes on.
- **Using the `minime` package** of `demo.acp.sdk/go`: steps 1 to 3 are `minime.Options{Tools: []minime.Tool{…}}`, each `Tool` with a `Handle` function; `ToolDiscovery: minime.DiscoveryRequest` selects the second mode. `go run ./examples/clienttools` runs a complete example.

## See also

- [Client tools specification](../reference/acp-client-tools.md) — the exact JSON, the validation of a list, the errors
- [Configuration file — `acp` keys](../reference/configuration.md#acp-keys)
- [How to drive mini-me from a program](drive-mini-me-from-a-program.md)