Write tools
Inputs, outputs, permissions, idempotency and quota behaviour for create_project, add_prompt and trigger_run.
Write tools are registered only when the configured key has write scope (and, for create_project, is authorised for a
workspace). They are annotated readOnlyHint=false, destructiveHint=false and idempotentHint=true; their descriptions
begin with MUTATION. None of them deletes or overwrites anything. Every create, replay and rejected duplicate is audited.
Only one tool spends money
create_project and add_prompt never start a measurement and never spend provider quota. trigger_run does, see
below.
create_project
Creates a project (brand) in the key's workspace. Counts against the plan's project limit. Needs a write credential
authorised for a workspace; a credential limited to specific brands is refused.
| Argument | Type | Notes |
|---|---|---|
domain | string, 1–253 chars | The brand's website domain, e.g. acme.com. Required. |
name | string, ≤255 | Display name. |
target_languages | string[] | Language codes, e.g. ["en"]. Sent exactly as given; omitted means unspecified. |
target_countries | string[] | ISO 3166-1 alpha-2, e.g. ["US"]. |
idempotency_key | string | Optional; see below. |
{ "domain": "acme.com", "name": "Acme", "target_languages": ["en"], "target_countries": ["US"] }{
"id": 12,
"name": "Acme",
"domain": "acme.com",
"replayed": false,
"idempotency_key": "mcp-create_project-3f2a..."
}Repeating the same call returns the same project with replayed: true. A project that already exists for the domain is a
"duplicate" error, and a reached plan limit is a "project limit" error.
add_prompt
Adds a tracked question to a project. Counts against the plan's prompt limit. Needs write scope and admin access to
the project.
| Argument | Type | Notes |
|---|---|---|
brand_id | integer | From list_brands. Required. |
content | string, 1–2000 chars | The question to track. Required. |
language | string | 2–3 letter code. Omitted: the project's first target language. |
target_country | string | ISO 3166-1 alpha-2. Omitted: the project's first target country. |
providers | string[] | Provider ids to measure with. Omitted: all live providers. |
title, category | string | Optional labels. |
status | paused (default) or active | paused never runs by itself; active joins scheduled runs. |
idempotency_key | string | Optional; see below. |
{ "brand_id": 12, "content": "Best CRM for startups?", "language": "en", "target_country": "US", "providers": ["openai"] }{
"id": 101,
"brand_id": 12,
"content": "Best CRM for startups?",
"language": "en",
"target_country": "US",
"status": "paused",
"cadence": "daily",
"providers": ["openai"],
"replayed": false,
"idempotency_key": "mcp-add_prompt-9c1e..."
}A question is created paused, so nothing runs until you set it active (in the dashboard) or start a run with
trigger_run. A question that is already tracked is a "duplicate" error; a reached prompt limit is a "prompt limit" error.
trigger_run
Starts a measurement run for explicit questions. This may consume quota and makes paid provider calls. Only call it when a run is wanted: no read tool ever starts one, and the tool description tells the model to run only when you asked.
| Argument | Type | Notes |
|---|---|---|
brand_id | integer | Required. |
question_ids | integer[], 1–25 | Existing, non-archived questions from list_questions. Required. |
providers | string[] | Narrows each question's own providers; never widens them. Omitted: each question's own providers. |
idempotency_key | string | Optional; see below. |
Needs write scope, admin access to the project, and active paid access.
{ "brand_id": 12, "question_ids": [101, 102], "providers": ["openai"] }{
"status": "queued",
"run_id": 57,
"brand_id": 12,
"run_status": "pending",
"dispatch_state": "queued",
"replayed": false,
"idempotency_key": "mcp-trigger_run-de97...",
"question_ids": [101, 102],
"providers": ["openai"],
"max_provider_calls": 2
}Cost and quota
- Each dispatch uses one of the plan's daily manual-run allowance and makes up to questions × providers paid
provider calls.
max_provider_callsreports that bound only when you narrowedproviders; otherwise it isnullbecause each question's own provider list applies. - The quota is checked and reserved before anything is queued. If it is used up, the result is
status: "rejected"withreason: "report_quota_reached"anddaily_run_limitwhen known. - If a run for the project is already in progress, the result is
status: "rejected"withreason: "run_in_flight"and the in-flightrun_idwhen reported. Nothing is reserved. - The tool does not wait. It returns
queuedwith arun_id; pollget_run_status. - Execution is at-most-once per dispatch, not exactly-once: a provider call that timed out may still have been processed and billed.
Retrying safely
If you omit idempotency_key, one is derived from the brand, the questions, the providers and today's UTC date. A retry
of the same call that day returns the existing run (replayed: true) without using more quota. To run the same questions
again on the same day, pass a new idempotency_key. If the queue refused a run (dispatch_failed), retrying with the
same key queues that same run again without using more quota.
Idempotency keys (all write tools)
| Situation | Result |
|---|---|
| Same key, same arguments | The original object, replayed: true. Nothing new is created or charged. |
| Same key, different arguments | Conflict: use a new key, or repeat the original arguments. |
| Concurrent duplicates | Exactly one object is created. |
| Refused create (validation, limit, duplicate) | Nothing is stored; the key can be reused. |
Keys are scoped to the credential and the operation. When the server derives a key, it is returned as idempotency_key so you
can reuse it.