How the MCP Server Works
This page is for developers building their own MCP client or integration, and for anyone who wants to know exactly what happens when an assistant calls a CheckFlow tool. If you only want to connect an existing client, Connecting a Client is enough.
Transport
The server implements the MCP Streamable HTTP transport in its stateless form:
- One endpoint, one method. Every message is a
POSTof a JSON-RPC 2.0 message — or a batch of them — to/mcp, and every answer is JSON in the response body. - No sessions. The server does not issue an
Mcp-Session-Idand remembers nothing between requests. Each request is authenticated and answered on its own. - No event stream. The server never streams Server-Sent Events. A
GETorDELETEon/mcp— what a client sends to open a stream or end a session — is answered405 Method Not Allowedwith anAllow: POSTheader. - The handshake is optional.
initializeandnotifications/initializedare answered properly for clients that need them, buttools/listandtools/callwork without them. - Authentication is per request. Every request must carry
X-API-KEY. The key decides the workspace and who the calls act as.
The server identifies itself as checkflow, version 3.0.0, and supports the tools and resources capabilities. It has no prompts.
A Session by Hand
You can drive the server with any HTTP client. These are the requests a client makes.
Initialise
POST https://api.checkflow.io/mcp
X-API-KEY: your-api-key-here
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": { "name": "my-integration", "version": "1.0.0" }
}
}
The answer carries the negotiated protocolVersion, the server's capabilities, serverInfo and the server's instructions — a short text that most clients add to the model's context:
This server is CheckFlow, a checklist and process tool. Every call acts on the one workspace the API key belongs to: there is no workspace to choose and none to name in an argument.
Names are not free text. Members, groups, tags and templates are resolved against what the workspace actually contains, so read get_workspace_info before sending any of them rather than inventing a name that looks right.
Before composing a template document for the first time, call get_authoring_guide. The schema tools say what a document may contain; the guide says what to put in one, including the things a schema cannot express.
If a write times out or its answer is lost, retry it with the same idempotencyKey rather than calling it again: the first answer comes back instead of the work happening twice.
A client then sends the notifications/initialized notification. Notifications have no id, so the server answers 202 Accepted with an empty body.
List the Tools
POST https://api.checkflow.io/mcp
X-API-KEY: your-api-key-here
Content-Type: application/json
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
Each of the 145 tools comes back with its name, a description written for a model, an inputSchema (JSON Schema for its arguments) and annotations — see Tool Annotations.
Call a Tool
POST https://api.checkflow.io/mcp
X-API-KEY: your-api-key-here
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "list_tags",
"arguments": {}
}
}
Batches
You can send an array of messages in one request. The answer is an array with one entry per message that had an id, in the same order; notifications get no entry. An array of one is still answered with an array.
Tool Results
A successful tool call returns the same data as the equivalent REST request — same property names, same omitted nulls, same UTC timestamps. It is returned twice in the result: as structuredContent for clients that parse JSON, and as a single text block containing the same JSON for clients that only show text to the model.
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\"items\":[{\"name\":\"q3-audit\",\"checklists\":14,\"templates\":1,\"tasks\":3,\"total\":18},{\"name\":\"urgent\",\"checklists\":9,\"templates\":0,\"tasks\":22,\"total\":31}],\"hasMore\":false}"
}
],
"structuredContent": {
"items": [
{ "name": "q3-audit", "checklists": 14, "templates": 1, "tasks": 3, "total": 18 },
{ "name": "urgent", "checklists": 9, "templates": 0, "tasks": 22, "total": 31 }
],
"hasMore": false
}
}
}
When a tool's REST equivalent returns an object, structuredContent is that object. When it returns something else — an array, a string — it is wrapped in { "result": ... }, because structuredContent must be an object.
The tool reference pages link each tool to the REST object it returns.
Errors
The server distinguishes a refusal — the request was understood and the answer is no — from a protocol error — the message itself was wrong. Only protocol errors change the HTTP status.
Refusals Are Tool Results
When a tool refuses — a template that does not exist, a value that fails validation, a permission the acting member lacks — the call still succeeds at the protocol level. The result has isError: true, and carries the same error body the REST API uses:
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"isError": true,
"content": [
{
"type": "text",
"text": "TEMPLATE_NOT_FOUND: Template '0e7ad584-7788-4ab1-95a6-ca0a5b444cbb' was not found.\nFull detail in structuredContent.error. requestId req_8c3f0e1a2b7d4c59a6e4d1f0b9c2a735."
}
],
"structuredContent": {
"error": {
"code": "TEMPLATE_NOT_FOUND",
"message": "Template '0e7ad584-7788-4ab1-95a6-ca0a5b444cbb' was not found.",
"requestId": "req_8c3f0e1a2b7d4c59a6e4d1f0b9c2a735"
}
}
}
}
This is deliberate. MCP clients pass tool results back to the model, but typically report HTTP and protocol errors to the user as a connection failure. Returning refusals as results means the model reads why the call failed and can correct itself — pick the right template, fix the value, or ask you.
The text block starts with the code and message, followed by the field at fault in brackets where there is one. For TEMPLATE_INVALID it also lists up to three violations and says how many more are in structuredContent.error.violations.
Refusals Before the Tool Runs
Three refusals are decided before any tool runs: the workspace is not on the Enterprise plan (ENTERPRISE_PLAN_REQUIRED), the key belongs to the other region (WRONG_REGION), and the rate limit is spent (RATE_LIMIT_EXCEEDED). They are also answered inside the protocol, with HTTP status 200:
- a
tools/callis answered with a result withisError: true, as above; - any other request, such as
initializeortools/list, is answered with a JSON-RPC error with code-32000, the message, and the error body indata.
Protocol Errors
| Situation | HTTP status | JSON-RPC error |
|---|---|---|
| The body is not JSON, or is empty. | 400 | -32700 Parse error |
| The body is JSON but not a JSON-RPC message, or is an empty batch. | 400 | -32600 Invalid request |
| The method does not exist. | 200 | -32601 Method not found |
| The tool does not exist. | 200 | -32602 Invalid params |
| The server failed while handling the request. | 500 | -32603 Internal error |
Protocol errors carry the request id in error.data.requestId.
Authentication Errors
A missing or invalid key is refused before the request is read as JSON-RPC, so it is answered exactly as the REST API answers it: HTTP 401 and the v3 error body, with a code such as INVALID_API_KEY or API_KEY_REVOKED. See Authentication Errors.
Tool Annotations
Every tool declares hints that clients use to decide whether to ask you before running it:
| Annotation | Meaning | Tools |
|---|---|---|
readOnlyHint: true | The tool only reads. | 57 |
destructiveHint: true | The tool can delete or overwrite data, or has an effect that cannot be taken back. | 20 |
idempotentHint: true | Calling it again with the same arguments has no further effect. | Most tools |
Tools that are neither read-only nor destructive — creating a checklist, completing a task, adding a comment — change your workspace in ways you can see and undo in the app. The tool reference pages list each tool's annotations.
Retries and Idempotency
MCP clients cannot set a header per tool call, so every tool that writes takes an optional idempotencyKey argument instead of the REST API's Idempotency-Key header. Its description tells the model:
Optional. Send the same key again to retry this call safely: the retry gives back the first call's answer instead of doing the work twice. Use a new key whenever you mean a new operation.
It behaves exactly like the REST header and uses the same store:
- The first call with a key does the work and keeps its answer for 24 hours.
- A later call with the same key and the same arguments gets the kept answer back.
- The same key with different arguments is refused with
CONFLICT. Arguments are compared as sent, so adding an optional argument on the retry counts as different. - A key is at most 255 characters.
Rate Limits
MCP requests share the workspace's rate limit budgets with the REST API. One POST to /mcp counts as one request, however many messages it batches.
The protocol's own traffic — initialize, ping, tools/list, resources/list, resources/read and notifications — and the schema, guide and validation tools are charged to the cheap budget, so a client can list tools and an assistant can validate a template repeatedly without eating into the budget for real work. A batch is charged to cheap only if every message in it is cheap.
Every response carries the X-RateLimit-* headers. When the budget is spent, the refusal carries retryAfterSeconds in its error body — the only way the wait reaches the model, which never sees headers.
The Authoring Guide Resource
The server publishes one resource:
| Property | Value |
|---|---|
| URI | checkflow://guides/template-authoring |
| Name | template_authoring_guide |
| Type | text/markdown |
It is a prose guide to composing a CheckFlow template document: the order to work in, what tasks and fields are, how to choose between the field types, how conditions and due dates are written, and how to publish. Clients that support resources let you attach it to a conversation. The same text is available from the get_authoring_guide tool and from GET /v3/schema/guide.
Related Pages
- Connecting a Client — setting up a client instead of writing one.
- Errors — every error code the tools can return.
- Idempotency — the mechanism behind
idempotencyKey. - Template Authoring Tools — the tools that use the guide, schema and validators.