Avo MCP tools reference
Every tool except list_workspaces and describe_tool 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 seven canonical tools mapped to agent intents:
| Intent | Tool | Scope |
|---|---|---|
| Entry point — find your workspace IDs | list_workspaces | read |
| Look up the fields — see exactly which parameters and fields a call accepts before making it | describe_tool | none |
| Discover — find items by meaning or by structural filter | search | read |
| Understand — full details for an event, property, journey, branch, source, etc. | get | read |
| Change — create, update, archive, or restore items on a branch | save_items | write |
| Progress — create a branch, update its description, pull main, set a source’s language, or bulk-import a plan | workflow | write |
| Tell Avo — report the agent’s own experience back to Avo’s product team | give_feedback | write |
Branch read flows are covered by get and search. One transitional tool — list_branches — remains available while branch enumeration is folded into search (as itemType: "branch").
How an agent finds out what to send
When an MCP client connects, it downloads a definition of every tool. Those definitions are kept short on purpose: several clients silently hide a tool whose definition is too long, and save_items accepts hundreds of fields across nine item types. So the save_items definition only describes the shape of an item, and the full list of fields for each item type is fetched when needed with describe_tool. The flow an agent follows:
- Connect. The short instructions the server sends on connect list every tool, its item types and actions, and what to call next.
describe_tool()— the same overview plus a short “Designing tracking” guide.describe_tool(tool:"save_items", type:"<type>", op:"<op>")— the exact fields for the item type and operation it is about to write.- Write with
save_items.
If a save_items item carries an unknown field or an invalid value, the error message repeats the field list for that item type, so an agent can correct itself without another describe_tool call.
Item type vocabulary
save_items, search, and get share one snake_case entity vocabulary:
| Type | search | get | save_items |
|---|---|---|---|
event | ✅ | ✅ | ✅ |
property | ✅ | ✅ | ✅ |
metric | ✅ | ✅ | ✅ |
category | ✅ | ✅ | ✅ |
event_variant | ✅ | ✅ | ✅ |
property_bundle | ✅ | ✅ | ✅ |
source | ✅ | ✅ | ✅ |
destination | ✅ | ✅ | ✅ |
group_type | ✅ | ✅ | ✅ |
journey | ✅ | ✅ (by id) | ❌ read-only |
workspace_config | ❌ | ✅ (takes no id) | ❌ |
branch | ❌ | ✅ | ❌ |
Deprecated spellings. The camelCase spellings eventVariant, propertyBundle, groupType, and workspaceConfig still work for now but are deprecated and will be removed. Use the snake_case spellings in anything you write today.
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.
describe_tool
Scope: none — read-only, rendered locally by the MCP server. No workspace data, no branch, no authentication, no side effects.
Tells you what a call accepts before you make it: an overview of every tool, what a save_items item looks like, the fields each item type accepts for a given operation, the parameters of each workflow action, and the item types search and get understand.
Parameters
All parameters are optional strings. There are no nested objects or unions.
| Parameter | Values | Description |
|---|---|---|
tool | list_workspaces, describe_tool, search, get, save_items, workflow, give_feedback, list_branches | Which tool to describe. Omit for an overview of every tool. |
type | An item type (see Item type vocabulary) | For save_items: list the fields that item type accepts. |
action | create_branch, update_branch_description, pull_main, set_source_language, import | For workflow: render that action’s parameters. |
op | create, update, archive, unarchive | For save_items: narrow the fields table to one operation. |
format | full (default), concise | concise returns the fields table only (no placement note, example, or limits line). |
Returns
Plain text, shaped by what you asked for:
describe_tool()— the tool overview (the same text the server sends on connect) followed by a “Designing tracking” guide and the link to the avo-mcp plugin. The guide only appears in this response, not in the instructions sent on connect.describe_tool(tool:"save_items")— an explanation of the item shape and a pointer to call again with atype.describe_tool(tool:"save_items", type:"property")— the fields table for that type: one line per field with its name, JSON type, whether it is required, which ops accept it, and a one-line doc. Fields with a closed value set list the accepted values (One of: …); thesetobject lists its accepted nested keys. Addop:"update"to narrow to one operation.describe_tool(tool:"workflow")— the list of actions. Addaction:"<action>"for that action’s parameters and an example call.describe_tool(tool:"search")/describe_tool(tool:"get")— each read tool’s item-type vocabulary, and forget(type:"branch")the acceptedincludevalues.- Unknown
typeoraction— an error that lists the valid values, so an agent can self-correct from the message alone.
Examples
Learn what a property update accepts before writing one
Prompt: “Rename the product_id property and add two allowed values.”
Before its first property write of the session, Claude fetches the list of fields a property update accepts and reads that renames go in set.name while allowed-value changes go in the top-level addAllowedValues.
{
"tool": "save_items",
"type": "property",
"op": "update"
}The response (abridged):
Fields for property (op: update):
- propertyId (string, required; update/archive/unarchive) — The property's id. Required for update/archive/unarchive.
- addAllowedValues (array; create/update) — Create/update (string property): allowed values to add.
- removeAllowedValues (array; update) — Update only: allowed values to remove …
- isList (boolean; update) — Update: toggle whether the property holds a list of values.
- addCategories (array; update) — Update: category names to attach …
- set (object; update) — Update only: scalar field changes … Nested keys (set.<key>): nameSuffix, description, propertyType, sendAs, name, platform, programmingLanguage, libraryName, libraryDestination, analyticsTool, triggers.
Placement: scalar renames go in set.*; collection deltas at the item top level.
Limits: up to 50 items per call; $tmp: refs resolve within one call.Get an overview of every tool
Prompt: “What can the Avo MCP do?”
Claude calls describe_tool with no parameters and summarizes the tool overview for the user.
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. An empty call (no query, no filter, no branch) is rejected too. branch and pageToken are filter-mode only and likewise cannot be combined with query. For ID-based lookups use get.
- Semantic search — pass
queryto 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"matchesAccount CreatedorRegistration Completedeven when no keyword overlaps. - Structured listing — omit
queryand pass filters to enumerate exact matches with keyset pagination. The same mode lists a branch’s journeys withitemType: "journey"— see Listing journeys.
Semantic search requires Avo Intelligence Smart Search to be enabled in your workspace. Workspace admins can enable it in Workspace Settings. 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 | The type of item to return. Defaults to event. What each type supports is in the table below. |
maxResults | No | Semantic mode: 1–20, default 10. Filter mode: 1–500, default 10. journey listings: 1–100, default 25. |
workspaceId | No | Workspace ID. Repeat it on every page when paginating. |
What each itemType supports
itemType | Semantic (query) | Filter mode | Content filters (tags, eventNames, …) | Scoped by |
|---|---|---|---|---|
event (default) | ✅ | ✅ | ✅ | branch, pageToken |
property | ✅ | ✅ | ✅ | branch, pageToken |
metric | ✅ | ✅ | ✅ (not sources) | branch, pageToken |
category | ✅ | ✅ | ✅ | branch, pageToken |
property_bundle | ✅ | ✅ | ✅ | branch, pageToken |
event_variant | ✅ | ✅ | ✅ | branch, pageToken |
source | ❌ | ✅ lists the workspace’s sources | ❌ | branch |
destination | ❌ | ✅ lists the workspace’s destinations | ❌ | branch |
group_type | ❌ | ✅ lists the workspace’s group types | ❌ | branch |
journey | ❌ | ✅ lists the branch’s journeys — see Listing journeys | ❌ | branch, pageToken |
The camelCase spellings (propertyBundle, eventVariant, groupType) still work but are deprecated. Passing query or a content filter with one of the list-only types is rejected.
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 with type: "workspace_config". |
pii | No | Filter by PII type. Resolve valid types from get with type: "workspace_config". |
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 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 useRank | 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 parameters — workspaceId, itemType, every filter, branch, and maxResults — plus the supplied pageToken. The token alone is not enough: omitting maxResults reverts the page size to the default, and omitting workspaceId can resolve the next page against a different workspace. Any filters that were ignored or coerced are listed under a Filter warnings section.
# 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. |Listing journeys
itemType: "journey" returns one entry per journey on the branch (main by default; pass branch to list another) instead of the ranked table above. Each entry carries the journey’s ID — the handle for get with type: "journey" — plus its name, description, screen count, and the screen(s) it starts at. Journey names collide and are sometimes blank, so a blank name falls back to the ID. Content filters (tags, eventNames, sources, …) are rejected on this type; only branch and pageToken scope it.
# Journeys (2)
## Checkout (4 screens)
_From cart to order confirmation_
id: jrn-7d3a…
starts at "Cart"
## Onboarding (3 screens)
id: jrn-1e9c…
starts at "Welcome"
_Walk one with `get(type: "journey", id: "<id>")`._
_More journeys available — call search again with itemType: "journey" and pageToken: "…" for the next page._A branch with no journeys returns No journeys found on this branch. If a paged call comes back empty because the branch changed between pages, the response says so and tells the agent to restart without pageToken.
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.
{
"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.
{
"itemType": "event",
"properties": ["product_id"],
"sources": ["iOS"],
"maxResults": 50
}List the journeys on a branch (filter)
Prompt: “Which journeys are defined on the checkout-v2 branch?”
Claude resolves the branch name to an ID with list_branches, then lists the journeys. Omit branch to list main. The response names each journey’s ID, which is what the follow-up get call needs.
{
"itemType": "journey",
"branch": "brc-2f8e…"
}Common errors
queryand structural filters combined — rejected with an error (no HTTP status). Choose one mode.querycombined withbranchorpageToken— rejected; those are filter-mode-only parameters.itemType: "journey"(orsource/destination/group_type) combined withqueryor with content filters such astagsoreventNames— rejected; these types enumerate a whole branch or workspace and take onlybranch(andpageToken).branchpassed as a name instead of an ID on a journey listing — the branch is not found; resolve the name withlist_branchesfirst.- Empty call (no
query, no filter, nobranch) — rejected. - 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 five type families:
- Tracking-plan items —
event,property,metric,category,property_bundle,event_variant. Look up byidor exactname— exceptevent_variant, which is identified by the base event’sidplusvariantId(never by name). For events,includePropertyDetails: truereturns each property’s type, constraints, and allowed values inline, and any triggers on the event are returned with full context — see Trigger context on events. 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,group_type.getreturns a single item, so passidorname. To enumerate the workspace list, usesearch(itemType: "source"/"destination"/"group_type"). - Workspace config —
workspace_config(takes noid). 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 intosearch’scustomFieldandpiifilters. - Journeys —
journey. Look up byidonly — journey names collide and are often blank, so there is nonamelookup; find the ID withsearch(itemType: "journey"). The response walks the journey screen by screen — each screen’s triggers, the events they fire, the property conditions that gate them, and where each trigger leads — see Journey graph. - Branches —
branch. Identify withbranchIdorbranchName. Useincludeto 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; requiressourceId),"implementation_guide"(numbered implementation steps + per-event codegen instructions). Multiple values union, andevent_changes+property_changesequalsall_changes.includedefaults 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, property_bundle, source, destination, group_type, event_variant, journey, branch, workspace_config. camelCase aliases are accepted but deprecated. |
id | Varies by type | The item’s unique ID. Required for event, property, metric, category, property_bundle unless name is provided. For source/destination/group_type, provide id or name — get is single-item only, so enumerate with search instead. event_variant 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). journey requires id — there is no name lookup. Not used by workspace_config. |
name | Varies by type | Exact name match. Alternative to id for most types (not accepted for journey). May return multiple matches for ambiguous names (especially properties) — use search for fuzzy lookup. |
variantId | For event_variant | 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 (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" \| "property_bundle" \| "event_variant"— the item’s full definition. Event results include a Triggers section with full trigger context — screen, connected event, gating conditions, screenshot — when the event has triggers; see Trigger context on events. 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 ofsave_items’sowner/stakeholdersfields. The section is omitted when the item has no stakeholders.type: "source" \| "destination" \| "group_type"— a single entity;idornameis required (enumerate the workspace list withsearch).type: "branch"— branch facets requested viainclude.overviewreturns resolved emails for the creator, reviewers, and collaborators, branch status, impacted source IDs, comments/approvals stats, and description.all_changesreturns the full structured diff vs. main (new, modified, and deleted events and properties with their descriptions);event_changesandproperty_changesreturn just the event- or property-side of that diff.code_snippetsreturns per-event code diffs for the source named insourceId— exact unified diffs for Avo Codegen sources and illustrative pseudocode for manually-instrumented sources.implementation_guidereturns numbered implementation steps plus per-event codegen instructions (scoped tosourceIdwhen provided).type: "workspace_config"— 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.type: "journey"— the journey rendered as a walkable graph: a header (name, description, entry screens), then one section per screen with its screenshot URL and each trigger leaving it — the trigger’s name and description, its marker position, the event(s) it fires (with IDs), the property conditions per event, and the screen it leads to. See Journey graph.
Trigger context on events
When an event has triggers, get with type: "event" returns each trigger with its full context, so an agent can see when the event fires — not just that a trigger exists. There are two trigger shapes:
- Journey triggers — triggers connected to a journey. Each renders with the trigger’s name and description, the screen it fires on, the screenshot URL, the marker position on that screenshot (a dot or an area, when the trigger has one), the connected event the trigger sends, and the gating property conditions —
is/is notconditions with their values andis set/is not setpresence conditions, with nested property paths shown in full (e.g.product.category is "shoes"). This is what lets an agent read a trigger as “on the Checkout screen, when the user taps Pay, sendPayment Startedwhenpayment_methodis"card"” — instead of guessing the firing moment from the event name. - Standalone triggers — “Triggered when” triggers not connected to a journey (including legacy ones). Each renders with its name/description, screenshot URL (when present), and the sources it applies to. There is no connected event or condition to resolve.
Fields that aren’t present are omitted. An event with no triggers has no Triggers section. Trigger context is returned on the main branch and on branch lookups (branchId / branchName) alike, and event-variant lookups render their triggers the same way.
To read the whole journey a trigger belongs to — every screen and what fires on each — list the branch’s journeys with search (itemType: "journey") and walk one with get (type: "journey"); see Journey graph.
Journey graph
get with type: "journey" and the journey’s id returns the journey as a walkable graph rather than a flat list. It starts at the journey’s entry screen(s) and follows each trigger’s connection to the next screen, so an agent reads the flow the way a user moves through it: on this screen, this action fires this event under these conditions, and leads here.
# Journey: Checkout
_From cart to order confirmation_
starts at "Cart"
## Cart
screenshot: https://…/cart.png
Tapped Checkout
User taps the Checkout button on the cart screen
marker: { x: 0.5, y: 0.9 }
- Event fired: "Checkout Started" (id: evt-4b2e…)
- Property conditions:
- cart_value is set
- Leads to: Payment
## Payment
screenshot: https://…/payment.png
Tapped Pay
- Event fired: "Payment Started" (id: evt-9a01…)
- Property conditions:
- payment_method is "card"
- Leads to: Confirmation
Tapped Back
- ↩ back to "Cart" (loop)
## Confirmation
screenshot: https://…/confirmation.png
Order confirmed
- Event fired: "Order Completed" (id: evt-77c3…)How to read it:
- Every screen appears exactly once. A screen reached from two places (a merge) is rendered at its first visit; later triggers just say
Leads to:it. A connection back to a screen already on the current path is marked↩ back to "<screen>" (loop)and is not followed, so the walk always ends. Screens unreachable from an entry are rendered at the end, so nothing is dropped. - IDs are the handle, names are hints. Journeys and screens are labelled by name when one is set and by ID otherwise. Each
Event firedline carries the event’s ID so the nextget(type: "event") needs no name lookup; an archived event is suffixed(archived). - Conditions show the full property path (
product.category is "shoes", never justcategory), withis/is notvalues andis set/is not setpresence checks — the same shape as Trigger context on events. - Blank fields are omitted, never rendered empty — no screenshot line without a URL, no empty description. A journey that exists but has no screens yet renders its header followed by
_This journey has no steps yet._, which is distinct from an unknown ID (an error).
Journeys are read on the main branch by default; pass branchId or branchName to read one on a branch. Journeys are read-only through the MCP — save_items has no journey type; create and edit journeys in the journey builder.
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.
{
"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.
{
"type": "branch",
"branchName": "checkout-v2",
"include": ["all_changes", "code_snippets"],
"sourceId": "src-ios"
}Walk a journey
Prompt: “Walk me through the checkout journey — which events fire on each screen?”
Claude first lists the branch’s journeys with search (itemType: "journey") to find the journey’s ID, then calls get with it. Journeys are addressed by ID only. A journey added on a branch exists only on that branch, so pass the branch the listing used as branchId (or branchName); omit it when the listing was on main.
{
"type": "journey",
"id": "jrn-7d3a…",
"branchId": "brc-2f8e…"
}Common errors
- Item not found.
- Ambiguous name (returns multiple matches — narrow by ID).
sourceIdmissing whenincludecontainscode_snippets.type: "journey"withoutid— rejected; journeys are looked up by ID only (find it withsearch,itemType: "journey").- Journey ID not found on the branch — check the ID and the
branchId/branchNameyou passed. - Workspace access denied.
save_items
Scope: write · Destructive: archive
Write access is in general beta — enabled for every workspace, no need to request access. Email 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.
Before your first write of a type in a session, call describe_tool with tool:"save_items", the type, and the op. The save_items tool definition only describes the shape of an item; the fields each item type accepts come from the describe_tool response, and the tables below are a snapshot of it.
Quick reference. Common patterns:
- Create an event with new properties — pair a
createevent andcreateproperty in one batch, usingtempIdto cross-reference. - Add allowed values to a property —
updateproperty withfields.addAllowedValues. - Rename an event or property —
updatewithfields.set.name. - Archive an event —
op: "archive"with the event’sid. - Create a funnel metric —
createmetric withfields.metricType: "Funnel"andfields.items. - Toggle a property between scalar and list —
updateproperty withfields.isList: true/false.
Parameters
Top-level
| Parameter | Required | Description |
|---|---|---|
branchId | Yes | The branch to write to. Get it from workflow (action: "create_branch"). Writes on main are rejected. |
items | Yes | Array of items to apply (see Item shape). |
onReviewedBranch | No | Acknowledgement for writing to a branch that is already in review. Omitted → the write is rejected so approvals aren’t silently invalidated. { kind: "revertToDraft" } reverts the branch to Draft and applies the edits; { kind: "reject" } is the default. |
workspaceId | No | Workspace ID |
The request is capped at 50 items per call.
Item shape
Every item has the same six keys. The type-specific content goes inside fields:
{ "op": "create", "type": "event", "id": "…", "name": "…", "tempId": "…", "fields": { } }| Key | Type | Notes |
|---|---|---|
op | "create" | "update" | "archive" | "unarchive" | Defaults to "create". Every op applies to every type. "remove" is no longer listed; for events and properties it still behaves as archive, on every other type it is rejected. |
type | string | Required. One of event, property, event_variant, property_bundle, metric, category, source, destination, group_type. The camelCase spellings (eventVariant, propertyBundle, groupType) still work but are deprecated. |
id | string | The identity ID for update / archive / unarchive on single-ID types — see the table below. You may pass it here or as the type’s ID field inside fields; passing both with different values is rejected. event_variant has a compound identity and passes baseEventId + variantId inside fields instead. |
name | string | Required on every create. Cosmetic on other ops. |
tempId | string | Create only. A temporary handle (letters, digits, _, -; max 64 characters) for cross-referencing inside the same call. Reference it elsewhere as "$tmp:<tempId>". |
fields | object | The fields for this item type and operation — exactly what describe_tool lists. Omit or pass null when the operation needs nothing beyond the ID. |
Identity ID per type (for update / archive / unarchive):
| Type | ID field |
|---|---|
event | eventId |
property | propertyId |
metric | metricId |
category | categoryId |
property_bundle | propertyBundleId |
source | sourceId |
destination | destinationId |
group_type | groupTypeId |
event_variant | baseEventId + variantId (both in fields, on every op) |
Inside fields, three placement rules apply to every type:
- Scalar edits go in
set(update only).setis a nested object with a closed key set:name,description,nameSuffix,propertyType,sendAs,platform,programmingLanguage,libraryName,libraryDestination,analyticsTool,triggers. Not every key applies to every type; unknown keys are rejected.set.sendAsis accepted only on create — a property’ssendAsis immutable after creation. - Collection changes stay at the top level of
fields—addProperties/removeProperties,addCategories/removeCategories,addAllowedValues/removeAllowedValues, and every otheradd*/remove*/clear*field. descriptionis create-only, and only for types whose create reads it:event,property,event_variant,property_bundle,metric,category. Acreatefor asource,destination, orgroup_typethat carriesdescriptionis rejected. On update, useset.description.
Validation is strict. An unknown key in fields, a field the current operation doesn’t accept (set on a create, description or tempId on an update, removeAllowedValues on a create), or an invalid value is rejected before anything is written. The error names the item type and operation and repeats the list of fields that type accepts.
Fields by item type
The tables below match the describe_tool(tool:"save_items", type:"<type>") output at the time of writing for the nine item types. The live response is authoritative. Fields marked create/update are accepted on both ops; the identity ID field is required on update / archive / unarchive. tempId (create only) and set (update only) apply to every type and are omitted from the tables.
event
| Field | Ops | Notes |
|---|---|---|
eventId | update, archive, unarchive | The event’s ID (or $tmp: of a same-batch create). |
description | create | Event description. |
sources | create | Source IDs to include the event in. ID or $tmp: only. Find source IDs with search (itemType: "source"). |
properties | create | Property IDs to attach. ID or $tmp: only. |
propertyBundles | create | Property bundle IDs to attach. ID or $tmp: only. |
actions | create | Initial action types (identify, page, revenue, …). Omit for logEvent only. |
nameComponents | create | Advanced-naming workspaces: per-building-block name values. |
tags | create | Tag names to attach (literal strings). |
addProperties / removeProperties | update | Property IDs (or $tmp:) to add / remove. |
addSources / removeSources | update | Source IDs to include / exclude. |
setSourceCodegen | update | Set the per-source include-in-codegen flag. |
addSourceDestinations / removeSourceDestinations | update | Link / unlink (source, destination) pairs on the event. |
setActions | update | Replace the event’s action types. |
customFieldValues | update | Set or clear per-custom-field values. |
addCategories / removeCategories | update | Category names (or $tmp: to a same-batch category). The event must already exist — attaching a category to an event created in the same batch is rejected; do it in a follow-up call. |
addTags / removeTags | update | Tag names to attach / detach. |
addGroupTypes / removeGroupTypes | update | Group type names (name or $tmp:, not raw IDs). |
addPropertyBundles / removePropertyBundles | update | Property bundle IDs to attach / detach. |
nameMappings | create/update | Per-destination name mapping entries — see Name mappings. |
owner, stakeholders | create/update | See Owner and stakeholder fields. |
set.name, set.description | update | Rename (renaming to the same name is a no-op; to a name already held by another live event is rejected) / new description. |
property
| Field | Ops | Notes |
|---|---|---|
propertyId | update, archive, unarchive | The property’s ID. |
description | create | Property description. |
propertyType | create | string, int, long, float, bool, object, any (aliases like integer, boolean, double accepted). Change later with set.propertyType. |
sendAs | create | event, user, or system. Immutable after create. |
nestedProperties | create | Object property: child-property slots { propertyId, … }. |
tags | create | Tag names to attach. |
addAllowedValues | create/update | String property: allowed values to add. |
removeAllowedValues | update | Allowed values to remove. Rejected on a create. Values not in the current list are silent no-ops. |
eventConfigs | create/update | Per-event property settings — see Per-event property settings. Max 50 entries. |
customFieldValues | create/update | Set or clear per-custom-field values. |
pii | create/update | The property’s PII state: { kind: "declared" | "notPii" | "unset" }. |
nameMappings | create/update | See Name mappings. |
isList | update | Boolean — toggle between scalar (false) and list (true). |
addNestedProperties / removeNestedProperties | update | Object property: child-property slots to add / child-property IDs to detach (live IDs only, no $tmp:). |
addPropertyRegex / removePropertyRegex | update | Set the global regex rule { regex, testValue } / pass true to remove it. |
addEventRegexOverride / removeEventRegexOverride | update | Set an event-specific regex override { eventId, regex, testValue } / the event ID whose override to remove. |
addCategories / removeCategories | update | Category names (or $tmp:). The property must already exist. |
addTags / removeTags | update | Tag names to attach / detach. |
owner, stakeholders | create/update | See Owner and stakeholder fields. |
set.name, set.description, set.propertyType | update | Rename / new description / new type. |
event_variant
| Field | Ops | Notes |
|---|---|---|
baseEventId | all | The parent event’s ID (or $tmp:). Required on every op. |
variantId | all | The variant’s ID — you supply it on create (must not contain .). Required on every op. |
description | create | Variant description. |
nameSuffix | create | Suffix appended to the parent event name (e.g. buy_now produces click / buy_now). Change later with set.nameSuffix. |
attachProperties | create/update | Property IDs (or $tmp:) to attach to this variant. |
overrides | create | Component-level overrides { propertyId, pinned } or { propertyId, allowed } (mutually exclusive per override). |
bundleOverrides | create | Bundle IDs to attach to this variant. |
addComponentOverrides / removeComponentOverrides | update | Component override specs to add or replace / property IDs whose overrides to clear. |
removeProperties | update | Property IDs to set as explicit not-on-variant overrides. |
clearAttachedProperties | update | Property IDs whose attachment override is cleared (inherit from base). |
addSourceOverrides / removeSourceOverrides / clearSourceOverrides | update | Source-level overrides to force-add / force-remove / reset to inherit. |
addBundleOverrides / removeBundleOverrides / clearBundleOverrides | update | Bundle IDs to force-add / force-remove / reset to inherit. |
addVariantPropertyRegex | update | Set a variant regex override { propertyId, regex, testValue }. |
removeVariantPropertyRegex | update | Property ID set to explicit no-regex on the variant. |
clearVariantPropertyRegexOverride | update | Property ID whose variant regex override is cleared (inherit). |
owner, stakeholders | create/update | See Owner and stakeholder fields. |
set.nameSuffix, set.description, set.triggers | update | New suffix / description / replace the trigger list. |
The override surface is a three-state lattice — add* / remove* / clear* — for attached properties, source overrides, bundle overrides, and regex overrides. Use add* / remove* for explicit overrides; use clear* to fall back to the base event.
property_bundle
| Field | Ops | Notes |
|---|---|---|
propertyBundleId | update, archive, unarchive | The bundle’s ID. |
description | create | Bundle description. |
addProperties | create/update | Property IDs (or $tmp:) to add to the bundle. |
attachToEvents | create | Event IDs (or $tmp:) to attach the new bundle to. |
removeProperties | update | Property IDs to remove from the bundle. |
set.name, set.description | update | Rename / new description. |
Bundle-to-event attachment after creation is managed from the event side via addPropertyBundles / removePropertyBundles on an event update.
metric
| Field | Ops | Notes |
|---|---|---|
metricId | update, archive, unarchive | The metric’s ID. |
description | create | Metric description. |
metricType | create | One of Funnel, EventSegmentation, Proportion, Retention, CustomEvent, Cohort. Immutable after create — archive and recreate to change. |
items | create | Non-cohort metrics: array of metric items. Each is { kind: "Event", id, eventId, where?, groupBy? }, { kind: "EventVariant", id, baseEventId, variantId, where?, groupBy? }, or { kind: "Metric", id, metricId }. id is a caller-supplied local key used to address the item in later updates; eventId / baseEventId / metricId accept $tmp:. where entries are { propertyId, operator, values } with a non-empty values. |
cohortConditions | create | Cohort metrics: array of conditions — { kind: "Action", id?, eventId, performed, frequency, timeWindow } or { kind: "Variable", id?, propertyId, binOp, literals }. |
setName / setDescription | update | Rename / new description (metrics use these top-level fields). |
addItems / updateItems / removeItems | update | Non-cohort metrics: add items / replace an item’s where and groupBy by id / remove item IDs. |
addCohortConditions / updateCohortConditions / removeCohortConditions | update | Cohort metrics: manage the condition list. |
addCategories / removeCategories | update | Category names to attach / detach. |
category
| Field | Ops | Notes |
|---|---|---|
categoryId | update, archive, unarchive | The category’s ID. |
description | create | Category description. |
set.name, set.description | update | Rename / new description. |
Category membership is managed from the member side: addCategories / removeCategories on event, property, and metric updates.
source
| Field | Ops | Notes |
|---|---|---|
sourceId | all | Required on update / archive / unarchive. On create, supply sourceId or a tempId. |
platform | create (required) | The development platform this source runs on. Change later with set.platform. |
programmingLanguage | create | The source’s programming language. Change later with set.programmingLanguage (or workflow set_source_language). |
libraryName / libraryDestination | create | Codegen library name / output path. Change later via set.*. |
connectDestinations | update | Destination IDs (or $tmp:) to connect for codegen routing on the source. |
disconnectDestinations | update | Destination IDs to disconnect. Destructive — cascades to every event routing it on this source (and inheriting variants). |
set.name, set.platform, set.programmingLanguage, set.libraryName, set.libraryDestination | update | Scalar edits. |
A create for a source does not accept description.
destination
| Field | Ops | Notes |
|---|---|---|
destinationId | all | Required on update / archive / unarchive. On create, supply destinationId or a tempId. |
analyticsTool | create (required) | The analytics platform this destination connects to. Change later with set.analyticsTool. |
includeUserPropsWithEventProps | create/update | Boolean. Defaults to false on create. |
disabledByDefault | create/update | Boolean — new events are disabled for this destination by default. Defaults to false on create. |
apiKey | update | Set or remove an API key for an environment: { kind: "set", env: "dev" | "prod", value: "…" } or { kind: "remove", env: "dev" | "prod" }. |
set.name, set.analyticsTool | update | Scalar edits. |
A create for a destination does not accept description.
group_type
| Field | Ops | Notes |
|---|---|---|
groupTypeId | all | Required on update / archive / unarchive. On create, supply groupTypeId or a tempId. |
set.name | update | Rename. |
A create for a group type takes name only and does not accept description. Attach group types to events with addGroupTypes on an event update.
Name mappings
nameMappings is accepted on event and property items, on create and update. Each entry is { destination, name }, where destination is { kind: "allDestinations" } or { kind: "destination", destinationId: "…" }. name must be a non-empty string when present; pass name: null (or omit it) to remove the mapping for that destination. On a create there is nothing to remove yet, so a name: null entry is a harmless no-op. Multiple entries are allowed, one per destination scope.
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 are accepted inside fields 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.
{
"op": "update",
"type": "event",
"id": "evt-3f01…",
"fields": {
"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:
- An
updateitem for every event, property, and metric inAcarryingfields: { addCategories: ["B"], removeCategories: ["A"] }. - A
{ op: "archive", type: "category", id: "<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 an array inside fields on a property item (create or update; max 50 entries). Each entry adjusts how the property behaves on a specific event, on several events, or across all events:
setPresence— change presence toalwaysSent,sometimesSent, orneverSent. Can be scoped per source.sometimesSentonallEventsrequires a migrated workspace.setPinnedValue— pin a value for the property. Withevents: { kind: "onEvent" }pins per-event; withevents: { kind: "allEvents" }pins property-wide.restrictAllowedValues— change the property’s allowed value list for an event. Carries avaluesChangedelta:addValues(non-empty),removeValues, orclear(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. allEvents entries apply to the freshly created property directly.
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>":
tempIdis create-only — setting it on anupdate,archive, orunarchivereturns an error.tempIdnames must be unique within a singlesave_itemscall and match^[\w-]+$(max 64 characters).$tmp:references resolve within one call only. Across calls, use the real ID returned from the previous call.$tmp:references are resolved in thesefieldsarrays:properties,addProperties,removeProperties,attachProperties,propertyBundles,addPropertyBundles,removePropertyBundles,attachToEvents,addSources,removeSources,bundleOverrides,connectDestinations,disconnectDestinations; insideoverrides[].propertyId/addComponentOverrides[].propertyId; withineventConfigs[*].events.eventId/eventIds; and within nested metric fields (items[].metricId/eventId/baseEventId,cohortConditions[].eventId/propertyId). These ID fields are ID-or-$tmp:only — a literal name is rejected. InaddCategories/removeCategories/addGroupTypes/removeGroupTypes, a$tmp:ref instead rewrites to the siblingcreateitem’s name (those fields take names, not IDs). Sources, destinations, and group types can be created in the same batch and referenced by theirtempIdtoo.- 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 hasname,entityId, andentityType(event,property,event_variant,property_bundle,metric,category,source,destination, orgroupType). Archived items are reported underremovedEntities(a legacy field name); restored items underunarchivedEntities. Created entries also echo thetempIdyou supplied, so you can map temporary handles to real IDs. (updatedEntitiesentries may also carry areasonwhen 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.
{
"success": true,
"createdEntities": [
{ "name": "Checkout Method", "entityId": "prop-9d44…", "entityType": "property", "tempId": "checkout_method" }
],
"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.
{
"branchId": "br-abc123",
"items": [
{
"op": "create",
"type": "property",
"tempId": "checkout_method",
"name": "Checkout Method",
"fields": {
"description": "How the user completed checkout",
"propertyType": "string",
"sendAs": "event",
"addAllowedValues": ["Card", "Apple Pay", "PayPal"]
}
},
{
"op": "create",
"type": "event",
"name": "Checkout Completed",
"fields": {
"description": "Sent when a user successfully pays and their order is placed.",
"properties": ["$tmp:checkout_method"],
"sources": ["src-web", "src-ios"]
}
}
]
}Rename a property and add an allowed value
Prompt: “Rename user_email to email and allow the value Unknown on Checkout Method.”
Scalar edits go in set; collection changes stay at the top level of fields.
{
"branchId": "br-abc123",
"items": [
{
"op": "update",
"type": "property",
"id": "prop-1a2b…",
"fields": { "set": { "name": "email" } }
},
{
"op": "update",
"type": "property",
"id": "prop-9d44…",
"fields": { "addAllowedValues": ["Unknown"] }
}
]
}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.
{
"branchId": "br-abc123",
"items": [
{
"op": "create",
"type": "metric",
"name": "Checkout Funnel",
"fields": {
"description": "Share of users who start checkout and complete it.",
"metricType": "Funnel",
"items": [
{ "kind": "Event", "id": "step1", "eventId": "evt-checkout-started" },
{ "kind": "Event", "id": "step2", "eventId": "evt-checkout-completed" }
]
}
}
]
}Archive an event
{
"branchId": "br-abc123",
"items": [
{ "op": "archive", "type": "event", "id": "evt-3f01…" }
]
}Common errors
- Missing
writescope — the client must re-authorize withwrite. branchId is required/items is required— malformed request.- Unknown field, a field the operation doesn’t accept, or an invalid value in
fields— the error names the item type and operation and repeats the fields that type accepts. Calldescribe_toolwith that type and op for the full list. namemissing on acreate.tempId is only valid on create items— don’t settempIdonupdate,archive, orunarchive.Duplicate tempId "<name>"— eachtempIdmust 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 identity ID.idandfields.<idField>both present with different values.too many items (got N, max 50)— batch is over the 50-item cap.NotYetImplemented—set.sendAson a property update (sendAsis immutable after create).- Per-item audit-pipeline validation failures (e.g. illegal name, duplicate property, etc.) — returned inside the
errorsarray 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 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 search (itemType: "source") or get (type: "source", by id or name). Call describe_tool with tool:"workflow" and an action for that action’s parameters and an example. |
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’sbranchId,branchName, and abranchUrlthat opens it in the Avo web app.update_branch_description— confirmation with the resolvedbranchIdand 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.
{
"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.
{
"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 (type: "source"), then sets the language on the branch.
{
"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.
{
"action": "import",
"branchId": "br-abc123",
"format": "csv",
"payload": "Event Name,Property Name,...\nCheckout Completed,checkout_method,...",
"importMethod": "add_only"
}Common errors
- Missing
writescope — re-authorize withwrite. - Workspace access denied.
- Unsupported action value.
- Both
branchIdandbranchNameprovided —update_branch_descriptionandset_source_languagerequire exactly one. pull_mainwithout Codegen access, orimportwithout Admin role — permission error.set_source_languagewith a language the source’s platform doesn’t support — the error lists the supported tokens.importmissingformatorpayload, or with an unknownformat/importMethodtoken.
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.
{
"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.
{
"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.
{
"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
writescope — the client must re-authorize withwrite. - Workspace access denied.
list_branches
Scope: read
Transitional. This tool stays available while branch enumeration is being folded into 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, default 25. Clamped to 1–50 (an out-of-range value is coerced, not rejected). |
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 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.
{
"reviewerEmail": "thora@avo.sh",
"branchStatuses": ["ReadyForReview"]
}Common errors
- Workspace access denied.
- Invalid date format on
createdAfter/createdBefore.
Troubleshooting
Tool-specific behavior issues. Authentication and workspace access issues are covered in Troubleshooting on the overview page.
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. Without it, fall back to get with an exact name or 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 first and pass branchId to the follow-up call.
save_items rejects an item and lists fields in the error. The item carried a field that type or operation does not accept, or a type-specific field at the item top level instead of inside fields. Read the field list in the error, or call describe_tool with the same type and op, and retry. Older examples that put fields like propertyType directly on the item no longer work.
save_items returns a NotYetImplemented error. Changing a property’s sendAs is not supported — it is immutable after create.
My client does not show save_items at all. Some MCP clients hide tools whose definition is longer than their size limit. The Avo MCP keeps every tool definition short precisely so this does not happen; if it still does, make sure the client is talking to https://mcp.avo.app/mcp and report it to support@avo.app.