MCP Connections
An MCP connection is one business system the assistant can act in: an order system, a CRM, a booking system. oHallo calls tools on it during a conversation. This page covers registering a connection and governing it from the API; Build an MCP Server covers writing the server itself. An account is wired up end to end from the API: create the brand, its workspaces and channels, then register the connections and assign their tools to an agent.
Required scope: mcp:read for GET endpoints, mcp:write for write endpoints. Assigning tools to an agent also needs agents:write.
Registering a connection, testing it, governing its tools and giving an agent its tools are also tools on the oHallo MCP server, under the same scopes and rules: Connections.
Register a connection
Section titled “Register a connection”curl -X POST "https://api.ohallo.eu/api/mcp-connections" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \ -H "Content-Type: application/json" \ -d '{ "name": "Example Company orders", "endpointUrl": "https://mcp.example.com/mcp", "authType": "api_key", "apiKey": "'"$EXAMPLE_MCP_KEY"'", "workspaceId": "a1a2c3d4-5678-4abc-8ef0-1234567890ab" }'Response (201 Created):
{ "connectionId": "c3d4e5f6-7890-4abc-8ef0-1234567890ab"}name and endpointUrl are required. The endpoint is a public https address, so an address on a private network is refused with 400.
The request body carries the credential oHallo presents to that server, under the field its authType uses: apiKey for a bearer token, headers for a server that authenticates by its own header names, or clientId, clientSecret and tokenEndpoint for oauth2_client. The value is a secret. Send the request over https, read it from an environment variable rather than typing it inline, and keep it out of shell history and source control. oHallo writes it to its secret store and returns it from no endpoint.
Registration discovers the tools: oHallo calls tools/list on the endpoint with the credential, and creates the connection only if that succeeds. An endpoint that cannot be reached, rejects the credential, or reports no tools answers 422 with what the endpoint said in detail, and nothing is stored.
brandId and workspaceId set the connection’s scope. With both left out the connection serves the whole account; with brandId it serves one brand; with workspaceId it serves one workspace. A restricted key registers only within its own restriction.
The tools are not in use yet. Assign them to an agent, below.
List connections
Section titled “List connections”curl "https://api.ohallo.eu/api/mcp-connections?scope=tenant-built" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"Returns the connections within the key’s reach, each with its scope, its status and how many tools it has. Every account also carries oHallo’s own system connections for the knowledge base, policies, the calendar and the web reader; scope=tenant-built leaves them out.
Get a connection
Section titled “Get a connection”curl "https://api.ohallo.eu/api/mcp-connections/c3d4e5f6-7890-4abc-8ef0-1234567890ab" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"Returns the connection with its endpoint, the tools discovered on it and their schemas, and the governance on each tool under toolGovernance: its risk classification, the assurance a caller must reach, its scope declaration as oHallo enforces it, callable, false for a tool an agent does not call (one with no risk classification or no scope declaration), the last scope outcome recorded on it, and factorRequirementUnreachable, true when the tool’s stored factor requirement asks for what no verification method gives. The credential is not part of the response.
Test a connection
Section titled “Test a connection”curl -X POST "https://api.ohallo.eu/api/mcp-connections/c3d4e5f6-7890-4abc-8ef0-1234567890ab/test" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"Calls the endpoint again with the stored credential and records the result as the connection’s status: active, degraded or error. Use it after changing the server or rotating its credential. It takes mcp:write rather than mcp:read, because it makes oHallo open an outbound connection and it rewrites stored state.
Assign tools to an agent
Section titled “Assign tools to an agent”curl -X PUT "https://api.ohallo.eu/api/mcp-connections/c3d4e5f6-7890-4abc-8ef0-1234567890ab/assignments" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \ -H "Content-Type: application/json" \ -d '{ "assignments": [ { "agentId": "d4e5f6a7-8901-4abc-8ef0-1234567890ab", "allowedTools": ["get_order", "list_orders"] } ] }'A tool is called only by an agent it is assigned to, so this is the step that puts a connection to work. Agent IDs come from GET /api/agent-definitions. Each entry replaces that agent’s allowed set for this connection.
An empty allowedTools is not a revocation. An empty list places no restriction, so the agent may call every tool on the connection. To narrow an agent’s access, send the tools it should keep. To remove its access entirely, detach it:
curl -X DELETE "https://api.ohallo.eu/api/agent-definitions/d4e5f6a7-8901-4abc-8ef0-1234567890ab/mcp-connections/c3d4e5f6-7890-4abc-8ef0-1234567890ab" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"Both endpoints need agents:write as well as mcp:write, and both act on an agent created in the account: an agent oHallo provides answers 403 with code: "platform_agent", and an agent the account does not have answers 404. An assignment names tools the connection declares; another name answers 422 with undeclaredTools and the connection’s declaredTools, and nothing is written. The Identity Verification connection, whose tools oHallo calls itself when it verifies a customer, is assigned to no agent: an assignment of it answers 422 with its connectionId, and nothing is written. Assigning tools changes the agent, so a key restricted to a brand or workspace assigns them only to an agent whose every connection is inside its restriction, as when it updates an agent; another agent answers 403 with code: "key_lock". A restricted key detaches an agent from any connection inside its restriction.
Classify each tool’s risk
Section titled “Classify each tool’s risk”curl -X PATCH "https://api.ohallo.eu/api/mcp-connections/c3d4e5f6-7890-4abc-8ef0-1234567890ab/tool-risk-classifications" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \ -H "Content-Type: application/json" \ -d '{ "classifications": { "get_order": "read_pii", "list_orders": "read", "cancel_order": "destructive" } }'The classification sets the assurance a caller has to reach before oHallo runs the tool. read runs for anyone; read_pii, write and write_pii require an identified caller; destructive requires high assurance. A tool with no classification is not called at all, and an agent does not call a tool with no scope declaration (Declare the scope of each tool), so classify and declare every tool an agent is meant to use. The body replaces the map, and a tool the connection still lists keeps its classification when the map omits it: a classification changes, it is never removed. A tool whose scope declaration names a record it creates stays in a write class: reclassifying it read or read_pii answers 422 with originates_on_read. A tool declared "unscoped" stays read: reclassifying it answers 422 with unscoped_needs_read. A tool declared at customer or account level with no injectArg stays read or read_pii: reclassifying it write, write_pii or destructive answers 422 with inject_arg_required. A reclassification that lowers a tool is made on the dashboard, where an administrator acknowledges it: one that drops the assurance the tool enforces, and one that has a call to the tool ask a customer to approve less, a read where it asked for a change, or a change where it asked for something that cannot be undone. A key that sends one receives 403 with code: "key_lock".
Raise the assurance one tool requires
Section titled “Raise the assurance one tool requires”curl -X PUT "https://api.ohallo.eu/api/mcp-connections/c3d4e5f6-7890-4abc-8ef0-1234567890ab/tools/get_order/assurance" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \ -H "Content-Type: application/json" \ -d '{ "assuranceLevel": "high-assurance" }'Sets one tool’s assurance level. The classification supplies the default; a key sets the level at or above the level the tool currently enforces, and any relaxation is made on the dashboard. A level below the default is set there, where an administrator acknowledges it; a key that lowers, or that sends acknowledged, receives 403 with code: "key_lock". Two rules hold whatever is sent: a destructive tool is never anonymous, and a level below identified needs a declared scope. A tool declared at customer level requires at least identified: a setting of anonymous is kept, and the tool is enforced and reported at identified. A refused setting answers 422 with the reason: destructive_never_anonymous, originates_on_read, unscoped_needs_read, inject_arg_required, lowered_below_identified_needs_scope, tool_unclassified or acknowledgement_required.
Declare the scope of each tool
Section titled “Declare the scope of each tool”curl -X PUT "https://api.ohallo.eu/api/mcp-connections/c3d4e5f6-7890-4abc-8ef0-1234567890ab/tools/create_quote/scope" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \ -H "Content-Type: application/json" \ -d '{ "scope": { "level": "record", "refArgs": [], "originates": "quote_id" } }'curl -X PUT "https://api.ohallo.eu/api/mcp-connections/c3d4e5f6-7890-4abc-8ef0-1234567890ab/tools/get_quote/scope" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \ -H "Content-Type: application/json" \ -d '{ "scope": { "level": "record", "refArgs": ["quote_id"], "ownerField": "quote_id" } }'Response (200 OK):
{ "connectionId": "c3d4e5f6-7890-4abc-8ef0-1234567890ab", "toolName": "get_quote", "scope": { "level": "record", "refArgs": ["quote_id"], "ownerField": "quote_id" }}The scope declaration says whose record a tool call touches, and oHallo checks every call against it. A tool with no declaration is not called: the call answers policy_denied, with a reason that tells the agent not to retry and to offer the customer a colleague, before any verification is asked for. A customer lookup the connection designates is still read to find and verify customers; an agent calls it only once it is declared. Declare each tool on its own. A tool that returns nothing anyone owns, a catalogue, opening hours or delivery pricing, is declared "unscoped", which the dashboard shows as General information, for anyone; oHallo accepts that on a tool classified read that no customer lookup on the connection uses, and treats an "unscoped" stored on any other tool as no declaration. A tool that returns records is declared at record, customer or account level. A tool that changes something fits one of these only when it creates the record it acts on (record level, with originates) or acts on the number oHallo fills in (customer or account level, with injectArg); a tool that changes whatever record a request names fits none, and is not called.
At customer level a verified customer reaches only their own records, and at account level only the records of the company their record names, whatever the request names: oHallo reads every record of every answer, lists and error bodies included, and withholds anything owned by someone else or by no one. ownerField names the field holding the owner’s number, through one list with [] (orders[].customer_id); injectArg names the input oHallo fills with the verified customer’s own customer reference, or at account level their company account number, on every call, and is one of the inputs the tool publishes; otherPartyFields names default owner fields (customer_id, account_id, client_id, owner_id, buyer_id, in any spelling) that name someone else on the record, such as the customer who placed a company’s order; refArgs names the inputs that name a record, such as order_id on a tool that reads one order, whose value the customer is shown as the request when asked to verify. A tool with no injectArg names at least one, and a tool that lists records names none. A caller who has not verified is refused scope_unverified before the tool runs. A tool classified write, write_pii or destructive at either level names an injectArg, and oHallo runs a call at either level only while its injectArg is an input the tool publishes; otherwise the call is refused with policy_denied before the tool runs.
At record level a caller acts on the records their own conversation created. A tool that creates a record names the response field carrying its id in originates; oHallo reads the id from the result and holds it for the conversation. A tool that acts on a record names the argument carrying that id in refArgs and the response field naming the record in ownerField, through a list with [] when the record sits in one (quotes[].quote_id); oHallo refuses the call before it runs when the id is not one the conversation created, and withholds a result whose owner is not. The refusal is one body, error_type: scope_unbound, the same for an id from another conversation, an expired one and one that never existed. A record is held for the conversation that created it for at most twelve hours, or for the workspace’s identified reauthentication window when that is shorter.
A declaration is checked against the tool and the connection’s other declarations, and a 422 names what is wrong in reason: tool_unclassified when the tool has no risk classification, unscoped_needs_read when "unscoped" is declared on a tool classified other than read, unscoped_on_lookup when it is declared on a tool a customer lookup on the connection uses, originates_on_read when a read or read_pii tool names a created record, originates_echoes_argument when the path names an argument of the same tool, originates_invalid when the originates path names a field inside a list, owner_field_required when neither ownerField nor originates is set, owner_field_invalid when ownerField holds an empty segment, a numeric index such as [0], a trailing [] or a second [], other_party_field_invalid, other_party_field_not_returned and other_party_fields_not_applicable for an otherPartyFields entry that is no default owner name or is the owner’s own, that the declared output does not carry, or that is set at record level, ref_arg_without_producer when no tool on the connection originates the reference an argument carries, inject_arg_required when a tool classified write, write_pii or destructive at customer or account level names no injectArg, ref_arg_unknown when a refArgs or injectArg entry is not an argument of the tool, or an injectArg is set on a tool that publishes no inputs, list_needs_inject_arg when a customer or account level ownerField names a list and no injectArg is set, ref_args_required when a record level tool that does not originate, or a customer or account level tool with no injectArg, names no reference argument, produced_argument_not_referenced when a write-class tool leaves out of refArgs an argument that a tool on the connection originates, originates_identifier_field when the originates path runs through a field designated on the connection as the eID reference or the national identity number, destructive_never_anonymous when the tool is destructive and its assurance setting is anonymous, national_id_field_in_scope when ownerField, injectArg or an otherPartyFields entry names the field designated on the connection as the national identity number, customer_lookup_missing when the level is account, or customer on a tool at high-assurance or one classified write, write_pii or destructive, and no customer lookup on the connection designates the customer reference with the eID reference or the national identity number on a tool classified read or read_pii, customer_tie_lookup_missing when the level is customer on a tool classified read or read_pii at identified and the connection holds neither such a lookup nor one that designates the customer reference with the email field on a tool classified read or read_pii: the lookup by email address, which also designates the email argument, the tool’s own designation, or the one lookup the tool’s inputs reach, nor a lookup by phone number on such a tool, one that designates the customer reference with the phone argument and the phone field, account_lookup_missing when the level is account and no such lookup designates the company account number. At account level a lookup for a business login on a tool classified read or read_pii, one that designates the customer reference, the company account number and the company registration number, also meets both rules, since a MitID Erhverv login ties the employee to that company. At customer level a tool classified read or read_pii at identified opens for a customer tied to their own record by National eID, by the email link, by the email sender address or by Caller ID, so the lookups those read meet the rule; a tool at high-assurance, and one that changes something, opens through National eID alone. Declare the creating tool first, then the tools that act on what it creates.
Every declaration publishes a connection version, recording who made it. GET /api/mcp-connections/{connectionId} returns each tool’s declaration as oHallo enforces it under toolGovernance[tool].scope, null for a tool it does not call for want of one, and its last outcome under toolGovernance[tool].scopeOutcome, as outcome and the time at, null until the first. outcome is one of:
captured: a successful call returns the created record’s id at theoriginatespath, and oHallo holds it for the conversation.originates_missing: a successful call returns no id at theoriginatespath.originates_refused: oHallo does not hold the id at theoriginatespath of a successful call: the tool is classifiedreadorread_pii, or the path runs through the field designated on the connection as the eID reference or the national identity number, whether or not an id is returned; or the result carries more than one id there, the id repeats one of the call’s arguments, another conversation on the connection already holds it, the sender is not proven, the call has no conversation or caller to hold it for, or the conversation’s held records expire as it is written.set_write_failed: a successful call returns an id at theoriginatespath, and the write that holds it for the conversation fails.owner_absent: atrecordlevel, on a tool declared with anownerField, no part of a result names an owner there and no created-record outcome applies, and the result is returned. Atcustomerandaccountlevel, a result, or any part of it, names no owner atownerFieldthat can be checked, and no record in its text content or structured content names another party: a record or list element without one, a list element that is not a record, a JSON scalar, text that is not JSON, an item that is not text, a field beside the content, a missing list, a result with no content, or a declaration with noownerField; the result is withheld. An empty list passes at those levels and records nothing.mismatch: a record in the result’s text content or structured content names a record or party the caller does not hold, and the result is withheld: atrecordlevel a value under theownerFieldname or arefArgsname that is not a record the conversation created; atcustomerlevel anyone but the verified customer atownerField, or anyone but that customer or their company under theownerFieldname or a default owner name; ataccountlevel anyone but their company atownerFieldor under either name;otherPartyFieldsexcepted. A JSON number that is not a whole number from 0 to 9007199254740991 under any of these names counts as another party. A result with a record in its text content or structured content that names another party, and also a part with no owner, recordsmismatch.
A withheld result records owner_absent or mismatch in place of any created-record outcome. On the dashboard the same declarations are edited on the connection’s Customer records section.
Guardrails on a tool
Section titled “Guardrails on a tool”curl -X POST "https://api.ohallo.eu/api/mcp-connections/c3d4e5f6-7890-4abc-8ef0-1234567890ab/guardrails" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \ -H "Content-Type: application/json" \ -d '{ "toolName": "cancel_order", "policyType": "approval_gate", "condition": "args.amount > 1000" }'A guardrail is a rule oHallo applies to a tool call: approval_gate holds the call for a person, deny refuses the call, mask removes named fields from the result, and escalate lets the call run, and when its condition matches a successful answer, the agent that called the tool receives an escalation_signal with the answer. A condition names the call’s arguments as args.<name>; an escalate condition is graded after the call and can also name the result, whose data is at result.structuredContent.<field>. With a condition an approval_gate, deny or mask rule applies only to calls whose arguments match it; without one a rule applies to every call of the tool. Guardrails describes the rules in full.
GET /api/mcp-connections/{connectionId}/guardrails lists a connection’s rules. PATCH and DELETE /api/mcp-call-guardrails/{guardrailId} change and remove one. PUT /api/mcp-connections/{connectionId}/policies replaces the whole set in one call.
Instructions
Section titled “Instructions”curl -X PATCH "https://api.ohallo.eu/api/mcp-connections/c3d4e5f6-7890-4abc-8ef0-1234567890ab/instructions" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \ -H "Content-Type: application/json" \ -d '{ "instructions": "Order numbers are six digits. Ask for one before searching." }'Instructions are your guidance on the connection. The assistant reads them before it uses the connection’s tools, and it reads the connection’s records through them when it decides, writes and checks a reply: a note such as “Amounts in fields ending in _minor are in cents” tells it that price_minor: 51124 is 511.24. Instructions hold at most 8 KB (8,192 bytes of UTF-8); a longer text is refused with 422 and instructions_too_long. null clears them.
Version history
Section titled “Version history”curl "https://api.ohallo.eu/api/mcp-connections/c3d4e5f6-7890-4abc-8ef0-1234567890ab/versions" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"Every change to a connection’s configuration publishes a version, recording who made it: a user ID for a person, api-key:<id> for a key. GET /api/mcp-connections/versions/{versionId} returns what one version held, and POST /api/mcp-connections/{connectionId}/versions/{versionId}/restore publishes it again as a new version, leaving the history complete. A version carries each tool’s risk classification, assurance setting and scope declaration, so a version that enforces less assurance on any tool than the connection does today, or that has a call to a tool ask a customer to approve less, is restored on the dashboard, behind the same acknowledgement as any other lowering; a key that restores one receives 403 with code: "key_lock". The credential and its location in the secret store are not part of a version. Only a version that was published restores. A version whose server address differs from the connection’s is restored only to a public https address (400 otherwise), and a system connection’s address is never restored.
Delete a connection
Section titled “Delete a connection”curl -X DELETE "https://api.ohallo.eu/api/mcp-connections/c3d4e5f6-7890-4abc-8ef0-1234567890ab" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"Removes the connection, its credential, and its assignment to every agent that used it. Deleting a connection needs a key without a brand or workspace restriction.
Keys restricted to a brand or workspace
Section titled “Keys restricted to a brand or workspace”A connection carries its own scope, and a key carries its own restriction; a key reaches a connection when the two agree. MCP connections with a restricted key lists what each restriction allows. A connection outside the restriction answers 404; an action the restriction does not allow, on a connection the key can see, answers 403 with "code": "key_lock".
oHallo’s own system connections, which carry the knowledge base, policies, the calendar and the web reader, are configured by oHallo: a key does not change their instructions or their tools’ risk classifications, and does not restore a past version of one. Everything the account decides about them stays open, because those decisions are the account’s: the calendar is a system connection, so an approval gate before the assistant books a meeting is a guardrail on one. Guardrails, the assurance a tool requires, and which agents may call its tools all work as they do on a connection you registered yourself.