MCP primer
A short orientation for contributors new to the Model Context Protocol. Not a replacement for the spec. Just enough context to read the codebase.
New to MCP? Read the official MCP quickstart first. This page assumes basic familiarity.
What MCP is
MCP is a JSON-RPC protocol. It lets an AI client (such as Claude Desktop, Cursor, Claude Code, or Continue) talk to a server that exposes capabilities.
The capabilities come in a few primitive types:
- Tools — functions the model calls
- Resources — data the application can read
- Prompts — templated workflows users invoke
The client side can also offer capabilities back: sampling, roots, and elicitation. coolify-mcp uses none of these client primitives today.
The protocol is transport-agnostic. Most servers today run over stdio: the client spawns the server as a child process. HTTP+SSE and streamable-HTTP transports also exist for remote-hosted servers.
The primitive types
Control hierarchy
MCP draws a clean distinction about who controls invocation:
| Primitive | Controlled by | Example |
|---|---|---|
| Prompts | User | Slash commands, menu picks |
| Resources | Application | File contents, git history attached to context |
| Tools | Model | API calls the LLM decides to make |
This is why tools have safety affordances (annotations, output schemas, error semantics) — the model is autonomously choosing to call them.
What coolify-mcp uses today
Tools only. All 42 entry points are MCP tools registered via this.tool(name, description, schema, handler). No resources, no prompts, no annotations, no structured outputs.
That approach worked well for v1 and v2 but leaves several capabilities unused:
- The client has no way to know which tools are destructive vs read-only
- The model gets text blobs back from
list_*instead of typed objects (harder to chain intoget_*calls) - Long-running ops like
deployblock the whole MCP transaction for minutes - There's no way for the client to subscribe to a Coolify entity and get pushed updates
See the v3 vision for the plan to address all of this.
How a tool call flows
The server doesn't see the user's prompt — it only sees the tool calls. That's a security boundary: the model decides what tools to call, the client decides whether to allow it, the server executes.
Error semantics
MCP draws a careful line between two error types:
| Type | Where | When | Why it matters |
|---|---|---|---|
| Protocol error | JSON-RPC error field | Unknown tool, malformed request | Returned to the client, NOT the LLM. Model can't self-correct. |
| Tool execution error | Result with isError: true | Validation failure, API down, business-logic error | Returned to the LLM. Model can read the error message and retry with adjusted parameters. |
Most things you'd want to communicate ("missing required field", "Coolify returned 404", "deployment timed out") should be tool execution errors, not protocol errors.