MCP server
The @capxul/mcp server exposes Capxul financial operations — auth, account readiness, balances, testnet faucet, payments — as MCP tools over Streamable HTTP.
@capxul/mcp is a runnable Model Context Protocol
server that exposes Capxul's financial operations as tools any MCP client
(Claude, Cursor, other agents) can call. The package ships a CLI, not a
library: the published artifact is the capxul-mcp executable. Like the SDK,
the tool surface speaks money — tool results carry Money values, labels, and
opaque ids, never custody internals.
Alpha surface
The MCP surface ships on the 1.0.0-alpha.x train and is still moving. Tool
names, inputs, and gating may change between alpha releases. Pin an exact
version, and treat the startup banner — which prints the exact tool set the
running server registered — as the authority for your launch.
Install and run
# one-off, via npx
npx @capxul/mcp serve --mode live
# or install globally
npm i -g @capxul/mcp
capxul-mcp serve --mode liveUsage:
capxul-mcp serve [--mode live|dev] [--port <port>] [--approval-url <url>] [--stdio]
Options:
--mode live|dev Launch mode. Defaults to live. There is no env-var mode selector.
--port <port> Bind port. Defaults to CAPXUL_MCP_PORT or 8788. (HTTP transport only.)
--approval-url <url> Approval app base URL. Defaults to CAPXUL_MCP_APPROVAL_URL or http://localhost:5998.
--stdio Serve over stdio (local spawn) instead of HTTP. Defaults to off (HTTP).
--help Show this help.The server speaks MCP over Streamable HTTP at POST /mcp — a fresh,
stateless server instance per request, with shared state (sessions, approvals)
held in the process. Pass --stdio to serve over stdio instead, for clients
that spawn the server locally.
On startup the banner prints the bound URL, the mode, whether the faucet is enabled, and the full list of registered tools. That list is computed from the live registry at boot, so it never drifts from what the server actually exposes.
Auth model
There are two independent layers:
Transport auth — OAuth bearer. An in-memory PKCE OAuth provider gates
POST /mcp when CAPXUL_MCP_OAUTH_REQUIRED=1. The server publishes the
standard discovery documents (/.well-known/oauth-protected-resource,
/.well-known/oauth-authorization-server) and the /oauth/authorize and
/oauth/token endpoints. A proof-issuer secret
(CAPXUL_MCP_OAUTH_PROOF_ISSUER_SECRET) can mint bearer tokens for automated
probes — and is currently required whenever OAuth is required, because
external-issuer validation is not implemented yet. Stdio mode skips OAuth
entirely: a local spawn is trusted IO with no bearer-token surface.
Financial auth — email OTP sessions. Signing in to Capxul happens through
tools: auth.requestOtp emails a one-time code, auth.verifyOtp exchanges it
for an opaque sessionToken that every session-scoped tool takes as an
argument. The OTP travels only as a tool argument, and no other credential —
no backend JWT, no key material — ever appears in a tool result.
The dev-mode custody gate
The --mode flag decides how money-moving operations get signed:
--mode live(default) uses the production signer path. Full account activation needs a one-time human approval in the browser:account.ensureReadywith requirementdeployedreturns{ status: "pending", approvalUrl, approvalId }— openapprovalUrl(the dedicated Approval app), complete the approval there, then pollaccount.deploymentStatuswith theapprovalId. The server itself never holds key material.--mode devruns against a deterministic dev signer seed (CAPXUL_MCP_DEV_SIGNER_SEED) and a dev backend deployment, so agent journeys can be exercised headlessly, without live custody. Dev mode is also the gate for the payment tools — see tool gating below.
Runtime configuration
Environment is read lazily on the first authenticated tool call, so liveness
(system.ping) needs no secrets. A missing variable surfaces as a structured
ENV_MISSING tool error naming the exact variable. Notable variables:
| Variable | Purpose |
|---|---|
CAPXUL_MCP_PORT | Bind port (default 8788; 0 = ephemeral). |
CAPXUL_FAUCET_ENABLED | 1 or true to register the testnet faucet tool. |
CAPXUL_MCP_DEV_SIGNER_SEED | Deterministic dev signer seed (--mode dev). |
CAPXUL_MCP_OAUTH_REQUIRED | 1 to require an OAuth bearer token on POST /mcp. |
CAPXUL_MCP_OAUTH_PROOF_ISSUER_SECRET | Secret to mint proof bearer tokens. |
CAPXUL_MCP_APPROVAL_URL | Base URL of the Approval app for activation approvals. |
Which tools are registered
Three tiers, decided at launch:
- Always registered — system, auth, account, identity, and payee tools.
- Faucet —
faucet.fundTestnetregisters only whenCAPXUL_FAUCET_ENABLEDis set. Otherwise it is absent fromtools/list. - Payments and the wider financial-ops set — payments, payment documents,
orgs, payment requests, the draft workbench, and the account/org projection
tools register only when the payments gate is on:
--mode devand the faucet enabled. In--mode livethese tools are absent today.
A typical first session, in order: system.ping → auth.requestOtp →
auth.verifyOtp (returns sessionToken) → account.ensureReady →
account.balance.
Tool reference
Inputs marked ? are optional. sessionToken is always the opaque token
returned by auth.verifyOtp.
System and auth
| Tool | Input | What it does |
|---|---|---|
system.ping | echo? | Liveness probe; echoes back the optional value. Needs no auth and no secrets. |
auth.requestOtp | email | Email a one-time sign-in code. Works for new and returning users alike. |
auth.verifyOtp | email, otp | Verify the emailed code and start a session. Returns the opaque sessionToken. |
auth.status | sessionToken? | Report whether the session is authenticated, and for whom. |
auth.signOut | sessionToken | End the session and revoke the token. |
Account readiness and balance
| Tool | Input | What it does |
|---|---|---|
account.status | sessionToken | Readiness ladder: loading, settingUp (with the current step), ready (with accountId and canTransact), or failed. |
account.ensureReady | sessionToken, requirement? (provisioned or deployed) | Run account setup to completion; idempotent. With deployed, may return pending plus an approvalUrl for the one-time browser approval. |
account.deploymentStatus | approvalId | Poll the activation approval started by account.ensureReady. pending until it completes, then ready with canTransact. |
account.balance | sessionToken | The account's balance and available balance, as Money. |
Identity and payees
| Tool | Input | What it does |
|---|---|---|
me.get | sessionToken | The authenticated identity: authUserId, email, displayName. |
me.depositInstructions | sessionToken | Instructions for depositing money into the account. |
handles.resolve | sessionToken, handle | Resolve a Capxul handle or org handle to its current payment destination. |
payees.create | sessionToken, label, recipient, handle? | Save a recipient as a Payee. The recipient must resolve by email or org handle. |
payees.get | sessionToken, payeeId | Load a saved Payee by id. |
payees.resolve | sessionToken, recipient | Resolve an email, handle, org handle, or Payee id to a payment destination. |
Faucet
Registered only when CAPXUL_FAUCET_ENABLED is set. Test environments only —
the same guardrails as the Sandbox CLI apply: never
wire the faucet into production UX.
| Tool | Input | What it does |
|---|---|---|
faucet.fundTestnet | sessionToken, amount { value, currency?, decimals? } | Mint test money into the session's account (testnet only). value is a decimal string, e.g. "10" for $10. Returns { status: "funded" } — no settlement internals. |
Payments
Registered only when the payments gate is on (--mode dev + faucet enabled).
Money-moving tools require the account to be ready with
canTransact: true, or they fail with a wrong-state error. Amounts are Money
strings such as "2 USDX". Recipients (to) are always validated — a handle,
email, org handle, or Payee id, never a raw address — except
payments.withdraw, whose entire purpose is an external destination.
| Tool | Input | What it does |
|---|---|---|
payments.pay | sessionToken, to, amount, document?, release?, lineItems? | Pay a validated recipient. An optional release clause (scheduled or stream) turns the payment into a recoverable Commitment. Returns a Payment record. |
payments.withdraw | sessionToken, to, amount, document | Cash out to an external wallet. Requires a Withdrawal payment document; the result redacts the destination to a short label. |
payments.list | sessionToken | List your payments, most recent first, each with its live vesting read-model (released, availableToClaim). |
payments.get | sessionToken, paymentId | One payment by id with its vesting read-model. |
payments.claim | sessionToken, paymentId | Claim the vested-and-unclaimed amount of a Commitment you are the recipient of. |
payments.cancel | sessionToken, paymentId, document? | Cancel a Commitment you created. Reclaims only the unvested remainder. |
payments.redirect | sessionToken, paymentId, to, document? | Redirect a Commitment to a new validated recipient before any claim. |
paymentDocuments.verify | sessionToken, documentHash | Re-derive a stored payment document's hash and confirm it matches the committed documentHash. |
paymentDocuments.render | sessionToken, documentHash | Render a stored document to HTML for the user; the model receives metadata only. |
The wider dev-mode surface
The same payments gate also registers these tool families. They mirror the SDK's org, receivables, and projection surfaces; expect them to evolve the fastest of anything on this page.
| Family | Tools | What it covers |
|---|---|---|
| Draft workbench | payments.draft, payments.simulate, approvals.request, approvals.status, approvals.cancel, payments.execute | The decomposed payment flow: the agent drafts and simulates, a human mints a one-time approval in the browser, execution consumes it. |
| Organizations | org.create, org.list, org.members, org.roles, org.invite, org.assignRole, org.removeMember, org.spendViaPayments, org.batchPayroll | Create and administer orgs, and spend from an org treasury under role caps. |
| Payment requests | paymentRequests.create, .markSent, .markViewed, .cancel, .collect, .list, .get, .reconcile | The receivables lifecycle: request, share a link, collect, reconcile. |
| Address book | account.addressBook.* and org.addressBook.* (list, get, add, hide, unhide, label) | Relationship entries per actor scope, refs and labels only. |
| Requests and inbox | account.requests.*, org.requests.* (issue, list, get, cancel, reconcile); account.inbox.*, org.inbox.* (list, approve, decline) | Issue requests to payers; list, approve, or decline payable items. |
| Insights | account.insights.summary, account.insights.history, and org equivalents | Read-only finance summary and payment-history projections. |
| Destinations and payouts | destinations.add, destinations.list, destinations.remove, payments.payout, subAccounts.transfer | Saved payout destinations, payouts to them, and transfers between sub-accounts. |
| Payroll | org.payroll.roster.add, .list, .update, .remove, org.payroll.run | Roster management and payroll runs. |
The capability constraints on the equivalent SDK surfaces apply here too — check Capability status before building a product flow on any of them.
What never crosses the wire
Tool results are leak-safe by design: they carry Money values, validated recipient labels, opaque ids, and committed document hashes. Settlement internals — transaction hashes, account addresses, raw minor units, approval token values — stay server-side. If you find one in a tool result, that is a defect worth reporting, not a feature to depend on.