Appearance
MCP API Reference
OneHazel's MCP server speaks JSON-RPC 2.0 over HTTP POST. Every request goes to the same canonical endpoint:
POST https://api.onehazel.com/mcp-server
Authorization: Bearer oh_live_<your-key>
Content-Type: application/jsonYour bearer token identifies the operator — no operator ID in the URL.
Backward compatibility
The legacy form POST https://api.onehazel.com/mcp-server/<operator_id> still works. The server enforces that the segment matches the bearer's operator and returns AUTH_OPERATOR_MISMATCH if not.
For most operators, you won't write these calls by hand — Claude Desktop, Cursor, or any other MCP client handles the protocol. This page documents the wire format for when you're debugging or building a custom integration.
Protocol overview
- Protocol version:
2025-06-18 - Server name:
onehazel - Server version: see
serverInfo.versionin theinitializeresponse - Transport: JSON-RPC 2.0 over HTTP (Streamable HTTP). An SSE channel is mounted at
/mcp-server/eventsfor forward compatibility; current behaviour is a single comment frame then close.
The supported methods are listed below. Anything else returns JSON-RPC -32601 Method not found.
initialize {#initialize}
Sent by the client when the connection opens. Returns the server's protocol version, declared capabilities, and identity.
Request
json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": { "name": "claude-desktop", "version": "0.7.0" }
}
}Response
json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": { "listChanged": false }
},
"serverInfo": {
"name": "onehazel",
"version": "0.1.0"
}
}
}Notification follow-up
Clients send a notifications/initialized notification after initialize to signal readiness. OneHazel accepts it and returns no response (per JSON-RPC 2.0 §4.1).
ping {#ping}
Lightweight liveness check. Returns an empty result.
Request
json
{ "jsonrpc": "2.0", "id": 2, "method": "ping" }Response
json
{ "jsonrpc": "2.0", "id": 2, "result": {} }tools/list {#tools-list}
Returns the set of MCP tools the calling API key can invoke. Each tool corresponds to one OneHazel workflow with mcp_exposed=true. The list is filtered against the key's mcp_workflow_allowlist:
nullallowlist — every MCP-exposed workflow on your operator- Array allowlist — only the listed workflows (empty array = none)
Request
json
{ "jsonrpc": "2.0", "id": 3, "method": "tools/list" }Response
json
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"tools": [
{
"name": "refund_lookup",
"description": "Look up a refund by customer ID and date range",
"inputSchema": {
"type": "object",
"properties": {
"customer_id": { "type": "string", "description": "Customer ID to look up" },
"since": { "type": "string", "format": "date" }
},
"required": ["customer_id"]
}
}
]
}
}nameis the workflow'smcp_tool_name— a sluggified, JSON-RPC-safe slug derived from the workflow's display namedescriptioncomes from the workflow's description field (operator-editable)inputSchemais derived from the trigger node's effective event schema — fields the workflow expects on its trigger payload, including operator-defined overrides
If you change the trigger schema in the workflow editor, the next tools/list reflects it.
tools/call {#tools-call}
Runs a workflow as if a real trigger event had arrived. The call is synchronous from the client's perspective: OneHazel runs the workflow against your live connections and returns the result inline.
Request
json
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "refund_lookup",
"arguments": {
"customer_id": "cust_123",
"since": "2026-05-01"
}
}
}params.name— must match a tool returned bytools/listparams.arguments— must satisfy that tool'sinputSchema(required fields present, types matching)
Response
json
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [
{
"type": "text",
"text": "Workflow completed. 2 refunds found."
}
],
"isError": false,
"_meta": {
"execution_id": "exec_abc123",
"duration_ms": 412
}
}
}Execution semantics
tools/callruns the workflow inmode='test'— real API calls, real side effects, persisted toworkflow_executionswithmetadata.source='mcp'- Failures (missing required input, downstream connector error, quota exceeded) come back as JSON-RPC results with
isError: trueand the failure detail incontent - The trigger payload validation gate (see Workflows → Trigger schemas) runs before any node executes; missing required fields produce a
MISSING_TRIGGER_FIELDerror and zero downstream nodes run - Workflows are billed against the calling operator's quota exactly as any other run — every billable node increments your Actions / AI Actions counters
Real side effects
A tools/call invocation is a real workflow run. If your workflow sends emails, writes to a downstream system, or calls a paid third-party API, it'll do so. There is no dry-run mode on MCP. Use a least-privileged API key with a narrow allowlist when exposing workflows that have external side effects.
Error codes
JSON-RPC standard codes (protocol-level):
| Code | Name | When |
|---|---|---|
-32700 | Parse error | Body was not valid JSON |
-32600 | Invalid Request | Missing jsonrpc: "2.0", missing method, or malformed envelope |
-32601 | Method not found | Unknown method name |
-32602 | Invalid params | Params shape doesn't match the method |
-32603 | Internal error | Unhandled server-side exception |
OneHazel-specific codes (returned as JSON-RPC -32001 with the SCREAMING_SNAKE code embedded in error.message as code: XYZ):
| Code | HTTP status | Meaning |
|---|---|---|
AUTH_MISSING | 401 | Authorization header not present |
AUTH_INVALID_FORMAT | 401 | Header isn't Bearer <token>, or token isn't an oh_live_* key |
AUTH_INVALID | 401 | Key not found or doesn't match hash |
AUTH_REVOKED | 401 | Key exists but is revoked |
MCP_NOT_ENABLED | 403 | Key is valid but mcp_enabled=false — flip the toggle in Configure MCP |
AUTH_OPERATOR_MISMATCH | 403 | URL operator ID doesn't match the key's operator |
MISSING_TRIGGER_FIELD | 200* | A tools/call argument set is missing a required field from the workflow's trigger schema |
QUOTA_EXCEEDED | 200* | Operator has hit their Actions / AI Actions quota for the month |
*Workflow-execution errors are returned as JSON-RPC results with isError: true and HTTP 200, not as JSON-RPC errors. The SCREAMING_SNAKE code lives inside the result content.
The full OneHazel error taxonomy is enforced by the OH-134 drift guard — every code: '...' literal in Edge Functions is registered in _shared/errors.ts:ERROR_CODES.
CORS
OPTIONS preflight is supported on every route. The server echoes the request origin if it's in the configured allowlist (default * for the MCP endpoint; production may narrow this). Required request headers: Authorization, Content-Type. Required response headers are returned automatically.
SSE channel
GET https://api.onehazel.com/mcp-server/events?session=<id>
Authorization: Bearer oh_live_<your-key>
Accept: text/event-streamReturns text/event-stream with a : ping heartbeat every 15s to keep proxies from closing the connection. The channel survives across many JSON-RPC requests.
Clients that want progress notifications during long-running tools/calls should:
- Open the SSE channel above with a chosen
session=<id>query parameter - On the sibling POST to
/mcp-serverinclude the headerMcp-Session-Id: <id>and_meta.progressToken: "<token>"insideparams - Receive
notifications/progressframes on the SSE channel
Distributed session affinity (OH-461)
Progress events now reliably bridge across Edge Function instances. When the MCP_SSE_REDIS_AFFINITY server flag is on, the SSE GET handler subscribes to an operator-namespaced Upstash Redis pub/sub channel and the sibling POST publishes every notifications/progress frame to the same channel — so clients receive every frame regardless of which instance handled the POST. Heartbeats remain at 15s. The synchronous tools/call response is unchanged. With the flag off, the server falls back to per-instance behaviour (channel opens, heartbeats fire, but cross-instance progress events may not arrive).
Per-operator concurrent session cap is 5. The 6th /events connection returns MCP_SESSION_LIMIT_EXCEEDED.