docs
fren get sandbox key

reference

mcp tools reference

One MCP server, two profiles. An agent key exposes 5 agent tools; an admin key exposes 12 admin tools. Every tool returns JSON as text content.

connection

settingvalue
endpointhttps://mcp.nostro.ag/mcp
transportStreamable HTTP, stateless (no session id), JSON responses
authAuthorization: Bearer <key>
headerscontent-type: application/json, accept: application/json, text/event-stream
servernostro v0.1.0

The tools you see depend on the key: nst_ gives the agent profile, nsa_ the admin profile. A wrong key fails at the HTTP level with 401 and JSON-RPC code -32001. Setup for Claude Code, Claude Desktop and Cursor is in the quickstart.

response shape

Results are MCP text content holding pretty-printed JSON. Business outcomes such as denied or pending_approval are normal results. Failures (not found, frozen agent, invalid input) come back with isError: true:

tool error
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"error\": \"Agent gelé par l'entreprise : aucune dépense possible\"\n}"
      }
    ],
    "isError": true
  }
}
Agent instructions With an agent key, the server tells the model: call get_wallet before any spend, respect every decision, never split amounts to get around the mandate.

agent tools

Scoped to the calling agent: its card, its mandate, its activity.

get_wallet

agent key nst_REST · GET /v1/agent/wallet

Remaining budget (day, month), caps, card status and the available balance of the company account. Agents are instructed to call it before any spend.

No parameters.

request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_wallet",
    "arguments": {}
  }
}
result (in content[0].text)
{
  "agent": {
    "id": "agt_3f2b8d10",
    "name": "demo-agent",
    "status": "active"
  },
  "card": {
    "id": "vcard_5e1d77a0c4b2",
    "maskedNumber": "4970 •••• •••• 3182",
    "status": "Enabled"
  },
  "accountAvailable": {
    "value": "10000.00",
    "currency": "EUR"
  },
  "budget": {
    "currency": "EUR",
    "perTxMax": 500,
    "approvalAbove": 250,
    "dailyRemaining": 2000,
    "monthlyRemaining": 5000
  },
  "transfersEnabled": false
}

pay

agent key nst_REST · POST /v1/agent/pay

Pays a merchant with the agent's virtual card. Evaluated against the mandate first: executed, blocked, or held for human approval.

nametypedescription
amountnumberrequiredAmount in EUR, > 0. Rounded to 2 decimals, e.g. 19.99.
merchantstringrequiredMerchant name. Matched against blockedMerchants.
mccstringoptionalMerchant category code, exactly 4 digits, e.g. 5734. Required when the mandate has allowedMccs.
descriptionstringoptionalWhat the spend is for. Stored in the audit log.
request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "pay",
    "arguments": {
      "amount": 49,
      "merchant": "Vercel",
      "mcc": "5734",
      "description": "Pro plan, November"
    }
  }
}
result · paid (in content[0].text)
{
  "status": "paid",
  "transactionId": "vtx_883b5610e2a1",
  "amount": 49,
  "merchant": "Vercel"
}
result · pending_approval
{
  "status": "pending_approval",
  "requestId": "req_ef5c846c",
  "reason": "Au-dessus du seuil d'approbation (250 €)",
  "message": "Demande transmise à un humain de l'entreprise. Vérifie son statut avec get_request_status."
}
result · denied
{
  "status": "denied",
  "reason": "Dépasse le plafond par transaction (500 €)"
}
result · declined_by_bank
{
  "status": "declined_by_bank",
  "reason": "InsufficientFunds"
}

send_transfer

agent key nst_REST · POST /v1/agent/transfer

Sends a SEPA transfer from the company account, only if the mandate enables transfers. Usually held for human approval.

