Skip to main content

Scopes & role permissions

The hosted connector requests two coarse scopes — didit:management (workspace operations) and didit:verification (running checks). Both boundaries are enforced server-side on every call: the token carries the scopes you approved, and within them your console role decides what you can do. Effective access is the intersection, so approving only didit:verification keeps the management surface closed even for an OWNER: The MCP never escalates beyond your role. A 403 on one tool means your role lacks that permission, not that the whole connection failed.

Cross-app & org resolution

The Didit management API is scoped per application (/organization/{org}/application/{app}/…). The MCP resolves scope for you:
  • Single org + app → resolved automatically; you never pass IDs.
  • Multiple apps → the cross-app tools (didit_session_search, didit_transaction_search, didit_case_search, didit_vendor_user_search, didit_vendor_business_search, didit_analytics) aggregate over all of them in one call, each row tagged with its organization_id / application_id.
  • The per-app list tools (didit_session_list, didit_transaction_list, …) auto-span every app when you don’t specify one, so a plain “list my sessions” works even with many apps. Pass organization_id + application_id to narrow to a single app.
Use didit_context_get to see all your organizations and applications (and their IDs) in a single call.

Branching workflows

Simple workflows are a flat list of features. For conditional logic — decline on a status, route on an extracted field, gate a step on a match, add a Document-AI request — the MCP drives Didit’s node graph (the same structure as the console’s visual builder). A graph is { start_node, nodes }; each node is a feature, branch, status (terminal), action, or webhook. Branch rules support operators including fuzzy_match (string fields, with a score 0–100 similarity threshold) on extracted fields like kyc.extra_fields.profession, and a DOCUMENT_AI feature node requests documents (e.g. proof of funds). Real workflows carry large feature configs — an OCR documents_allowed allow-list can be 150 KB+, plus POA lists and phone-country lists. So don’t read the whole graph and resend it. Instead edit with small operations:
1

Locate & inspect

didit_workflow_search { workflow_id } finds it across all your apps. didit_workflow_get_graph returns the structure with big configs summarized (it never overflows); didit_workflow_get_field_definitions lists branchable fields + valid operators.
2

Edit with ops

didit_workflow_edit_graph takes a few small ops — set_next, set_node, set_branches, merge_node_config — and the MCP fetches the full graph server-side, applies them, validates, auto-creates a draft, and saves. Your allow-lists are preserved byte-for-byte and never pass through the request. A validation failure returns applied: false (nothing saved).
3

Review & publish

It stays a reviewable draft until you publish (in the console or via didit_workflow_publish). The live version is never touched.
For example, to insert a branch after ID verification that rejoins your existing pipeline, you only send: rewire the OCR node’s next to a new branch, and add the branch + a terminal Declined node + a DOCUMENT_AI step (whose next points back at the original next step). The other 10 nodes — and their allow-lists — are untouched.
“After ID verification, if it was declined, decline; if profession ≈ ‘Software Engineer’ (≥80%), request proof of funds; then continue.” → one branch node: kyc.status == Declined → a Declined status node; kyc.extra_fields.profession fuzzy_match “Software Engineer” score 80 → a DOCUMENT_AI proof-of-funds node → and an else path back into the pipeline.
A branch always needs an explicit else. The catch-all (the path taken when no condition matches) must be a branch with empty rules — e.g. { "id": "else", "rules": [], "goto": "<liveness>" } — not the branch node’s bare next. The MCP (and the backend) auto-convert a branch node’s next into an else branch, so a branch never renders as an ambiguous edge with no visible else.
To build a new workflow from scratch, use didit_workflow_validate_graph then didit_workflow_set_graph (full graph). Even a plain didit_workflow_create is saved as this same node graph — the MCP assembles your features list into chained feature nodes ending in an auto-decide status, so the features actually run (it publishes by default; pass status: "draft" to keep it unpublished). See Create Workflow → branching & node-based workflows for the full JSON shape.

NFC skip-reason branches with MCP

For a new graph, ask:
“Create a draft workflow with ID verification followed by NFC. If nfc.skip_reason is DEVICE_WITHOUT_NFC or DOCUMENT_WITHOUT_CHIP, send the session to In Review; otherwise approve it. Keep an explicit else branch and validate the graph before saving.”
The agent should call didit_workflow_get_field_definitions, didit_workflow_validate_graph, then didit_workflow_set_graph. For an existing workflow, ask:
“Edit workflow 26d33c04…: after its NFC node, route any of the six known nfc.skip_reason values to In Review and preserve the existing successful-NFC path as the explicit else. Save a draft; do not publish it.”
The agent should call didit_workflow_search, didit_workflow_get_graph, didit_workflow_get_field_definitions, then didit_workflow_edit_graph. The edit tool applies small operations server-side, so it preserves unrelated nodes and large allow-lists. See Create Workflow → Branch on why NFC did not run for the six values, equals/in rules, null behavior, and a complete ordered graph.

