Skip to content
AISARAISAR

MCP server

Connect an AI agent to your AISAR workspace over the Model Context Protocol.

AISAR exposes a Model Context Protocol server — this lets any MCP-capable AI client (Claude Code, claude.ai, Cursor, and others) work with your workspace through self-describing tools, without hand-writing an integration against every REST endpoint.

Endpoint and authentication

The server speaks Streamable HTTP:

text
POST https://mcp.aisar.app/mcp/company

Authentication uses the same company API token as the REST API (created in the cabinet: Settings → Systems → API, requires the Business plan), sent in the Authorization header:

text
Authorization: Bearer 123|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
The server only accepts service-account tokens — the same tokens the REST API uses. Mobile session, embed-widget, and browser-session tokens are rejected. An active company subscription is also required.

Transport is Bearer-only — there is no cookie/session path and no OAuth flow. A request without a valid token gets 401; a token of the wrong class gets 401/403.

Connecting a client

Claude Code

bash
claude mcp add --transport http aisar https://mcp.aisar.app/mcp/company \
  --header "Authorization: Bearer YOUR_API_TOKEN"

claude.ai

Settings → Connectors → Add custom connector, URL https://mcp.aisar.app/mcp/company, header Authorization: Bearer YOUR_API_TOKEN.

Generic MCP client (e.g. Cursor)

json
{
  "mcpServers": {
    "aisar": {
      "url": "https://mcp.aisar.app/mcp/company",
      "headers": {
        "Authorization": "Bearer YOUR_API_TOKEN"
      }
    }
  }
}

After connecting, call the whoami tool first — it confirms which company the token maps to.

Server tools

Documentation

These read the platform's live documentation straight from the server — answers are always in sync with the latest deploy.

ToolPurpose
list_docsList available documents
list_doc_sectionsA document's section headings
get_docA whole document, or one section
search_docsFull-text search across the docs
list_webhook_eventsThe platform's webhook event catalogue
get_webhook_payload_specThe exact payload spec for one event
lookup_error_codeExplanation of a WhatsApp Cloud (Meta) error code, e.g. 131047

Reads

ToolArguments
whoami
list_channelsstatus?, type?, page, per_page
list_funnels
list_dealsfunnel_id?, stage_id?, status?, search?, page
get_dealdeal_id
search_contactsquery, page
get_contactcontact_id
list_conversationsstatus?, channel_id?, assigned_to_me?, search?, page
get_conversationconversation_id
search_messagesq, conversation_id? (omit to search the whole company), per_page? (max 50), page? — rate-limited
list_templateschannel_id?, language?, sendable_only?
list_broadcasts / get_broadcast—, broadcast_id
get_dashboard_summaryperiod? (today/7d/30d)

Every response uses an explicit field allow-list — channel credentials, provider keys, integration secrets, tokens and passwords are never returned.

Writes

Every write tool enforces the matching permission, is scoped to the token's company, and is audited.

ToolArguments
send_messageconversation_id, text
send_template_messagechannel_id, contact_id\|conversation_id, template_id, variables?
create_contact / update_contactcontact fields
add_internal_noteconversation_id, text
assign_conversationconversation_id, user_id\|team_id
close_conversationconversation_id
create_deal / update_dealdeal fields (stage_id must belong to the funnel)
move_deal_stagedeal_id, stage_id
create_deal_notedeal_id, text

Webhook self-service

ToolArguments
list_integrations
create_webhook_integrationurl, events[], signing_secret?
test_webhookintegration_id\|url
get_webhook_deliveriesintegration_id

The webhook URL passes the same SSRF guard as the REST integration-creation endpoint (private/loopback/link-local/metadata addresses are refused, including DNS-rebinding and encoded-IP forms); there is a per-company cap on the number of integrations; event names are validated against the platform's event catalogue.

AI agents & voice

