Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
420 changes: 420 additions & 0 deletions PLAN_IDEAL_ANNOTATION_OVERRIDES.md

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions docs/configuration/config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,32 @@ A profile is a named view over your upstream servers with a tool policy. The ful
}
```

### `annotation_overrides` (per-server per-tool annotation fixes)

Fixes false or missing behavioural hints from an upstream MCP server without forking it. An operator can override the four MCP `ToolAnnotations` hints (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`; plus optional `title`) per tool, with a wildcard `"*"` that applies to every tool. A per-tool entry wins over the wildcard per hint (not whole-object replace), and both win over what the server sent. Changes are hot (no restart), admin-only, and audited to both the activity log and the tamper-evident audit sink (`config_change` line correlated via `request_id`).

- **Merge:** `PATCH /api/v1/servers/{id}` merges per hint (`nil` in the patch = inherit); whole-tool delete is `{"annotation_overrides":{"tool":null}}` and whole-map clear is `{"annotation_overrides":null}` (RFC 7396).
- **Validation:** at most 100 entries; key `"*"` is the only wildcard (otherwise `^[A-Za-z0-9._:-]+$`, no `"__"`, 1–256 chars); each entry must set at least one hint; persisted config never stores `null` entries.
- **Effective:** resolved once at tool capture (`upstream/core/client.go`) and reused by `DeriveCallWith`, `classifyToolRisk`, and `toolannotations.ExcludeReason`. The approval hash (`tool_quarantine.go`) is intentionally **not** affected.

**BrowserOS example — 24 tools, 9 marked `destructiveHint:true`, 7 with no hints (nil → destructive by default).** Fix in config:

```json
{
"mcpServers": [
{
"name": "browseros",
"url": "http://127.0.0.1:9001/mcp",
"annotation_overrides": {
"*": { "destructiveHint": false, "openWorldHint": false },
"act": { "destructiveHint": true, "readOnlyHint": false }
}
}
]
}
```

<<<<<<< HEAD
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `profiles[].name` | string | required | Slug `^[a-z0-9][a-z0-9_-]{0,62}$`. Reserved: `all`, `code`, `call`, `p` (URL segments), `active`, `try` (REST routes). Duplicates are a fatal error |
Expand All @@ -444,6 +470,11 @@ A profile is a named view over your upstream servers with a tool policy. The ful
A profile that sets only `name` and `servers` behaves exactly as before: no cap, unannotated tools count as read, no rules. Invalid input (an unknown tier or `unannotated` value, a malformed pattern, `switchable_to` naming the profile itself) is refused with the same message on every surface. Both `profiles` and `anonymous_profile` are **live**: an edit takes effect without a restart, and `PATCH /api/v1/config` reports them in `changed_fields`.

With `require_mcp_auth` off, a change that would let a client bound to a profile escape it by omitting its credential is refused with `409 binding_bypassable_without_auth` (see [Profiles, the binding guard](/features/profiles#the-binding-guard)). **Before downgrading to a pre-profiles-v3 binary, turn `require_mcp_auth` on**; the older binary does not know client credentials or `anonymous_profile`.
=======
`*` clears `destructive`/`openWorld` for all 24 tools; `act` opts that one tool back into `destructive:true`. After a save the Tools tab badges flip from red `destructive` to green `read` and `call_tool_read browseros:snapshot` succeeds without `call_tool_destructive`.

See [Upstream Servers](/configuration/upstream-servers) for the per-server option table, the REST/MCP PATCH shapes, and the Web UI card in the ServerDetail Configuration tab.
>>>>>>> db7e5ad9 (✨ FEAT: 🧪 TESTS + 📚 DOCS + 📝 AUDIT — PLATINUM COVERAGE FOR OVERRIDES 🛡️)

### MCP Servers

Expand Down
1 change: 1 addition & 0 deletions docs/configuration/upstream-servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ than stopping the daemon from booting.
| `health_check_interval` | duration | No | Per-server override for the liveness `ping` cadence (`0s` disables; falls back to the global value, then the `30s` default). No-op for Docker-isolated servers. |
| `tool_discovery_interval` | duration | No | Per-server override for the `tools/list` re-index sweep (`0s` disables; falls back to the global value, then the `5m` default). |
| `forward_headers` | array | No | Names of inbound MCP client HTTP headers to forward to this server on `tools/call` (streamable HTTP only; ignored for `stdio` and `sse`). See [Client Header Forwarding](#client-header-forwarding). |
| `annotation_overrides` | map[string]object | No | Per-server per-tool annotation fixes: `{"*": {destructiveHint:false}, "act": {destructiveHint:true}}`. Key is tool name or wildcard `"*"`; value is `ToolAnnotations` (`title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`). Admin-only, hot (no restart), audited (`config_change`), at most 100 entries. Fixes false MCP hints (e.g. `browseros` marks 9 of 24 tools `destructive:true` and leaves 7 with no hints → nil-default destructive); wildcard `*` clears a hint for every tool, per-tool `act` wins per hint over `"*"`. |

See [Tool Discovery & Health Check Intervals](/configuration/config-file#tool-discovery--health-check-intervals) for the global defaults, accepted ranges, and trade-offs.

Expand Down
Loading