nametypedescription
amountnumberrequiredAmount in EUR, > 0.
ibanstringrequiredBeneficiary IBAN. Spaces are stripped; must match ^[A-Z]{2}\d{2}[A-Z0-9]{10,30}$.
beneficiaryNamestringrequiredBeneficiary name.
labelstringoptionalTransfer label, max 140 characters. Defaults to "Nostro <agent name>".
request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "send_transfer",
    "arguments": {
      "amount": 1200,
      "iban": "FR7630006000011234567890189",
      "beneficiaryName": "Acme SAS",
      "label": "Invoice 2026-114"
    }
  }
}
result · pending_approval (in content[0].text)
{
  "status": "pending_approval",
  "requestId": "req_51c0aa9e",
  "reason": "Virement soumis à approbation humaine",
  "message": "Demande transmise à un humain de l'entreprise. Vérifie son statut avec get_request_status."
}
result · initiated
{
  "status": "initiated",
  "paymentId": "vpay_9d21c4e07a11",
  "bankStatus": "Initiated"
}
result · denied
{
  "status": "denied",
  "reason": "Virements désactivés pour cet agent"
}

list_my_activity

agent key nst_REST · GET /v1/agent/activity

The agent's own history: every payment, transfer and decision (executed, denied, pending). Newest first.

nametypedescription
limitintegeroptional1 to 100, default 20. (REST returns the last 50.)
request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_my_activity",
    "arguments": {
      "limit": 2
    }
  }
}
result (in content[0].text)
[
  {
    "id": 7,
    "workspace_id": "ws_7c1e9a42b0d3",
    "agent_id": "agt_3f2b8d10",
    "type": "card_payment",
    "amount": 320,
    "counterparty": "AWS",
    "decision": "executed",
    "reason": null,
    "bank_ref": "vtx_acf5376d09be",
    "created_at": "2026-11-05T09:20:41.560Z",
    "agent_name": "demo-agent"
  },
  {
    "id": 5,
    "workspace_id": "ws_7c1e9a42b0d3",
    "agent_id": "agt_3f2b8d10",
    "type": "card_payment",
    "amount": 900,
    "counterparty": "Apple",
    "decision": "denied",
    "reason": "Dépasse le plafond par transaction (500 €)",
    "bank_ref": null,
    "created_at": "2026-11-05T09:18:02.491Z",
    "agent_name": "demo-agent"
  }
]

get_request_status

agent key nst_REST · GET /v1/agent/requests/:id

Status of a spend held for human approval. Agents can only read their own requests.

nametypedescription
requestIdstringrequiredThe req_... id returned by pay or send_transfer.
request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_request_status",
    "arguments": {
      "requestId": "req_ef5c846c"
    }
  }
}
result (in content[0].text)
{
  "id": "req_ef5c846c",
  "kind": "card_payment",
  "amount": 320,
  "status": "executed",
  "reason": "Au-dessus du seuil d'approbation (250 €)",
  "result": {
    "amount": 320,
    "status": "paid",
    "merchant": "AWS",
    "transactionId": "vtx_acf5376d09be"
  },
  "createdAt": "2026-11-05T09:19:33.478Z",
  "decidedAt": "2026-11-05T09:20:41.561Z"
}

admin tools

Scoped to the workspace of the admin key. Give these only to humans, or to a supervised operator agent you trust with the whole budget.

overview

admin key nsa_REST · GET /api/overview

Account, balance, number of active agents, pending approvals and spend this month.

No parameters.

request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "overview",
    "arguments": {}
  }
}
result (in content[0].text)
{
  "workspace": {
    "id": "ws_7c1e9a42b0d3",
    "name": "Acme",
    "kind": "sandbox",
    "account_id": "vacc_2b9d01e6f3a8",
    "created_at": "2026-11-05T09:12:03.870Z"
  },
  "bankMode": "ledger",
  "account": {
    "id": "vacc_2b9d01e6f3a8",
    "name": "Acme",
    "iban": "FR7699999481207365519024618",
    "status": "Opened",
    "available": {
      "value": "9631.00",
      "currency": "EUR"
    },
    "booked": {
      "value": "9631.00",
      "currency": "EUR"
    }
  },
  "agents": 1,
  "pendingApprovals": 0,
  "monthSpend": 369,
  "swanConfigured": false,
  "swanUserId": null
}

list_agents

admin key nsa_REST · GET /api/agents

Every non-revoked agent with its card, mandate and current spend.

No parameters.