Managing a company's AI agents (including the voice profile) is the same functionality available on the "AI agents" tab in the cabinet. list_ai_agents/get_ai_agent/create_ai_agent/update_ai_agent/activate_ai_agent/deactivate_ai_agent manage the text/chat side of an agent (prompt, enabled tools, guardrails, budgets); list_knowledge_documents/create_knowledge_document/delete_knowledge_document manage its knowledge base; get_voice_agent/update_voice_agent manage the optional voice profile. As with every tool on this server, the company id is never passed as an argument — the scope is always "your own company" from the token.

ToolArguments
list_ai_agentsstatus? (active\|inactive\|suspended)
get_ai_agentinstance_id
create_ai_agentname, provider, model, config (config.system_prompt required), optional template_id, description, api_key_id, budget limits
update_ai_agentinstance_id, same fields as create_ai_agent, partial — only the fields given are changed, config is deep-merged onto the existing one
activate_ai_agent / deactivate_ai_agentinstance_id
list_knowledge_documentsinstance_id?
create_knowledge_documentinstance_id, title, source_type (text\|url), content\|source_url (rate-limited)
delete_knowledge_documentinstance_id, document_id
get_voice_agentinstance_id
update_voice_agentinstance_id + voice fields (language, voice_id, greeting, follow-up, escalation, TTS tuning, voice_tools) — only the fields given are changed
The voice tools (get_voice_agent, update_voice_agent) run three checks in order: the company is in the voice AI pilot → it has the ai_agent.* permission → the plan has the voice_ai feature (Business and up). A company outside the pilot gets a plain error message rather than data — that is expected behaviour, not a bug.

In update_voice_agent, the voice_tools field covers only voice-exclusive settings with no text-agent equivalent — currently the single key history ({enabled: bool}, reading back the chat tail during a call). Every other voice agent tool (CRM, messaging, human transfer) is switched on through the same enabled_tools on update_ai_agent used for the text agent on the same instance_id.

Editing the system prompt (update_ai_agent) or the knowledge base (create_knowledge_document/delete_knowledge_document) automatically queues a resync with the voice vendor for agents that have a voice profile — no separate publish step is needed.

Named external actions

A named external action is a saved recipe for calling one operation on one external HTTP API (host, path, method, auth, response handling) with typed parameters the AI agent fills in itself when it calls the action by name. create_external_action/update_external_action/list_external_actions/get_external_action/delete_external_action manage the action itself; bind_external_action/unbind_external_action attach it to a specific agent instance — creating an action does not, by itself, let any agent call it; test_external_action sends one real test call with sample parameter values. create_ai_agent_secret/list_ai_agent_secrets create and list the credentials an action authenticates with: the secret value is write-only and is never returned anywhere, the response carries only an id (for secret_id) and a masked hint — creating a secret used to require leaving MCP for REST, now the whole "secret → action → test → bind" path stays inside MCP. update_ai_agent_secret updates a secret in place, keeping its id — this is the rotation path: secret is optional, sending it replaces the value and recomputes the masked hint, omitting it changes only the name, header_name/query_param, or allowed_domains, and the value is still never returned either way; the response carries a rotated flag showing whether the value actually changed. delete_ai_agent_secret deletes a secret but refuses while any action still references it — listing their slugs in the error — because external_actions.secret_id is a plain reference, and deleting the secret out from under an action would leave it looking configured while failing every call with an authentication error that explains nothing; the actions need to be pointed at another secret or removed first. In practice that means a compromised key gets rotated with update_ai_agent_secret, not deleted and recreated — the actions bound to it keep working throughout. list_external_action_calls reads the call log with filters by action, outcome, and transport — without it there was no way to find out why an agent-visible call failed. Thirteen tools in total. Whether a call is a write is derived by the server from the HTTP method and is never accepted as an input — force_write may only ratchet a GET action UP to write status, never downgrade a POST/PUT/PATCH/DELETE to a read. All of this requires the ai_action.manage permission, which by default is granted only to the company's admin role — DIFFERENT from ai_agent.update, holding one does not imply the other. Not available for companies on the Kids product. Calling an action works over both transports — from a live chat and during a voice call (governed by the available_in field, see the table below).

