ReferenceAvo MCPTools reference

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:

IntentToolScope
Entry point — find your workspace IDslist_workspacesread
Look up the fields — see exactly which parameters and fields a call accepts before making itdescribe_toolnone
Discover — find items by meaning or by structural filtersearchread
Understand — full details for an event, property, journey, branch, source, etc.getread
Change — create, update, archive, or restore items on a branchsave_itemswrite
Progress — create a branch, update its description, pull main, set a source’s language, or bulk-import a planworkflowwrite
Tell Avo — report the agent’s own experience back to Avo’s product teamgive_feedbackwrite

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 ten 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:

  1. Connect. The short instructions the server sends on connect list every tool, its item types and actions, and what to call next.
  2. describe_tool() — the same overview plus a short “Designing tracking” guide.
  3. describe_tool(tool:"save_items", type:"<type>", op:"<op>") — the exact fields for the item type and operation it is about to write.
  4. 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:

Typesearchgetsave_items
event✅✅✅
property✅✅✅
metric✅✅✅
category✅✅✅
event_variant✅✅✅
property_bundle✅✅✅
source✅✅✅
destination✅✅✅
group_type✅✅✅
gateway✅✅✅
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.

ParameterValuesDescription
toollist_workspaces, describe_tool, search, get, save_items, workflow, give_feedback, list_branchesWhich tool to describe. Omit for an overview of every tool.
typeAn item type (see Item type vocabulary)For save_items: list the fields that item type accepts.
actioncreate_branch, update_branch_description, pull_main, set_source_language, importFor workflow: render that action’s parameters.
opcreate, update, archive, unarchiveFor save_items: narrow the fields table to one operation.
formatfull (default), conciseconcise 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 a type.
  • 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: …); the set object lists its accepted nested keys. Add op:"update" to narrow to one operation.
  • describe_tool(tool:"workflow") — the list of actions. Add action:"<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 for get(type:"branch") the accepted include values.
  • Unknown type or action — 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.


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 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. The same mode lists a branch’s journeys with itemType: "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

ParameterRequiredDescription
itemTypeNoFilter by type: event (default), property, metric, category, property_bundle, event_variant, source, destination, group_type, gateway, or journey (camelCase aliases are accepted but deprecated). The workspace-metadata types (source / destination / group_type / gateway) enumerate the workspace list and journey enumerates a branch’s journeys. These enumerate-only types are valid in filter mode only (omit query) and accept just branch — plus pageToken for journey; content filters such as tags or eventNames are rejected. See Listing journeys.
maxResultsNoSemantic mode: 1–20, default 10. Filter mode: 1–500, default 10. itemType: "journey" pages separately: 1–100, default 25.
workspaceIdNoWorkspace ID. Repeat it on every page when paginating.

Semantic mode (pass query)

ParameterRequiredDescription
queryYesNatural 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)

