@solidus-network/mcp
v0.1.1
Published
Solidus MCP server — run-time agent tools: resolve did:solidus, verify credentials, check/create scoped spend mandates, and authorize payments against them.
Maintainers
Readme
@solidus-network/mcp
An MCP (Model Context Protocol) server that exposes Solidus as
run-time tools an agent can call — resolve a did:solidus DID, verify a credential, and
check/create/authorize a scoped spend mandate. Point any MCP-compatible client (Claude Code, Cursor,
Windsurf, or a custom agent loop) at it over stdio and those become ordinary tool calls.
Status — read this first
- First release (0.1.0). New package, testnet-grade.
- Solidus L1 is testnet-only — there is no mainnet. DIDs resolve as
did:solidus:testnet:<addr>. Treat anything you create through these tools (DIDs, credentials, mandates) as sandbox data, not a durable production record. - Credential signing (BBS+) has no external security audit yet. Do not protect production-grade value with it.
agent_payauthorizes; it never settles. Solidus is not a payment rail. The tool checks a mandate and, if it's within scope, hands back a signed envelope for the caller to present to their own rail (Moka, x402, whatever they already use). No money moves through this package or through Solidus at any point.
Install / run
You don't install this globally — an MCP client launches it on demand:
npx @solidus-network/mcpClaude Code (.mcp.json)
{
"mcpServers": {
"solidus": {
"command": "npx",
"args": ["-y", "@solidus-network/mcp"],
"env": {
"SOLIDUS_API_KEY": "<your operator key, only needed for create_mandate>"
}
}
}
}Any other MCP client (Cursor, Windsurf, a custom StdioClientTransport) works the same way — it's a
standard stdio MCP server, command: npx, args: ["-y", "@solidus-network/mcp"].
Getting a SOLIDUS_API_KEY
Only create_mandate needs one; the other four tools work with no configuration at all. There is no
signup form and no dashboard step — an operator bearer token comes back from registerOperator(),
which itself requires no auth:
npm i @solidus-network/agent-identityimport { createAgentIdentity } from '@solidus-network/agent-identity'
const solidus = createAgentIdentity({ baseUrl: 'https://agents.solidus.network' })
const { token } = await solidus.registerOperator({
kyc: {
// The credential id verify.solidus.network issued you.
credentialRef: 'urn:solidus:credential:<your-kyc-vc-id>',
assuranceLevel: 'substantial',
},
})
// Put `token` in SOLIDUS_API_KEY. Treat it as a secret — it authorizes
// mandate creation for every agent under your operator account.Creating agents requires a KYC credential ref from verify.solidus.network.
On testnet the backend records verify's verdict but does not yet reject an unconfirmed ref by default
(KYC_REF_VALIDATION=advisory); do not read that as the gate being optional — it is what the operator
of a given deployment has chosen, and enforcement is a one-setting change.
Tools
5 tools, 3 read (no side effects, no auth) and 2 write (side effects; a human should confirm the terms before an agent calls them):
| Tool | Type | What it does |
|---|---|---|
| resolve_did | read | Resolve a did:solidus DID document from the Solidus chain. |
| verify_credential | read | Verify a Solidus verifiable credential by id against the chain. |
| check_mandate | read | Check whether a spend-mandate envelope authorizes a given amount/currency/rail. Pure — no auth, moves nothing. This is the counterparty refusal check. |
| create_mandate | write | Issue a scoped spend_mandate credential (bounded by maxAmount/currency/rail/expiresAt) for an agent, on behalf of a principal DID. Returns the checkable envelope — never the private key or credential bundle. |
| agent_pay | write | Authorize a payment under a mandate and return the envelope for the caller's own rail. Settles nothing — a refusal short-circuits and does no further work. |
Configuration
All optional except where noted; the server has working defaults for every read tool out of the box.
| Env var | Default | Used by |
|---|---|---|
| SOLIDUS_RPC_URL | https://rpc.solidus.network | resolve_did, verify_credential (chain SDK) |
| SOLIDUS_AGENTS_URL | https://agents.solidus.network | check_mandate, create_mandate, agent_pay (agents backend) |
| SOLIDUS_API_KEY | — | create_mandate only — an operator bearer token from registerOperator(), see Getting a SOLIDUS_API_KEY. The other four tools need no auth. |
| SOLIDUS_SDK_MODE | testnet | Chain SDK mode: testnet (public chain, no key needed for reads), mainnet, or stub (local Postgres-backed simulation for development — requires SOLIDUS_STUB_DB_URL, never point a real client at it). |
End-to-end example
A mandate bounds what an agent can spend; check_mandate is the refusal an agent's counterparty runs
before accepting a payment. This walks resolving a DID, hitting the refusal, then raising the mandate
and succeeding:
// 1. Resolve the paying agent's DID
resolve_did({ "did": "did:solidus:testnet:<addr>" })
// → { "did": "did:solidus:testnet:<addr>", "document": { ... }, "resolved": true }
// 2. Issue a mandate capped at 500 TRY
create_mandate({
"agentId": "<agent-id>",
"principalDid": "did:solidus:testnet:<principal-addr>",
"maxAmount": "500",
"currency": "TRY",
"rail": "moka",
"expiresAt": "2026-08-01T00:00:00.000Z"
})
// → { "mandateId": "...", "envelope": "<base64url envelope>", "expiresAt": "2026-08-01T00:00:00.000Z" }
// 3. The counterparty checks a 750 TRY charge against it — over the cap, refused
check_mandate({ "envelope": "<envelope from step 2>", "amount": 750, "currency": "TRY", "rail": "moka" })
// → { "authorized": false, "reason": "amount exceeds mandate limit" }
// 4. Raise the mandate to 1000 TRY
create_mandate({ /* same as step 2, "maxAmount": "1000" */ })
// → { "mandateId": "...", "envelope": "<new envelope>", "expiresAt": "..." }
// 5. agent_pay authorizes the same 750 TRY charge under the raised mandate
agent_pay({ "envelope": "<envelope from step 4>", "amount": 750, "currency": "TRY", "rail": "moka" })
// → { "authorized": true, "envelope": "<same envelope>", "header": "x-solidus-mandate" }Step 5's envelope and header are what the caller sends to their own payment rail — Solidus's
part ends at authorization.
Trust boundary
This package is a thin adapter over @solidus-network/sdk
(chain reads) and @solidus-network/agent-identity
(mandates, credentials). It holds no issuer keys and does no cryptography of its own — credential
signing happens in the Solidus backend; trust in a credential is trust in the issuer key, not in this
code.
License
Apache-2.0
