MCP Integration (AI Tool Access)
Canopy includes a built-in MCP server (Model Context Protocol) that allows AI tools — such as Claude Desktop, GitHub Copilot, Cursor, and VS Code — to query and update your EA data directly. AI tools can also upload artifacts (spreadsheets, BPMN diagrams, DrawIO diagrams, freeform documents) and turn them into cards, relations and diagrams that fit the existing metamodel. Users authenticate through your existing SSO provider, and every action respects their individual permissions.
This feature is optional and does not start automatically. It requires SSO to be configured, the MCP profile to be activated in Docker Compose, and an admin to toggle it on in the settings UI.
How It Works
AI Tool (Claude, Copilot, etc.)
│
│ MCP protocol (HTTP + SSE)
▼
Canopy MCP Server (:8001, internal)
│
│ OAuth 2.1 with PKCE
│ delegates to your SSO provider
▼
Canopy Backend (:8000)
│
│ Per-user RBAC
▼
PostgreSQL
- A user adds the MCP server URL to their AI tool.
- On first connection, the AI tool opens a browser window for SSO authentication.
- After login, the MCP server issues its own access token (backed by the user's Canopy JWT). The token is proactively refreshed roughly an hour before it expires.
- The AI tool uses this token for all subsequent requests.
- Every query goes through the normal Canopy permission system — users only see data they have access to.
Prerequisites
Before enabling MCP, you must have:
- SSO configured and working — MCP delegates authentication to your SSO provider (Microsoft Entra ID, Google Workspace, Okta, or generic OIDC). See the Authentication & SSO guide.
- HTTPS with a public domain — The OAuth flow requires a stable redirect URI. Deploy behind a TLS-terminating reverse proxy (Caddy, Traefik, Cloudflare Tunnel, etc.).
Setup
Step 1: Start the MCP service
The MCP server is an opt-in Docker Compose profile. Add --profile mcp to your startup command:
docker compose --profile mcp up -d
This starts a lightweight Python container (port 8001, internal only) alongside the backend and frontend. Nginx proxies /mcp/ requests to it automatically — if the container isn't running, requests get a clean 502 rather than crashing the edge proxy.
Step 2: Configure environment variables
Add these to your .env file:
CANOPY_PUBLIC_URL=https://your-domain.example.com
MCP_PUBLIC_URL=https://your-domain.example.com/mcp
| Variable | Default | Description |
|---|---|---|
CANOPY_URL | http://backend:8000 | Internal backend URL the MCP container talks to (Docker service name — rarely needs changing). |
CANOPY_PUBLIC_URL | http://localhost:8920 | The public URL of your Canopy instance. |
MCP_PUBLIC_URL | http://localhost:8920/mcp | The public URL of the MCP server (used in OAuth redirect URIs and metadata). |
MCP_PORT | 8001 | Internal port for the MCP container (rarely needs changing). |
Step 3: Add the OAuth redirect URI to your SSO app
In your SSO provider's app registration (the same one you set up for Canopy login), add this redirect URI:
https://your-domain.example.com/mcp/oauth/callback
This is required for the OAuth flow that authenticates users when they connect from their AI tool.
Step 4: Enable MCP in admin settings
- Go to Settings in the admin area and select the AI tab.
- Scroll to the MCP Integration (AI Tool Access) section.
- Toggle the switch to enable MCP.
- The UI will show the MCP Server URL and setup instructions to share with your team.
The toggle is disabled if SSO is not configured. Set up SSO first.
Connecting AI Tools
Once MCP is enabled, share the MCP Server URL with your team. Each user adds it to their AI tool:
Claude Desktop
- Open Settings > Connectors > Add custom connector.
- Enter the MCP server URL:
https://your-domain.example.com/mcp - Click Connect — a browser window opens for SSO login.
- After authentication, Claude can query and update your EA data.
VS Code (GitHub Copilot / Cursor)
Add to your workspace .vscode/mcp.json:
{
"servers": {
"canopy": {
"type": "http",
"url": "https://your-domain.example.com/mcp/mcp"
}
}
}
The double /mcp/mcp is intentional — the first /mcp/ is the Nginx proxy path, the second is the MCP protocol endpoint.
Local Testing (stdio mode)
For local development or testing without SSO/HTTPS, you can run the MCP server in stdio mode — Claude Desktop spawns it directly as a local process.
1. Install the MCP server package:
pip install ./mcp-server
2. Add to your Claude Desktop config (claude_desktop_config.json):
{
"mcpServers": {
"canopy": {
"command": "python",
"args": ["-m", "canopy_mcp", "--stdio"],
"env": {
"CANOPY_URL": "http://localhost:8000",
"CANOPY_PASSWORD": "your-password"
}
}
}
}
In this mode, the server authenticates with email/password directly (no OAuth) and refreshes the Canopy JWT automatically in the background.
Available Capabilities
The MCP server exposes 47 tools: 30 read tools across nine clusters, and 17 write tools (13 additive, 4 destructive). Every tool carries ToolAnnotations (readOnlyHint / destructiveHint / idempotentHint) so connectors like Claude Desktop can surface destructiveness in their UI before a user approves a call.
Dry-run safety on writes
Every write tool defaults to dry_run=true. In this mode the backend runs every validator and resolver, builds the complete plan, then rolls back the transaction so nothing is persisted. The AI tool returns the preview to the user; only after explicit confirmation should it call the tool again with dry_run=false to commit. This prevents an enthusiastic agent from quietly seeding hundreds of cards on a misinterpreted spreadsheet.
For larger commits, there's a second gate: any commit above MCP_BATCH_CONFIRMATION_THRESHOLD (default 20 rows) must echo back a one-shot confirm_token that was issued by the prior dry-run. The token has a 15-minute TTL. This is enforced twice — once by the MCP wrapper before it even calls the backend, and again by the backend itself on the commit endpoint — so a client that skips the dry-run step can't slip a large, unreviewed batch through.
Mutation batches (audit trail)
Every write call — dry-run or not — opens a mutation_batches row before touching any data, and every event it emits (card created, relation upserted, comment posted, …) is tagged with that batch id plus the calling tool name and the authenticated user. The get_change_history tool reconstructs the full per-event diff of a batch from its id, and rollback_batch can reverse a batch's writes (see below). This means any MCP-driven change is traceable back to exactly who ran which tool and when — distinct from the same action taken through the web UI.
Read tools
The server exposes 30 read tools across nine clusters.
Cards & metamodel
| Tool | Description |
|---|---|
search_cards | Search and filter cards by type, status, or free text |
get_card | Get full details of a card by UUID |
get_card_relations | Get all relations connected to a card |
get_card_hierarchy | Get ancestors and children of a card |
list_card_types | List all card types in the metamodel, with fields and configuration |
get_relation_types | List relation types, optionally filtered by card type |
resolve_card_refs | Pre-validate name-based card references (e.g. "Sales / Customer Mgmt / CRM") before a bulk import — surfaces resolved / ambiguous / missing matches |
analyze_impact | Multi-hop impact analysis — walks the relation graph outward from a card up to 3 hops, grouped by depth, with optional relation-type and card-type filters |
Dashboards
| Tool | Description |
|---|---|
get_dashboard | KPI dashboard (counts, data quality, approvals, activity) |
get_landscape | Cards of one type grouped by a related type |
GRC — Risk Register
| Tool | Description |
|---|---|
list_risks | Paginated, filterable EA risk listing (TOGAF Phase G) |
get_risk | Single risk detail with linked cards + audit trail |
get_risk_metrics | KPIs + 4×4 initial / residual probability × impact matrices |
get_card_risks | All risks currently linked to a specific card |
GRC — Compliance
| Tool | Description |
|---|---|
list_compliance_findings | Compliance findings bundled by regulation (EU AI Act, GDPR, NIS2, DORA, SOC 2, ISO 27001) |
get_compliance_overview | Compliance scores + per-regulation status matrix + last-scan metadata |
Governance & Delivery
| Tool | Description |
|---|---|
list_principles | Published EA principles (statement, rationale, implications) |
list_adrs | Architecture Decision Records, filterable by initiative / card / status / search |
get_adr | Single ADR with sections, linked cards, related ADRs, signature trail |
list_soaws | Statements of Architecture Work for an initiative |
Reports
| Tool | Description |
|---|---|
get_portfolio_report | Bubble-chart data for a card type (functional × technical fit by default) |
get_cost_treemap | Treemap of card cost, optionally grouped by a related type |
get_capability_heatmap | Hierarchical business-capability heatmap |
get_data_quality_report | Per-card-type completeness breakdown |
Card context
| Tool | Description |
|---|---|
get_card_stakeholders | Users + roles assigned to a card |
get_card_comments | Threaded comments on a card |
get_card_documents | Document links attached to a card |
Diagrams
| Tool | Description |
|---|---|
list_diagrams | List free-draw diagrams, optionally filtered to one card |
get_diagram | Fetch a single diagram by id, including its DrawIO XML |
Audit & change history
| Tool | Description |
|---|---|
get_change_history | Look up a mutation batch by id (returns the batch + every event emitted under it, in order), or browse recent batches by actor, tool name, or origin (mcp / web / api) |
All read tools are bound by the authenticated user's RBAC — a viewer will simply get an empty list (or a 403) for areas they cannot see; nothing on the MCP layer needs configuring per tool.
Write tools
The server exposes 17 write tools: 13 additive (create/update actions that don't destroy data) and 4 destructive (can overwrite or remove data, though several of those are themselves recoverable — see notes below).
Cards & lifecycle
| Tool | Annotation | Description |
|---|---|---|
create_cards_bulk | Additive | Create many cards in one call from artifact-extracted rows (e.g. spreadsheet rows). Supports same-batch parent references by name with server-side topological sort. |
update_cards_bulk | Additive | Update many cards in one call — field-level patches with a per-row before/after diff on dry-run. attributes is a full replace per row; strict_attributes=true rejects unknown field keys with a recovery hint. |
transition_card_lifecycle | Additive | Move a card through approval actions (approve / reject / reset), lifecycle phases (phaseIn / active / phaseOut / endOfLife), or status values (ACTIVE / PHASING_IN / PHASING_OUT / END_OF_LIFE / ARCHIVED). Returns a pending response with a UI deep-link if the caller lacks the permission, instead of failing outright. |
archive_cards | Destructive | Soft-delete (archive) one or more cards, with a cascade preview on dry-run (children, orphaned relations). Archived cards are recoverable for 30 days before auto-purge. Hard/permanent delete is intentionally not exposed over MCP. |
Relations & diagrams
| Tool | Annotation | Description |
|---|---|---|
upsert_relations_bulk | Destructive | Create or delete relations between cards. action: "delete" is refused by default (see guardrails below) — that's what makes this tool destructive-annotated even though creates are the common case. |
create_diagram | Additive | Create a free-form DrawIO diagram, optionally linked to existing cards by UUID. |
update_diagram | Destructive | Update an existing diagram's XML, name, description, or linked cards — drawio_xml replaces the canvas verbatim. |
import_bpmn | Additive | Save a BPMN 2.0 diagram against an existing BusinessProcess card, walking the draft → submit → approve workflow. If no matching card exists, returns a card_not_found error pointing at create_cards_bulk instead of silently creating a sparse card. |
GRC — Risk Register
| Tool | Annotation | Description |
|---|---|---|
create_risks | Additive | Create one or more risks, optionally linking affected cards in the same call. |
update_risks | Additive | Patch existing risks by id; linked_card_ids replaces the affected-card link set when supplied. |
Governance & delivery
| Tool | Annotation | Description |
|---|---|---|
create_soaw | Additive | Create a Statement of Architecture Work for an initiative. |
create_adr | Additive | Create an Architecture Decision Record (lands in draft by default). |
update_adr | Additive | Update an existing ADR's title, sections, status, or linked cards. |
sign_adr | Additive | Sign an ADR. Returns a pending response with a deep-link if the caller lacks adr.sign. |
Collaboration
| Tool | Annotation | Description |
|---|---|---|
add_card_comment | Additive | Post a (optionally threaded) comment on a card. |
assign_stakeholders | Additive | Assign or remove stakeholder roles on cards. The backend has no bulk endpoint for this, so the tool fans out per-op inside a single mutation batch. |
Audit
| Tool | Annotation | Description |
|---|---|---|
rollback_batch | Destructive | Reverse the writes performed under a mutation batch by walking its events in reverse and applying the inverse of each. Covers card.created / card.updated / card.archived / card.restored / relation.created / relation.upserted; other event types surface as unsupported_events in the dry-run plan. The rollback itself is recorded as a new batch, so history is never erased. Refuses if a later batch touched the same entities unless force=true, which requires admin.events. |
Content stored by these tools (comment bodies, ADR/SoAW section text) is treated as untrusted data on later read-back — the server never interprets it as instructions, only displays it.
Typical artifact-import workflow
When a user shares a spreadsheet with the AI agent:
- The agent calls
list_card_typesandget_relation_typesto understand the metamodel. - The agent parses the spreadsheet (in its own context, not in MCP) and builds row dicts.
- Optionally, the agent calls
resolve_card_refsto check that any name-based parent/relation references resolve unambiguously. - The agent calls
create_cards_bulk(cards=…, dry_run=True)and shows the preview to the user. - The user confirms; the agent calls again with
dry_run=False(echoingconfirm_tokenif the batch is above the confirmation threshold) to commit. - If relation columns are present, the agent then calls
upsert_relations_bulkwith the same dry-run / confirm cycle.
The MCP server itself never parses uploaded files — the calling AI tool reads the source artifact (spreadsheet, BPMN XML, DrawIO XML, PDF, image) in its own context and sends already-structured rows.
Write-tool guardrails
Defense in depth on top of dry-run, so an LLM mishap can't cause mass damage:
- Per-call size caps. The MCP write tools enforce a much smaller cap than the underlying Excel-importer endpoints: 200 cards for
create_cards_bulk/update_cards_bulk/archive_cards, 500 ops forupsert_relations_bulk. Big enough for any realistic single artifact upload, small enough that a dry-run preview is still scannable. The backend endpoints themselves accept up to 2000/5000 for the legitimate web-UI Excel importer. - Confirmation token above threshold. Commits touching more than
MCP_BATCH_CONFIRMATION_THRESHOLDrows (default 20) must echo theconfirm_tokenissued by the prior dry-run. Enforced by both the MCP wrapper and the backend. - No relation deletion by default.
upsert_relations_bulkrefusesaction: "delete"ops — to remove relations, use the web UI where the action is captured under the user's identity with an explicit audit trail. Operators can opt in withMCP_ALLOW_RELATION_DELETE=true. - No hard delete. The toolset deliberately omits permanent card deletion.
archive_cards(soft-delete, 30-day restore window) androllback_batchare the only ways to remove data via MCP, and both are dry-run-gated and destructive-annotated. Any future tool that performs an irreversible mutation needs an RFC discussion first. - Kill switch.
MCP_WRITES_ENABLED=falseturns off all 17 write tools without redeploying code. The 30 read tools keep working. - Audit origin tag. Every backend request from the MCP server carries an
X-Canopy-Origin: mcpheader (whitelisted server-side to{mcp, web, api}— any other value is dropped). Events emitted from those requests are taggedorigin: "mcp"in the audit payload, so admins can filter MCP-driven writes out of the timeline. TheX-Canopy-Batchheader threads the current mutation-batch id through every call in a single tool invocation.
The guardrail environment variables on the MCP container:
| Variable | Default | Effect |
|---|---|---|
MCP_WRITES_ENABLED | true | Master switch for write tools. false → read-only MCP. |
MCP_MAX_CARDS_PER_CALL | 200 | Hard cap on rows per call for create_cards_bulk, update_cards_bulk, archive_cards. |
MCP_MAX_RELATIONS_PER_CALL | 500 | Hard cap on upsert_relations_bulk operations per request. |
MCP_ALLOW_RELATION_DELETE | false | When true, upsert_relations_bulk accepts action: "delete" ops. |
MCP_BATCH_CONFIRMATION_THRESHOLD | 20 | Row count above which a commit must echo a confirm_token from a prior dry-run. |
MCP_REQUIRE_DRYRUN_FIRST | true | When true, a commit above the threshold without a matching prior dry-run in the same session is rejected. Operators can disable for trusted automation pipelines. |
Resources
| URI | Description |
|---|---|
turbo-ea://types | All card types in the metamodel |
turbo-ea://relation-types | All relation types |
turbo-ea://dashboard | Dashboard KPIs and summary statistics |
Guided Prompts
| Prompt | Description |
|---|---|
analyze_landscape | Multi-step analysis: dashboard overview, types, relationships |
find_card | Search for a card by name, get details and relations |
explore_dependencies | Map what a card depends on and what depends on it |
Permissions
| Role | Access |
|---|---|
| Admin | Configure MCP settings (admin.mcp permission). Full read + write through MCP. |
| All authenticated users | Read access governed by their existing RBAC. Write tools require the matching backend permission for the action they perform. |
Data access through MCP — read or write — follows the same RBAC model as the web UI. If a user cannot create cards in the inventory UI, they cannot create them through MCP either; there are no separate MCP-specific data permissions. Selected permissions by tool:
| Permission | Gates |
|---|---|
inventory.create | create_cards_bulk |
inventory.edit | update_cards_bulk, lifecycle/status transitions via transition_card_lifecycle |
inventory.approval_status | Approval transitions (approve / reject / reset) via transition_card_lifecycle |
inventory.archive | archive_cards |
relations.manage | upsert_relations_bulk |
diagrams.manage | create_diagram, update_diagram |
bpm.edit | import_bpmn (drafting); publishing additionally needs card.approval_status on the process, held via the process_owner stakeholder role, admin, or bpm_admin |
risks.manage | create_risks, update_risks |
comments.create | add_card_comment |
stakeholders.manage | assign_stakeholders |
soaw.create | create_soaw |
adr.create | create_adr, update_adr |
adr.sign | sign_adr |
admin.events | rollback_batch(force=True) — overriding a rollback conflict with a later batch |
The admin.mcp permission controls who can manage MCP settings. It is only available to the Admin role by default. Custom roles can be granted this permission through the Roles administration page.
Several write tools degrade gracefully instead of hard-failing when the caller lacks a permission: transition_card_lifecycle and sign_adr return a pending response with a deep-link into the web UI so a human with the right permission can complete the action there.
Security
- SSO-delegated authentication: Users authenticate via their corporate SSO provider. The MCP server never sees or stores passwords (in HTTP mode).
- OAuth 2.1 with PKCE: The authentication flow uses Proof Key for Code Exchange (S256) to prevent authorization code interception.
- Per-user RBAC: Every MCP query — read or write — runs with the authenticated user's permissions. No shared service accounts.
- Dry-run by default on writes: Write tools default to a validate-and-rollback preview. The AI tool must explicitly call again with
dry_run=falsebefore anything is persisted, and every change is audited under the user's identity as part of a mutation batch. - Confirm-token gate on large commits: Commits above the configurable row threshold require a token minted by a prior dry-run, checked by both the MCP wrapper and the backend independently.
- Full audit trail with rollback:
get_change_historyreconstructs any batch's diff from its id;rollback_batchcan reverse a batch's supported event types, itself recorded as a new, auditable batch. - No file parsing in MCP: The MCP server itself does not accept PDFs, Excel files, images or other binary artifacts. The calling AI tool parses them in its own context and sends structured rows. This keeps the attack surface narrow and avoids exposing the server to malformed binary input.
- No hard delete over MCP: Permanent card deletion is not exposed as a tool. The only removal paths are
archive_cards(30-day recoverable soft-delete) androllback_batch. - Token rotation: Access tokens expire after 1 hour and are proactively refreshed. Refresh tokens last 30 days. Authorization codes are single-use and expire after 10 minutes.
- Internal-only port: The MCP container exposes port 8001 only on the internal Docker network. All external access goes through the Nginx reverse proxy.
- Single-instance token store: OAuth tokens are held in-memory on the MCP container. If you run multiple replicas behind a load balancer, pin sessions to one instance or expect users to re-authenticate after a failover — tokens are not shared across replicas.
Troubleshooting
| Issue | Solution |
|---|---|
| MCP toggle is disabled in settings | SSO must be configured first. Go to Settings > Authentication tab and set up an SSO provider. |
| "host not found" in Nginx logs | The MCP service is not running. Start it with docker compose --profile mcp up -d. The Nginx config handles this gracefully (502 response, no crash). |
| OAuth callback fails | Verify you added https://your-domain.example.com/mcp/oauth/callback as a redirect URI in your SSO app registration. |
| AI tool cannot connect | Check that MCP_PUBLIC_URL matches the URL accessible from the user's machine. Ensure HTTPS is working. |
| User gets empty results | MCP respects RBAC permissions. If a user has restricted access, they will only see the cards their role allows. |
Write tool returns writes_disabled | MCP_WRITES_ENABLED=false on this deployment. Read tools still work; ask an operator to re-enable writes if needed. |
Commit rejected with confirm_token_required | The batch is above MCP_BATCH_CONFIRMATION_THRESHOLD rows. Re-run with dry_run=true first, then pass the returned confirm_token back on the commit call. |
| Connection drops after 1 hour | The AI tool should handle token refresh automatically. If not, reconnect. |