ParameterRequiredDescription
tagsNoFilter by tag.
categoriesNoFilter by category name.
sourcesNoFilter by source name. Does not apply to metrics.
eventNamesNoFilter by event name. With itemType: "property", returns properties on those events.
variantNamesNoFilter by event variant name.
propertiesNoWith itemType: "event", returns events referencing any of these properties.
includeVariantsNoWith itemType: "event", interleaves each event’s variants in the result set.
stakeholdersNoFilter 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).
ownersNoFilter 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).
destinationsNoFilter by destination name.
typeNoWith itemType: "event", filter by event type.
customFieldNoFilter by custom-field name. Resolve valid names from get with type: "workspace_config".
piiNoFilter by PII type. Resolve valid types from get with type: "workspace_config".
nameMappingNoFilter 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.
checkpointNoEvent and property listings only. Narrow to items whose data reaches one pipeline location. Tagged object by kind: { kind: "source", sourceId }, { kind: "gatewayInput", gatewayId, inputId }, { kind: "gateway", gatewayId }, { kind: "gatewayOutput", gatewayId, outputId }, or { kind: "destination", destinationId }. An unwired or dangling checkpoint returns no items plus a warning, never an error.
branchNoBranch 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.)
pageTokenNoPagination 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 parameters — workspaceId, itemType, every filter, branch, maxResults, and checkpoint when you used it — 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

  • 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.
  • itemType: "journey" (or source / destination / group_type / gateway) combined with query or with content filters such as tags or eventNames — rejected; these types enumerate a whole branch or workspace and take only branch (and pageToken).
  • branch passed as a name instead of an ID on a journey listing — the branch is not found; resolve the name with list_branches first.
  • Empty call (no query, no filter, no branch) — 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 by id or exact name — except event_variant, 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, 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, gateway. get returns a single item, so pass id or name. To enumerate the workspace list, use search (itemType: "source" / "destination" / "group_type" / "gateway").
  • Workspace config — workspace_config (takes no id). 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 customField and pii filters.
  • Journeys — journey. Look up by id only — journey names collide and are often blank, so there is no name lookup; find the ID with search (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 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

ParameterRequiredDescription
typeYesItem type. One of: event, property, metric, category, property_bundle, source, destination, group_type, gateway, event_variant, journey, branch, workspace_config. camelCase aliases are accepted but deprecated.
idVaries by typeThe item’s unique ID. Required for event, property, metric, category, property_bundle unless name is provided. For source/destination/group_type/gateway, 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.
nameVaries by typeExact 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.
variantIdFor event_variantThe variant ID. Combined with id (the base event ID).
checkpointNoEvents and properties only. Annotates whether the item’s data reaches one pipeline location — same tagged-object shape as the search checkpoint filter. Never filters the item out, never errors.
includeFor branchArray 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.
sourceIdRequired for code_snippets; optional elsewhereSource 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").
branchIdNoBranch to look up on. Defaults to main. branchId takes precedence over branchName.
branchNameNoAlternative to branchId.
includePropertyDetailsNoEvents only. When true, includes full property definitions (type, constraints, allowed values). Defaults to false, which returns only property ID + name references.
includeArchivedNoWhen true (default), includes archived items in results. When false, only active items.
workspaceIdNoWorkspace 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 of save_items’s owner / stakeholders fields. The section is omitted when the item has no stakeholders.
  • type: "source" \| "destination" \| "group_type" \| "gateway" — a single entity; id or name is required (enumerate the workspace list with 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 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: "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 not conditions with their values and is set / is not set presence 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, send Payment Started when payment_method is "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 fired line carries the event’s ID so the next get (type: "event") needs no name lookup; an archived event is suffixed (archived).
  • Conditions show the full property path (product.category is "shoes", never just category), with is / is not values and is set / is not set presence 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).
  • sourceId missing when include contains code_snippets.
  • type: "journey" without id — rejected; journeys are looked up by ID only (find it with search, itemType: "journey").
  • Journey ID not found on the branch — check the ID and the branchId / branchName you 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, group types, and gateways 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 create event and create property in one batch, using tempId to cross-reference.
  • Add allowed values to a property — update property with fields.addAllowedValues.
  • Rename an event or property — update with fields.set.name.
  • Archive an event — op: "archive" with the event’s id.
  • Create a funnel metric — create metric with fields.metricType: "Funnel" and fields.items.
  • Toggle a property between scalar and list — update property with fields.isList: true / false.

Parameters

Top-level

ParameterRequiredDescription
branchIdYesThe branch to write to. Get it from workflow (action: "create_branch"). Writes on main are rejected.
itemsYesArray of items to apply (see Item shape).
onReviewedBranchNoAcknowledgement 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.
workspaceIdNoWorkspace 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": { } }
KeyTypeNotes
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.
typestringRequired. One of event, property, event_variant, property_bundle, metric, category, source, destination, group_type, gateway. The camelCase spellings (eventVariant, propertyBundle, groupType) still work but are deprecated.
idstringThe 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.
namestringRequired on every create. Cosmetic on other ops.
tempIdstringCreate only. A temporary handle (letters, digits, _, -; max 64 characters) for cross-referencing inside the same call. Reference it elsewhere as "$tmp:<tempId>".
fieldsobjectThe 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):

TypeID field
eventeventId
propertypropertyId
metricmetricId
categorycategoryId
property_bundlepropertyBundleId
sourcesourceId
destinationdestinationId
group_typegroupTypeId
gatewaygatewayId
event_variantbaseEventId + variantId (both in fields, on every op)

Inside fields, three placement rules apply to every type:

  • Scalar edits go in set (update only). set is 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.sendAs is accepted only on create — a property’s sendAs is immutable after creation.
  • Collection changes stay at the top level of fields — addProperties / removeProperties, addCategories / removeCategories, addAllowedValues / removeAllowedValues, and every other add* / remove* / clear* field.
  • description is create-only, and only for types whose create reads it: event, property, event_variant, property_bundle, metric, category. A create for a source, destination, group_type, or gateway that carries description is rejected. On update, use set.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. 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

