# Avo MCP tools reference

Every tool except `list_workspaces` operates on a workspace. Pass `workspaceId` as a parameter; stdio clients can also set the `WORKSPACE_ID` environment variable.

> 🔒 Each tool lists the OAuth scope it requires. Write tools (`workflow`, `save_items`) require the `write` scope, which is requested as a separate consent step on first use.

The MCP exposes six canonical tools mapped to agent intents:

| Intent | Tool | Scope |
|---|---|---|
| **Entry point** — find your workspace IDs | [`list_workspaces`](#list_workspaces) | `read` |
| **Discover** — find items by meaning or by structural filter | [`search`](#search) | `read` |
| **Understand** — full details for an event, property, branch, source, etc. | [`get`](#get) | `read` |
| **Change** — create, update, archive, or restore items on a branch | [`save_items`](#save_items) | `write` |
| **Progress** — create a branch, update its description, pull main, set a source's language, or bulk-import a plan | [`workflow`](#workflow) | `write` |
| **Tell Avo** — report the agent's own experience back to Avo's product team | [`give_feedback`](#give_feedback) | `write` |

Branch read flows are covered by `get` and `search`. One transitional tool — [`list_branches`](#list_branches) — remains available while branch enumeration is folded into `search` (as `itemType: "branch"`).

---

## `list_workspaces`

**Scope:** `read`

List the Avo workspaces the authenticated user has access to. Call this first to discover workspace IDs before invoking any workspace-scoped tool.

### Parameters

None.

### Returns

One row per workspace: name, workspace ID, and the user's role.

### Examples

#### Discover the workspaces you can access

**Prompt:** *"What Avo workspaces do I have access to?"*

Claude calls `list_workspaces` with no parameters and uses the returned `workspaceId` to scope every other tool call in the session.

---

## `search`

**Scope:** `read`

Find tracking plan items in one of two modes — the mode is selected automatically by which parameters you pass. **Combining `query` with structural filters is rejected** (the tool returns an error message, not an HTTP status) — pick one mode. `branch` and `pageToken` are filter-mode only and likewise cannot be combined with `query`. For ID-based lookups use [`get`](#get).

- **Semantic search** — pass `query` to find items by meaning across events, properties, metrics, categories, property bundles, and event variants. Avo embeds each item with OpenAI embeddings and runs a vector-similarity search at query time, so `"user signed up"` matches `Account Created` or `Registration Completed` even when no keyword overlaps.
- **Structured listing** — omit `query` and pass filters to enumerate exact matches with keyset pagination.

> 🔒 Semantic search requires Avo Intelligence Smart Search to be enabled in your workspace. Workspace admins can enable it in [Workspace Settings](https://www.avo.app/schemas/default?settings=general). If you don't have admin access, ask a workspace admin to enable it. Filter-mode listing does not require Smart Search.

### Parameters

**Shared across modes**

| Parameter | Required | Description |
|---|---|---|
| `itemType` | No | Filter by type: `event` (default), `property`, `metric`, `category`, `propertyBundle`, `eventVariant`, `source`, `destination`, or `groupType`. The workspace-metadata types (`source` / `destination` / `groupType`) enumerate the workspace list and are only valid in filter mode (omit `query`). |
| `maxResults` | No | Semantic mode: 1–20, default 10. Filter mode: 1–500, default 10. |
| `workspaceId` | No | Workspace ID. |

**Semantic mode** *(pass `query`)*

| Parameter | Required | Description |
|---|---|---|
| `query` | Yes | Natural language search query. |

Semantic search is performed against the **main branch** only. The semantic index may lag slightly for very recently created or updated items.

**Filter mode** *(omit `query`, pass any of the filter fields)*

| Parameter | Required | Description |
|---|---|---|
| `tags` | No | Filter by tag. |
| `categories` | No | Filter by category name. |
| `sources` | No | Filter by source name. Does not apply to metrics. |
| `eventNames` | No | Filter by event name. With `itemType: "property"`, returns properties on those events. |
| `variantNames` | No | Filter by event variant name. |
| `properties` | No | With `itemType: "event"`, returns events referencing any of these properties. |
| `includeVariants` | No | With `itemType: "event"`, interleaves each event's variants in the result set. |
| `stakeholders` | No | Filter by stakeholder team (names only). Tagged object: `{ kind: "any" }` (items with any stakeholder) or `{ kind: "matches", values: [...], includeNoneAssigned?: bool }` (items whose stakeholder team name is in `values`; with `includeNoneAssigned: true` items with no stakeholder also pass). |
| `owners` | No | Filter by owner stakeholder (names only). Tagged object: `{ kind: "any" }` (items with any owner) or `{ kind: "matches", values: [...], includeNoneAssigned?: bool }` (items whose owner stakeholder name is in `values`; with `includeNoneAssigned: true` unowned items also pass). |
| `destinations` | No | Filter by destination name. |
| `type` | No | With `itemType: "event"`, filter by event type. |
| `customField` | No | Filter by custom-field name. Resolve valid names from [`get`](#get) with `type: "workspaceConfig"`. |
| `pii` | No | Filter by PII type. Resolve valid types from [`get`](#get) with `type: "workspaceConfig"`. |
| `nameMapping` | No | Filter by destination-name-mapping. Tagged object: `{ kind: "any" }` (items with any mapping) or `{ kind: "matchesAny", names: [...], includeNoMapping?: bool }` (items whose mapped name is in `names`; with `includeNoMapping: true` items without a mapping rule also pass). Omitting `nameMapping` means no filter on mapping. |
| `branch` | No | Branch **ID** to enumerate items on. Defaults to main. (Filter mode only — there is no `branchName` alias on `search`; resolve a name to an ID with [`list_branches`](#list_branches) first.) |
| `pageToken` | No | Pagination token from a previous response. |

Multiple values inside one array are OR'd; values across different filter keys are AND'd.

### Returns

A **Markdown document** (not JSON) — a `# Search Results` heading, a result count, and a ranked table. Rows are ordered best-match first; there is **no relevance score** (ranking uses a fused rank, not an intuitive 0–100% relevance). Descriptions are truncated to ~80 characters.

- **Semantic mode** columns: `Rank | Name | Type | Item ID | Branch | Description`.
- **Filter mode** columns: `Rank | Name | Type | Item ID | Description` — event-variant rows instead use `Rank | Name | Base Event | Variant ID | Description`.

In filter mode, when more results are available the document ends with a **Next page** instruction: call `search` again with the same `itemType` / filters / `branch` **plus** the supplied `pageToken` (the token alone is not enough — you must repeat the original filters). Any filters that were ignored or coerced are listed under a **Filter warnings** section.

```markdown
# Search Results

Found 2 results for "user signed up"

| Rank | Name | Type | Item ID | Branch | Description |
|------|------|------|---------|--------|-------------|
| 1 | **Account Created** | event | evt-9f2b… | main | Sent when a new account is successfully created. |
| 2 | **Signup Started** | event | evt-3c11… | main | Sent when the user opens the signup screen. |
```

### Examples

#### Find events by meaning (semantic)

**Prompt:** *"What events do we have for signup?"*

Claude passes the user's phrasing directly to `query`. Semantic mode returns events whose meaning matches the query, even when the exact words differ — `"user signed up"` will match `Account Created` or `Registration Completed`.

```json
{
  "query": "user signed up",
  "itemType": "event",
  "maxResults": 5
}
```

#### List events using a specific property (filter)

**Prompt:** *"Which events on iOS use the `product_id` property?"*

Filter mode is selected by omitting `query`. Multiple filter keys are AND'd, so this returns only events that reference `product_id` *and* are tracked from the `iOS` source.

```json
{
  "itemType": "event",
  "properties": ["product_id"],
  "sources": ["iOS"],
  "maxResults": 50
}
```

### Common errors

- `query` and structural filters combined — rejected with an error (no HTTP status). Choose one mode.
- `query` combined with `branch` or `pageToken` — rejected; those are filter-mode-only parameters.
- Smart Search not enabled in the workspace — semantic mode fails; fall back to filter mode or `get`.
- Workspace access denied.

---

## `get`

**Scope:** `read`

Get item details for any of four type families:

- **Tracking-plan items** — `event`, `property`, `metric`, `category`, `propertyBundle`, `eventVariant`. Look up by `id` or exact `name` — except `eventVariant`, which is identified by the base event's `id` plus `variantId` (never by name). For events, `includePropertyDetails: true` returns each property's type, constraints, and allowed values inline. Event, property, and event-variant results also list their owner and stakeholders under a **Domain Stakeholders** section (the owner is suffixed `(owner)`); it is omitted when the item has no stakeholders.
- **Workspace metadata** — `source`, `destination`, `groupType`. `get` returns a **single** item, so pass `id` or `name`. To enumerate the workspace list, use [`search`](#search) (`itemType: "source"` / `"destination"` / `"groupType"`).
- **Workspace config** — `workspaceConfig`. Naming/casing rules and event/property validation rules (with the enforcement point), custom-field definitions, the PII type list, and the workspace's tags and categories. Custom-field and PII-type names plug straight into [`search`'s](#search) `customField` and `pii` filters.
- **Branches** — `branch`. Identify with `branchId` or `branchName`. Use `include` to pick content: `"overview"` (branch metadata + baseline status + resolved creator/reviewer/collaborator emails + impacted sources + comments/approvals stats), `"all_changes"` (full diff vs. main, like the web branch screen), `"event_changes"` (events + event variants only), `"property_changes"` (properties + property bundles + categories only), `"code_snippets"` (per-source generated code; requires `sourceId`), `"implementation_guide"` (numbered implementation steps + per-event codegen instructions). Multiple values union, and `event_changes` + `property_changes` equals `all_changes`. `include` defaults to `["overview"]`.

Defaults to the main branch when no branch is specified.

### Parameters

| Parameter | Required | Description |
|---|---|---|
| `type` | Yes | Item type. One of: `event`, `property`, `metric`, `category`, `propertyBundle`, `source`, `destination`, `groupType`, `eventVariant`, `branch`, `workspaceConfig`. |
| `id` | Varies by type | The item's unique ID. Required for `event`, `property`, `metric`, `category`, `propertyBundle` unless `name` is provided. For `source`/`destination`/`groupType`, provide `id` or `name` — `get` is single-item only, so enumerate with [`search`](#search) instead. `eventVariant` uses `id` (the base event ID) **plus** `variantId`, not `name`. For `branch`, identify with `branchId`/`branchName` (`id`/`name` are not branch identifiers; omitting both errors). Not used by `workspaceConfig`. |
| `name` | Varies by type | Exact name match. Alternative to `id` for most types. May return multiple matches for ambiguous names (especially properties) — use [`search`](#search) for fuzzy lookup. |
| `variantId` | For `eventVariant` | The variant ID. Combined with `id` (the base event ID). |
| `include` | For `branch` | Array of branch facets to return: `overview`, `all_changes`, `event_changes`, `property_changes`, `code_snippets`, `implementation_guide`. Defaults to `["overview"]`. Multiple values union. The deprecated value `changes` is accepted as an alias for `all_changes`. Unknown values are dropped with a warning. Combine `all_changes` and `code_snippets` (or use `implementation_guide`) for an implementer-ready picture of the branch. |
| `sourceId` | Required for `code_snippets`; optional elsewhere | Source ID to scope branch content. **Required** when `include` contains `code_snippets` (single-source in v1). Optional on `all_changes` / `event_changes` / `property_changes` / `implementation_guide`: it filters event-shaped diffs to one source (property, bundle, and category diffs stay workspace-wide) and gates the per-event codegen instructions in `implementation_guide`. Find source IDs with [`search`](#search) (`itemType: "source"`). |
| `branchId` | No | Branch to look up on. Defaults to main. `branchId` takes precedence over `branchName`. |
| `branchName` | No | Alternative to `branchId`. |
| `includePropertyDetails` | No | Events only. When `true`, includes full property definitions (type, constraints, allowed values). Defaults to `false`, which returns only property ID + name references. |
| `includeArchived` | No | When `true` (default), includes archived items in results. When `false`, only active items. |
| `workspaceId` | No | Workspace ID |

### Returns

Full details for the item, shaped per item type.

- `type: "event" \| "property" \| "metric" \| "category" \| "propertyBundle" \| "eventVariant"` — the item's full definition. For events, properties, and event variants this includes a **Domain Stakeholders** section listing the item's stakeholders by name, with the owner suffixed `(owner)` — the read side of [`save_items`](#save_items)'s `owner` / `stakeholders` fields. The section is omitted when the item has no stakeholders.
- `type: "source" \| "destination" \| "groupType"` — a single entity; `id` or `name` is required (enumerate the workspace list with [`search`](#search)).
- `type: "branch"` — branch facets requested via `include`. `overview` returns resolved emails for the creator, reviewers, and collaborators, branch status, impacted source IDs, comments/approvals stats, and description. `all_changes` returns the full structured diff vs. main (new, modified, and deleted events and properties with their descriptions); `event_changes` and `property_changes` return just the event- or property-side of that diff. `code_snippets` returns per-event code diffs for the source named in `sourceId` — exact unified diffs for [Avo Codegen](https://www.avo.app/docs/implementation/avo-codegen-overview.md) sources and illustrative pseudocode for manually-instrumented sources. `implementation_guide` returns numbered implementation steps plus per-event codegen instructions (scoped to `sourceId` when provided).
- `type: "workspaceConfig"` — the workspace's event/property naming conventions and casing rules, the event and property **validation rules** (with their severities and the enforcement point — where Avo blocks), custom field definitions, the list of recognized PII types, and the workspace's **tags** and **categories** listings. Use this before proposing new events or properties so names match the workspace's audit rules.

### Examples

#### Look up an event by exact name

**Prompt:** *"How is the `Account Created` event defined?"*

Claude calls `get` with the exact name and `includePropertyDetails: true` so the response includes each attached property's type, constraints, and allowed values. Useful when the agent already knows the canonical name and wants the full schema in one call.

```json
{
  "type": "event",
  "name": "Account Created",
  "includePropertyDetails": true
}
```

#### Read what changed on a branch

**Prompt:** *"What's on the `checkout-v2` branch — and can you show me the iOS code diff?"*

Claude calls `get` with `type: "branch"` and combines `all_changes` and `code_snippets` in `include`. `sourceId` scopes the diff to a single source.

```json
{
  "type": "branch",
  "branchName": "checkout-v2",
  "include": ["all_changes", "code_snippets"],
  "sourceId": "src-ios"
}
```

### Common errors

- Item not found.
- Ambiguous name (returns multiple matches — narrow by ID).
- `sourceId` missing when `include` contains `code_snippets`.
- Workspace access denied.

---

## `save_items`

**Scope:** `write` · **Destructive:** `archive` (and legacy `remove`)

> 🚧 Write access is in general beta — enabled for every workspace, no need to request access. [Email support@avo.app](mailto:support@avo.app) if you hit anything unexpected.

> ⚠️ **Destructive operations.** `op: "archive"` archives the target item on the branch, and `op: "unarchive"` restores it. Archiving a property **cascades** — references on every event that uses the property are also removed. All archives are reversible from the Avo web app, but the cascade means a single call can touch many events.

Batch create, update, archive, and unarchive events, properties, event variants, property bundles, metrics, categories, sources, destinations, and group types on a branch. A single call can mix item types and operations, and can cross-reference new items via temporary IDs.

> 💡 **Quick reference.** Common patterns:
>
> - **Create an event with new properties** — pair `create-event` and `create-property` items in one batch, using `tempId` to cross-reference.
> - **Add allowed values to a property** — `update-property` with top-level `addAllowedValues`.
> - **Rename an event or property** — `update-event` / `update-property` with `set.name`.
> - **Archive an event** — `op: "archive"` (or legacy `op: "remove"`).
> - **Create a funnel metric** — `create-metric` with `metricType: "Funnel"` and `items`.
> - **Toggle a property between scalar and list** — `update-property` with top-level `isList: true` / `false`.

### Parameters

**Top-level**

| Parameter | Required | Description |
|---|---|---|
| `branchId` | Yes | The branch to write to. Get it from [`workflow`](#workflow) (`action: "create_branch"`). |
| `items` | Yes | Array of items to apply. Each item is typed by its `type` field and has an `op` (default `create`). |
| `workspaceId` | No | Workspace ID |

The request is capped at **50 items** per call.

### Item shape

Every item shares these fields:

| Field | Type | Notes |
|---|---|---|
| `op` | `"create"` \| `"update"` \| `"archive"` \| `"unarchive"` | Defaults to `"create"`. `"archive"` archives an item (recoverable) and `"unarchive"` restores it — both apply uniformly to **every** archivable type (event, event_variant, metric, property, property_bundle, category, source, destination, groupType). `"remove"` is a **legacy alias accepted only for events and properties** (behaves as `archive`); on any other type `"remove"` is rejected with a "use `op: \"archive\"`" error. Not every op applies to every type — see "Supported ops" below. |
| `type` | `"event"` \| `"property"` \| `"event_variant"` \| `"property_bundle"` \| `"metric"` \| `"category"` \| `"source"` \| `"destination"` \| `"groupType"` | Required. |
| `name` | string | Required on `create` for events, properties, property bundles, metrics, and categories. Cosmetic on `event_variant` items and on `update`/`remove`/`archive` of other types. |
| `tempId` | string | **Create only.** Declares a temporary handle (e.g. `"prop1"`). Reference it elsewhere in the same call as `"$tmp:prop1"`. The server allocates a real ID and resolves all `$tmp:` references before writing. |
| `eventId` | string | Required on `update` / `archive` / `unarchive` (or legacy `remove`) of events. |
| `propertyId` | string | Required on `update` / `archive` / `unarchive` (or legacy `remove`) of properties. |
| `propertyBundleId` | string | Required on `update` / `archive` / `unarchive` of property bundles. |
| `metricId` | string | Required on `update` / `archive` / `unarchive` of metrics. |
| `categoryId` | string | Required on `update` / `archive` / `unarchive` of categories. |
| `sourceId` | string | Required on `update` / `archive` / `unarchive` of sources. On `create`, supply either `sourceId` or a `tempId`. |
| `destinationId` | string | Required on `update` / `archive` / `unarchive` of destinations. On `create`, supply either `destinationId` or a `tempId`. |
| `groupTypeId` | string | Required on `update` / `archive` / `unarchive` of group types. On `create`, supply either `groupTypeId` or a `tempId`. |
| `baseEventId` | string | Required for `event_variant` items — the parent event's ID. |
| `variantId` | string | Required on `update` / `archive` / `unarchive` of `event_variant`. On `create`, provide either `variantId` directly or a `tempId` on the same item. |
| `nameSuffix` | string | Required on `create` of `event_variant` — suffix appended to the parent event name (e.g. `buy_now` produces `click / buy_now`). |
| `description` | string | Create-only. Event / property / event variant / bundle / metric / category description. |

**Supported ops by type:**

| Type | create | update | archive | unarchive | remove (legacy) |
|---|:-:|:-:|:-:|:-:|:-:|
| `event` | ✅ | ✅ | ✅ | ✅ | ✅ (alias for `archive`) |
| `property` | ✅ | ✅ | ✅ (cascades references) | ✅ | ✅ (alias for `archive`) |
| `event_variant` | ✅ | ✅ | ✅ | ✅ | ❌ |
| `property_bundle` | ✅ | ✅ | ✅ | ✅ | ❌ |
| `metric` | ✅ | ✅ | ✅ | ✅ | ❌ |
| `category` | ✅ | ✅ | ✅ (does **not** cascade) | ✅ | ❌ |
| `source` | ✅ | ✅ | ✅ | ✅ | ❌ |
| `destination` | ✅ | ✅ | ✅ | ✅ | ❌ |
| `groupType` | ✅ | ✅ | ✅ | ✅ | ❌ |

**Create-event fields:**

| Field | Notes |
|---|---|
| `properties` | Array of property IDs (or `$tmp:` refs) to attach. |
| `propertyBundles` | Array of property bundle IDs (or `$tmp:` refs) to attach to the event. |
| `sources` | Array of source IDs to include the event in. Find source IDs with [`search`](#search) (`itemType: "source"`). |
| `tags` | Array of literal tag-name strings to apply to the event. |
| `nameMappings` | Per-destination name mappings. Array of tagged objects — `{ kind: "allDestinations", name: "..." }` to map across every destination, or `{ kind: "destination", destinationId: "...", name: "..." }` to map per destination. Also accepted on `update`-event items. |

**Create-property fields:**

| Field | Notes |
|---|---|
| `propertyType` | `string`, `int`, `long`, `float`, `bool`, `object`, `any` (aliases like `integer`, `boolean`, `double` accepted). |
| `sendAs` | `event`, `user`, or `system`. |
| `tags` | Array of literal tag-name strings to apply to the property. |
| `nameMappings` | Per-destination name mappings. Array of tagged objects — `{ kind: "allDestinations", name: "..." }` or `{ kind: "destination", destinationId: "...", name: "..." }`. Also accepted on `update`-property items. |
| `eventConfigs` | *(optional)* Per-event/per-source property settings on a fresh property. On `create`, only `events: { kind: "allEvents" }` entries are accepted — per-event-scoped entries are rejected because the property isn't attached to any event yet. See "Per-event property settings (`eventConfigs`)" below. |

**Create-event_variant fields:**

| Field | Notes |
|---|---|
| `attachProperties` | Array of property IDs (or `$tmp:` refs) to attach to the variant. |
| `overrides` | Component-level overrides. Each entry: `{ propertyId, pinned }` or `{ propertyId, allowed }`. `pinned` and `allowed` are mutually exclusive per override. |
| `bundleOverrides` | Bundle IDs to attach to the variant as property bundles. |

**Create-property_bundle fields:**

| Field | Notes |
|---|---|
| `addProperties` | Property IDs (or `$tmp:` refs) to include in the bundle. |
| `attachToEvents` | Event IDs (or `$tmp:` refs) the new bundle should be attached to. |

**Create-metric fields:**

| Field | Notes |
|---|---|
| `metricType` | Required. One of `Funnel`, `EventSegmentation`, `Proportion`, `Retention`, `CustomEvent`, `Cohort`. Immutable after create — archive and recreate to change. |
| `items` | Required for non-cohort metrics. Array of metric items referencing events by `eventId` (or `$tmp:` ref), with optional `baseEventId` for event variants. |
| `cohortConditions` | Required for `Cohort` metrics. Array of conditions referencing events by `eventId` (or `$tmp:` ref) and optional `propertyId`. |

**Update-event fields:** _(require `eventId`)_

| Field | Notes |
|---|---|
| `set.name` | Rename the event. Renaming to the same name is a no-op; renaming to a name already held by another live event is rejected. |
| `set.description` | New event description. |
| `addProperties` / `removeProperties` | Property IDs (or `$tmp:` refs) to associate / disassociate. |
| `addPropertyBundles` / `removePropertyBundles` | Property bundle IDs (or `$tmp:` refs) to attach / detach. |
| `addSources` / `removeSources` | Source IDs to include / exclude. |
| `addTags` / `removeTags` | **Top-level on the item, not inside `set`.** Tag-name strings to apply or remove. |
| `addCategories` / `removeCategories` | **Top-level on the item.** Category **names** (or `$tmp:` refs to `create`-category items in the same batch) to add or remove. Note: categories are referenced by name, not ID. |

**Update-event_variant fields:** _(require `baseEventId` + `variantId`)_

| Field | Notes |
|---|---|
| `set.nameSuffix` | New variant suffix. |
| `set.description` | New variant description. |
| `set.triggers` | Replace the trigger list on the variant. |
| `attachProperties` / `removeProperties` | Property IDs to attach / detach from this variant. |
| `clearAttachedProperties` | Boolean — when `true`, detaches all properties from the variant in one call. |
| `addComponentOverrides` | Override specs to add or replace. Same shape as create `overrides`. |
| `removeComponentOverrides` | Property IDs whose component overrides should be cleared. |
| `addSourceOverrides` / `removeSourceOverrides` | Source-override specs to add or remove. |
| `clearSourceOverrides` | Boolean — when `true`, clears all source overrides on the variant. |
| `addBundleOverrides` / `removeBundleOverrides` | Bundle-override specs to add or remove. |
| `clearBundleOverrides` | Boolean — when `true`, clears all bundle overrides on the variant. |
| `addVariantPropertyRegex` / `removeVariantPropertyRegex` | Variant property regex specs to add or remove. |
| `clearVariantPropertyRegexOverride` | Boolean — when `true`, clears all variant property regex overrides. |

The variant override surface uses a three-state lattice — `add*` / `remove*` / `clear*` — for attached properties, source overrides, bundle overrides, and variant property regex overrides. Use `add*` / `remove*` for incremental changes; use `clear*` only when you need to wipe the whole set in one call.

**Update-property fields:** _(require `propertyId`)_

| Field | Notes |
|---|---|
| `set.name` | Rename the property. Renaming to the same name is a no-op; renaming to a name already held by another live property is rejected. |
| `set.description` | New property description. |
| `set.propertyType` | New type. Same value set as create: `string`, `int`, `long`, `float`, `bool`, `object`, `any` (aliases like `integer`, `boolean`, `double` accepted). |
| `addAllowedValues` | **Top-level on the item, not inside `set`.** String values to add to a string-typed property's allowed list. |
| `removeAllowedValues` | **Top-level on the item, not inside `set`.** Values not in the current list are silently no-ops. |
| `addTags` / `removeTags` | **Top-level on the item, not inside `set`.** Tag-name strings to apply or remove. |
| `addCategories` / `removeCategories` | **Top-level on the item.** Category names (or `$tmp:` refs) to add or remove. Categories are referenced by name, not ID. |
| `isList` | **Top-level on the item, not inside `set`.** Boolean — switches the property between scalar (`false`) and list (`true`). Property-only. |
| `eventConfigs` | **Top-level on the item.** Per-event/per-source property settings — change presence, pin a value, or restrict allowed values per event. See "Per-event property settings (`eventConfigs`)" below. |

**Update-property_bundle fields:** _(require `propertyBundleId`)_

| Field | Notes |
|---|---|
| `set.name` | Rename the bundle. |
| `set.description` | New bundle description. |
| `addProperties` / `removeProperties` | Property IDs (or `$tmp:` refs) to add / remove from the bundle. Bundle-to-event attachment is managed from the event side via `addPropertyBundles` / `removePropertyBundles` on event-update items. |

**Update-metric fields:** _(require `metricId`)_

| Field | Notes |
|---|---|
| `setName` | Rename the metric. |
| `setDescription` | New metric description. |
| `addItems` / `removeItems` | For non-cohort metrics, add or remove metric items. |
| `addCohortConditions` / `updateCohortConditions` / `removeCohortConditions` | For `Cohort` metrics, manage the cohort condition list. |
| `addCategories` / `removeCategories` | **Top-level on the item.** Category names (or `$tmp:` refs) to add or remove. Categories are referenced by name, not ID. |

**Update-category fields:** _(require `categoryId`)_

| Field | Notes |
|---|---|
| `set.name` | Rename the category. |
| `set.description` | New category description. |

**Source, destination, and group-type items:**

These workspace-metadata types also support `create` / `update` / `archive` / `unarchive`. On `create`, set the shared `name` field and supply either the type's ID or a `tempId`; on `update` / `archive` / `unarchive`, supply the type's ID (`sourceId` / `destinationId` / `groupTypeId`).

| Type | Fields |
|---|---|
| `source` | **Create:** `platform` (required — one of Avo's source platforms), `programmingLanguage`, `libraryName`, `libraryDestination`. **Update:** change these with `set.platform` / `set.programmingLanguage` / `set.libraryName` / `set.libraryDestination`. (A source's language can also be set via [`workflow`](#workflow) `action: "set_source_language"`.) |
| `destination` | **Create:** `analyticsTool` (the analytics platform), `includeUserPropsWithEventProps` (bool, default `false`), `disabledByDefault` (bool, default `false`). **Update:** set or remove an API key with `apiKey: { kind: "set", env: "dev" \| "prod", value: "..." }` or `{ kind: "remove", env: "dev" \| "prod" }`. |
| `groupType` | **Create:** `name` only (group types have no description). |

The tool's per-field schema (the `describe` text on each parameter) is the authoritative reference for the full set of accepted fields.

**Archive and unarchive (every archivable type).** `op: "archive"` archives an item on the branch; `op: "unarchive"` restores it. Both take the type's lookup-ID field (`eventId`, `propertyId`, `propertyBundleId`, `metricId`, `categoryId`, `sourceId`, `destinationId`, `groupTypeId`) — or, for event variants, `baseEventId` + `variantId`. All archives are reversible from the Avo app.

| Type | Archive example | Notes |
|---|---|---|
| `event` | `{ op: "archive", type: "event", eventId: "..." }` | `op: "remove"` is accepted as a legacy alias. |
| `property` | `{ op: "archive", type: "property", propertyId: "..." }` | **Cascades** — references on every event using the property are removed. `op: "remove"` is accepted as a legacy alias. If you only have the name, resolve the ID first with [`search`](#search) so name-conflict ambiguity surfaces before the mutation runs. |
| `event_variant` | `{ op: "archive", type: "event_variant", baseEventId: "...", variantId: "..." }` | `op: "remove"` is **not** supported for variants. |
| `property_bundle` | `{ op: "archive", type: "property_bundle", propertyBundleId: "..." }` | — |
| `metric` | `{ op: "archive", type: "metric", metricId: "..." }` | — |
| `category` | `{ op: "archive", type: "category", categoryId: "..." }` | Does **not** cascade — events/properties/metrics keep their `addCategories` history pointing at the now-archived category. Migrate members first (see "Merging categories"). |
| `source` / `destination` / `groupType` | `{ op: "archive", type: "source", sourceId: "..." }` (likewise `destinationId` / `groupTypeId`) | — |

`op: "remove"` on any type other than `event` / `property` is rejected with an error directing you to `op: "archive"`.

### Owner and stakeholder fields

> ⚠️ **Branch-independent.** Owner and stakeholder assignments take effect **immediately workspace-wide**, even if the branch is later discarded. Discarding the branch will **not** roll back these changes. Treat these fields as out-of-branch mutations, not draft edits.

`owner` and `stakeholders` fields are accepted on event, property, and event variant items, on both `create` and `update`. Both reference **existing** stakeholders — the MCP does not create stakeholders (create them in the Avo web app). A **stakeholder reference** is an object with exactly one of `stakeholderId` or `stakeholderName` (a name is resolved server-side to an ID).

| Field | Notes |
|---|---|
| `owner` | Tagged object selected by `action`. `{ "action": "set", "stakeholder": <ref> }` assigns the owning stakeholder; `{ "action": "clear" }` removes the owner assignment. Omitting `owner` leaves it unchanged. `clear` **demotes** the current owner to a non-owning stakeholder — it stays in the item's stakeholder set; to drop the relationship entirely, also pass that stakeholder under `stakeholders.remove`. |
| `stakeholders.add` | Array of stakeholder references to add to the item's stakeholder set. An empty array is an explicit no-op. A stakeholder named as `owner` need not be repeated here — the writer dedupes. |
| `stakeholders.remove` | Array of stakeholder references to remove from the set. An empty array is an explicit no-op. |

Omitting `stakeholders` (or `owner`) leaves that aspect unchanged.

```json
{
  "op": "update",
  "type": "event",
  "eventId": "evt-3f01…",
  "owner": { "action": "set", "stakeholder": { "stakeholderName": "Growth" } },
  "stakeholders": { "add": [{ "stakeholderName": "Data Platform" }] }
}
```

### Merging categories

The MCP has no dedicated "merge categories" op. To merge category `A` into category `B`, send a single `save_items` batch containing:

1. An `update` item for every event, property, and metric in `A` carrying `addCategories: ["B"], removeCategories: ["A"]`.
2. A `{ op: "archive", type: "category", categoryId: "<id of A>" }` item.

Both must travel in the same batch — the category archive in step 2 does **not** cascade to its members, so step 1 has to move the members first.

### Per-event property settings (`eventConfigs`)

`eventConfigs` is a top-level array on an `update`-property item (and, restricted to `events: { kind: "allEvents" }`, on a `create`-property item). Each entry adjusts how the property behaves on a specific event or across all events:

- **`setPresence`** — change presence to `alwaysSent`, `sometimesSent`, or `neverSent`. Can be scoped per source.
- **`setPinnedValue`** — pin a value for the property. With `events: { kind: "onEvent" }` pins per-event; with `events: { kind: "allEvents" }` pins property-wide.
- **`restrictAllowedValues`** — change the property's allowed value list for an event. Carries a `valuesChange` delta: `addValues` (non-empty), `removeValues`, or `clear` (no payload).

`eventConfigs` entries reference events by `eventId`, and `$tmp:` references **are** supported here: an `eventConfigs[*].events.eventId` (or `eventIds`) may point at a `create`-event item earlier in the same batch via `$tmp:`, and preprocessing rewrites it to the real event ID before saving — no separate call needed. (On a property `create`, the referenced event must also attach this property in the same batch, otherwise the call fails with `UnresolvableReference`.)

> 🚧 **Not yet implemented.** `set.sendAs` on `op: "update"` of a `property` item returns a `NotYetImplemented` error — `sendAs` is immutable after create.
>
> Everything else works today: create / update for events, properties, event variants, property bundles, metrics, categories, sources, destinations, and group types; `archive` / `unarchive` for every archivable type; rename events and properties via `set.name`; toggle a property between scalar and list via `isList`; apply or remove tags via `addTags` / `removeTags`; per-event property settings via `eventConfigs`. (To remove an event variant, use `op: "archive"` — `op: "remove"` on a variant is rejected.)

### Temporary IDs (`tempId` / `$tmp:`)

To reference a newly-created item from another item in the same call, declare a `tempId` on the `create` item and reference it elsewhere as `"$tmp:<name>"`:

- `tempId` is **create-only** — setting it on an `update`, `remove`, or `archive` returns an error.
- `tempId` names must be unique within a single `save_items` call.
- `$tmp:` references are resolved in these array fields: `properties`, `addProperties`, `removeProperties`, `attachProperties`, `propertyBundles`, `addPropertyBundles`, `removePropertyBundles`, `attachToEvents`, `addSources`, `removeSources`, `bundleOverrides`; inside `overrides[].propertyId` / `addComponentOverrides[].propertyId`; within `eventConfigs[*].events.eventId` / `eventIds`; and within nested metric fields (`items[].metricId` / `eventId` / `baseEventId`, `cohortConditions[].eventId` / `propertyId`). In `addCategories` / `removeCategories` / `addGroupTypes` / `removeGroupTypes`, a `$tmp:` ref instead rewrites to the sibling `create` item's **name** (those fields take names, not IDs). Sources, destinations, and group types can be created in the same batch and referenced by their `tempId` too.
- If a `$tmp:` ref names a tempId that wasn't declared on any item, the server returns a validation error.

### Returns

A structured result with:

- `createdEntities`, `updatedEntities`, `removedEntities`, `unarchivedEntities` — each entry has `name`, `entityId`, and `entityType` (`event`, `property`, `event_variant`, `property_bundle`, `metric`, `category`, `source`, `destination`, or `groupType`). Archived items are reported under `removedEntities` (a legacy field name); restored items under `unarchivedEntities`. (`updatedEntities` entries may also carry a `reason` when the update was a no-op.)
- `errors` — per-item validation or audit errors, each with the item index, name, and a message.
- `warnings` — non-fatal notices, same shape as errors.
- `success` — overall boolean.

```json
{
  "success": true,
  "createdEntities": [
    { "name": "Checkout Method", "entityId": "prop-9d44…", "entityType": "property" }
  ],
  "updatedEntities": [
    { "name": "Checkout Completed", "entityId": "evt-3f01…", "entityType": "event" }
  ],
  "removedEntities": [],
  "unarchivedEntities": [],
  "errors": [],
  "warnings": []
}
```

### Examples

#### Create a new event with a new property in one call

**Prompt:** *"Add a `Checkout Completed` event with a `Checkout Method` property for Web and iOS."*

Claude declares a `tempId` on the new property so the new event can attach it before the server has allocated a real ID. The server resolves the `$tmp:` reference, allocates the real `propertyId`, attaches the property to the event, and includes the event in both sources — all atomically.

```json
{
  "branchId": "br-abc123",
  "items": [
    {
      "op": "create",
      "type": "property",
      "tempId": "checkout_method",
      "name": "Checkout Method",
      "propertyType": "string",
      "sendAs": "event",
      "description": "How the user completed checkout"
    },
    {
      "op": "create",
      "type": "event",
      "name": "Checkout Completed",
      "description": "Fired when the user finishes checkout",
      "properties": ["$tmp:checkout_method"],
      "sources": ["src-web", "src-ios"]
    }
  ]
}
```

#### Define a checkout funnel metric

**Prompt:** *"Add a funnel metric on this branch that tracks the share of users who start checkout and complete it."*

Claude creates a `Funnel` metric whose `items` reference two existing events in order. The same call could chain in new events with `$tmp:` references if the funnel needed events that don't exist yet.

```json
{
  "branchId": "br-abc123",
  "items": [
    {
      "op": "create",
      "type": "metric",
      "name": "Checkout Funnel",
      "description": "Share of users who start checkout and complete it.",
      "metricType": "Funnel",
      "items": [
        { "eventId": "evt-checkout-started" },
        { "eventId": "evt-checkout-completed" }
      ]
    }
  ]
}
```

### Common errors

- Missing `write` scope — the client must re-authorize with `write`.
- `branchId is required` / `items is required` — malformed request.
- `tempId is only valid on create items` — don't set `tempId` on `update` or `remove`.
- `Duplicate tempId "<name>"` — each `tempId` must be unique across items in the batch.
- `Unknown $tmp: reference` — a `$tmp:` ref names a tempId that wasn't declared.
- `<idField> is required for <op> <entity> items` — missing `eventId` / `propertyId` / `baseEventId` / `variantId` / `propertyBundleId` / `metricId`.
- `too many items (got N, max 50)` — batch is over the 50-item cap.
- `NotYetImplemented` — one of the unsupported operations above.
- Per-item audit-pipeline validation failures (e.g. illegal name, duplicate property, etc.) — returned inside the `errors` array rather than failing the whole call.
## `workflow`

**Scope:** `write` · **Destructive:** `import` with `importMethod: "add_update_and_remove"`

> 🚧 Write access is in general beta — enabled for every workspace, no need to request access. [Email support@avo.app](mailto:support@avo.app) if you hit anything unexpected.

Branch-lifecycle write operations, selected by the `action` parameter. Five actions are supported:

- `create_branch` — open a new branch.
- `update_branch_description` — set the description on an existing open branch.
- `pull_main` — pull the latest changes from main into an open branch. **Requires Codegen access**; on overlapping changes, incoming main changes win.
- `set_source_language` — set a source's programming language on an open branch.
- `import` — bulk-import a tracking-plan export (CSV or Avo JSON Schema) into an open branch. **Requires Admin role**; never targets main.

> 💡 A branch is a draft workspace for tracking-plan changes, analogous to a git branch. All write operations via the MCP happen on a branch — `save_items` requires a `branchId` that exists. The MCP never merges to main; open the branch in the Avo app to review and merge.

> ⚠️ **Destructive import.** `import` with `importMethod: "add_update_and_remove"` removes properties from events when they are absent from the import payload. Only use it with a **complete** export — never a partial one. The change lands on a branch and is reviewable before merge, but a partial payload can strip large numbers of properties.

### Parameters

Which parameters apply depends on `action` — see "Required" below.

| Parameter | Required | Description |
|---|---|---|
| `action` | Yes | The workflow action. One of: `create_branch`, `update_branch_description`, `pull_main`, `set_source_language`, `import`. |
| `branchName` | For `create_branch`; alternative to `branchId` for `update_branch_description` / `set_source_language` | Name for the new branch (`create_branch`), or the existing branch's name. |
| `branchId` | Alternative to `branchName` for `update_branch_description` / `set_source_language`; required for `pull_main` and `import` | ID of the existing branch. For `update_branch_description` and `set_source_language`, provide exactly one of `branchId` or `branchName`. |
| `description` | For `update_branch_description` (optional on `create_branch`) | Branch description text. On `create_branch`, optionally sets the new branch's description. Empty / whitespace-only strings are treated as a no-op on `update_branch_description`. |
| `sourceId` | For `set_source_language` | The source whose language to set. Find it with [`get`](#get) (`type: "source"`) or [`search`](#search) (`itemType: "source"`). |
| `language` | For `set_source_language` | The source's programming language. One of: `Swift`, `JavaScript_V2`, `Reason_V2`, `Java`, `JSON`, `Python`, `Python3`, `PHP`, `Kotlin`, `C#`, `TypeScript`, `Objective-C`, `Ruby`, `Dart`, `Go`. The server validates the language against the source's platform and returns the supported list on a mismatch. |
| `format` | For `import` | Payload format. `csv` (a CSV export — auto-detects Avo, Amplitude, Mixpanel, Segment, and spreadsheet formats) or `json_schema` (an Avo JSON Schema document). |
| `payload` | For `import` | The raw CSV text or Avo JSON Schema document, as a string. |
| `importMethod` | Optional for `import` | `add_only` (default — only appends new items), `add_and_update` (also overwrites existing items with imported values), or `add_update_and_remove` (**destructive** — additionally removes properties from events when absent from the import; use only with a complete export). |
| `workspaceId` | No | Workspace ID |

### Returns

- `create_branch` — the new branch's `branchId`, `branchName`, and a `branchUrl` that opens it in the Avo web app.
- `update_branch_description` — confirmation with the resolved `branchId` and the updated description.
- `pull_main` — confirmation that main was pulled into the branch.
- `set_source_language` — confirmation with the resolved source and language.
- `import` — a summary of what the import added, updated, and removed on the branch.

### Examples

#### Create a branch for a new feature

**Prompt:** *"Start an Avo branch for the new checkout flow we're shipping next sprint."*

Claude calls `workflow` with `action: "create_branch"` and a descriptive `branchName`. The returned `branchId` is required for the follow-up `save_items` calls that write the new events and properties.

```json
{
  "action": "create_branch",
  "branchName": "add-checkout-tracking"
}
```

#### Update a branch's description

**Prompt:** *"Update the description on the `add-checkout-tracking` branch to mention that we're now also tracking abandonment."*

Claude looks up the branch by name (no separate `branchId` lookup needed for this action) and replaces the description in one call.

```json
{
  "action": "update_branch_description",
  "branchName": "add-checkout-tracking",
  "description": "Adds the Checkout Completed and Checkout Abandoned events with the Checkout Method property."
}
```

#### Set a source's codegen language

**Prompt:** *"Set the iOS source on the `add-checkout-tracking` branch to Swift."*

Claude resolves the `sourceId` with [`get`](#get) (`type: "source"`), then sets the language on the branch.

```json
{
  "action": "set_source_language",
  "branchName": "add-checkout-tracking",
  "sourceId": "src-ios",
  "language": "Swift"
}
```

#### Bulk-import a tracking plan onto a branch

**Prompt:** *"Import this Amplitude CSV export onto a fresh branch."*

Claude opens a branch with `create_branch`, then calls `import` with the CSV `payload` on the returned `branchId`. `add_only` (the default) only appends new items, so it never removes anything.

```json
{
  "action": "import",
  "branchId": "br-abc123",
  "format": "csv",
  "payload": "Event Name,Property Name,...\nCheckout Completed,checkout_method,...",
  "importMethod": "add_only"
}
```

### Common errors

- Missing `write` scope — re-authorize with `write`.
- Workspace access denied.
- Unsupported action value.
- Both `branchId` and `branchName` provided — `update_branch_description` and `set_source_language` require exactly one.
- `pull_main` without Codegen access, or `import` without Admin role — permission error.
- `set_source_language` with a language the source's platform doesn't support — the error lists the supported tokens.
- `import` missing `format` or `payload`, or with an unknown `format` / `importMethod` token.

---

## `give_feedback`

**Scope:** `write`

> 💡 This tool submits feedback to Avo's product team. It does **not** read or modify the tracking plan — nothing about your workspace's events, properties, or branches changes when you call it.

Report the agent's own experience with the Avo MCP directly to Avo's product team. Any agent on the MCP can call this proactively, in the moment — there is no human relaying the feedback. Each submission lands instantly as a triaged item in the queue Avo's product team already works from.

Call `give_feedback` whenever the MCP couldn't do what you set out to accomplish — a missing capability, a tool that behaved confusingly, or a goal you couldn't finish — as well as for any other observation worth passing along. Alongside the feedback message, include the **intent** behind the call: what you were actually trying to achieve. That "why" — the unmet goal behind a call — is the part ordinary telemetry can't see, and it's what helps Avo decide what to build next.

### Parameters

| Parameter | Required | Description |
|---|---|---|
| `feedback` | Yes | The feedback message — what the agent observed, what was missing, or what went wrong. |
| `intent` | No | Why the agent made the call: the goal it was trying to achieve (e.g. the task it couldn't finish). Supplies the "why" that telemetry alone can't capture. |
| `workspaceId` | No | Workspace ID. |

### Returns

A confirmation that the feedback was recorded and routed to Avo's product team's triage queue. No tracking-plan data is read or returned. The block below is illustrative — see the per-field schema on the tool for the authoritative shape.

```json
{
  "success": true,
  "message": "Thanks — your feedback was recorded and sent to Avo's product team."
}
```

### Examples

#### Report a capability the MCP is missing

**Prompt:** *"Merge my `add-checkout-events` branch for me."*

Claude finds the branch but no way to merge it through the MCP — branch merging is a deliberate human review-and-publish gate, not an MCP action. Rather than silently giving up, it calls `give_feedback` with the observation and the intent behind it, so Avo's product team sees the unmet goal.

```json
{
  "feedback": "There's no way to merge a tracking-plan branch through the MCP.",
  "intent": "The user asked me to merge their add-checkout-events branch, and I couldn't complete it."
}
```

#### Flag a confusing tool

**Prompt:** *"Why did that last change not show up on main?"*

After explaining that MCP writes land on a branch and need a human merge, Claude passes along that the behavior wasn't obvious from the tools alone.

```json
{
  "feedback": "It wasn't clear that save_items writes only land on a branch and never reach main without a human merge.",
  "intent": "I was trying to explain to the user why their change wasn't visible on main."
}
```

### Common errors

- Empty or missing `feedback` — the message is required.
- Missing `write` scope — the client must re-authorize with `write`.
- Workspace access denied.

---

## `list_branches`

**Scope:** `read`

> 🚧 **Transitional.** This tool stays available while branch enumeration is being folded into [`search`](#search) (as `itemType: "branch"`). Until that ships, use `list_branches` to enumerate branches.

Browse branches in a workspace with filtering and pagination. Results are paginated newest-first.

### Parameters

| Parameter | Required | Description |
|---|---|---|
| `workspaceId` | No | Workspace ID |
| `branchStatuses` | No | Filter by status. Valid values: `Draft`, `ReadyForReview`, `ChangesRequested`, `Approved`, `Merged`, `Closed`, `Open`. Defaults to open/active branches only. |
| `pageSize` | No | Results per page (1–50, default 25) |
| `pageToken` | No | Pagination token from a previous response |
| `branchName` | No | Substring match on branch name (case-insensitive) |
| `creatorEmail` | No | Filter by creator email |
| `creatorUserId` | No | Filter by creator user ID |
| `reviewerEmail` | No | Filter by reviewer email |
| `reviewerUserId` | No | Filter by reviewer user ID |
| `collaboratorEmail` | No | Filter by collaborator email |
| `collaboratorUserId` | No | Filter by collaborator user ID |
| `createdAfter` | No | ISO 8601 date — only branches created after |
| `createdBefore` | No | ISO 8601 date — only branches created before |
| `impactedSourceId` | No | Filter to branches affecting a specific source |

By default `Merged` and `Closed` branches are excluded. Pass `branchStatuses: ["Merged"]` (or any other value) to include them.

> 💡 To find "my branches," pass your own email as `creatorEmail` or `reviewerEmail`. The tool does not auto-inject your identity into the filter.

### Returns

Compact per-branch summary — name, status, ID, creation date, and (when present) creator email, reviewer count, and description — plus a `nextPageToken` when more results are available. Call [`get`](#get) with `type: "branch"` and `include: ["overview"]` for full resolved data.

### Examples

#### Find branches I'm reviewing

**Prompt:** *"What branches am I assigned to review?"*

Claude passes the user's email as `reviewerEmail` and filters status to `ReadyForReview`. The tool does not auto-inject the caller's identity, so the email has to be supplied explicitly.

```json
{
  "reviewerEmail": "thora@avo.sh",
  "branchStatuses": ["ReadyForReview"]
}
```

### Common errors

- Workspace access denied.
- Invalid `pageSize` (outside 1–50).
- Invalid date format on `createdAfter` / `createdBefore`.

---

## Troubleshooting

Tool-specific behavior issues. Authentication and workspace access issues are covered in [Troubleshooting on the overview page](https://www.avo.app/docs/reference/avo-mcp/overview.md#troubleshooting).

**[`search`](#search) returns nothing for a clearly relevant query.** Semantic search requires Avo Intelligence Smart Search to be enabled. Workspace admins can turn it on in [Workspace Settings](https://www.avo.app/schemas/default?settings=general). Without it, fall back to [`get`](#get) with an exact name or [`search`](#search) in filter mode.

**The wrong branch is returned by name.** `branchName` resolves to a best match and prioritizes open branches, so an ambiguous name can pick the wrong one. Resolve the name to a `branchId` with [`list_branches`](#list_branches) first and pass `branchId` to the follow-up call.

**[`save_items`](#save_items) returns a `NotYetImplemented` error.** Changing a property's `sendAs` is not supported — it is immutable after create. (To remove an event variant, use `op: "archive"`; `op: "remove"` on a variant is rejected, not `NotYetImplemented`.) See the [`save_items` reference](#save_items).
