MCP Server
Connect an AI assistant to your event data through the Model Context Protocol.
Overview
The MCP server gives an AI assistant the same read-only access as the Analytics Query API. The assistant writes the SQL. The server runs it under the ClickHouse account of your organization and returns the rows.
The endpoint is POST https://api.centinelanalytica.com/mcp. It speaks the Streamable HTTP transport of protocol revision 2025-06-18.
The server keeps no state between requests. There is no event stream and no session identifier. Each request carries its own key, and each request receives one answer.
The assistant reads only the events of your own organization. The server applies this filter to every statement. A statement cannot widen past your own rows.
Connect a client
Each request must have your API key in an Authorization header. This rule includes the initialize request. The server has no unauthenticated method and no OAuth flow.
Use the same key as the Analytics Query API. Create a key in the dashboard.
Add the server to Claude Code with one command:
claude mcp add --transport http centinel https://api.centinelanalytica.com/mcp \
--header "Authorization: Bearer sk_api_ro_your_key_here"For a client that reads a JSON configuration file, add this block:
{
"mcpServers": {
"centinel": {
"type": "http",
"url": "https://api.centinelanalytica.com/mcp",
"headers": {
"Authorization": "Bearer sk_api_ro_your_key_here"
}
}
}
}The key gives read access to all of your event data. Keep the configuration file on a machine that you control. Do not commit the key to a repository.
To test the connection without a client, send the initialize request yourself:
curl -X POST https://api.centinelanalytica.com/mcp \
-H "Authorization: Bearer sk_api_ro_your_key_here" \
-H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'The server returns the protocol revision and its one capability:
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":false}},"serverInfo":{"name":"centinel-analytics","version":"1"}}}Tools
The server has three tools. The list is the same for each caller, because the tenant boundary is the database account and not the tool list.
| Tool | Arguments | Returns |
|---|---|---|
query | sql | The rows of one read-only SELECT. |
query_help | none | The rules of the account, and example queries. |
list_columns | none | Each readable column of events_distributed, with its ClickHouse type. |
Call query with one statement:
curl -X POST https://api.centinelanalytica.com/mcp \
-H "Authorization: Bearer sk_api_ro_your_key_here" \
-H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"query","arguments":{"sql":"SELECT action, uniqExact(request_id) AS requests FROM events_distributed WHERE created_at >= now() - INTERVAL 1 DAY GROUP BY action FORMAT JSONEachRow"}}}'The query_help tool returns the account rules and worked examples. Call it once before your first statement, because it reads no data and costs no query time.
The list_columns tool reads the column grant live. DESCRIBE and SHOW CREATE are denied to the account, so this tool is the only way to read the current column list. Where a document and this tool disagree, this tool is correct.
Query rules
The tool descriptions carry the rules that the assistant must know. Two of them change your numbers rather than fail:
- Select
FROM events_distributed. A per-shard table such aseventsholds the rows of one shard. The query succeeds, the count comes back low, and nothing reports it. decisionis what the engine concluded.actionis what your policy enforced. Most blocks run in monitor first, sodecision = 'block'overstates the traffic that you stopped.
Refer to Analytics Query API for the event schema and the full rule list.
Errors
The server separates a failed statement from a failed request. The two have different shapes, because an assistant can correct only the first.
A tool error means that the assistant can correct the call itself. The status is 200. The result has isError set to true, and the text says what to change. A statement that the database refused carries the message of the database:
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"Code: 47. DB::Exception: Unknown expression identifier `hostnme` ..."}],"isError":true}}The assistant reads that message and writes the statement again. A query call without a sql argument is a tool error too. That call never reaches the database, but the assistant supplies the argument and calls again.
A protocol error gives the assistant nothing to correct, so the server uses one only when the request never became a tool call:
| Code | Meaning |
|---|---|
-32700 | The request body is not valid JSON. |
-32600 | The body is valid JSON, but it is not a JSON-RPC 2.0 request. |
-32601 | The method is unknown. The server serves initialize, tools/list, and tools/call. |
-32602 | The tool name is unknown. The server has query, query_help, and list_columns. |
Three failures happen before the JSON-RPC layer:
- The server returns
401and an empty body if the key is absent, unknown, revoked, or expired. - The server returns
405for each method other thanPOST. - The server returns
202and an empty body for a notification. A notification is a request without anid, and the protocol gives it no answer.
Limits
| Limit | Value |
|---|---|
Statement in one query call | 65,456 bytes |
| Request body | 64 KB |
| Answer of one tool call | 256 KiB |
| Query execution time | 30 seconds |
| Rows returned | 1,000,000 |
| Row retention | 90 days |
headers and body retention | 14 days |
An answer above 256 KiB is discarded, not shortened. The server returns a tool error that asks for a LIMIT. A short answer is worse than an error here, because a model cannot see the rows that are absent.
This route collects the whole answer before it sends it, because a JSON-RPC result carries the answer as one string. The Analytics Query API streams the answer instead, so the 256 KiB cap does not apply there. Use that endpoint for a large export.
The database gateway collects each answer before it relays it, and it refuses an answer above 4 MiB. That ceiling applies to both endpoints. On this route the 256 KiB cap is reached first.
See also
- Analytics Query API for the event schema and the raw SQL endpoint
- Crawlers for the crawler categories
- Dashboard to create and revoke API keys