FieldOpsNotes
eventIdupdate, archive, unarchiveThe event’s ID (or $tmp: of a same-batch create).
descriptioncreateEvent description.
sourcescreateSource IDs to include the event in. ID or $tmp: only. Find source IDs with search (itemType: "source").
propertiescreateProperty IDs to attach. ID or $tmp: only.
propertyBundlescreateProperty bundle IDs to attach. ID or $tmp: only.
actionscreateInitial action types (identify, page, revenue, …). Omit for logEvent only.
nameComponentscreateAdvanced-naming workspaces: per-building-block name values.
tagscreateTag names to attach (literal strings).
addProperties / removePropertiesupdateProperty IDs (or $tmp:) to add / remove.
addSources / removeSourcesupdateSource IDs to include / exclude.
setSourceCodegenupdateSet the per-source include-in-codegen flag.
addSourceDestinations / removeSourceDestinationsupdateLink / unlink (source, destination) pairs on the event.
setActionsupdateReplace the event’s action types.
customFieldValuesupdateSet or clear per-custom-field values.
addCategories / removeCategoriesupdateCategory 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 / removeTagsupdateTag names to attach / detach.
addGroupTypes / removeGroupTypesupdateGroup type names (name or $tmp:, not raw IDs).
addPropertyBundles / removePropertyBundlesupdateProperty bundle IDs to attach / detach.
nameMappingscreate/updatePer-destination name mapping entries — see Name mappings.
owner, stakeholderscreate/updateSee Owner and stakeholder fields.
set.name, set.descriptionupdateRename (renaming to the same name is a no-op; to a name already held by another live event is rejected) / new description.

property

FieldOpsNotes
propertyIdupdate, archive, unarchiveThe property’s ID.
descriptioncreateProperty description.
propertyTypecreatestring, int, long, float, bool, object, any (aliases like integer, boolean, double accepted). Change later with set.propertyType.
sendAscreateevent, user, or system. Immutable after create.
nestedPropertiescreateObject property: child-property slots { propertyId, … }.
tagscreateTag names to attach.
addAllowedValuescreate/updateString property: allowed values to add.
removeAllowedValuesupdateAllowed values to remove. Rejected on a create. Values not in the current list are silent no-ops.
eventConfigscreate/updatePer-event property settings — see Per-event property settings. Max 50 entries.
customFieldValuescreate/updateSet or clear per-custom-field values.
piicreate/updateThe property’s PII state: { kind: "declared" | "notPii" | "unset" }.
nameMappingscreate/updateSee Name mappings.
isListupdateBoolean — toggle between scalar (false) and list (true).
addNestedProperties / removeNestedPropertiesupdateObject property: child-property slots to add / child-property IDs to detach (live IDs only, no $tmp:).
addPropertyRegex / removePropertyRegexupdateSet the global regex rule { regex, testValue } / pass true to remove it.
addEventRegexOverride / removeEventRegexOverrideupdateSet an event-specific regex override { eventId, regex, testValue } / the event ID whose override to remove.
addCategories / removeCategoriesupdateCategory names (or $tmp:). The property must already exist.
addTags / removeTagsupdateTag names to attach / detach.
owner, stakeholderscreate/updateSee Owner and stakeholder fields.
set.name, set.description, set.propertyTypeupdateRename / new description / new type.

event_variant

FieldOpsNotes
baseEventIdallThe parent event’s ID (or $tmp:). Required on every op.
variantIdallThe variant’s ID — you supply it on create (must not contain .). Required on every op.
descriptioncreateVariant description.
nameSuffixcreateSuffix appended to the parent event name (e.g. buy_now produces click / buy_now). Change later with set.nameSuffix.
attachPropertiescreate/updateProperty IDs (or $tmp:) to attach to this variant.
overridescreateComponent-level overrides { propertyId, pinned } or { propertyId, allowed } (mutually exclusive per override).
bundleOverridescreateBundle IDs to attach to this variant.
addComponentOverrides / removeComponentOverridesupdateComponent override specs to add or replace / property IDs whose overrides to clear.
removePropertiesupdateProperty IDs to set as explicit not-on-variant overrides.
clearAttachedPropertiesupdateProperty IDs whose attachment override is cleared (inherit from base).
addSourceOverrides / removeSourceOverrides / clearSourceOverridesupdateSource-level overrides to force-add / force-remove / reset to inherit.
addBundleOverrides / removeBundleOverrides / clearBundleOverridesupdateBundle IDs to force-add / force-remove / reset to inherit.
addVariantPropertyRegexupdateSet a variant regex override { propertyId, regex, testValue }.
removeVariantPropertyRegexupdateProperty ID set to explicit no-regex on the variant.
clearVariantPropertyRegexOverrideupdateProperty ID whose variant regex override is cleared (inherit).
owner, stakeholderscreate/updateSee Owner and stakeholder fields.
set.nameSuffix, set.description, set.triggersupdateNew 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

