Scopes & role permissions
The connector works under thedidit: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 itsorganization_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. Passorganization_id+application_idto narrow to a single app.
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).
Editing an existing workflow (recommended)
Real workflows carry large feature configs — an OCRdocuments_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.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.” → onebranchnode:kyc.status == Declined→ aDeclinedstatus node;kyc.extra_fields.professionfuzzy_match“Software Engineer” score 80 → aDOCUMENT_AIproof-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.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. IfThe agent should callnfc.skip_reasonisDEVICE_WITHOUT_NFCorDOCUMENT_WITHOUT_CHIP, send the session to In Review; otherwise approve it. Keep an explicit else branch and validate the graph before saving.”
didit_workflow_get_field_definitions, didit_workflow_validate_graph, then didit_workflow_set_graph. For an existing workflow, ask:
“Edit workflowThe agent should call26d33c04…: after its NFC node, route any of the seven knownnfc.skip_reasonvalues to In Review and preserve the existing successful-NFC path as the explicit else. Save a draft; do not publish it.”
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’sconfig 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_createreturns aurlthe user must complete; the decision arrives by webhook ordidit_session_get_decision.
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:- The agent calls
didit_webhook_createonce with alabel, your public HTTPSurl,webhook_version: "v3", andsubscribed_events(typically["status.updated", "data.updated"]). The response redacts the signing secret, as dodidit_webhook_getanddidit_webhook_list; copy it from Webhooks in the console and store it asDIDIT_WEBHOOK_SECRET. - Your backend verifies the canonical X-Signature-V2 header on every delivery:
shortenFloats→sortKeys→JSON.stringify(unescaped unicode) → HMAC-SHA256 →timingSafeEqual. Reject ifabs(now − X-Timestamp) > 300seconds. - Deduplicate on
event_id. Return2xxwithin 5 seconds; move heavy work to a queue. - Didit retries on
5xx/404up to 2 times (~1 min, then ~4 min), then drops the delivery — replay failed ones from the Deliveries tab in the console.
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
The agent calls a tool and gets a 403 or missing_scope
The agent calls a tool and gets a 403 or missing_scope
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.)A write or delete didn't run
A write or delete didn't run
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.'organization_id is required' or no results
'organization_id is required' or no results
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.Browser didn't open for sign-in (hosted)
Browser didn't open for sign-in (hosted)
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.Tools don't appear in the client
Tools don't appear in the client
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.VS Code (or another client) can't discover OAuth / never opens a browser
VS Code (or another client) can't discover OAuth / never opens a browser
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.A file upload is rejected
A file upload is rejected
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
READERmember, 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.