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.