Feature configs and allow-lists

A feature node’s config restricts what it accepts. The canonical OCR allow-list is per-country → per-document → object: { "<ISO3>": { "<CODE>": { "enabled": 1 } } } (e.g. { "ESP": { "P": { "enabled": 1 } } }). The MCP normalizes friendlier shapes for you before validating, so you don’t have to hand-write that structure: PROOF_OF_ADDRESS’s poa_languages_allowed is the same idea — pass a language array (["es"]) or a map ({ "es": 1 }). These conveniences apply wherever you give the MCP a feature config: didit_workflow_create features, and the node configs in set_graph / edit_graph / validate_graph.

Response shape (V3 contract)

Every tool returns the verbatim JSON of the underlying REST endpoint. Two things to know:
  • Synchronous tools (workflows, billing, standalone verify_*, list management) return their final result inline — act on it the same turn.
  • Session creation is asynchronousdidit_session_create returns a url the user must complete; the decision arrives by webhook or didit_session_get_decision.
Decision keys are always plural arrays with a node_id per entry (id_verifications[], liveness_checks[], face_matches[], aml_screenings[], poa_verifications[], phone_verifications[], email_verifications[], database_validations[], reviews[], plus KYB registry_checks[], document_verifications[], key_people_checks[]). See Data models and Verification statuses.

Handling webhooks

Let a webhook deliver the final decision to your backend rather than polling from the agent:
  1. The agent calls didit_webhook_create once with your public HTTPS endpoint, webhook_version: "v3", and subscribed_events (typically ["status.updated", "data.updated"]). The response includes a secret_shared_key — store it as DIDIT_WEBHOOK_SECRET.
  2. Your backend verifies the canonical X-Signature-V2 header on every delivery: shortenFloatssortKeysJSON.stringify (unescaped unicode) → HMAC-SHA256 → timingSafeEqual. Reject if abs(now − X-Timestamp) > 300 seconds.
  3. Deduplicate on event_id. Return 2xx within 5 seconds; move heavy work to a queue.
  4. Didit retries on 5xx / 404 up to 2 times (~1 min, then ~4 min), then drops the delivery — replay failed ones from the Deliveries tab in the console.
The agent only needs to wire the receiver; it doesn’t listen itself. Full contract: Webhooks.

Troubleshooting

Re-authenticate: reconnect / re-run Log in with Didit. If only one tool 403s, your role lacks that permission (e.g. a READER attempting a write). (Note: the MCP is Bearer/OAuth only — an x-api-key is rejected by every tool, so an API key won’t fix a 403.)
Destructive tools require confirmation in your client. Approve the call, or check that the tool’s confirmation prompt isn’t being auto-denied. didit_org_reveal_application_api_key, didit_session_delete, and didit_session_batch_delete additionally need confirm: true in the tool arguments - without it the call is rejected before anything runs.
You likely have multiple apps. Use a *_search tool (which spans everything) or run didit_context_get and pass the organization_id / application_id you want.
Some clients (Windsurf, Zed) connect through the mcp-remote bridge — make sure npx -y mcp-remote@latest https://mcp.didit.me/mcp is in the config. Re-open the client to retrigger the OAuth handshake.
Confirm the server connected (most clients show a status dot) and that you completed Log in with Didit. Restart the MCP host after editing its config.
Make sure you’re pointed at https://mcp.didit.me/mcp, not the bare https://mcp.didit.me origin — the resource metadata some clients probe for is served at /.well-known/oauth-protected-resource/mcp, scoped to that path. If you’re self-hosting, this usually means MCP_RESOURCE_URI is set to the origin instead of the full transport URL; see Self-hosting.

Security

The MCP acts with your real permissions and can mutate live resources. Keep “review tool calls before running” enabled in your client — especially for Write and Destructive tools (deletes, org_top_up, org_reveal_application_api_key). The MCP itself stores no secret in your config (it uses Log in with Didit); and if a tool returns an application API key (a REST credential), don’t paste it into a shared chat.

Self-hosting

The server is open source (MIT) — github.com/didit-protocol/mcp. Clone it and run with Docker or Node; it always authenticates as a Didit user (there is no API-key mode), in either transport:
Base URLs and OAuth endpoints are environment variables with public defaults (verification.didit.me, apx.didit.me, business.didit.me) — override them for a private deployment. See the repository’s README and ARCHITECTURE for the full environment-variable reference and the resource-server / authorization-server split. MCP_RESOURCE_URI must match your deployment’s actual Streamable HTTP transport endpoint, including the path — for example https://your-host.example.com/mcp, not just the bare origin. The server derives its RFC 9728 protected-resource metadata URL from this value, so a mismatch breaks OAuth discovery for clients (such as VS Code) that request the path-specific metadata document.

Tools reference

Every tool, grouped by area.

Examples

Prompts and end-to-end conversations.