Skip to main content

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
  1. A user adds the MCP server URL to their AI tool.
  2. On first connection, the AI tool opens a browser window for SSO authentication.
  3. 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.
  4. The AI tool uses this token for all subsequent requests.
  5. 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
VariableDefaultDescription
CANOPY_URLhttp://backend:8000Internal backend URL the MCP container talks to (Docker service name — rarely needs changing).
CANOPY_PUBLIC_URLhttp://localhost:8920The public URL of your Canopy instance.
MCP_PUBLIC_URLhttp://localhost:8920/mcpThe public URL of the MCP server (used in OAuth redirect URIs and metadata).
MCP_PORT8001Internal 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

  1. Go to Settings in the admin area and select the AI tab.
  2. Scroll to the MCP Integration (AI Tool Access) section.
  3. Toggle the switch to enable MCP.
  4. The UI will show the MCP Server URL and setup instructions to share with your team.
warning

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

  1. Open Settings > Connectors > Add custom connector.
  2. Enter the MCP server URL: https://your-domain.example.com/mcp
  3. Click Connect — a browser window opens for SSO login.
  4. 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_EMAIL": "[email protected]",
"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

ToolDescription
search_cardsSearch and filter cards by type, status, or free text
get_cardGet full details of a card by UUID
get_card_relationsGet all relations connected to a card
get_card_hierarchyGet ancestors and children of a card
list_card_typesList all card types in the metamodel, with fields and configuration
get_relation_typesList relation types, optionally filtered by card type
resolve_card_refsPre-validate name-based card references (e.g. "Sales / Customer Mgmt / CRM") before a bulk import — surfaces resolved / ambiguous / missing matches
analyze_impactMulti-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

ToolDescription
get_dashboardKPI dashboard (counts, data quality, approvals, activity)
get_landscapeCards of one type grouped by a related type

GRC — Risk Register

ToolDescription
list_risksPaginated, filterable EA risk listing (TOGAF Phase G)
get_riskSingle risk detail with linked cards + audit trail
get_risk_metricsKPIs + 4×4 initial / residual probability × impact matrices
get_card_risksAll risks currently linked to a specific card

GRC — Compliance

ToolDescription
list_compliance_findingsCompliance findings bundled by regulation (EU AI Act, GDPR, NIS2, DORA, SOC 2, ISO 27001)
get_compliance_overviewCompliance scores + per-regulation status matrix + last-scan metadata

Governance & Delivery

ToolDescription
list_principlesPublished EA principles (statement, rationale, implications)
list_adrsArchitecture Decision Records, filterable by initiative / card / status / search
get_adrSingle ADR with sections, linked cards, related ADRs, signature trail
list_soawsStatements of Architecture Work for an initiative

Reports

ToolDescription
get_portfolio_reportBubble-chart data for a card type (functional × technical fit by default)
get_cost_treemapTreemap of card cost, optionally grouped by a related type
get_capability_heatmapHierarchical business-capability heatmap
get_data_quality_reportPer-card-type completeness breakdown

Card context

ToolDescription
get_card_stakeholdersUsers + roles assigned to a card
get_card_commentsThreaded comments on a card
get_card_documentsDocument links attached to a card

Diagrams

ToolDescription
list_diagramsList free-draw diagrams, optionally filtered to one card
get_diagramFetch a single diagram by id, including its DrawIO XML

Audit & change history

ToolDescription
get_change_historyLook 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

ToolAnnotationDescription
create_cards_bulkAdditiveCreate 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_bulkAdditiveUpdate 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_lifecycleAdditiveMove 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_cardsDestructiveSoft-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

ToolAnnotationDescription
upsert_relations_bulkDestructiveCreate 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_diagramAdditiveCreate a free-form DrawIO diagram, optionally linked to existing cards by UUID.
update_diagramDestructiveUpdate an existing diagram's XML, name, description, or linked cards — drawio_xml replaces the canvas verbatim.
import_bpmnAdditiveSave 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

ToolAnnotationDescription
create_risksAdditiveCreate one or more risks, optionally linking affected cards in the same call.
update_risksAdditivePatch existing risks by id; linked_card_ids replaces the affected-card link set when supplied.

Governance & delivery

ToolAnnotationDescription
create_soawAdditiveCreate a Statement of Architecture Work for an initiative.
create_adrAdditiveCreate an Architecture Decision Record (lands in draft by default).
update_adrAdditiveUpdate an existing ADR's title, sections, status, or linked cards.
sign_adrAdditiveSign an ADR. Returns a pending response with a deep-link if the caller lacks adr.sign.