request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_agents",
    "arguments": {}
  }
}
result (in content[0].text)
[
  {
    "id": "agt_3f2b8d10",
    "name": "demo-agent",
    "purpose": "Demo agent (sandbox, fake euros)",
    "status": "active",
    "keyPrefix": "nst_k3Jd",
    "card": {
      "id": "vcard_5e1d77a0c4b2",
      "status": "Enabled",
      "maskedNumber": "4970 •••• •••• 3182",
      "consentUrl": null
    },
    "policy": {
      "currency": "EUR",
      "perTxMax": 500,
      "dailyMax": 2000,
      "monthlyMax": 5000,
      "approvalAbove": 250,
      "allowedMccs": [],
      "blockedMerchants": [],
      "transfersEnabled": false,
      "transferApprovalAbove": 0
    },
    "spend": {
      "today": 49,
      "month": 49
    },
    "createdAt": "2026-11-05T09:12:04.118Z"
  }
]

create_agent

admin key nsa_REST · POST /api/agents

Creates an agent with its own virtual card in the company's name, its mandate and its API key. The key is returned once and never again.

nametypedescription
namestringrequiredAgent name, also printed on the card label.
purposestringoptionalFree text, what this agent is for.
perTxMaxnumberoptionalMaximum amount of a single card payment, in EUR. Above it the payment is blocked.
dailyMaxnumberoptionalMaximum executed spend per calendar day (UTC), card payments and transfers combined.
monthlyMaxnumberoptionalMonthly budget. Also set as the spending limit on the card itself (defense in depth).
approvalAbovenumberoptionalCard payments strictly above this amount wait for a human approval instead of executing.
allowedMccsstring[]optionalAllowed merchant category codes (4 digits). Empty means every category is allowed.
blockedMerchantsstring[]optionalMerchant names to block. Case-insensitive substring match on the merchant name.
transfersEnabledbooleanoptionalWhether the agent may send SEPA transfers at all.
transferApprovalAbovenumberoptionalTransfers strictly above this amount need a human approval. 0 means every transfer is reviewed.
request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_agent",
    "arguments": {
      "name": "procurement-bot",
      "purpose": "Buys SaaS seats",
      "perTxMax": 200,
      "dailyMax": 600,
      "monthlyMax": 3000,
      "approvalAbove": 100,
      "allowedMccs": [
        "5734",
        "7372"
      ]
    }
  }
}
result (in content[0].text)
{
  "agent": {
    "id": "agt_91ad04c2",
    "name": "procurement-bot",
    "purpose": "Buys SaaS seats",
    "status": "active",
    "keyPrefix": "nst_Qm7v",
    "card": {
      "id": "vcard_0c44e1b9a7d0",
      "status": "Enabled",
      "maskedNumber": "4970 •••• •••• 7745",
      "consentUrl": null
    },
    "policy": {
      "currency": "EUR",
      "perTxMax": 200,
      "dailyMax": 600,
      "monthlyMax": 3000,
      "approvalAbove": 100,
      "allowedMccs": [
        "5734",
        "7372"
      ],
      "blockedMerchants": [],
      "transfersEnabled": false,
      "transferApprovalAbove": 0
    },
    "spend": {
      "today": 0,
      "month": 0
    },
    "createdAt": "2026-11-05T09:12:04.118Z"
  },
  "apiKey": "nst_Qm7v••••••••••••••••••••••••"
}

update_agent_policy

admin key nsa_REST · POST /api/agents/:id/policy

Patches an agent's mandate. Only the fields you send change; the rest is kept. Changing monthlyMax also updates the card limit.

