The MCP gateway is a Model Context Protocol
server that lets an AI agent drive the platform programmatically. It speaks
JSON-RPC 2.0 over a single endpoint, POST /mcp, and exposes the same
analytics, flow, connection and configuration capabilities you reach through
the admin UI as agent-callable tools — gated by exactly the same
permissions.
It sits alongside the public Chat API but serves a completely
different audience: the Chat API is the browser-facing widget surface, the MCP
gateway is the server-side agent surface. Authentication, scoping and
rate-limit budgets differ accordingly.
When to use it
Use the MCP gateway when an autonomous agent (or any LLM tool-calling client)
needs to read your analytics, inspect flows, or check connections on a
caller’s behalf — for example, an assistant that answers “how many conversions
did we get last week?” by calling analytics.goal_summary, or one that creates
a goal and records a server-side conversion.
For a fixed integration that just records deferred conversions or triggers
flows from your own backend, prefer the Engine API — it uses a
deploy-wide token and a flat REST surface, not the agent-oriented JSON-RPC tool
protocol.
Endpoint
POST /mcp HTTP/1.1
Host: flow.example.com
Authorization: Bearer cfp_…
MCP-Protocol-Version: 2025-06-18
Content-Type: application/json
The endpoint is stateless and bearer-only — it never inherits the
session, form login or CSRF rules of the interactive admin firewall. One HTTP
request carries exactly one JSON-RPC request (batch arrays are rejected).
Authentication
The gateway authenticates with a Personal Access Token (PAT) presented as a
bearer credential:
Authorization: Bearer cfp_xxxxxxxxxxxxxxxxxxxxxxxx
Tokens are minted from Account → Personal access tokens
(/admin/account/tokens) — see
Your account & security › Personal access tokens.
A token is shown once in plaintext at creation; copy it then. It acts as
you: the call inherits your role and is pinned to your workspace.
A token grants access as the issuing user. Treat it like a password, give
each agent its own narrowly-scoped token, and revoke it the moment it leaks.
A request with a missing or invalid token never reaches a method handler — the
firewall returns a clean 401, and a request bound to a deactivated user is
rejected before dispatch.
Scopes
When you create the token, grant it only the MCP scopes the agent needs. The
vocabulary is:
| Scope | Label | Grants |
|---|
mcp | MCP: full access | Every MCP capability below (root scope). |
mcp:analytics:read | Analytics: read | Read reports, datasets, goals, funnels and telemetry. |
mcp:analytics:write | Analytics: write | Create and update goals; record conversions. |
mcp:flows:read | Flows: read | List and read flows. |
mcp:connections:read | Connections: read | List connections and their metadata. |
mcp:connections:manage | Connections: manage | Run live connection tests. |
mcp:history:read | History: read | Read execution and run history. |
mcp:config:read | Config: read | Read non-secret configuration values. |
Scope resolution is table-driven and fails closed: an unrecognised scope
grants nothing. Holding mcp implies every scope; a group root implies its
leaves — mcp:analytics implies both mcp:analytics:read and
mcp:analytics:write, mcp:connections implies both connection scopes.
Prefer leaf scopes (mcp:analytics:read) over the root mcp so a leaked
token can do as little as possible.
Authorization model
Every tool call is gated by three independent layers, all of which must
pass. Effective access is the intersection:
effective access = user ACL ∩ token scope ∩ tenant (RLS)
- User ACL — your role must grant the tool’s required ACL resource (e.g.
comerix.insights.analytics). This is the same check the admin UI uses, so
the gateway can never do more than you can do in the UI.
- Token scope — the presented token’s scopes must imply the tool’s required
scope (e.g.
mcp:analytics:read). A token scoped below your role is the
tighter bound.
- Tenant isolation — every read and write is scoped to your workspace
(the token’s owning user’s tenant), enforced at the database layer by
Postgres row-level security. The tenant is derived from the token and is
never read from the request body, so a token cannot reach another
workspace.
tools/list applies layers 1 and 2 as a filter: a tool you cannot call is
omitted from the listing entirely, so an agent never discovers a capability it
cannot use.
Protocol version negotiation
The server speaks these protocol revisions, newest first:
| Revision | |
|---|
2025-06-18 | Latest (default). |
2025-03-26 | Supported. |
Send the revision you want in the MCP-Protocol-Version request header. If
it is supported it is honoured and echoed back on the response; otherwise the
server falls back to its latest (2025-06-18) and returns that in the response
header so the client can decide whether to proceed. The same negotiation runs
for the protocolVersion field of the initialize request body. Every response
carries the negotiated value in the MCP-Protocol-Version header.
The JSON-RPC envelope
Requests and responses follow JSON-RPC 2.0.
Request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "analytics.goal_summary", "arguments": {} }
}
Success result:
{ "jsonrpc": "2.0", "id": 1, "result": { /* method-specific */ } }
Error:
{
"jsonrpc": "2.0",
"id": 1,
"error": { "code": -32002, "message": "…", "data": { /* optional */ } }
}
Notes on framing:
jsonrpc must be the string "2.0", and method a non-empty string.
params, when present, must be an object (not an array).
- Batch payloads (a top-level JSON array) are rejected with
-32600 Invalid request.
- A request with no
id is a notification: it expects no response and
the server replies with an empty 202 Accepted. A request carrying an
id (string, number, or null) always gets a JSON-RPC response with HTTP
200.
- Framing and authorization failures are returned as a JSON-RPC error body
(HTTP
200), not as an HTTP error status — read the error.code to branch.
Standard methods
| Method | Purpose |
|---|
initialize | Negotiate the protocol revision and read server capabilities + identity. |
ping | Liveness probe; returns an empty result. |
tools/list | List the tools the caller is permitted to call (filtered by ACL + scope). |
tools/call | Invoke one tool by name with an arguments object. |
resources/list | List the read-only resources the caller may read. |
resources/read | Read one resource by URI. |
initialize
Returns the negotiated protocolVersion, the advertised capabilities, and
serverInfo:
{
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": { "listChanged": false },
"resources": { "listChanged": false, "subscribe": false }
},
"serverInfo": { "name": "comerix-flow-mcp", "version": "1.0.0" }
}
Tool and resource lists are stable within a session (listChanged: false), and
resource subscriptions are not offered.
params.name is the tool name; params.arguments is the validated arguments
object. The arguments are validated against the tool’s inputSchema (JSON
Schema, draft 2020-12) before the call runs.
A business failure inside a tool (e.g. “Flow not found”, “Invalid report
definition”, “Unknown or inactive goal”) is not a JSON-RPC error — it comes
back as a normal result whose isError is true:
{
"content": [{ "type": "text", "text": "Flow not found." }],
"isError": true
}
A successful result carries a text block plus, for JSON-returning tools, a
structuredContent mirror of the same data:
{
"content": [{ "type": "text", "text": "{\"flows\":[…]}" }],
"isError": false,
"structuredContent": { "flows": [ /* … */ ] }
}
Only protocol and authorization problems (bad framing, denied scope/ACL, unknown
tool) surface as JSON-RPC errors.
Tools are contributed by their owning module. The table below lists the full
catalogue, grouped by module, with each tool’s read/write classification.
Read tools (readOnlyHint) never mutate state; write tools
(destructiveHint) may create, change or remove data. Tools marked external
(openWorldHint) reach an outside system. Each tool requires both the listed
scope and the corresponding role permission.
| Tool | Type | Scope | Purpose |
|---|
analytics.list_datasets | read | mcp:analytics:read | List reporting datasets with their dimensions, measures and filters. |
analytics.run_report | read | mcp:analytics:read | Run an ad-hoc report definition and return columns + rows. mode caps rows: preview=200, view=500 (default), export=5000. |
analytics.list_reports | read | mcp:analytics:read | List saved reports; optionally only pinned or only scheduled. |
analytics.get_report | read | mcp:analytics:read | Return a saved report definition by report_id. |
analytics.suggest_filter_values | read | mcp:analytics:read | Suggest distinct values for a filterable field in a dataset. |
analytics.goal_summary | read | mcp:analytics:read | Summarise goal completions and value over a date range; set breakdown for per-goal and daily series. |
analytics.engagement_summary | read | mcp:analytics:read | Summarise conversation/message engagement over a date range, with a daily series. |
analytics.funnel | read | mcp:analytics:read | Return step-by-step reach counts for a flow_id over a date range. |
analytics.top_customers | read | mcp:analytics:read | List the customers with the most conversions (limit ≤ 50). |
analytics.list_telemetry | read | mcp:analytics:read | List recent raw telemetry events over a date range, optionally filtered by name (limit ≤ 100). |
analytics.export_report_csv | read | mcp:analytics:read | Run a report definition at export scale and return the result as CSV text. |
analytics.create_goal | write | mcp:analytics:write | Create a conversion/engagement goal. Idempotent. |
analytics.update_goal | write | mcp:analytics:write | Update an existing goal by goal_id. |
analytics.change_goal_status | write | mcp:analytics:write | Activate, pause or archive a goal (status: active / paused / archived). |
analytics.record_conversion | write, external | mcp:analytics:write | Record a server-side conversion against a goal; idempotent on external_ref. |
Date-range tools accept from / to as YYYY-MM-DD and default to the
last 30 days (today inclusive). analytics.list_telemetry requires the
comerix.insights.telemetry permission; the goal-writing tools require
comerix.insights.goals.manage; all other analytics tools require
comerix.insights.analytics.
| Tool | Type | Scope | Purpose |
|---|
flows.list_flows | read | mcp:flows:read | List the workspace’s flows with status and metadata. |
flows.get_flow | read | mcp:flows:read | Return a single flow, including its definition, by flow_id. |
Both flow tools require the comerix.flows.view permission.
| Tool | Type | Scope | Purpose |
|---|
connections.list_connections | read | mcp:connections:read | List workspace connections (metadata only — never credentials), optionally filtered by provider_key / status. |
connections.get_connection_metadata | read | mcp:connections:read | Return non-secret metadata for one connection_id, or for every connection when none is given. |
connections.test_connection | external | mcp:connections:manage | Perform a live connectivity check against the external provider for one connection_id. |
Connection reads require comerix.connections.view; connections.test_connection
requires comerix.connections.manage. Stored credentials are never
serialised by these tools.
| Tool | Type | Scope | Purpose |
|---|
config.list | read | mcp:config:read | List the configuration fields readable over the API (non-secret only), with their kind and label. |
config.get | read | mcp:config:read | Read one non-secret configuration value by path, optionally at a scope (global, organization:<uuid>, tenant:<uuid>; defaults to the caller’s own scope). |
Both require the comerix.config permission, and results are further filtered
by the per-section view ACL (comerix.config.<section>.view). Only fields a
module declares api_readable are exposed; secret (encrypted or sensitive)
values can never be read, and a non-superadmin may only target their own
workspace scope. The payloads and rules are identical to the REST surface —
see the Configuration read API.
Idempotency
Write tools support safe retries via an Idempotency-Key request header.
When you set the header and call a tool that mutates state or claims idempotency
(analytics.create_goal, analytics.update_goal,
analytics.change_goal_status, analytics.record_conversion), the gateway
caches the successful result and replays it for a repeated call instead of
performing the side effect again.
POST /mcp HTTP/1.1
Authorization: Bearer cfp_…
Idempotency-Key: create-goal-2026-06-07-purchase
Content-Type: application/json
Details:
- The cached entry is bound to tenant + calling user + tool name + key + a
canonical fingerprint of the arguments. Reusing a key with different
arguments (or by a different user) does not replay a stale result — it
runs fresh.
- Only non-error results are cached. A failed call is not stored, so a retry
re-attempts the operation.
- Entries are held for 24 hours.
- The header is ignored for read-only tools (there is nothing to make
idempotent) and when no key is supplied.
Some tools are idempotent at the domain level regardless of the header:
analytics.record_conversion deduplicates on its external_ref argument, so a
replay returns the existing completion.
Error codes
Errors use the standard JSON-RPC transport codes plus a small set of
application codes:
code | Meaning |
|---|
-32700 | Parse error — the body was not valid JSON. |
-32600 | Invalid request — not a JSON-RPC request object, a batch array, or a bad jsonrpc/method/id. |
-32601 | Method not found — unknown JSON-RPC method. |
-32602 | Invalid params — params was not an object, or tool arguments failed schema validation. |
-32603 | Internal error — unexpected server fault (details are logged, never leaked). |
-32001 | Tool not found — no provider owns the requested tool name. |
-32002 | Authorization denied — the ACL check or the scope check failed; error.data distinguishes them (resource vs required_scope). |
-32003 | Rate limited — the per-token rate limit was exceeded. |
These are returned in the JSON-RPC error object (HTTP 200); the firewall
still returns plain HTTP 401 for a missing or invalid bearer token before any
JSON-RPC processing. For the platform-wide REST error envelope, see
Errors.
Worked example
A full minimal session — initialize, discover tools, then call one. Replace
cfp_… with your token and the host with your deployment.
1. Initialize the session and read capabilities:
curl -sS https://flow.example.com/mcp \
-H "Authorization: Bearer cfp_…" \
-H "MCP-Protocol-Version: 2025-06-18" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": { "protocolVersion": "2025-06-18" }
}'
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": { "listChanged": false },
"resources": { "listChanged": false, "subscribe": false }
},
"serverInfo": { "name": "comerix-flow-mcp", "version": "1.0.0" }
}
}
2. List the tools your token may call:
curl -sS https://flow.example.com/mcp \
-H "Authorization: Bearer cfp_…" \
-H "Content-Type: application/json" \
-d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }'
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "analytics.goal_summary",
"title": "Goal / conversion summary",
"description": "Summarises goal completions and value over a date range; set breakdown for per-goal and daily series.",
"inputSchema": { "type": "object", "properties": { /* … */ } },
"annotations": {
"readOnlyHint": true, "destructiveHint": false,
"idempotentHint": true, "openWorldHint": false
}
}
]
}
}
3. Call a tool — a goal summary for a custom date range:
curl -sS https://flow.example.com/mcp \
-H "Authorization: Bearer cfp_…" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "analytics.goal_summary",
"arguments": { "from": "2026-05-01", "to": "2026-05-31", "breakdown": true }
}
}'
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [{ "type": "text", "text": "{\"summary\":{…},\"by_goal\":[…],\"timeseries\":[…]}" }],
"isError": false,
"structuredContent": {
"summary": { /* … */ },
"by_goal": [ /* … */ ],
"timeseries": [ /* … */ ]
}
}
}
Read result.structuredContent for the machine-readable payload; the text block
mirrors it as JSON for clients that only consume content blocks.
Permissions
The MCP gateway grants no permissions of its own — every tool is gated by the
permission of the module that owns it, in addition to the token scope.
| Tool group | Permission |
|---|
| Analytics reports, datasets, funnels, engagement, customers | comerix.insights.analytics |
analytics.list_telemetry | comerix.insights.telemetry |
| Goal create/update/status, record conversion | comerix.insights.goals.manage |
| Flow tools | comerix.flows.view |
| Connection reads | comerix.connections.view |
connections.test_connection | comerix.connections.manage |
| Configuration tools | comerix.config (+ per-section comerix.config.<section>.view) |
The bearer token itself is managed under your account
(comerix.account.tokens.manage — see
Your account & security).
Related pages