bind-mcp
// Bind Protocol MCP server for credential verification, policy authoring, and zero-knowledge proof generation.
Bind MCP Server — Agent Skill Guide
You have access to the Bind Protocol MCP server. This document teaches you how to use it.
Prerequisites & Installation
Requirements
- Node.js >= 18 — Required to run the server (
npxhandles package installation automatically) - Bind account — Required for API-backed tools. Create one at https://dashboard.bindprotocol.xyz
- Agent key (
idbr_agent_...) — Required for API-backed tools. Regular API keys (idbr_) are not supported for MCP.
Credential Setup
Bind uses agent keys for MCP authentication. Agent keys are scoped API keys that let org admins control exactly which tools are available, set daily rate limits, and get audit logs.
| Key type | Format | MCP supported |
|---|---|---|
| Agent key | idbr_agent_<keyId>_<secret> | Yes — required for API-backed tools |
| Regular API key | idbr_<keyId>_<secret> | No — rejected by MCP server |
To create an agent key:
- Sign in at https://dashboard.bindprotocol.xyz
- Navigate to Settings > Agent Keys
- Select which tool categories the key can access (e.g., credential verification only, or policy authoring + verification)
- Copy the key — it is shown only once
MCP Server Configuration
Add the server to your MCP client configuration. The exact file depends on your tool:
| Tool | Config file |
|---|---|
| Claude Code | .mcp.json in your project root, or ~/.claude/claude_desktop_config.json |
| Claude Desktop | Settings > Developer > Edit Config |
| Cursor | .cursor/mcp.json in your project root |
| Windsurf | MCP settings |
Configuration JSON:
{
"mcpServers": {
"bind": {
"command": "npx",
"args": ["@bind-protocol/mcp-server"],
"env": {
"BIND_API_KEY": "${BIND_API_KEY}"
}
}
}
}
The BIND_API_KEY environment variable must be set in your shell to your agent key before launching your AI tool. Never hardcode the key directly in config files — always use environment variable references to avoid accidental credential leakage in shared configs or repositories.
Environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
BIND_API_KEY | For API-backed tools | — | Agent key (idbr_agent_...). Without this, only local tools are available. |
BIND_API_URL | No | https://api.bindprotocol.xyz | Base URL for API calls |
BIND_RECEIPTS_PATH | No | ~/.bind/receipts | Directory for receipt chain data |
LOG_LEVEL | No | info | Logging verbosity (debug, info, warn, error) |
Verifying the Setup
If you do not have bind tools available, prompt the user to complete the setup above. You can test connectivity by calling bind_whoami — if it returns org info, the agent key is authenticated. Without a BIND_API_KEY, only local tools (parse, verify, hash) are available.
Architecture & Data Flow
The server runs locally via npx and communicates with your AI tool over stdio. It provides both local tools (always available) and API-backed tools (require an agent key). You just call the tool by name — routing is handled automatically.
| Tool type | Auth | Purpose |
|---|---|---|
| Local tools | None | Parse, verify, and hash VC-JWTs on-device |
| API-backed tools | Agent key via BIND_API_KEY | Policies, proofs, issuers, revocation, and more |
What stays local vs. what calls the API
This is critical for understanding the privacy model:
Local tools (credential data NEVER leaves the machine):
bind_parse_credential— Decodes the JWT entirely on your machinebind_verify_credential— Fetches the issuer's public JWKS from the Bind API (public keys only), then verifies the signature locally. The credential itself is never sent.bind_hash_credential— Computes a SHA-256 hash locally. Only the irreversible hash is used for revocation checks.
API-backed tools (send requests to api.bindprotocol.xyz):
bind_check_revocation— Sends only the credential hash (not the credential). The hash is not reversible.bind_resolve_issuer— Fetches public keys for an org. No credential data involved.bind_explain_policy,bind_list_policies,bind_list_circuits— Read-only public metadata. No credential data involved.bind_submit_prove_job— Sends circuit inputs to the Bind proving service. These are the raw values being proven (e.g., income amount, mileage count).bind_issue_credential— Requests the Bind API to sign and issue a VC-JWT from a completed proof.bind_create_policy,bind_validate_policy— Sends policy spec JSON to Bind for validation/storage.bind_share_proof— Shares a proof record with a verifier org via the Bind API.
In short: Raw credentials are local-only. Hashes, policy specs, proof inputs, and metadata go to the API.
Tool Inventory
Local Tools (no auth required, credential data stays on-machine)
| Tool | What it does |
|---|---|
bind_parse_credential | Decode a VC-JWT into header + payload + signature without verification |
bind_verify_credential | Full verification: parse, fetch issuer JWKS, verify ES256 signature, check expiration. Does NOT check revocation. |
bind_hash_credential | SHA-256 hash a VC-JWT. Use the hash with bind_check_revocation. |
API-Backed Tools (require agent key via BIND_API_KEY)
Discovery & Inspection
| Tool | What it does |
|---|---|
bind_resolve_issuer | Fetch an org's public signing keys (JWKS) by org ID |
bind_explain_policy | Get the public spec for a policy by policy ID |
bind_check_revocation | Check if a credential is revoked by its hash (hash only — not the credential) |
bind_list_policies | List available policies (supports limit/offset pagination) |
bind_list_circuits | List available ZK circuits |
Proof Generation & Credential Issuance
| Tool | What it does |
|---|---|
bind_submit_prove_job | Submit a ZK proof generation job with circuit ID and inputs |
bind_get_prove_job | Poll a prove job's status by job ID |
bind_list_prove_jobs | List prove jobs, optionally filtered by status |
bind_issue_credential | Issue a verifiable credential from a completed prove job |
bind_share_proof | Share a completed proof with a verifier org |
bind_list_shared_proofs | List proofs shared with/by your org |
Policy Authoring
| Tool | What it does |
|---|---|
bind_whoami | Get authenticated org info, tier, policy limits, and agent key permissions |
bind_validate_policy | Dry-run validation of a policy spec (catches errors before creation) |
bind_create_policy | Create a new verification policy |
bind_generate_circuit | Trigger ZK circuit compilation for a saved policy |
bind_get_circuit_status | Poll circuit compilation job status |
Workflow 1: Full Credential Verification
When to use: A user gives you a VC-JWT string (starts with eyJ...) and wants to know if it's valid.
Steps:
bind_parse_credential— Decode the JWT to inspect claimsbind_verify_credential— Verify signature and expirationbind_hash_credential— Compute SHA-256 hashbind_check_revocation— Send the hash (not the credential) to check revocation
Steps 1 and 2 can be combined (verify includes parsing), but parsing first lets you show the user what the credential contains before the full check. Steps 1–3 run locally; step 4 sends only the hash to the API.
Important: bind_verify_credential does NOT check revocation. You must always follow up with hash + revocation check for a complete verification.
parse → verify → hash → check_revocation
Workflow 2: Investigate an Issuer
When to use: A user wants to know about an organization's keys or policies.
bind_resolve_issuer(orgId)— Fetch their JWKSbind_list_policiesorbind_explain_policy— Look up their policies
Workflow 3: Create a Policy
When to use: A user wants to define a new verification policy.
Steps:
bind_whoami— Check org name, tier, and limits. You need the org name for the namespace.- Build the policy spec (see Policy Spec Reference below)
bind_validate_policy— Dry-run to catch errors before creation- Fix any validation errors and re-validate
bind_create_policy— Save the policybind_generate_circuit— Queue ZK circuit compilationbind_get_circuit_status— Poll until status iscompletedorfailed
Critical rules:
metadata.namespaceMUST start with your org's slugified name (frombind_whoami). The namespacesbindandsystemare reserved.- The policy
idMUST start with the namespace (e.g.,acme.finance.creditCheckfor namespaceacme). - ALWAYS validate before creating. Fix all errors first.
- String inputs MUST have an
encodingblock mapping values to numbers (ZK circuits only work with numbers).
Workflow 4: Generate a Proof and Issue a Credential
When to use: A user wants to generate a ZK proof and get a verifiable credential.
bind_list_policiesorbind_explain_policy— Find the right policy/circuitbind_submit_prove_job(circuitId, inputs)— Submit the proof jobbind_get_prove_job(jobId)— Poll until status iscompletedbind_issue_credential(proveJobId)— Issue the VC from the completed proof- Optionally:
bind_share_proof(proveJobId, verifierOrgId)— Share with a verifier
Policy Spec Reference
A policy is a JSON object with this structure. All fields shown are required unless marked optional.
{
"id": "<namespace>.<category>.<name>",
"version": "0.1.0",
"metadata": {
"title": "Human-readable title",
"description": "What this policy verifies",
"category": "finance|mobility|identity|demo",
"namespace": "your-org-name"
},
"subject": {
"type": "individual|organization|vehicle|device",
"identifier": "wallet_address|did|vin|vehicleTokenId"
},
"inputs": [
{
"id": "input_name",
"source": { "kind": "static|api", "api": "optional_api_name" },
"signal": "input_name",
"valueType": "number|boolean|string",
"unit": "USD|count|months",
"time": { "mode": "point|range|relative", "lookback": "30d" },
"aggregation": { "op": "latest|sum|mean|count" },
"encoding": {
"type": "enum",
"values": { "label1": 1, "label2": 2 }
}
}
],
"rules": [
{
"id": "rule_name",
"description": "Human-readable description",
"assert": { /* expression — see below */ },
"severity": "fail|warn|info"
}
],
"evaluation": {
"kind": "PASS_FAIL|SCORE",
"scoreRange": { "min": 0, "max": 100 },
"baseline": 50,
"contributions": [
{ "ruleId": "rule_name", "points": 30, "whenPasses": true }
]
},
"outputs": [
{
"name": "output_name",
"type": "boolean|enum|number",
"derive": {
"kind": "PASS_FAIL|SCORE|BAND|CONST",
"from": "SCORE|input_id",
"bands": [
{ "label": "LOW", "minInclusive": 0, "maxExclusive": 40 },
{ "label": "HIGH", "minInclusive": 40, "maxExclusive": 101 }
],
"value": 42
},
"disclosed": true
}
],
"validity": { "ttl": "P30D" },
"disclosure": {
"default": "SELECTIVE",
"exposeClaims": ["output_name"]
},
"proving": {
"circuitId": "<namespace>.<name>.v<version>",
"inputTypes": { "input_name": "u32" },
"outputType": "u8"
}
}
Expression Types (used in rules[].assert)
| Type | Shape | Example |
|---|---|---|
ref | { "type": "ref", "inputId": "<input_id>" } | Reference an input value |
const | { "type": "const", "value": <number|boolean> } | Literal number or boolean (never strings) |
cmp | { "type": "cmp", "cmp": ">=|<=|>|<|==|!=", "left": <expr>, "right": <expr> } | Comparison |
op | { "type": "op", "op": "+|-|*|/", "args": [<expr>, ...] } | Arithmetic |
and | { "type": "and", "args": [<expr>, ...] } | Logical AND |
or | { "type": "or", "args": [<expr>, ...] } | Logical OR |
not | { "type": "not", "arg": <expr> } | Logical NOT |
Field name gotchas:
- Use
"inputId"in ref expressions, NOT"path" - Use
"cmp"for the comparison operator, NOT"operator" - Use
"args"for operand lists, NOT"children" - Use
"arg"(singular) fornot, NOT"expr" constvalues must be numbers or booleans, never strings
Evaluation Kinds
PASS_FAIL: All severity: "fail" rules must pass. No scoring.
SCORE: Starts at baseline, adds/subtracts points from rule contributions.
{
"kind": "SCORE",
"scoreRange": { "min": 0, "max": 100 },
"baseline": 50,
"contributions": [
{ "ruleId": "has_high_income", "points": 25, "whenPasses": true },
{ "ruleId": "has_delinquencies", "points": -20, "whenPasses": true }
]
}
Output Derive Kinds
| Kind | Use for | Required fields |
|---|---|---|
PASS_FAIL | Boolean pass/fail from evaluation | None |
SCORE | Raw numeric score | from: "SCORE" |
BAND | Map score to labeled bands | from: "SCORE", bands array |
CONST | Fixed value | value |
Working with String Inputs
ZK circuits only work with numbers. When a policy uses string inputs (employer names, country codes, etc.), you MUST include an encoding block:
{
"id": "employer",
"source": { "kind": "static" },
"signal": "employer",
"valueType": "string",
"encoding": {
"type": "enum",
"values": {
"Acme Corp": 1,
"Globex Inc": 2,
"Initech": 3
}
}
}
Proving Section
The proving section maps inputs to Noir types for the ZK circuit:
{
"proving": {
"circuitId": "acme.safe_driver.v0_1_0",
"inputTypes": {
"miles_driven": "u32",
"hard_brake_pct": "u8",
"is_commercial": "bool"
},
"outputType": "u8"
}
}
Available Noir types: u8, u16, u32, u64, i8, i16, i32, i64, bool, Field
Tier Restrictions
Policy authoring is gated by organization tier. Always call bind_whoami first to check limits.
| Tier | Can Create Policies | Notes |
|---|---|---|
| Basic | No | Verification only |
| Premium | Yes | Limited inputs/rules/outputs |
| Scale | Yes | Expanded limits, can create extractors |
| Enterprise | Yes | Unlimited |
| Verifier | No | Cannot create proofs or policies |
Common Errors and Fixes
| Error | Cause | Fix |
|---|---|---|
NAMESPACE_MISMATCH | Policy namespace doesn't match your org | Use your org name from bind_whoami as the namespace prefix |
TIER_LIMIT_EXCEEDED | Your tier doesn't allow this operation | Check bind_whoami for limits |
INVALID_EXPRESSION | Malformed rule assertion | Check expression field names (inputId, cmp, args, arg) |
MISSING_ENCODING | String input without encoding | Add encoding.type: "enum" with values map |
CIRCUIT_COMPILATION_FAILED | Circuit couldn't compile | Check the error in bind_get_circuit_status, fix the policy, recreate, and regenerate |
Example: Complete Policy Creation
Here is a complete example of creating a credit score policy for org acme:
{
"id": "acme.finance.credit-check",
"version": "0.1.0",
"metadata": {
"title": "Credit Eligibility Check",
"description": "Evaluates creditworthiness based on income and debt ratio",
"category": "finance",
"namespace": "acme"
},
"subject": {
"type": "individual",
"identifier": "wallet_address"
},
"inputs": [
{
"id": "annual_income",
"source": { "kind": "static" },
"signal": "annual_income",
"valueType": "number",
"unit": "USD"
},
{
"id": "debt_ratio",
"source": { "kind": "static" },
"signal": "debt_ratio",
"valueType": "number",
"unit": "percent"
}
],
"rules": [
{
"id": "min_income",
"description": "Annual income must be at least $30,000",
"assert": {
"type": "cmp",
"cmp": ">=",
"left": { "type": "ref", "inputId": "annual_income" },
"right": { "type": "const", "value": 30000 }
},
"severity": "fail"
},
{
"id": "max_debt_ratio",
"description": "Debt-to-income ratio must be under 40%",
"assert": {
"type": "cmp",
"cmp": "<",
"left": { "type": "ref", "inputId": "debt_ratio" },
"right": { "type": "const", "value": 40 }
},
"severity": "fail"
}
],
"evaluation": {
"kind": "PASS_FAIL"
},
"outputs": [
{
"name": "eligible",
"type": "boolean",
"derive": { "kind": "PASS_FAIL" },
"disclosed": true
}
],
"validity": { "ttl": "P30D" },
"disclosure": {
"default": "SELECTIVE",
"exposeClaims": ["eligible"]
},
"proving": {
"circuitId": "acme.credit_check.v0_1_0",
"inputTypes": {
"annual_income": "u32",
"debt_ratio": "u8"
},
"outputType": "u8"
}
}
Agent workflow for this:
bind_whoami— Confirm org isacmeand tier allows policy creationbind_validate_policy(policy)— Dry-run validationbind_create_policy(policy)— Create the policybind_generate_circuit("acme.finance.credit-check")— Compile the circuitbind_get_circuit_status(jobId)— Poll until complete