Skip to content
VestiarionDocs

AI integration

MCP server

Connect an AI agent to your workspace's records through MCP tools: every API read, and with a read-and-write key, adding counterparties, invoices, milestones and payee links.

Vestiarion runs a remote MCP server. Connect an AI agent to it with a workspace API key, and the agent can answer questions from your workspace's own records: which payments are held and why, whether the ledger is intact, what is due to contractors.

Each tool is one of the API's operations. A tool call runs that operation with your key and returns its JSON exactly as the API does. With a read-only key, an agent connected here can read and explain, and nothing more. With a read-and-write key, it can also add counterparties, invoices and milestones, which the agent in Vestiarion then decides as it decides any other, and make payee links. No tool verifies work, approves or pays.

The server

text
https://www.vestiarion.xyz/api/mcp
  • Transport: Streamable HTTP. The server keeps no session, so a client sends every message as a POST. A GET or a DELETE with a valid key answers 405: there is no event stream to open and no session to end.
  • Protocol versions: the 2026-07-28 revision of MCP, and clients on the earlier revisions, such as 2025-06-18 and 2025-11-25, at the same URL.
  • Surface: tools only. The server has no resources and no prompts.

Authentication

Send a workspace API key in the Authorization header, exactly as for /api/v1:

http
Authorization: Bearer <key>

The key is checked before any MCP message is read:

  • A missing, malformed, unknown or revoked key answers 401 with the API's own error body and a WWW-Authenticate: Bearer realm="vestiarion" header.
  • A key without the read scope answers 403, with error="insufficient_scope" in WWW-Authenticate. Every key issued today has that scope.
  • The header is the only place a key is read. A key in the query string or in an MCP message's _meta is ignored: without the header, the request answers 401.
  • If the key cannot be looked up at all, the answer is 500, not 401: retry later, and keep the key. See When authentication fails.

Every tool call reads the key's own workspace and no other. Asking get_counterparty for another workspace's counterparty answers not_found, as the API does.

Tools

One tool per API operation. Its name is the operation's id in snake case, its arguments are the operation's parameters, or for a write the fields of its request body and an optional idempotencyKey, with their types, allowed values and bounds, and its description, which is what the agent reads, is the operation's summary and description from its reference page. A read's tool is marked read-only and idempotent. The four write tools are marked as adding records: not read-only, and not idempotent.

  • create_counterparty, create_invoice and create_milestone are not destructive. A repeat adds another record, unless it passes the same idempotencyKey.
  • create_payee_link is marked destructive, because a new link revokes the payee's unused one. It takes no idempotencyKey: the link is in its answer only, so a repeat makes a new link.
ToolWhat it answersReference
get_statusGet workspace statusGET /api/v1/status
list_ledger_entries
Arguments: limit, cursor, domain, actor
List ledger entriesGET /api/v1/ledger
verify_ledgerVerify the ledgerGET /api/v1/ledger/verify
list_invoices
Arguments: limit, cursor, direction, status, counterpartyId
List invoicesGET /api/v1/invoices
create_invoice
Arguments: direction, counterpartyId, amount, currency, dueDate, memo, poReference, goodsReceived, earlyPayDiscount, idempotencyKey
Add an invoicePOST /api/v1/invoices
list_counterparties
Arguments: limit, cursor, role, riskLevel
List counterpartiesGET /api/v1/counterparties
get_counterparty
Arguments: id
Get a counterpartyGET /api/v1/counterparties/{id}
create_counterparty
Arguments: name, role, address, chain, jurisdiction, paymentLimit, noticeEmail, idempotencyKey
Add a counterpartyPOST /api/v1/counterparties
create_payee_link
Arguments: counterpartyId
Create a payee linkPOST /api/v1/payee-links
list_milestones
Arguments: limit, cursor, status, contractorId
List milestonesGET /api/v1/milestones
create_milestone
Arguments: contractorId, title, amount, verificationSource, idempotencyKey
Add a milestonePOST /api/v1/milestones
get_treasuryGet the treasuryGET /api/v1/treasury
get_insightsGet insightsGET /api/v1/insights

