magi-code 0.104.0

Repository-aware CLI coding agent for terminal work
Documentation
# Client-provided local-agent tools

`magi-code serve --stdio` can advertise client-provided tools to the existing agent runner. This optional version-1 extension supplements built-ins; it never replaces their execution, permissions or safeguards. The spawning client executes only its registered tools and owns any approval UI. Registration does not certify that approval occurred.

## Negotiate once

Require `client.tools` and supply `client_tools` in `initialize.params`. The field is required when negotiated and forbidden otherwise; null is invalid, an empty array is valid. Example params (not a complete frame):

```json
{
  "required_capabilities": ["client.tools", "session.create", "turn.start"],
  "client_tools": [{
    "name": "client__lookup_record",
    "description": "Look up a record by identifier.",
    "input_schema": {
      "type": "object",
      "properties": {"record_id": {"type": "string"}},
      "required": ["record_id"],
      "additionalProperties": false
    },
    "timeout_ms": 30000
  }]
}
```

Descriptors accept exactly `name`, `description`, `input_schema` and optional `timeout_ms`; unknown or duplicate descriptor fields fail. Names must be unique. The entire registry is validated before startup effects; invalid registration returns `invalid_params`. Corrected preflight requests need fresh request IDs. Once initialization is admitted, restart the child to change registration. There is no registration/update/unregister method.

Only negotiated clients receive `client.tools` in `capabilities`, `client_tools: {"tools":[names],"limits":{...}}`, and `extensions["client.tools"]`. Names retain registration order. The extension descriptor has `schema_version:1`, `registration:"startup_only"`, the same names and limits. Up to 18 required-capability entries are allowed when requesting this extension; without it, the existing maximum remains 16. `tool.cards` negotiates independently.

## Bounds and schemas

| Limit key | Value / meaning |
| --- | --- |
| `tools` | 32 descriptors per child |
| `name_bytes` | 64 ASCII bytes: `client__` plus 1–56 letters, digits or underscores; case-sensitive |
| `description_bytes` | 2048 UTF-8 bytes; nonblank; no controls except LF/tab |
| `registry_bytes` | 32768 compact serialized bytes of the effective descriptor array, including defaulted timeouts |
| `schema_bytes` | 8192 compact serialized bytes per input schema |
| `schema_depth` | 16 containers, root counts as one |
| `arguments_bytes` | 16384 compact serialized bytes; arguments must be an object |
| `result_bytes` | 32768: UTF-8 text bytes plus compact structured JSON bytes if present |
| `pending_calls` | 16 across root, all subagents and Code Mode |
| `default_timeout_ms` | 30000 |
| `min_timeout_ms` / `max_timeout_ms` | 1000 / 300000 inclusive |

The schema root must explicitly have `"type":"object"`. Backend validates a bounded inline subset, not instances against JSON Schema. Supported schema keywords:

