Skip to content
For LLMsView as Markdown·

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/json

Your 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.version in the initialize response
  • Transport: JSON-RPC 2.0 over HTTP (Streamable HTTP). An SSE channel is mounted at /mcp-server/events for 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:

  • null allowlist: 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"]
        }
      }
    ]
  }
}
  • name is the workflow's mcp_tool_name, a sluggified, JSON-RPC-safe slug derived from the workflow's display name
  • description comes from the workflow's description field (operator-editable)
  • inputSchema is 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 by tools/list
  • params.arguments: must satisfy that tool's inputSchema (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/call runs the workflow in mode='test', real API calls, real side effects, persisted to workflow_executions with metadata.source='mcp'
  • Failures (missing required input, downstream connector error, quota exceeded) come back as JSON-RPC results with isError: true and the failure detail in content
  • The trigger payload validation gate (see Workflows → Trigger schemas) runs before any node executes; missing required fields produce a MISSING_TRIGGER_FIELD error 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):

CodeNameWhen
-32700Parse errorBody was not valid JSON
-32600Invalid RequestMissing jsonrpc: "2.0", missing method, or malformed envelope
-32601Method not foundUnknown method name
-32602Invalid paramsParams shape doesn't match the method
-32603Internal errorUnhandled server-side exception

OneHazel-specific codes (returned as JSON-RPC -32001 with the SCREAMING_SNAKE code embedded in error.message as code: XYZ):

CodeHTTP statusMeaning
AUTH_MISSING401Authorization header not present
AUTH_INVALID_FORMAT401Header isn't Bearer <token>, or token isn't an oh_live_* key
AUTH_INVALID401Key not found or doesn't match hash
AUTH_REVOKED401Key exists but is revoked
MCP_NOT_ENABLED403Key is valid but mcp_enabled=false, flip the toggle in Configure MCP
AUTH_OPERATOR_MISMATCH403URL operator ID doesn't match the key's operator
MISSING_TRIGGER_FIELD200*A tools/call argument set is missing a required field from the workflow's trigger schema
QUOTA_EXCEEDED200*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-stream

Returns 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:

  1. Open the SSE channel above with a chosen session=<id> query parameter
  2. On the sibling POST to /mcp-server include the header Mcp-Session-Id: <id> and _meta.progressToken: "<token>" inside params
  3. Receive notifications/progress frames 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.