MCP guide
https://mcp.mxaura.ai/mcp is a remote Model Context Protocol server.
Point an MCP-capable assistant at it, sign in, and the assistant can read
(and, with the right scopes, act on) your mxAURA account through its tools (the list below).
It speaks MCP protocol 2025-11-25 over streamable HTTP, authenticates with
OAuth 2.1 (PKCE, dynamic client registration), and every call goes through
the same public API as an API key would, with the same scopes, rate limits
and tenant boundary.
Connect
Claude.ai (web / desktop app)
Settings → Connectors → Add custom connector → URL
https://mcp.mxaura.ai/mcp → Connect. A browser window opens to
auth.mxaura.ai; sign in to mxAURA, choose the access level, Authorize.
Done. (In the app, Account → MCP has these steps for each client, with
copy buttons, and lists every connected assistant with a Revoke button.)
Claude Code
claude mcp add --transport http mxaura https://mcp.mxaura.ai/mcpthen /mcp inside a session to authenticate.
Cursor / other MCP clients
Add a server of type "streamable HTTP" (or "remote") with the URL above. If
the client cannot do OAuth itself, use the mcp-remote bridge:
{ "mcpServers": { "mxaura": { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.mxaura.ai/mcp"] } } }What happens underneath
- The client fetches
https://mcp.mxaura.ai/.well-known/oauth-protected-resourceand learns the authorization server ishttps://auth.mxaura.ai. - It reads
https://auth.mxaura.ai/.well-known/oauth-authorization-server, registers itself at/register(RFC 7591), and opens/authorizewith a PKCE S256 challenge. - You sign in to mxAURA (2FA if enabled) and approve the scopes shown.
- The client exchanges the code at
/tokenand gets an access token (mxa_oat_…, 1 hour) and a refresh token (30 days).
No secrets are typed anywhere. To disconnect, revoke it in Account → API and MCP (effective within seconds: every token for that assistant is revoked), or remove the connector in the client.
Scopes and the support profile
A client that asks for no specific scopes gets the support profile:
components.read brandings.read pools.read campaigns.read flows.read
hubs.read articles.read identity.read dispatch.readEvery read, nothing that sends, publishes or spends. A client may request it
by name (scope=support) or ask for specific scopes.
The person approving picks an access level on the consent screen, which replaces what the client asked for:
| Level | Scopes |
|---|---|
| Read only (default) | the support profile above |
| Content | support + hubs.write articles.write publishing.read publishing.write media.read media.write folders.read folders.write ai.write |
| Campaigns | support + components.write pools.write campaigns.write flows.write brandings.write folders.read folders.write ai.write |
| Full | * (everything, including enrollments.write) |
Delete scopes are never part of a level; deleting needs an API key that has one.
Step-up, the account ceiling and revoking
- Step-up (MCP 2025-11-25).
tools/listreturns every tool. When a call needs a scope the token does not hold, the server answers HTTP 403 withWWW-Authenticate: Bearer error="insufficient_scope", scope="<held scopes and the one needed>", resource_metadata="…". The client asks the person to approve that and retries; the consent screen pre-selects the smallest level that covers it. A connection can start Read only and grow only as far as the work needs. - Ceiling. The account owner sets the highest level any assistant may be
given (Account → API and MCP). The consent screen offers nothing above it, the
authorization server refuses to approve above it (
403 above_ceiling), and the public API cuts every OAuth token down to it on every request, so lowering it takes effect at once for connections already approved; connections above the new ceiling are also revoked. Content and Campaigns are separate: a ceiling of Content does not allow Campaigns. No ceiling set means Full. - Revoke. Account → API and MCP lists the API keys (with their MCP use) and the connected assistants; revoking one stops it within about 15 seconds, on the API and the MCP alike.
- API keys on the MCP (v3.1.947). A key works on the MCP as it does on the
API. Used through the MCP it is held to the account ceiling as well as its
own scopes; used on the API directly it keeps its own scopes. Its MCP use is
shown in Access with the assistant's name (
clientInfo.namefrominitialize, else the client's User-Agent). tools/listlists every tool, whatever the credential (the 2026 spec). A call the credential cannot make answers 403insufficient_scopenaming the permission it needs.
The tools
| Tool | Scope | What it does |
|---|---|---|
get_me |
any | Who the token is: account, scopes |
list_components, get_component |
components.read | Messages, headers, summaries, signatures, senders, templates |
list_pools, get_pool, get_pool_messages |
pools.read | Pools and the ordered messages composed into them |
compose_message |
pools.write | Compose a message into a pool |
list_campaigns, get_campaign |
campaigns.read | Campaigns |
simulate_campaign |
campaigns.read | The full journey a contact would receive. Read-only, and the same engine dispatch uses. With contact_id, rows the contact's properties fail are skipped |
preview_next_message |
campaigns.read | The exact message a contact would get next: the production cascade with their real properties, lists and progress; the row, pool, message, sender, subject and blocks; every row with whether it matched and why. Writes nothing, so the next run gives the same answer. Field content and HTML come back as character counts (the API returns them in full) |
dispatch_next_message |
dispatch.write | A real dispatch for one contact: advances their journey and counts in the dispatch log and every stat. write_properties: true also writes the message to the HubSpot contact. Not a test |
review_messages |
ai.write | The Composer's three critics and nine lines on messages that already exist (up to 10 in one pool, read as they would send; the ones either side read as context, never scored) or on raw copy. Writes nothing to the messages; spends model budget; leaves a review stamp. Where a critic quoted the message, the words come back in quoted_tenant_text between « and », capped at 120 characters: the one place this server returns a message's own words. The message text itself comes back as character counts |
list_campaign_pools |
campaigns.read | A campaign's pools. Each row is an assignment with its own filter, sender, Brand Kit and blocks; a pool can appear twice, once per audience |
attach_pool |
campaigns.write | Attach a pool to a campaign. allow_duplicate: true attaches it again for another audience |
get_assignment_blocks |
campaigns.read | The Blocks chosen on a campaign pool row, per block definition |
bind_assignment_blocks |
campaigns.write | Choose the Block for each block definition on a campaign pool row (source: set, set_id) and how its entries go out: sequential (next entry each send to a contact), fixed (block_id: one entry) or rotational (next entry each time the same message repeats). Definitions left out keep theirs; source: inherit clears one |
pin_block |
campaigns.write | Advanced: pin one entry to one message of a campaign pool row |
get_row_variants |
campaigns.read | A row's block variants in the order they are checked, each with its filter and Blocks, the row's own filter, and warnings |
radar_reads, radar_read |
campaigns.read | Radar's reads as data, no model and no spend: list them with their inputs, then run one (performance, two periods compared, links clicked, what changed, results, hub traffic, DMARC sources, domain health, reputation, live HubSpot and Google reads). Your assistant does the reasoning |
radar_ask |
ai.write | Radar answers in plain words with every number cited to a read (T1, T2...). The reads come back as tool and arguments; radar_read gets their data. Uses the AI allowance |
radar_review, radar_findings |
campaigns.read | The week in review, and the findings from the last check run |
radar_run_findings |
campaigns.read | Run the checks now (rules, no model) |
radar_dismiss_finding |
campaigns.write | Quiet a finding for 30 days |
radar_threads |
campaigns.read | Ask threads, or one thread |
radar_agency_review, radar_agency_learning |
campaigns.read | Agency accounts: clients this week; what works across clients |
architect_plans, architect_plan, architect_stage, architect_estimate |
campaigns.read | The Architect's campaign plans: list them, open one (brief, every stage, the review, the build), read one stage in full with its reads, read the bill Go would spend |
architect_start, architect_edit, architect_delete |
campaigns.write | Start a plan; edit, confirm or unconfirm a stage (the form is the truth; confirming suggestion, templates or content runs the review, a refusal comes back with the lines); delete a plan |
architect_chat, architect_run |
ai.write | Talk to the Architect to fill the brief; run a stage (research, suggestion, templates, content, setup, automation). Uses the AI allowance |
architect_build |
ai.write + campaigns.write | Go: the review, then the campaign, rows, pools, Blocks, bindings, variants, the Composer per pool and the drafts, in mxAURA. Nothing enabled or published; calling it again fills gaps |
architect_setup_apply, architect_automation_apply, architect_register_library |
campaigns.write | The portal buttons: create the ticked properties and lists (create only); create the workflow, off, or the flow and form; create and register the library designs |
set_row_variants |
campaigns.write | Replace a row's variants (first match wins, no match gets the default). Refused when a variant can never apply: contradicts the row's filter, sits below a variant with no filter, or repeats a filter above it |
list_flows, get_flow |
flows.read | Flows with enrollment counts |
enroll_contact |
enrollments.write | Enroll a HubSpot contact. Sends real email, capped per hour |
get_dispatch_log |
dispatch.read | What was sent, to whom, with what result |
list_hubs, get_hub, get_hub_stats |
hubs.read | Hubs, their settings and embed details, traffic |
list_articles, get_article |
articles.read | Articles: metadata, status, placements, SEO, stats |
get_identity |
identity.read | Positioning (frameworks) |
list_brandings |
brandings.read | Brand Kits |
Each tool's description and input schema are served by tools/list; the
assistant reads them directly.
What the model can and cannot see
Bodies are never returned to the model. Message HTML, article markdown,
layout templates, personal messages come back as their length
(html_chars: 2140) instead of their text. Content a user wrote is untrusted
input to a model: a hub article must never be able to instruct an assistant.
A workflow that truly needs a body fetches it over the REST API.
Everything else comes back as structured JSON. Names, ids, statuses, positions, stats, reasons.
For the same reason there are no MCP tools for the evidence library, competitors or the market
brief: what they hold is text read from other people's websites. Use the REST API for those
(Resources). Radar's market read is left out for the same reason. DMARC and the Google
connection are app-only; Radar reads them.
Limits
MCP calls are API calls. The token's burst (25 / 10 s), per-minute (120) and
account (600 / min) limits apply, and tools that spend the AI allowance share the
account's 30 a minute with the app. The assistant is told when it hits one
(rate_limited (HTTP 429): ...) so it can back off rather than crash the
session. A refusal from the AI allowance reads budget_exhausted (HTTP 402): ... [reason: ...].
Tool results are truncated at 100 KB.
Typical questions it can answer
- "Why didn't Darren get Tuesday's email?" →
get_dispatch_log, filter by contact; the entry saysblocked / no_senderorfiltered / …. - "What would this contact receive from the Q4 campaign?" →
simulate_campaign. - "Send partners the partner closes from the same intro pool." →
attach_pool(allow_duplicate), thenbind_assignment_blockson each row. - "Make a Partner Summary for MXA 2.0 and use it on the partner row." →
update_manifest(Summary sectionreusable: true, if it is not a block definition yet),list_block_kinds,compose_block_set,compose_block(one per entry,set_id), thenbind_assignment_blockswithsource: set, theset_idand a mode (sequential, fixed withblock_id, or rotational). - "Which hub is the pricing article in, and is it live?" →
get_article. - "How much traffic did the Insights hub get this month?" →
get_hub_stats. - "What's our positioning?" →
get_identity.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Client says "unauthorized" after connecting | Access token expired and the client did not refresh | Reconnect; most clients refresh silently |
| A tool is missing | The token lacks its scope | Reconnect and approve the scope, or accept that support is read-only |
enroll_contact returns enrollment_cap |
Hourly cap reached | Wait for Retry-After |
A tool returns budget_exhausted (402) |
The AI allowance refused; [reason: ...] says why (exhausted, would_exceed, disabled, new_account, email_unverified) |
Wait for the month, ask support to raise it, connect HubSpot, or confirm the email in the app |
| "server did not respond" | Network, or the client is using a stdio transport | Use a streamable-HTTP transport or mcp-remote |
https://mcp.mxaura.ai/health reports the server version (1.7.0) and the protocol
version (2025-11-25) without authentication.
Hub tools (v3.1.720)
The idea-to-live path: a piece is composed in the library, drafted by AI, placed in a hub, published. Every call goes through the public API with the caller's own credential, so the hub sub-family scopes (articles, publishing, ai, hubs) are enforced again there.
| Tool | Scope | Does |
|---|---|---|
ask_help |
ai.write | The documentation as a tool: a grounded answer with the help topics used and the app page to open. Answers questions about mxAURA only; anything else is refused. Metered against the account's AI allowance |
list_content_types |
hubs.read | Article templates and their fields. Read one before composing so the fields you pass exist |
list_authors |
articles.read | Bylines, with ids for author_id |
list_hub_articles |
hubs.read | What one hub's gallery shows: each placement's state, featured flag and position |
compose_article |
articles.write | Create an article from a content type: title, optional slug and author, data by field key. Importing: display_date, created_at, published_at carry the original dates (the last two set the record's own stamps; update_article takes them too) |
update_article |
articles.write | Change fields; only the keys passed change. Importing: created_at and published_at back-date the record's stamps (created_at never clears, published_at: null clears), display_date sets the date readers see. None of them publishes anything |
bulk_ai_assist |
ai.write | Fill what many articles are missing in one background job: seo, social, answer. Up to 300 articles, only what is empty, one run per account at a time. Poll get_ai_job; the label is the progress |
draft_article_with_ai |
ai.write | Start an AI draft (subtitle, body, title, reviewed) from a brief and optional source. A job: poll get_ai_job until done. Spends the AI budget |
get_ai_job |
ai.read | One job: status and result |
generate_article_seo |
ai.write | SEO title and description from the body |
place_article_in_hub |
publishing.write | Put an article into a hub's gallery as a draft; it gets its URL on every site the hub is on |
publish_article |
publishing.write | Live now, or scheduled when publish_at is given. Public: the assistant confirms first |
unpublish_article |
publishing.write | Off the gallery; the article stays in the library |
generate_social_posts |
ai.write | Queue the long and short social posts from the article (review slot by default) |
get_article_faqs, save_article_faqs |
articles.read / articles.write | Read the answer layer; save it and approve FAQs (approved: true). Applied AI FAQs arrive unapproved |
list_guardrails, check_guardrails |
identity.read | An identity's words and phrases, and the same check the generators run, for any text |
set_guardrails |
identity.write | Set the words (the AI does not use them) and the phrases (instructions; a phrase that names a word wins). Each list sent replaces that list |
self_test_guardrails |
identity.read | Every rule against its own examples |
suggest_guardrails |
ai.write | AI Assist suggests words and phrases to avoid; saves nothing |
fix_guardrail_conflicts |
ai.write | AI Assist repairs text that breaks the guardrails, changing only the sentence with the conflict; returns before and after; saves nothing |
compose_guardrail, import_guardrails |
identity.write | Add or import (load format) rules of a specific kind. Saving is the approval |
generate_answer_layer |
ai.write | Queue the lead answer and FAQs, grounded in the article (review slot; applied FAQs arrive unapproved) |
delete_articles |
articles.delete | Permanent; refused while an article is in a hub. Needs an API key with that scope |
retire_article, restore_article |
publishing.write | Reversible take-down, and bring back with the same id and slug |
bulk_hub_placement |
publishing.write | One action for up to 200 articles in a hub: publish, schedule (publish_at), unpublish or remove. Public: the assistant confirms first |
apply_ai_draft |
articles.write | Copy the AI draft from the review slot into the live article (all, or the fields named) |
discard_ai_draft |
articles.write | Throw away the AI draft; the live article is untouched |
Typical run: list_content_types > compose_article (send the slug to keep an address exactly; for an import, created_at, published_at and display_date for its original dates) > place_article_in_hub (status: unpublished) > draft_article_with_ai or your own body > generate_article_seo (fields: ["tags"], write: "live" for tags only) > generate_social_posts and generate_answer_layer > get_ai_job > get_article (read data.ai_review) > apply_ai_draft > get_article_faqs / save_article_faqs (approve) > publish_article or bulk_hub_placement. Everything runs on mxAURA's AI with your identity and prompt packs; the packs are never returned. A draft never overwrites live content: it waits in the review slot until applied.
Many at once: list_hub_articles > pick the ids > bulk_hub_placement with action: publish (or schedule with publish_at, unpublish, remove). The response says how many were done and lists any that failed, with the reason.
Composition tools (v3.1.383)
| Tool | Scope | What it does |
|---|---|---|
import_template, get_manifest, update_manifest |
components | Import a HubSpot email; read and correct its field manifest |
list_sequence, compose_message, clone_message, reorder_messages, remove_message_from_pool |
pools | The ordered messages in a pool |
get_bindings, bind_fields |
components | The typed values of each field of a message |
list_block_kinds, list_block_sets, get_block_set, compose_block_set, update_block_set, clone_block_set, delete_block_sets, list_blocks, compose_block, update_block, delete_blocks, set_message_slots, block_clone_targets, clone_block |
components | Block definitions (template sections), Blocks (named sets of entries on a definition: Client Summary, Partner Summary) and their entries, which block a message uses, and cloning a block to another template whose section matches exactly |
generate_rows, generate_variants |
ai | AI drafts; nothing is saved until you compose it |
preview_message |
campaigns.read | Signed link rendering the message for a contact |
Vocabulary: tools compose and bind; nothing is "created". A typical run:
import_template → update_manifest (fix any unknown types) → compose_message
→ bind_fields until summary.ready → preview_message.