ToolArguments
create_external_actionname, model_description (≥20 chars — how the model decides WHEN to call it), method, base_url (scheme+host only, no {{...}}), optional path_template/query_template/header_template, body_type+body_template, parameters[] (what the model fills in), context_bindings[] (what the server fills in), secret_id+auth_type, response_config, timeout_ms, retries, available_in, force_write, log_response, optional slug (immutable after creation)
update_external_actionid + any subset of create_external_action's fields (partial — only the fields given are changed), plus status (draft\|active\|disabled); slug cannot be changed
list_external_actionsstatus? (draft\|active\|disabled)
get_external_actionid
delete_external_actionid (soft delete — bindings to agents are removed, call history is preserved)
bind_external_actioninstance_id, external_action_id, enabled? (default true), hint?, confirmation_required? (chat only)
unbind_external_actioninstance_id, external_action_id
test_external_actionid, parameters? (sample values)
create_ai_agent_secretname, auth_type (bearer\|basic\|api_key_header\|api_key_query), secret (the value — write-only, never returned), header_name\|query_param (for api_key_*), allowed_domains[] (effectively required — a secret with an empty list fails validation for every action)
list_ai_agent_secrets— (your company's secrets only; the value is never returned, only preview_hint)
update_ai_agent_secretid, optional name, secret (the new value — this is the rotation; omit to leave the current value untouched), header_name\|query_param, allowed_domains[] (replaces the current list wholesale)
delete_ai_agent_secretid (refuses if the secret is still used by any action — their slugs are listed in the error text)
list_external_action_callsaction_id?, outcome? (ok\|refused\|budget_exceeded\|rate_limited\|http_error\|timeout\|blocked), transport? (chat\|voice\|test), per_page?, page?

base_url is scheme+host only, no {{...}} (the action's destination never depends on the model); path_template/query_template/header_template/body_template are templates with {{name}} substitutions. Headers get a tighter rule: header_template may only reference names from context_bindings (what the server fills in), never parameters (what the model fills in) — a model-supplied value can never physically reach an HTTP header, and Authorization/Host/Cookie plus a few other names are reserved outright. A secret passed as secret_id must have a non-empty allowed_domains covering base_url's host — this is checked already at create_external_action/update_external_action, not only at call time.

Action lifecycle: create_external_action creates it in draft status → test_external_action verifies the call with real sample parameters (works in any status, including draft) → update_external_action sets it to activebind_external_action attaches it to an agent. test_external_action is rate-limited (see "Limits & behaviour") and NEVER accepts or returns the raw response body — only model_sees (what the model would see) and a masked request_preview of what was sent. This is deliberate: a saved base_url can be edited, so it is auditable but not trusted — returning the raw body would recreate exactly the SSRF read-oracle this feature guards against elsewhere.

Limits & behaviour

  • Rate limits (per token, per company): search_messages — 30/min, send_message and send_template_message — 60/min, create_webhook_integration — 10/min, create_knowledge_document — 20/min, named external action calls (chat and voice combined) — 60/min per company, test_external_action — a separate 10/min, documentation tools — 120/min.
  • External-action call ceilings (independent of the rate limit above): at most 3 calls per model conversation turn, 20 per conversation per hour, 5 per voice call — bounding the blast radius of a looping call or a successful prompt injection.
  • Pagination: per_page is capped at 50.
  • No bulk send: messaging tools only reach an existing conversation or a single contact — there is no mass-broadcast surface here (use the Broadcasts feature in the cabinet for that).
  • Untrusted content: message bodies, contact names and notes returned by these tools are third-party data, not instructions for the model.

Troubleshooting

SymptomCause / fix
401 UnauthorizedThe token is missing or invalid.
403 ForbiddenThe token is not a service-account API token (e.g. a mobile/embed token), or the subscription is inactive / the api feature is off.
A tool returns "not found" for an id that existsThe id belongs to a different company — tenancy is enforced on every argument.
"Rate limit exceeded"A per-tool limit was exceeded (see "Limits & behaviour") — retry shortly.