MCP server
The Model Context Protocol interface gives external AI clients scoped access to the same platform. It is opt-in, and its limits are enforced structurally rather than by prompt instructions.
| Aspect | Detail |
|---|---|
| Transport | Streamable HTTP at /mcp |
| Identity | Exactly one authenticated account per call; user_id is never a tool argument |
| Default state | Off — MCP_ENABLED=true mounts it, read at boot |
| Live governance | An admin can disable it, force read-only, or restrict token minting without a redeploy |
| Resources | mate://docs/usage, mate://processes, mate://process/{log_id}/module/{module_id} |
| Discovery | get_server_info reports the live toolset and scope set; mate://docs/usage is the agent's starting document |
Enabling the server#
MCP_ENABLED=true
API_BASE_URL=https://mate.example.org # required for OAuth discovery
MCP_TOOLSETS= # empty = everything except admin
MCP_REQUIRE_EGRESS_CONSENT=true
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d # recreate, not restart
The proxy already routes /mcp and /.well-known/oauth-protected-resource to the API.
Authentication#
| Method | How it works |
|---|---|
| Personal access token | A mate_pat_… token minted in Settings → API & MCP. It acts as its owner, on that owner's data only, and can never carry admin. An empty scope grant means all read scopes. |
| OAuth 2.1 | The client discovers Keycloak from the 401 challenge (RFC 9728), runs auth-code with PKCE, then retries with the JWT. The admin scope additionally needs the admin realm role on the same token. |
Independently of the credential, each user opts in once to external data access; without it, tools answer [consent_required]. That makes consent a per-user switch rather than a per-token one, so a stolen token cannot silently widen egress.
Scopes and toolsets#
| Scope | Grants |
|---|---|
processes:read / processes:write | Log metadata and aggregates / import, rename, filter, delete, folders |
modules:read / modules:write / modules:manage | Module outputs and datasets / per-module configuration / install lifecycle |
dashboards:read / dashboards:write | Read boards (own and shared) / create, edit, share, delete |
jobs:read / jobs:control | List, get, wait / cancel, retry, queue pause and resume |
watched:read / watched:write | Read watched folders / create, update, scan, delete |
account:read / account:write | Usage summary and token list / revoke your own tokens |
admin | The admin toolset — OAuth plus the admin realm role only |
| Toolset | Covers |
|---|---|
meta (always on) | get_server_info, whoami |
processes | Log listing and detail, activities, variants, data quality, OCEL views, folders, import, remap, re-import, duplicate, delete |
analysis | Module inventory and availability, curated module outputs, dataset catalogue, cached guidance, module configuration, plus the convenience readers get_bottlenecks, get_conformance, get_process_model, get_drifts |
dashboards | Board CRUD, card catalogue, export and import, shares, share targets |
jobs | List, get, wait, cancel, retry, queue control |
watched | Watched-folder CRUD and scan |
account | Usage summary, token list, revoke |
admin | Cross-user jobs, users, teams, insights, MCP governance, token oversight — module policy stays REST-only |
Conventions#
| Convention | Behaviour |
|---|---|
| Dry runs | Destructive tools take confirm: bool; without it they return a preview and mutate nothing. |
| Long operations | Answer with { job_id }; follow up with get_job or block on wait_for_job, where a timeout returns the current state with timed_out: true rather than an error. |
| Pagination | cursor and limit (max 200), returning { items, next_cursor, total }. |
| Output cap | About 200 KB per result, truncated with a preview — narrow the query instead. |
| Rate limits | 120 requests/min per user (burst 40), writes 30/min (burst 10), 4 concurrent calls, 30 s per call. |
| Errors | A stable prefix: [not_found], [forbidden], [conflict], [invalid], [rate_limited], [timeout], [read_only], [consent_required], [scope_missing], [confirm_required], [internal]. |
The data wall#
Client setup#
{
"mcpServers": {
"mate": {
"type": "http",
"url": "https://mate.example.org/mcp",
"headers": {
"Authorization": "Bearer mate_pat_…"
}
}
}
}
claude mcp add --transport http mate https://mate.example.org/mcp \
--header "Authorization: Bearer $MATE_PAT"
For an OAuth-capable client, omit the header and point it at the same URL: the first unauthenticated call answers 401 with the Keycloak authorization server, and the client runs auth-code with PKCE.
Common workflows#
| Task | Tools behind it |
|---|---|
| Orient | get_server_info, whoami, list_processes |
| Import and wait | import_process_from_url → wait_for_job |
| Understand a process | get_process, get_process_overview, then get_bottlenecks / get_drifts / get_conformance |
| Drill into detail | get_variants → get_variant → get_variant_cases |
| Explain a number | get_cached_guidance, get_module_output (cache-only, never triggers a model call) |
| Build a board | get_dashboard_card_catalog → create_dashboard → share_dashboard |
| Recover a failure | list_jobs(status="failed") → get_job → retry_job |
Failure modes#
| Symptom | Cause |
|---|---|
503 "MCP is disabled" | The boot flag is off, or the live admin kill switch is on. |
401 on every call | Token missing, expired, revoked, or wrong OAuth audience. |
[consent_required] | The user has not enabled external data access. |
[scope_missing] | The token lacks the scope; scopes are fixed at mint time. |
[read_only] on a write | Server-wide read-only, from the boot flag or the live toggle. |
404 on /mcp | The flag never reached the container, or the proxy route is missing. |
403 "Origin not allowed" | A browser-based client on a foreign origin — use a native or server-side client. |