Connect an MCP host to your VTA

Run vta-mcp so an MCP-speaking host like Claude Code or Claude Desktop can sign, read the vault, and manage a VTA directly as tools, with no custom integration code.

By the end of this guide, an MCP-speaking host such as Claude Code or Claude Desktop can call your VTA’s signing, vault, and management operations directly as tools, with no custom integration code.

vta-mcp is a Model Context Protocol server that puts a VTA’s operations within reach of an MCP host. It is a bridge, not an authority: every call still runs as an authenticated request against your VTA, and the VTA’s role, access control list (ACL), and context scope decide what actually happens. For how that authorisation is bounded, see Roles and access.

Connecting an agent framework to a signing or secrets backend otherwise means writing and maintaining bespoke client code for each project. Running the agent under your own operator credential avoids that work, at the cost of handing a tool-calling model the full reach of your admin session. The bridge adds its own local policy on top (--read-only, --allow, --deny, --confirm), because an MCP host approves a tool, not an individual call.

Use this guide when:

  • You want an AI coding assistant or agent framework to sign, read the vault, or manage a VTA without you writing an integration.
  • You want that access scoped to a dedicated identity, not your own operator session.
  • You want every tool call to remain an authenticated, policy-checked operation against the VTA, not a bypass around it.

Prerequisites

  • A running VTA connected to a DIDComm mediator, the relay service that carries DIDComm traffic between the bridge and the VTA. See DIDComm mediator deployment options if you haven’t set one up.

  • Install Rust 1.95 or later on your machine (required to build vta-mcp, which isn’t published to crates.io).

  • pnm installed and configured with a working VTA connection.

    cargo install pnm-cli@0.16.4 --locked --registry crates-io
  • Super-admin access to the VTA (required for pnm contexts create).

  • An MCP-speaking host: Claude Code, Claude Desktop, or similar.

Step 1: Create a context for the bridge

Run the bridge as its own identity, scoped to its own context, not as yourself. This is the isolation boundary: whatever the bridge can reach is exactly what this context’s ACL grants it.

pnm contexts create \
    --id    mcp-bridge \
    --name  "MCP bridge"

Step 2: Provision the bridge’s agent identity

This is the same sealed-transfer bootstrap used to provision any application credential. See Sealed transfer for how the digest check and the sealed bundle work, or Sign application payloads without exposing your keys for another worked example of this same procedure.

pnm bootstrap request --out request.json

pnm auth-credential create \
    --role      application \
    --contexts  mcp-bridge \
    --label     "mcp-bridge-agent" \
    --recipient request.json > bundle.txt

Copy the SHA-256 digest printed to stderr, then open the bundle:

pnm bootstrap open \
    --bundle        bundle.txt \
    --expect-digest <digest-from-previous-command> \
    --out           credential.json

credential.json now holds the three values vta-mcp needs: did, privateKeyMultibase, and vtaDid. The application role grants only what a signing/vault-reading agent needs, not context or ACL management. Delete bundle.txt and request.json once you’ve read credential.json, they’re single-use.

Step 3: Get your mediator DID

pnm services list

Copy the DID from the DIDComm row.

Step 4: Build vta-mcp

vta-mcp ships in the same source repository as the VTA and pnm, but isn’t published as a crate, so cargo install doesn’t reach it.

git clone https://github.com/OpenVTC/verifiable-trust-infrastructure.git
cd verifiable-trust-infrastructure
cargo build -p vta-mcp --release

The binary is at target/release/vta-mcp.

Step 5: Configure your MCP host

Add vta-mcp to your host’s MCP server config, passing the values from Steps 2 and 3. For Claude Code, this is .mcp.json in your project (or claude mcp add); for Claude Desktop, claude_desktop_config.json. Most other hosts take the same shape:

{
  "mcpServers": {
    "vta": {
      "command": "/path/to/target/release/vta-mcp",
      "args": [
        "--agent-did", "<did-from-credential.json>",
        "--vta-did", "<vtaDid-from-credential.json>",
        "--mediator-did", "<mediator-did-from-step-3>",
        "--confirm", "sensitive"
      ],
      "env": { "VTA_MCP_AGENT_KEY": "<privateKeyMultibase-from-credential.json>" }
    }
  }
}

--confirm sensitive asks a human, through the host’s own approval prompt, before any operation that signs, releases a secret, or moves authority. That is tighter than the bridge’s destructive-only default.

What the bridge exposes

The bridge registers four MCP tools, rather than one per VTA operation:

ToolWhat it does
vta_statusReports the bridge’s identity, its configured transports, and its current policy.
vta_list_operationsReturns the catalogue of operations this bridge can reach. This is where the slugs for --allow and --deny come from.
vta_supported_tasksAsks the VTA which tasks it serves.
vta_callRuns one named operation. Every VTA operation is reached through this tool.

Because vta_call is a single tool that names its operation per call, an MCP host approving “the vta_call tool” has approved the whole reachable surface at once. That is what the bridge’s own policy flags exist to narrow.

Narrow what the bridge can reach

FlagEffect
--read-onlyRefuses every operation that is not read-only, whatever the ACL permits.
--deny <glob>Refuses matching operation slugs. Checked first, and wins over --allow.
--allow <glob>Once set, anything not matching is denied.
--confirm <level>Asks a human through the host’s approval prompt. sensitive is tighter than the destructive default.

Both filters match operation slugs as globs. Run vta_list_operations first to see the slugs this bridge actually reaches, rather than guessing at names.

These filters are local to the bridge and sit on top of the VTA’s own authorisation. They can only narrow what the bridge’s identity could already do; they never widen it. See the vta-mcp README for the full flag reference and every supported authentication mode.

Confirm

Restart your MCP host so it picks up the new server, then ask it to call vta_status. Expect a response naming the bridge’s identity, its configured transports, and its current policy, confirming the connection to your VTA is live.

Troubleshooting

SymptomLikely causeFix
The host lists no tools for vtaThe server exited before speaking MCP, usually a bad --confirm value or an unopenable pathRead the host’s own MCP server log for the exact startup error.
Every tool returns “not connected to its VTA”vta-mcp couldn’t authenticate with the flags given, so it’s serving MCP in degraded mode rather than exiting silentlyThe error names the missing value. Re-check --agent-did, --vta-did, --mediator-did, and VTA_MCP_AGENT_KEY.
A tool call fails with '...' is not in this bridge's --allow list--allow is set and the operation’s slug isn’t in itAdd the slug, or drop --allow if you don’t need it.
vault_release fails with UnsupportedTransportIt opens a sealed bundle using this client’s own keys, which requires DIDCommConfirm you’re using the did:key agent mode from this guide, not a REST/token mode.
“Not authenticated” even though the credential looks right--mediator-did is wrong or points at a different VTARe-run pnm services list against the same VTA and copy the DIDComm row again.

Next steps

  Sealed transfer: the mechanism behind the credential bootstrap in Step 2.

  Give your AI agent persistent memory: a natural pairing, run the memory tools alongside vta-mcp for the same agent.

  Roles and access: how the application role bounds what the bridge can reach.

  Grant and revoke access: withdraw the bridge’s credential when you stop using it.

  Manage the contexts on a VTA: remove the mcp-bridge context and everything provisioned into it.