FieldOpsNotes
propertyBundleIdupdate, archive, unarchiveThe bundle’s ID.
descriptioncreateBundle description.
addPropertiescreate/updateProperty IDs (or $tmp:) to add to the bundle.
attachToEventscreateEvent IDs (or $tmp:) to attach the new bundle to.
removePropertiesupdateProperty IDs to remove from the bundle.
set.name, set.descriptionupdateRename / new description.

Bundle-to-event attachment after creation is managed from the event side via addPropertyBundles / removePropertyBundles on an event update.

metric

FieldOpsNotes
metricIdupdate, archive, unarchiveThe metric’s ID.
descriptioncreateMetric description.
metricTypecreateOne of Funnel, EventSegmentation, Proportion, Retention, CustomEvent, Cohort. Immutable after create — archive and recreate to change.
itemscreateNon-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.
cohortConditionscreateCohort metrics: array of conditions — { kind: "Action", id?, eventId, performed, frequency, timeWindow } or { kind: "Variable", id?, propertyId, binOp, literals }.
setName / setDescriptionupdateRename / new description (metrics use these top-level fields).
addItems / updateItems / removeItemsupdateNon-cohort metrics: add items / replace an item’s where and groupBy by id / remove item IDs.
addCohortConditions / updateCohortConditions / removeCohortConditionsupdateCohort metrics: manage the condition list.
addCategories / removeCategoriesupdateCategory names to attach / detach.

category

FieldOpsNotes
categoryIdupdate, archive, unarchiveThe category’s ID.
descriptioncreateCategory description.
set.name, set.descriptionupdateRename / new description.

Category membership is managed from the member side: addCategories / removeCategories on event, property, and metric updates.

source

FieldOpsNotes
sourceIdallRequired on update / archive / unarchive. On create, supply sourceId or a tempId.
platformcreate (required)The development platform this source runs on. Change later with set.platform.
programmingLanguagecreateThe source’s programming language. Change later with set.programmingLanguage (or workflow set_source_language).
libraryName / libraryDestinationcreateCodegen library name / output path. Change later via set.*.
connectDestinationsupdateDestination IDs (or $tmp:) to connect for codegen routing on the source.
disconnectDestinationsupdateDestination 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.libraryDestinationupdateScalar edits.

A create for a source does not accept description.

destination

FieldOpsNotes
destinationIdallRequired on update / archive / unarchive. On create, supply destinationId or a tempId.
analyticsToolcreate (required)The analytics platform this destination connects to. Change later with set.analyticsTool.
includeUserPropsWithEventPropscreate/updateBoolean. Defaults to false on create.
disabledByDefaultcreate/updateBoolean — new events are disabled for this destination by default. Defaults to false on create.
apiKeyupdateSet or remove an API key for an environment: { kind: "set", env: "dev" | "prod", value: "…" } or { kind: "remove", env: "dev" | "prod" }.
set.name, set.analyticsToolupdateScalar edits.

A create for a destination does not accept description.

group_type

FieldOpsNotes
groupTypeIdallRequired on update / archive / unarchive. On create, supply groupTypeId or a tempId.
set.nameupdateRename.

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.

gateway

🔒

Gateways are gated by a per-workspace feature. A gateway write returns 403 when the workspace does not have gateways enabled.

FieldOpsNotes
gatewayIdupdate, archive, unarchiveThe gateway’s ID. A create is server-minted, so no gatewayId is supplied then.
inputscreateInput checkpoints, each { sourceId? } (ID or $tmp:; omit or null for an unwired input).
outputscreateOutput checkpoints, each { destinationId? } (ID or $tmp:; omit or null for an unwired output).
addInputs / removeInputsupdateInput checkpoints to add, each { sourceId? } / input-checkpoint IDs to remove.
addOutputs / removeOutputsupdateOutput checkpoints to add, each { destinationId? } / output-checkpoint IDs to remove.
setInputOrigins / setOutputTargetsupdateRe-point existing checkpoints: { inputId, sourceId? } / { outputId, destinationId? }.
outputIdupdateApply this item’s transformation keys at one output checkpoint (an output ID from get, or $tmp: of a same-batch outputs / addOutputs entry) instead of at the gateway itself. Has no meaning without transformation keys.
addTransformationPropertiesupdateUpsert per-property overrides at the gateway (or at outputId). Each { propertyId, pinned?, absence?, disallowedValues?, regex?, nameMapping? }; a bare { propertyId } inherits every dimension. pinned / regex null = explicitly none; nameMapping is { kind: "set" | "clear" }. disallowedValues are add-only and must already be allowed values of the property.
removeTransformationPropertiesupdateProperty IDs to stop sending from this checkpoint on.
clearTransformationPropertiesupdateProperty IDs whose overrides are removed at this checkpoint (back to inherit). The only way to drop a disallowed value: clear, then re-add the overrides you still want.
addTransformationPropertyBundles / removeTransformationPropertyBundles / clearTransformationPropertyBundlesupdateProperty-bundle IDs to include / stop sending / reset to inherit at this checkpoint. $tmp: works for a bundle created in the same batch.
setTransformationEventNamesupdateRename events as sent from this checkpoint on. Each { eventId, sendAs }.
removeTransformationEventNamesupdateEvent IDs whose send-as rename is removed (back to the event’s own name).
set.nameupdateRename.

