The oHallo MCP server
Alongside calling the MCP servers you build, oHallo exposes its own MCP server. You can point an MCP client or an agent at it and work with your oHallo data and actions directly: brands, channels, knowledge base, policies, conversations, contacts, accounts, and agent definitions. It authenticates with a normal API key, and every tool is gated by that key’s scopes, exactly like the REST API.
Endpoint
Section titled “Endpoint”POST https://api.ohallo.eu/api/mcpThe server speaks JSON-RPC 2.0 over HTTP and implements the standard tools/list and tools/call methods.
Authentication
Section titled “Authentication”Send your API key as a Bearer token:
Authorization: Bearer sf_live_v1_...The key resolves your account. Each tool checks the key’s scopes and returns a permission_denied error if a required scope is missing, so grant the key the scopes for the tools you intend to call. See Authentication for the scope catalogue.
Listing the tools
Section titled “Listing the tools”curl -X POST https://api.ohallo.eu/api/mcp \ -H "Authorization: Bearer sf_live_v1_..." \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }'Calling a tool
Section titled “Calling a tool”The account is taken from your key, so you do not pass it. Workspace-scoped tools take a workspace_id argument (optional when the key is itself scoped to one workspace).
curl -X POST https://api.ohallo.eu/api/mcp \ -H "Authorization: Bearer sf_live_v1_..." \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "search_kb_entries", "arguments": { "workspace_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "query": "return policy" } } }'Tool catalogue
Section titled “Tool catalogue”Each tool requires the scope shown. This is the same permission model as the REST API, so a key that can read conversations over REST can also call the conversation read tools here.
| Tool | Required scope | Purpose |
|---|---|---|
get_context | any valid key | Return the account, brand, and workspace context the key reaches |
list_brands, get_brand | brands:read | List and read the brands the key reaches |
create_brand, update_brand | brands:write | Create a brand, safe to retry with the same slug and name; update a brand, where null clears an optional field and the slug stays fixed |
list_workspaces | workspaces:read | List the workspaces the key reaches |
get_workspace | any valid key | Read a workspace the key reaches, with its spoken greeting per language and whether each greeting’s AI-disclosure confirmation is valid |
list_channels, get_channel, check_channel_setup | channels:read | Read a workspace’s channels; check a channel’s setup, and an email channel’s DNS records |
create_chat_channel, create_email_channel, update_channel | channels:write | Create a chat or email channel; change a chat or email channel |
list_kb_entries, get_kb_entry, search_kb_entries, list_kb_topics | kb:read | List, read or search knowledge base entries; list the topics in use |
create_kb_entry, update_kb_entry, approve_kb_entry, reject_kb_entry | kb:write | Manage knowledge base entries |
list_policy_entries, get_policy_entry, search_policy_entries, list_policy_topics | policy:read | List, read or search policies; list the topics in use |
create_policy_entry, update_policy_entry, approve_policy_entry, reject_policy_entry | policy:write | Manage policies |
list_conversations, get_conversation, list_attention_items | conversations:read | Read conversations and attention items |
reply_to_conversation, compose_conversation, resolve_attention_item | conversations:write | Reply, compose, and resolve attention items |
search_contacts, get_contact | contacts:read | Read contacts |
create_contact, update_contact | contacts:write | Create and update contacts |
search_accounts, get_account, get_account_contacts | accounts:read | Read accounts |
create_account, update_account | accounts:write | Create and update accounts |
list_agents, get_agent | agents:read | Read agent definitions |
update_agent_instructions | agents:write | Update an agent’s instructions |
create_agent, update_agent | agents:write, and mcp:write for the agent’s MCP connections | Create or change an agent |
list_agent_models, check_agent_reachability | agents:read | List the models an agent can use; check whether the assistant can hand tasks to an agent in a workspace |
list_mcp_connections, get_mcp_connection | mcp:read | Read the MCP connections the key reaches, their tools and each tool’s governance |
register_mcp_connection, test_mcp_connection | mcp:write | Register one of the account’s business systems as a connection; call it again and record its status |
set_mcp_connection_instructions, set_mcp_tool_risk_classifications, set_mcp_tool_assurance, set_mcp_tool_scope | mcp:write | Govern a connection and its tools |
assign_mcp_tools_to_agent, detach_mcp_connection_from_agent | mcp:write, agents:write | Give an agent the tools it may call on a connection; remove its access |
list_mcp_guardrails | mcp:read | List a connection’s guardrails |
create_mcp_guardrail | mcp:write | Add a guardrail to a tool |
Call tools/list for the authoritative set and each tool’s input schema; the catalogue above is a summary.
Knowledge base and policies
Section titled “Knowledge base and policies”list_kb_entries and list_policy_entries return every entry of the workspace as a list, or those with the status given. With limit (1 to 100), cursor or both, they return one page in creation order, { "data": [...], "nextCursor": "..." }, of at most limit entries, 50 when only cursor is sent; pass nextCursor as cursor to read the next page, and the last page carries no nextCursor.
get_kb_entry and get_policy_entry take an entry_id and return that entry. list_kb_topics and list_policy_topics return { "topics": [...] }: the topics of the workspace’s approved entries, each once, sorted by name.
approve_kb_entry and approve_policy_entry approve an entry that awaits review, with status proposed or pending_review. The question, answer and topics sent, or for a policy the topic, rule_text and conditions, are approved in place of the entry’s own, and the fields not sent stay as they are. A proposal that changes an existing entry is applied to that entry: it takes the proposal’s fields, with the edits in place of the ones sent, and a knowledge base entry keeps its own question, and its own topics when the proposal has none and the edits give none; topics given as an empty list clear them. When that entry was edited after the proposal was written, the tool answers validation_error with the entry as it stands, and calling it again with expected_target_version_id set to that entry’s publishedVersionId applies the proposal onto it.
A workspace holds one knowledge base entry per question and one policy per rule text. A create, an update or an approval that would give a second one the same question or rule text answers validation_error naming the entry that holds it, to be changed with update_kb_entry or update_policy_entry instead. When the account reviews changes before they apply, an update is saved for approval in the dashboard, and its question or rule text is checked when it is approved.
Deleting an entry, its version history and restore use the REST API: for knowledge base entries Delete entry and Version history, for policies Delete entry and Version history.
Agents
Section titled “Agents”An agent is a specialist the assistant hands a task to when the task matches the agent’s description. The agents created in your account have the type custom, and these tools create and change them. The agents oHallo provides have the type platform: they are listed for reference, and a change to one answers permission_denied.
| Tool | Required scope | Purpose |
|---|---|---|
list_agents | agents:read | List the account’s agents: id, name, description, type, and for a custom agent its MCP connections and tools |
get_agent | agents:read | Read one agent, including its instructions |
create_agent | agents:write, mcp:write | Create an agent that uses one or more MCP connections |
update_agent | agents:write, and mcp:write when mcp_connections is sent | Change an agent; only the fields sent change |
update_agent_instructions | agents:write | Replace an agent’s instructions; null clears them |
list_agent_models | agents:read | List the models an agent can use |
check_agent_reachability | agents:read | Report whether the assistant can hand tasks to an agent in a workspace, and if not, why |
create_agent takes display_name, description, system_prompt and mcp_connections, the fields of the REST create in snake case, and needs all four. The description tells the assistant when to use the agent, so name the requests it covers. The system prompt is the agent’s instructions, how it does its work: get_agent returns it as systemPrompt, and update_agent_instructions replaces it on its own. mcp_connections is a list of { "connection_id", "allowed_tools" } entries: each connection comes from list_mcp_connections, and allowed_tools names the tools on it the agent may call. The optional model is an id from list_agent_models; without one the agent runs on Automatic. Each call to create_agent creates a new agent, so check list_agents first.
update_agent changes only the fields sent. mcp_connections replaces the agent’s connections and tools as a whole and names at least one connection; detach_mcp_connection_from_agent removes one. "model": null returns the agent to Automatic.
When your account reviews agent changes before they apply, update_agent and update_agent_instructions return { "pending": true, "versionId": "..." } and the change waits for approval in the dashboard. In that mode, an agent’s connections are changed with assign_mcp_tools_to_agent and detach_mcp_connection_from_agent, or the assignment endpoints.
Deleting an agent uses the REST API: Delete custom agent.
Setting up an agent
Section titled “Setting up an agent”list_mcp_connectionsfinds the connection to the business system and the names of its tools. A system that is not connected is registered first, as Registering a connection describes.list_agent_modelslists the models, when the agent should run on a particular one.create_agentcreates the agent with that connection and the tools it needs.check_agent_reachability, with the workspace the agent serves, confirms the assistant hands matching tasks to the agent there ("reachable": true). Otherwisereasonssays what to change:no_mcp_connectionsmeans the agent has no connection, andmcp_connection_unavailablemeans the connections inconnectionIdsdo not serve that workspace: each is scoped to another workspace or another brand, isinactive, or has been removed. A connection serves a workspace when it is scoped to that workspace, to the workspace’s brand or to the whole account.
Agents and restricted keys
Section titled “Agents and restricted keys”A key restricted to a brand or workspace gives an agent only connections its restriction covers. A connection outside the restriction answers validation_error, as an unknown one does, and a key restricted to a workspace answers permission_denied for a connection its brand shares across workspaces, as the assignment endpoints do. The key changes an agent only when every connection the agent uses is inside its restriction, and detaches an agent from any connection inside its restriction. Deleting an agent cannot be undone and takes a key without a restriction.
Connections
Section titled “Connections”| Tool | Required scope | Purpose |
|---|---|---|
list_mcp_connections | mcp:read | List the MCP connections the key reaches, with the names of their tools |
get_mcp_connection | mcp:read | Read one connection: its instructions, and each tool’s name, description and governance |
register_mcp_connection | mcp:write | Register one of the account’s business systems as a connection |
test_mcp_connection | mcp:write | Call the connection’s endpoint again and record the result as its status |
set_mcp_connection_instructions | mcp:write | Replace the instructions the assistant reads before using the connection |
set_mcp_tool_risk_classifications | mcp:write | Classify the risk of the connection’s tools |
set_mcp_tool_assurance | mcp:write | Raise the assurance a caller must reach before one tool runs |
set_mcp_tool_scope | mcp:write | Declare whose record one tool touches |
assign_mcp_tools_to_agent | mcp:write, agents:write | Give an agent the tools it may call on the connection |
detach_mcp_connection_from_agent | mcp:write, agents:write | Remove an agent’s access to the connection |
list_mcp_guardrails | mcp:read | List the connection’s guardrails |
create_mcp_guardrail | mcp:write | Add a guardrail to one of the connection’s tools |
Each connection carries its id, name, type, status and scope (tenant, brand or workspace, with brandId and workspaceId). A connection with the status active is available to the assistant. list_mcp_connections leaves out oHallo’s own connections (type system) unless include_system is true. A key restricted to a brand or workspace reads and changes the connections its restriction covers, and any other connection answers not_found. Deleting a connection, its version history and restore, replacing its whole guardrail set, and changing or removing one guardrail use the REST API: MCP connections.
Registering a connection
Section titled “Registering a connection”register_mcp_connection takes name, endpoint_url, the server’s public https address, and auth_type with the credential that type uses: api_key for a bearer token, headers for a server that authenticates by its own header names, or client_id, client_secret and token_endpoint for oauth2_client. brand_id or workspace_id sets where the connection serves; with neither it serves the whole account, and a key restricted to a brand or workspace registers inside its restriction. oHallo calls tools/list on the endpoint with the credential and registers the connection when that succeeds; otherwise the tool answers validation_error with what the endpoint said, and nothing is stored. The answer is connection_id, name, scope and status. Each call registers a new connection, so check list_mcp_connections first.
oHallo stores the credential in its secret store. A connection can also be registered with the REST API, Register a connection, and governed with the tools here.
Governing a connection’s tools
Section titled “Governing a connection’s tools”An agent calls a tool only once it has a risk classification and a scope declaration: set_mcp_tool_risk_classifications sets the classifications, and each one supplies the default assurance a caller must reach before the tool runs; set_mcp_tool_scope sets each tool’s declaration. get_mcp_connection returns each tool’s governance: its risk, the enforcedAssurance a caller must reach, its default and source, its scope declaration as oHallo enforces it, and callable, false for a tool that has no classification or no declaration.
A key raises assurance and never lowers it. set_mcp_tool_assurance takes a level at or above the tool’s enforcedAssurance; a lower level, a reclassification that lowers what a tool enforces, and an acknowledged argument each answer permission_denied. An owner or admin makes a lowering in the dashboard, where it is acknowledged.
set_mcp_tool_scope takes a declaration in the shape get_mcp_connection returns, which Declare the scope of each tool describes. create_mcp_guardrail adds a rule oHallo applies to the calls of one tool, which Guardrails describes. The instructions and risk classifications of oHallo’s own connections are configured by oHallo; their guardrails, assurance, scope declarations and the agents that call their tools are the account’s to set.
Giving an agent a connection’s tools
Section titled “Giving an agent a connection’s tools”assign_mcp_tools_to_agent takes connection_id, agent_id and allowed_tools, which replaces what the agent may call on that connection. It names at least one tool, each one the connection declares, and an agent created in the account (type custom). detach_mcp_connection_from_agent removes the access of an agent created in the account to a connection. The assistant applies either within five minutes, and check_agent_reachability confirms it. A key restricted to a brand or workspace assigns tools only to an agent whose every connection is inside its restriction, as it does with update_agent, and detaches an agent from any connection inside its restriction.
Channels
Section titled “Channels”| Tool | Required scope | Purpose |
|---|---|---|
list_channels | channels:read | List a workspace’s channels: email, chat, voice and WhatsApp |
get_channel | channels:read | Read one channel; a chat channel carries the parts of its embed code |
create_chat_channel | channels:write | Create a chat channel for a website |
create_email_channel | channels:write | Create an email channel in one of four inbound modes |
update_channel | channels:write | Rename a chat or email channel, turn it off or on, move it to another workspace, and change a chat channel’s origins and appearance |
check_channel_setup | channels:read | Report a chat or email channel’s status, and whether an email channel’s DNS records resolve |
Each call to create_chat_channel or create_email_channel creates a new channel, so check list_channels first.
Setting up a chat channel
Section titled “Setting up a chat channel”create_chat_channel takes display_name and, optionally, allowed_origins (each an origin such as https://www.example.com, which a page’s origin must match exactly), mode (floating, inline or sidebar), primary_color, position and suggested_prompts. The channel is active at once. The result carries embed, the parts of the code that puts the chat on a page:
{ "id": "...", "channelType": "chat", "status": "active", "publicKey": "ck_...", "embed": { "script_src": "https://widget.ohallo.eu/chat.js", "data_key": "ck_...", "data_api_base": "https://api.ohallo.eu", "mode": "floating" }}They assemble into the chat’s script tag, as Embedding widgets describes:
<script src="https://widget.ohallo.eu/chat.js" data-key="ck_..." data-api-base="https://api.ohallo.eu" async></script>For the inline mode, embed also carries data_target: add data-target="ohallo-chat" to the script tag and place <div id="ohallo-chat"></div> where the chat appears. get_channel and check_channel_setup return the same parts for an existing chat channel, which has no records to check: an active, enabled chat channel runs on every page that carries the code and whose origin allowed_origins permits.
Setting up an email channel
Section titled “Setting up an email channel”create_email_channel takes display_name, inbound_mode and the arguments that mode needs:
inbound_mode | How mail reaches oHallo | Arguments |
|---|---|---|
subdomain | A subdomain given to oHallo receives the mail itself | from_address, an address on that subdomain. An address on a registrable domain itself, such as support@example.com or support@example.co.uk, answers validation_error, and so does an address on a domain a channel of another oHallo account uses, or on a domain oHallo sends its own mail from |
forwarding | The mailbox stays where it is and forwards its mail to an oHallo address | from_address; mail_provider: office365, google or other |
mail_routing_rule | The mailbox stays where it is and a rule in its provider redirects its mail to an oHallo address; replies come from from_address itself, signed by three DKIM records on its domain. The MX record stays as it is | from_address; mail_provider: office365 or google |
inbox_api | oHallo reads and sends through a Microsoft 365 mailbox | mail_provider: microsoft365; from_address, the mailbox, which can also be entered in the dashboard |
The result is the channel with setup: inbound_address, where mail is delivered at oHallo (for forwarding and mail_routing_rule, the address the mailbox’s rule forwards or redirects the mail to), and dns_records, the records to publish at the domain’s DNS host, each with its purpose (verification, dkim, mx, spf or dmarc), type, name, value, and priority for MX. Provisioning continues after the call returns.
An inbox_api channel is finished in a browser: open the channel under Settings > Channels in the dashboard and choose Connect, which signs in to the mailbox’s provider. The channel stays in draft until then.
check_channel_setup lists each record with its state: verified, mismatch (with the value found as observed), not_found or pending. It returns the channel’s status, and when the status is error, provisioning_error with the reason when it is known; Retry on the channel’s page in the dashboard provisions it again. complete is true when the records that route and verify mail resolve: the verification TXT, the DKIM CNAMEs and, where the mode has one, the MX. forwarding and inbox_api need none. SPF and DMARC are listed with their state and do not count toward complete: SPF reads verified when it is a v=spf1 record that includes amazonses.com, one merged with your own senders included, and DMARC when it is v=DMARC1 with p=quarantine or p=reject, whatever report address it names. complete means the records resolve, not that mail has arrived. The status turns active once the domain is verified and its MX record resolves for subdomain, when the first message arrives for forwarding, mail_routing_rule and subdomain_with_redirect, and when the mailbox is connected for inbox_api. Each message the channel receives appears as a conversation under Conversations in the dashboard. A channel is checked at most six times a minute; more answer rate_limit.
Changing a channel
Section titled “Changing a channel”update_channel changes only the arguments sent. move_to_workspace_id moves the channel to another workspace, and with it under that workspace’s brand. Voice and WhatsApp channels are changed with the REST API or in the dashboard. Deleting a channel uses the REST API: Delete channel.
A key restricted to a brand or workspace lists, reads, creates and changes channels in the workspaces its restriction covers, and moves a channel only between them; any other channel or workspace answers not_found.
Keys restricted to a brand or workspace
Section titled “Keys restricted to a brand or workspace”For a key restricted to a brand or a workspace, list_brands, list_workspaces and get_context return what the key reaches. A tool that takes a workspace_id acts only in a workspace the key reaches, and the conversation tools act only on conversations, channels and attention items in those workspaces. A workspace, conversation or channel outside the restriction answers not_found. Scoping keys to brand or workspace describes the restrictions.
Changes
Section titled “Changes”list_workspacesrequiresworkspaces:read. A key created without that scope receivespermission_deniedfromlist_workspaces. Create a key that includesworkspaces:readin Settings > API Access and move the integration to it.
Errors
Section titled “Errors”A tool returns a structured error object on failure:
{ "error_type": "permission_denied", "message": "Missing scopes: kb:write" }Common error_type values are authentication_error, permission_denied, not_found, validation_error, rate_limit, and server_error.
When to use this instead of REST
Section titled “When to use this instead of REST”Use the REST API for ordinary request and response integrations. Use this MCP server when you want an agent or an MCP-aware client to discover and call oHallo capabilities as tools, with the same account scoping and permissions as your API keys.
Connecting an MCP client covers the client side: the claude mcp add command, the mcp.json configuration shape for any Streamable HTTP client, the packaged Claude Desktop extension, and the Context7 library id for this documentation.