nametypedescription
agentIdstringrequiredThe agt_... id.
perTxMaxnumberoptionalMaximum amount of a single card payment, in EUR. Above it the payment is blocked.
dailyMaxnumberoptionalMaximum executed spend per calendar day (UTC), card payments and transfers combined.
monthlyMaxnumberoptionalMonthly budget. Also set as the spending limit on the card itself (defense in depth).
approvalAbovenumberoptionalCard payments strictly above this amount wait for a human approval instead of executing.
allowedMccsstring[]optionalAllowed merchant category codes (4 digits). Empty means every category is allowed.
blockedMerchantsstring[]optionalMerchant names to block. Case-insensitive substring match on the merchant name.
transfersEnabledbooleanoptionalWhether the agent may send SEPA transfers at all.
transferApprovalAbovenumberoptionalTransfers strictly above this amount need a human approval. 0 means every transfer is reviewed.
request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_agent_policy",
    "arguments": {
      "agentId": "agt_3f2b8d10",
      "approvalAbove": 100,
      "blockedMerchants": [
        "casino",
        "crypto"
      ]
    }
  }
}
result (in content[0].text)
{
  "id": "agt_3f2b8d10",
  "name": "demo-agent",
  "purpose": "Demo agent (sandbox, fake euros)",
  "status": "active",
  "keyPrefix": "nst_k3Jd",
  "card": {
    "id": "vcard_5e1d77a0c4b2",
    "status": "Enabled",
    "maskedNumber": "4970 •••• •••• 3182",
    "consentUrl": null
  },
  "policy": {
    "currency": "EUR",
    "perTxMax": 500,
    "dailyMax": 2000,
    "monthlyMax": 5000,
    "approvalAbove": 100,
    "allowedMccs": [],
    "blockedMerchants": [
      "casino",
      "crypto"
    ],
    "transfersEnabled": false,
    "transferApprovalAbove": 0
  },
  "spend": {
    "today": 49,
    "month": 49
  },
  "createdAt": "2026-11-05T09:12:04.118Z"
}

freeze_agent

admin key nsa_REST · POST /api/agents/:id/freeze

Kill switch: blocks every spend of the agent immediately. Reversible.

nametypedescription
agentIdstringrequiredThe agt_... id.
request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "freeze_agent",
    "arguments": {
      "agentId": "agt_3f2b8d10"
    }
  }
}
result (in content[0].text)
{
  "id": "agt_3f2b8d10",
  "name": "demo-agent",
  "purpose": "Demo agent (sandbox, fake euros)",
  "status": "frozen",
  "keyPrefix": "nst_k3Jd",
  "card": {
    "id": "vcard_5e1d77a0c4b2",
    "status": "Enabled",
    "maskedNumber": "4970 •••• •••• 3182",
    "consentUrl": null
  },
  "policy": {
    "currency": "EUR",
    "perTxMax": 500,
    "dailyMax": 2000,
    "monthlyMax": 5000,
    "approvalAbove": 250,
    "allowedMccs": [],
    "blockedMerchants": [],
    "transfersEnabled": false,
    "transferApprovalAbove": 0
  },
  "spend": {
    "today": 49,
    "month": 49
  },
  "createdAt": "2026-11-05T09:12:04.118Z"
}

unfreeze_agent

admin key nsa_REST · POST /api/agents/:id/unfreeze

Re-activates a frozen agent.

nametypedescription
agentIdstringrequiredThe agt_... id.
request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "unfreeze_agent",
    "arguments": {
      "agentId": "agt_3f2b8d10"
    }
  }
}
result (in content[0].text)
{
  "id": "agt_3f2b8d10",
  "name": "demo-agent",
  "purpose": "Demo agent (sandbox, fake euros)",
  "status": "active",
  "keyPrefix": "nst_k3Jd",
  "card": {
    "id": "vcard_5e1d77a0c4b2",
    "status": "Enabled",
    "maskedNumber": "4970 •••• •••• 3182",
    "consentUrl": null
  },
  "policy": {
    "currency": "EUR",
    "perTxMax": 500,
    "dailyMax": 2000,
    "monthlyMax": 5000,
    "approvalAbove": 250,
    "allowedMccs": [],
    "blockedMerchants": [],
    "transfersEnabled": false,
    "transferApprovalAbove": 0
  },
  "spend": {
    "today": 49,
    "month": 49
  },
  "createdAt": "2026-11-05T09:12:04.118Z"
}

revoke_agent

admin key nsa_REST · POST /api/agents/:id/revoke

Revokes the agent's key and cancels its card. Irreversible.

nametypedescription
agentIdstringrequiredThe agt_... id.
request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "revoke_agent",
    "arguments": {
      "agentId": "agt_3f2b8d10"
    }
  }
}
result (in content[0].text)
{
  "ok": true
}

list_pending_approvals

admin key nsa_REST · GET /api/requests?status=pending

Spends held for a human decision, newest first (max 100).

No parameters.

