Skip to main content

Scopes & role permissions

The connector works under the didit:management and didit:verification scopes (a third, didit:staff, is advertised but only matters for Didit staff accounts). Both boundaries are enforced 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. Each tool maps to one backend permission (read:sessions, write:workflows, delete:lists, and so on; the tools reference lists it per tool). The server pre-checks that permission with the organization permissions the token introspection returns, controlled by MCP_PERMISSION_MODE: 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_workflow_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, didit_case_list, didit_vendor_user_list, didit_vendor_business_list, didit_workflow_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. It also reports each application’s mode (live or sandbox), so never guess the environment from an application’s name; didit_session_create accepts a sandbox_scenario on sandbox applications for unbilled test runs.

What the hosted catalog contains

The hosted connector gives you 169 tools. It also lists 17 Investigator tools, which only work once the Investigator is enabled for your organization. Two more tools, didit_org_top_up (Stripe checkout) and didit_org_reveal_application_api_key (raw secret), are hidden from the tools/list of every OAuth-authenticated caller. The five account-setup tools (didit_account_*) are excluded from remote servers entirely and refuse to run there, because a remote connector must never take credentials as tool arguments. A local stdio server keeps all of them, 193 in total. Each listed tool carries MCP annotations (readOnlyHint, destructiveHint, openWorldHint, a title) and an outputSchema, and a _meta["anthropic/toolGroup"] label that names its area (Sessions, Workflows & Questionnaires, Transactions (AML), and so on). Clients such as Claude use the annotations to split the connector’s tools into read-only, write and destructive groups.

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; pass include_config: true for the raw values); 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), or let didit_workflow_build_graph assemble a complete graph from a plain feature spec without saving. 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 seven 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 seven 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. didit_workflow_get_feature_config_schema returns every key each feature takes, with types and defaults, and the two catalog tools (didit_workflow_get_id_verification_methods_catalog, didit_workflow_get_kyb_registry_catalog) answer the server-driven questions a config depends on. 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 asynchronous — didit_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 a label, your public HTTPS url, webhook_version: "v3", and subscribed_events (typically ["status.updated", "data.updated"]). The response redacts the signing secret, as do didit_webhook_get and didit_webhook_list; copy it from Webhooks in the console and store it as DIDIT_WEBHOOK_SECRET.
  2. Your backend verifies the canonical X-Signature-V2 header on every delivery: shortenFloats → sortKeys → JSON.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.

Transport and endpoints

The endpoint is dual-era: clients that pin a 2026-07-28 or later protocol version on every message skip the initialize handshake and can call server/discover; every earlier client (Claude Code, Cursor, SDK-based clients) uses the standard Streamable HTTP handshake unchanged. CORS is open, so browser-based clients (the Claude web connector, MCP Inspector) can reach /mcp and the discovery documents cross-origin. The JSON body limit is 25 MB, enough for a 15 MB base64 file input.

Troubleshooting

Your role in that organization lacks the permission the tool needs (e.g. a READER attempting a write); the missing_scope error names it. If every tool fails, reconnect / re-run Log in with Didit. (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_session_delete, didit_session_batch_delete and didit_org_reveal_application_api_key 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. didit_org_top_up and didit_org_reveal_application_api_key are never listed on the hosted connector.
Make sure you’re pointed at https://mcp.didit.me/mcp, not the bare https://mcp.didit.me origin — the resource metadata is served at /.well-known/oauth-protected-resource/mcp, scoped to that path (with a copy at the origin root for clients that probe there). 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.
Against the hosted endpoint send the file as *_base64 (raw base64 or a data: URL); *_path only works on a local stdio server. Files are capped at 15 MB and must be PNG, JPG, WebP, GIF, BMP, ICO or PDF by content, not just by extension.

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. The MCP itself stores no secret in your config (it uses Log in with Didit), redacts API keys and webhook signing secrets in tool output, hides the top-up and raw-key tools from the hosted catalog, and never accepts account credentials as tool arguments on a remote server. Every tool call is written to the server’s audit log with the caller, the tool and the permission verdict.

Prompt injection

Didit tools return what your users typed or uploaded, such as names, document text and questionnaire answers. A person being verified controls that text, so they can write instructions aimed at your agent into a name or a document, and the server returns those fields as they were captured. An agent cannot reliably tell that text from your own instructions, so treat every tool result as data:
  • Keep tool approval on for Write and Destructive tools, and read what the agent is about to do before you approve it.
  • Sign in as a member whose role fits the job. An agent that only reads sessions can use a READER member, and the backend refuses its writes whatever the agent tries.
  • Try a new agent or prompt on a sandbox application first.
  • Never approve or decline a verification because text inside that verification says to. Rely on the decision, its warnings and your own review.

Personal data

Tool results can include personal data such as names, dates of birth, document numbers and links to images, and your MCP client sends them to the model it uses. Before you connect a live application, check that your agreement with your AI provider covers this data, or work on a sandbox application.

Self-hosting

The hosted server is built from github.com/didit-protocol/mcp (MIT). Clone it and run with Docker or Node; it always authenticates as a Didit user (there is no API-key mode), in either transport. Setup steps are on Installation → self-hosting.

Environment variables

.env.example in the repository is a copyable template, and ARCHITECTURE.md explains the resource-server / authorization-server split. At boot the HTTP server fetches the authorization server’s discovery document and falls back to the configured URLs if it is unreachable.

Tools reference

Every tool, grouped by area.

Examples

Prompts and end-to-end conversations.
Last modified on October 31, 2026