A property ID may appear in only one of add / remove / clear transformation fields per item. A create for a gateway does not accept description.

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

FieldNotes
ownerTagged 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.addArray 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.removeArray 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:

  1. An update item for every event, property, and metric in A carrying fields: { addCategories: ["B"], removeCategories: ["A"] }.
  2. 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 to alwaysSent, sometimesSent, or neverSent. Can be scoped per source. sometimesSent on allEvents requires a migrated workspace.
  • 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. 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>":

  • tempId is create-only — setting it on an update, archive, or unarchive returns an error.
  • tempId names must be unique within a single save_items call 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 these fields arrays: properties, addProperties, removeProperties, attachProperties, propertyBundles, addPropertyBundles, removePropertyBundles, attachToEvents, addSources, removeSources, bundleOverrides, connectDestinations, disconnectDestinations, gateway inputs / outputs and transformation bundle fields; inside overrides[].propertyId / addComponentOverrides[].propertyId; within eventConfigs[*].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. 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, group types, and gateways 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, groupType, or gateway). Archived items are reported under removedEntities (a legacy field name); restored items under unarchivedEntities. Created entries also echo the tempId you supplied, so you can map temporary handles to real IDs. (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.
{
  "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 write scope — the client must re-authorize with write.
  • 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. Call describe_tool with that type and op for the full list.
  • name missing on a create.
  • tempId is only valid on create items — don’t set tempId on update, archive, or unarchive.
  • 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 identity ID.
  • id and fields.<idField> both present with different values.
  • too many items (got N, max 50) — batch is over the 50-item cap.
  • 403 on a gateway write — the workspace does not have gateways enabled.
  • NotYetImplemented — set.sendAs on a property update (sendAs is immutable after create).
  • 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 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.

ParameterRequiredDescription
actionYesThe workflow action. One of: create_branch, update_branch_description, pull_main, set_source_language, import.
branchNameFor create_branch; alternative to branchId for update_branch_description / set_source_languageName for the new branch (create_branch), or the existing branch’s name.
branchIdAlternative to branchName for update_branch_description / set_source_language; required for pull_main and importID of the existing branch. For update_branch_description and set_source_language, provide exactly one of branchId or branchName.
descriptionFor 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.
sourceIdFor set_source_languageThe 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.
languageFor set_source_languageThe 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.
formatFor importPayload format. csv (a CSV export — auto-detects Avo, Amplitude, Mixpanel, Segment, and spreadsheet formats) or json_schema (an Avo JSON Schema document).
payloadFor importThe raw CSV text or Avo JSON Schema document, as a string.
importMethodOptional for importadd_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).
workspaceIdNoWorkspace 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.

{
  "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 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

ParameterRequiredDescription
feedbackYesThe feedback message — what the agent observed, what was missing, or what went wrong.
intentNoWhy 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.
workspaceIdNoWorkspace 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 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 (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

ParameterRequiredDescription
workspaceIdNoWorkspace ID
branchStatusesNoFilter by status. Valid values: Draft, ReadyForReview, ChangesRequested, Approved, Merged, Closed, Open. Defaults to open/active branches only.
pageSizeNoResults per page, default 25. Clamped to 1–50 (an out-of-range value is coerced, not rejected).
pageTokenNoPagination token from a previous response
branchNameNoSubstring match on branch name (case-insensitive)
creatorEmailNoFilter by creator email
creatorUserIdNoFilter by creator user ID
reviewerEmailNoFilter by reviewer email
reviewerUserIdNoFilter by reviewer user ID
collaboratorEmailNoFilter by collaborator email
collaboratorUserIdNoFilter by collaborator user ID
createdAfterNoISO 8601 date — only branches created after
createdBeforeNoISO 8601 date — only branches created before
impactedSourceIdNoFilter 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.