request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_pending_approvals",
    "arguments": {}
  }
}
result (in content[0].text)
[
  {
    "id": "req_ef5c846c",
    "agent_id": "agt_3f2b8d10",
    "kind": "card_payment",
    "amount": 320,
    "payload": {
      "amount": 320,
      "merchant": "AWS",
      "mcc": "7372"
    },
    "reason": "Au-dessus du seuil d'approbation (250 €)",
    "status": "pending",
    "result": null,
    "created_at": "2026-11-05T09:19:33.478Z",
    "decided_at": null,
    "agent_name": "demo-agent"
  }
]

approve_request

admin key nsa_REST · POST /api/requests/:id/approve

Approves a pending request and executes it right away. One decision per request, even with concurrent clicks.

nametypedescription
requestIdstringrequiredThe req_... id.
request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "approve_request",
    "arguments": {
      "requestId": "req_ef5c846c"
    }
  }
}
result (in content[0].text)
{
  "id": "req_ef5c846c",
  "kind": "card_payment",
  "amount": 320,
  "status": "executed",
  "reason": "Au-dessus du seuil d'approbation (250 €)",
  "result": {
    "amount": 320,
    "status": "paid",
    "merchant": "AWS",
    "transactionId": "vtx_acf5376d09be"
  },
  "createdAt": "2026-11-05T09:19:33.478Z",
  "decidedAt": "2026-11-05T09:20:41.561Z"
}

reject_request

admin key nsa_REST · POST /api/requests/:id/reject

Rejects a pending request. Nothing is charged.

nametypedescription
requestIdstringrequiredThe req_... id.
request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "reject_request",
    "arguments": {
      "requestId": "req_51c0aa9e"
    }
  }
}
result (in content[0].text)
{
  "id": "req_51c0aa9e",
  "kind": "transfer",
  "amount": 1200,
  "status": "rejected",
  "reason": "Virement soumis à approbation humaine",
  "result": null,
  "createdAt": "2026-11-05T09:22:10.002Z",
  "decidedAt": "2026-11-05T09:25:47.310Z"
}

fund_account

admin key nsa_REST · POST /api/fund

Sandbox only: credits the workspace's virtual ledger (simulated incoming SEPA transfer, fake euros).

nametypedescription
amountnumberrequiredEUR, > 0 and ≤ 100 000 per call. A sandbox balance is capped at 1 000 000.
request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "fund_account",
    "arguments": {
      "amount": 5000
    }
  }
}
result (in content[0].text)
{
  "transactionId": "vtx_c7b3a7331f0c"
}

audit_log

admin key nsa_REST · GET /api/events?agentId=&limit=

Every decision and operation in the workspace, newest first, optionally for one agent.

nametypedescription
agentIdstringoptionalFilter on one agent.
limitintegeroptional1 to 500, default 100.
request · tools/call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "audit_log",
    "arguments": {
      "agentId": "agt_3f2b8d10",
      "limit": 3
    }
  }
}
result (in content[0].text)
[
  {
    "id": 7,
    "workspace_id": "ws_7c1e9a42b0d3",
    "agent_id": "agt_3f2b8d10",
    "type": "card_payment",
    "amount": 320,
    "counterparty": "AWS",
    "decision": "executed",
    "reason": null,
    "bank_ref": "vtx_acf5376d09be",
    "created_at": "2026-11-05T09:20:41.560Z",
    "agent_name": "demo-agent"
  },
  {
    "id": 4,
    "workspace_id": "ws_7c1e9a42b0d3",
    "agent_id": "agt_3f2b8d10",
    "type": "card_payment",
    "amount": 320,
    "counterparty": "AWS",
    "decision": "pending_approval",
    "reason": "Au-dessus du seuil d'approbation (250 €)",
    "bank_ref": "req_ef5c846c",
    "created_at": "2026-11-05T09:19:33.480Z",
    "agent_name": "demo-agent"
  },
  {
    "id": 2,
    "workspace_id": "ws_7c1e9a42b0d3",
    "agent_id": "agt_3f2b8d10",
    "type": "agent_created",
    "amount": null,
    "counterparty": null,
    "decision": "executed",
    "reason": "Carte 4970 •••• •••• 3182 (Enabled)",
    "bank_ref": "vcard_5e1d77a0c4b2",
    "created_at": "2026-11-05T09:12:04.120Z",
    "agent_name": "demo-agent"
  }
]