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.