- `type`; `properties`, `$defs`, `definitions`, `patternProperties`; `items`, `additionalProperties`, `not`, `contains`, `propertyNames`; `allOf`, `anyOf`, `oneOf`, `prefixItems`.
- `required`, `enum`, `const`, `default`, `examples`; `description`, `title`, `pattern`, `format`, `$comment`.
- `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, positive `multipleOf`; nonnegative integer `minLength`, `maxLength`, `minItems`, `maxItems`, `minProperties`, `maxProperties`, `minContains`, `maxContains`.
- Boolean `uniqueItems`, `readOnly`, `writeOnly`, `deprecated`.

Nested schemas may be booleans. Schema containers and keyword value types are checked; unknown keywords, including `$schema` and schema references, fail rather than being ignored. Literal `enum`, `const`, `default` and `examples` values remain data. No resolution, network/file access, regex compilation or full schema evaluation occurs. Providers may reject unsupported schemas; backend does not replace them with an unconstrained schema. Client must validate actual arguments and return an error on mismatch.

All bounds apply together with the [base protocol](local-agent-protocol.md): input frame 65536 bytes excluding LF, output frame 65536 including LF, JSON depth 32, 4096 lifetime request IDs, bounded output queues/deadlines. Escaping and envelopes count toward wire bounds. No argument/schema/result truncation to fit. An outgoing call exceeding wire bounds fails locally with `client_tool_payload_limit`; within-frame invalid result fields return `invalid_params`; oversized input frames retain base close behavior.

## Execute and reply

`client.tool.call` is a critical event, independent of cards. Its data contains exactly:

```json
{
  "session_id": "root-session",
  "run_id": "root-run",
  "call_id": "fresh-UUID",
  "task_id": null,
  "subagent_id": null,
  "name": "client__lookup_record",
  "arguments": {"record_id": "example"},
  "timeout_ms": 30000
}
```

Envelope `instance_id` identifies the child. Session/run stay root-scoped. Root calls have null task/subagent IDs; delegated calls use backend task and activity identities, not model labels. Nested calls identify the executing subagent. Call UUIDs are separate from provider call IDs and request IDs; never reuse them or replay uncertain execution.

Reply with ordinary method `client.tool.result`, a fresh request ID, and exactly these params:

```json
{
  "instance_id": "child-instance",
  "session_id": "root-session",
  "run_id": "root-run",
  "call_id": "fresh-UUID",
  "task_id": null,
  "subagent_id": null,
  "status": "success",
  "text": "Record found.",
  "structured": {"found": true}
}
```

All fields except `structured` are required. Copy the complete identity from the call/envelope. Status is `success` or `error`; text may be empty. Structured output accepts any JSON, including explicit null, distinct from omission. Both statuses may include it. No streaming, attachments, images or chunks.

`{"accepted":true}` acknowledges first valid broker acceptance, **not durable history**. Protected text and structured JSON reach ordinary tool-result handling; strings and credential-like fields are sanitized/redacted, including structured keys. Redaction collisions or excessive expanded output are rejected, never silently dropped. Error status is untrusted tool output, not a backend diagnosis. Existing required history appends and `run.finished` remain authoritative.

Without negotiation the method returns `unsupported_method`. With negotiation, normal readiness errors apply. Unknown, completed, expired, cancelled, wrong-instance or mismatched-scope calls return `stale_client_tool_call`. Invalid payload returns `invalid_params` without consuming a pending call; correct it with a fresh request ID before deadline. Duplicate request IDs retain `duplicate_id`; a repeated result under a fresh ID is stale. Results consume the existing request quota; reserve headroom for replies, cancellation and shutdown.

## Cancellation and permissions

The absolute monotonic timeout begins before publishing the call and includes delivery, approval and execution. It is not restarted on receipt or invalid replies. Calls may finish out of order; match complete identities, not arrival order. A seventeenth pending call fails promptly with `client_tool_busy`, without another execution queue.

`client.tool.cancel` repeats session/run/call/task/subagent identity and adds `reason`: `cancelled`, `timeout`, `run_finished` or `shutdown`. It carries no arguments/results and requires no acknowledgement. Cancellation/expiry compete with completion; late replies are rejected. Stop client work when notified. No rollback guarantee and no automatic broker retries. Disconnect may prevent notice delivery; client must stop outstanding work on transport loss. Existing two-second output/cleanup bounds still apply.

Tools pass through normal pre/post hooks, protected results, cancellation and persistence. Argument shape/size is rechecked after pre-call hook transformation. Direct full/compact catalogs and Code Mode discovery/calls expose only permitted tools; existing MCP compact visibility is unchanged. Direct and Code Mode exclusions remain independent. Client tools cannot claim built-in/MCP names, bypass their approvals or serve as fallback execution for them.

Noninspection subagents inherit the registry within profile, task and per-surface restrictions. Inspection tasks cannot call any client tool, including tools described as read-only. Saved delegated permissions capture explicit names and SHA-256 effective-descriptor fingerprints (default timeout included, object keys sorted, array order retained). Resume intersects current registry and saved/current restrictions. New, removed or changed tools are unavailable; missing legacy client permissions deny all client tools. Legacy delegated sessions without a saved scope also receive none. Top-level reopened sessions use the current startup registry. Registration itself is not persisted or reconstructed from tool history.

Generic tool cards may display names/status when negotiated; execution does not depend on cards. Raw arguments/results are not added to presentation history. Transport success, client execution, protected-result acceptance and durable recording remain separate outcomes.