What a call returns:

  • A success is the API's JSON, as text and as structured content.
  • An API error is a tool error (isError) whose text is the API's error body, such as a 400 invalid_request for a cursor from another endpoint, or a 404 not_found for an unknown id. The agent reads the message and corrects its call. A read-only key calling a write tool gets the API's 403 forbidden the same way. The error codes are on Errors.
  • A collection is one page, 50 items unless the agent passes limit, up to 200. The agent passes page.nextCursor back as cursor for the next page, as described in Pagination.
  • Nothing is cut. A tool returns the whole response, get_insights included, as the API documents it.

Setup

Keep the key in the VESTIARION_API_KEY environment variable. None of the blocks below writes the key itself into a file.

Claude Code

bash
claude mcp add --transport http vestiarion https://www.vestiarion.xyz/api/mcp \
  --header "Authorization: Bearer $VESTIARION_API_KEY"

Your shell puts the key into the command, and Claude Code saves the header in its own configuration, ~/.claude.json. To share the server with a project instead, without the key, add it to .mcp.json at the project root. Claude Code expands ${VESTIARION_API_KEY} from the environment when it starts:

json
{
  "mcpServers": {
    "vestiarion": {
      "type": "http",
      "url": "https://www.vestiarion.xyz/api/mcp",
      "headers": {
        "Authorization": "Bearer ${VESTIARION_API_KEY}"
      }
    }
  }
}

Cursor

Add the server to .cursor/mcp.json in your project, or to ~/.cursor/mcp.json for every project. Cursor resolves ${env:VESTIARION_API_KEY} from the environment:

json
{
  "mcpServers": {
    "vestiarion": {
      "url": "https://www.vestiarion.xyz/api/mcp",
      "headers": {
        "Authorization": "Bearer ${env:VESTIARION_API_KEY}"
      }
    }
  }
}

Codex

Add the server to ~/.codex/config.toml, or to .codex/config.toml in a trusted project. bearer_token_env_var names the variable Codex reads the key from and sends as Authorization: Bearer:

toml
[mcp_servers.vestiarion]
url = "https://www.vestiarion.xyz/api/mcp"
bearer_token_env_var = "VESTIARION_API_KEY"

Clients that only start local servers

A client that runs MCP servers as local commands over stdio can reach this one through mcp-remote, which relays between stdio and the remote server:

json
{
  "mcpServers": {
    "vestiarion": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://www.vestiarion.xyz/api/mcp",
        "--header",
        "Authorization: Bearer ${VESTIARION_API_KEY}"
      ]
    }
  }
}

mcp-remote replaces ${VESTIARION_API_KEY} from its own environment, so the key never appears in the arguments. If your client does not pass VESTIARION_API_KEY on to the servers it starts, set the variable in that server's env entry.

Some clients do not escape spaces inside args when they start npx; the mcp-remote README names Cursor, the Codex CLI and Claude Desktop on Windows. There, write the header as Authorization:${VESTIARION_AUTH_HEADER}, with no spaces, and set VESTIARION_AUTH_HEADER to Bearer followed by the key.

Questions to ask

Once the server is connected, ask in plain language. The agent picks the tools:

  • "Is our ledger intact, and are any payments held? Why was each one held?" uses verify_ledger, then list_invoices and list_milestones with status: "held", whose entries carry the agent's reasoning.
  • "Which contractor milestones are verified but not paid yet?" uses list_milestones.
  • "Which counterparties are high risk, and what did their latest screening find?" uses list_counterparties with riskLevel: "high", then get_counterparty.
  • "How much do we hold in reserve, and what does the latest liquidity forecast say?" uses get_treasury.
  • "Are payments on this workspace live or simulated?" uses get_status.
  • With a read-and-write key, "Add Quill Studio's invoice for 420 USDC against PO-4012, due October 31; the work was delivered" uses list_counterparties to find Quill Studio, then create_invoice.
  • With a read-and-write key, "Pay Mona 150 USDC for https://github.com/acme/app/pull/42 once it is merged" uses list_counterparties to find Mona, then create_milestone.

What it does not do

  • It does not decide. Approving or rejecting a payment, confirming an address, pausing the agent, and managing members, API keys or webhooks are a person's decisions, made in the console. No tool can make them, and the write tools only add records.
  • It has no OAuth. The server authenticates with a key in a header and has no authorization server, so it cannot be added as a claude.ai web connector, which needs OAuth. Use a client that sends a header, like the ones above.