Collaboration

ToolAnnotationDescription
add_card_commentAdditivePost a (optionally threaded) comment on a card.
assign_stakeholdersAdditiveAssign 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

ToolAnnotationDescription
rollback_batchDestructiveReverse 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:

  1. The agent calls list_card_types and get_relation_types to understand the metamodel.
  2. The agent parses the spreadsheet (in its own context, not in MCP) and builds row dicts.
  3. Optionally, the agent calls resolve_card_refs to check that any name-based parent/relation references resolve unambiguously.
  4. The agent calls create_cards_bulk(cards=…, dry_run=True) and shows the preview to the user.
  5. The user confirms; the agent calls again with dry_run=False (echoing confirm_token if the batch is above the confirmation threshold) to commit.
  6. If relation columns are present, the agent then calls upsert_relations_bulk with 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 for upsert_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_THRESHOLD rows (default 20) must echo the confirm_token issued by the prior dry-run. Enforced by both the MCP wrapper and the backend.
  • No relation deletion by default. upsert_relations_bulk refuses action: "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 with MCP_ALLOW_RELATION_DELETE=true.
  • No hard delete. The toolset deliberately omits permanent card deletion. archive_cards (soft-delete, 30-day restore window) and rollback_batch are 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=false turns 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: mcp header (whitelisted server-side to {mcp, web, api} — any other value is dropped). Events emitted from those requests are tagged origin: "mcp" in the audit payload, so admins can filter MCP-driven writes out of the timeline. The X-Canopy-Batch header threads the current mutation-batch id through every call in a single tool invocation.

The guardrail environment variables on the MCP container:

VariableDefaultEffect
MCP_WRITES_ENABLEDtrueMaster switch for write tools. false → read-only MCP.
MCP_MAX_CARDS_PER_CALL200Hard cap on rows per call for create_cards_bulk, update_cards_bulk, archive_cards.
MCP_MAX_RELATIONS_PER_CALL500Hard cap on upsert_relations_bulk operations per request.
MCP_ALLOW_RELATION_DELETEfalseWhen true, upsert_relations_bulk accepts action: "delete" ops.
MCP_BATCH_CONFIRMATION_THRESHOLD20Row count above which a commit must echo a confirm_token from a prior dry-run.
MCP_REQUIRE_DRYRUN_FIRSTtrueWhen 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

URIDescription
turbo-ea://typesAll card types in the metamodel
turbo-ea://relation-typesAll relation types
turbo-ea://dashboardDashboard KPIs and summary statistics

Guided Prompts

PromptDescription
analyze_landscapeMulti-step analysis: dashboard overview, types, relationships
find_cardSearch for a card by name, get details and relations
explore_dependenciesMap what a card depends on and what depends on it

Permissions

RoleAccess
AdminConfigure MCP settings (admin.mcp permission). Full read + write through MCP.
All authenticated usersRead 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:

PermissionGates
inventory.createcreate_cards_bulk
inventory.editupdate_cards_bulk, lifecycle/status transitions via transition_card_lifecycle
inventory.approval_statusApproval transitions (approve / reject / reset) via transition_card_lifecycle
inventory.archivearchive_cards
relations.manageupsert_relations_bulk
diagrams.managecreate_diagram, update_diagram
bpm.editimport_bpmn (drafting); publishing additionally needs card.approval_status on the process, held via the process_owner stakeholder role, admin, or bpm_admin
risks.managecreate_risks, update_risks
comments.createadd_card_comment
stakeholders.manageassign_stakeholders
soaw.createcreate_soaw
adr.createcreate_adr, update_adr
adr.signsign_adr
admin.eventsrollback_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=false before 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_history reconstructs any batch's diff from its id; rollback_batch can 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) and rollback_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

IssueSolution
MCP toggle is disabled in settingsSSO must be configured first. Go to Settings > Authentication tab and set up an SSO provider.
"host not found" in Nginx logsThe 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 failsVerify you added https://your-domain.example.com/mcp/oauth/callback as a redirect URI in your SSO app registration.
AI tool cannot connectCheck that MCP_PUBLIC_URL matches the URL accessible from the user's machine. Ensure HTTPS is working.
User gets empty resultsMCP respects RBAC permissions. If a user has restricted access, they will only see the cards their role allows.
Write tool returns writes_disabledMCP_WRITES_ENABLED=false on this deployment. Read tools still work; ask an operator to re-enable writes if needed.
Commit rejected with confirm_token_requiredThe 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 hourThe AI tool should handle token refresh automatically. If not, reconnect.