Appearance
MCP Server
Overview
ODP includes a built-in Model Context Protocol (MCP) server that enables AI agents and LLMs to interact with ODP data and schema through a standardized tool-calling interface.
The MCP server exposes ODP's data operations (CRUD on collections, schema management, workflow operations) as typed tools that can be discovered and called by any MCP-compatible AI client (Claude, ChatGPT plugins, etc.).
Architecture
ODP implements MCP in two modes:
1. Stateless HTTP (per-request)
POST /server/mcp — A simple endpoint where each request is handled by a fresh MCP Server instance and FastifyTransport. No persistent connection required.
2. Full MCP Protocol (via OdpMCP)
Routes using OdpMCP.handleRequest() implement the full JSON-RPC message protocol per the MCP specification. Each request creates a fresh Server instance and FastifyTransport to avoid connection conflicts with concurrent requests.
Access Control
MCP is not a permission bypass. Every MCP operation runs under the caller's own access policies — exactly like the equivalent REST call. Access is enforced in two layers.
Layer 1 — Gate: who can use MCP at all
- Authenticated request — the request must carry a valid token resolving to
accountability.user,accountability.role, oraccountability.admin === true. Anonymous requests are rejected with403 Forbidden(odp-mcp.ts). - App permission
mcp / access— whenENABLE_APP_PERMISSIONS !== 'false', the caller must be granted the app permissionmodule = mcp, action = accessvia their access policy. Without it, no tool can be invoked (validateAppAccess).
Layer 2 — Per-operation: what they can see/do through MCP
Each tool instantiates the matching service (ItemsService, CollectionsService, UsersService, workflow services, …) with the caller's accountability — never a system/null bypass. Because services are the only layer that touches the database and they enforce permissions internally, every MCP operation passes the full permission pipeline:
- Collection-level —
validateAccess(read | create | update | delete)throws403 Forbiddenif the policy denies the action. - Row-level — permission filters are merged into the query (via
processAst), so the caller only sees rows their policy allows. - Field-level — fields the policy hides are stripped from reads and rejected on writes.
Implication for your team
You do not configure permissions separately for MCP. MCP inherits whatever access policies the user already has. If a user cannot read collection articles over REST, the MCP items_read tool returns the same 403 / empty result for articles. To change what an MCP user can do, edit their role / access policy — not the MCP module.
Layer 0 — Module settings (further narrowing)
On top of permissions, the MCP module has its own settings (managed via GET/PATCH /server/mcp/settings, see Configuration) that constrain everyone, including admins:
mcp_enabled— global on/off. Whenfalse, every/server/mcprequest returns403regardless of permissions.mcp_allowed_collections— allowlist; if set, MCP tools can only touch these collections (null= all).mcp_read_only_collections— collections exposed read-only through MCP even if the user's policy allows writes.mcp_allow_deletes— whenfalse, delete tools are not exposed at all.
These narrow what MCP can do; they never widen a user's permissions. Effective access = user's access policy ∩ MCP module settings.
Setup checklist
To let a team member use MCP:
- Enable the module — set
mcp_enabled = true(and optionally scopemcp_allowed_collections/mcp_read_only_collections/mcp_allow_deletes) viaPATCH /server/mcp/settings. - Create a token for the user (login or a sub-token) — MCP requests authenticate with
Authorization: Bearer <token>. - Grant the MCP app permission — in their access policy, add
module = mcp, action = access(skip only if you run withENABLE_APP_PERMISSIONS=false, which disables Layer 1 globally — Layer 2 still applies). - Grant the data permissions they need — read/create/update/delete on the specific collections, with any row filters and field restrictions. These are the same policy rules that govern REST; MCP reuses them.
| User case | Result through MCP |
|---|---|
mcp_enabled = false (module off) | 403 Forbidden for everyone, including admins |
| No token / invalid token | 403 Forbidden — gate rejects before any tool runs |
Authenticated but no mcp / access app permission | 403 Forbidden — cannot invoke any tool |
Has MCP access, but policy denies read on collection ABC | items_read on ABC → 403 / no rows (identical to REST) |
Policy allows read on ABC with a row filter | Only the permitted rows returned; hidden fields stripped |
Collection not in mcp_allowed_collections | Not reachable via MCP even if the policy allows it |
Collection in mcp_read_only_collections | Reads work; create/update/delete blocked through MCP |
accountability.admin === true | Bypasses auth gate + policy checks, but still bound by module settings (Layer 0) |
Endpoints
POST /server/mcp
Simple tool execution endpoint (non-standard, simpler protocol).
Auth required: Authenticated user with the mcp / access app permission (see Access Control). Each tool then runs under the caller's own access policies.
Request Body:
json
{
"tool": "items_read",
"params": {
"collection": "articles",
"query": { "limit": 10 }
}
}Response:
json
{
"data": {
"items": [...]
}
}Error response:
json
{
"error": "Unknown tool: nonexistent_tool"
}GET /server/mcp/tools
List all available MCP tools with their schemas.
Auth required: Authenticated user with the mcp / access app permission (see Access Control).
Response:
json
{
"data": [
{
"name": "items_read",
"description": "Read items from a collection",
"parameters": {
"type": "object",
"properties": {
"collection": { "type": "string" },
"query": { "type": "object" }
},
"required": ["collection"]
}
}
]
}Available Tools
Items
| Tool | Description |
|---|---|
items_read | Read items from a collection with optional query |
items_create | Create a single item |
items_update | Update a single item by ID |
items_delete | Delete a single item |
items_create_many | Create multiple items |
items_update_many | Update multiple items |
items_delete_many | Delete multiple items |
Schema
| Tool | Description |
|---|---|
schema_list | List all collections with their fields |
schema_read | Read schema for a specific collection |
Collections
| Tool | Description |
|---|---|
collections_create | Create a new collection |
collections_list | List all collections |
collections_read | Read a collection's metadata |
collections_update | Update a collection's metadata |
collections_delete | Delete a collection |
Fields
| Tool | Description |
|---|---|
fields_create | Add a field to a collection |
fields_update | Update a field's configuration |
fields_delete | Remove a field from a collection |
Relations
| Tool | Description |
|---|---|
relations_create | Create a relation between collections |
relations_list | List all relations |
relations_read | Read a specific relation |
relations_update | Update a relation |
relations_delete | Delete a relation |
Files
| Tool | Description |
|---|---|
files_list | List files with optional filters |
files_read | Read a file's metadata by ID |
Users
| Tool | Description |
|---|---|
users_list | List users |
users_read | Read a user by ID |
Versions
| Tool | Description |
|---|---|
versions_list | List content versions |
versions_read | Read a version by ID |
versions_promote | Promote a version to live content |
Activity
| Tool | Description |
|---|---|
activity_list | Query activity log |
Workflow (Definition)
| Tool | Description |
|---|---|
workflow_create | Create a workflow definition |
workflow_list | List workflow definitions |
workflow_read | Read a workflow |
workflow_update | Update a workflow |
workflow_delete | Delete a workflow |
workflow_duplicate | Duplicate a workflow |
workflow_activate | Activate a workflow |
workflow_deactivate | Deactivate a workflow |
Workflow Steps & Transitions
| Tool | Description |
|---|---|
workflow_step_add | Add a step to a workflow |
workflow_step_update | Update a step |
workflow_step_remove | Remove a step |
workflow_transition_add | Add a transition between steps |
workflow_transition_update | Update a transition |
workflow_transition_remove | Remove a transition |
Workflow Instances
| Tool | Description |
|---|---|
workflow_instance_start | Start a workflow instance |
workflow_instance_list | List instances |
workflow_instance_read | Read an instance |
workflow_instance_cancel | Cancel an instance |
workflow_instance_approve | Approve a step |
workflow_instance_reject | Reject a step |
workflow_instance_delegate | Delegate a step to another user |
workflow_instance_comment | Add a comment to an instance |
Workflow Tasks
| Tool | Description |
|---|---|
workflow_tasks_mine | Get tasks assigned to current user |
workflow_tasks_all | Get all pending tasks |
workflow_tasks_count | Count pending tasks |
OdpMCP Configuration
The OdpMCP class accepts an allowDeletes option:
typescript
const mcp = new OdpMCP({ allowDeletes: false });When allowDeletes = false (default), items_delete and items_delete_many tools are excluded from the tool list. This provides a safety guard for production MCP deployments.
MCPContext
Each tool handler receives a context object:
typescript
interface MCPContext {
knex: Knex; // Database connection
accountability: Accountability | null; // Caller's permissions
schema: SchemaOverview; // Current database schema
settings: MCPSettings; // Collection access settings
}
interface MCPSettings {
allowedCollections: string[] | null; // null = all collections
readOnlyCollections: string[] | null; // null = none read-only
}Example: Using MCP with Claude
json
{
"mcpServers": {
"odp": {
"url": "https://api.example.com/api/mcp",
"headers": {
"Authorization": "Bearer <access_token>"
}
}
}
}Then in Claude:
Use the items_read tool to get the latest 5 published articles from the "articles" collection.Configuration
| Variable | Default | Description |
|---|---|---|
ENABLE_APP_PERMISSIONS | true | Set to false to disable app permission checks |
MCP settings are also stored in odp_extension_settings with key system_mcp_settings and can include allowedCollections and readOnlyCollections lists.