Model Context Protocol
The Flowie Exchange API ships two Model Context Protocol servers so AI agents — Claude Desktop, Claude Code, Cursor, n8n, custom Python agents — can send, receive, and manage Peppol e-invoices as native tool calls.
Authorization header preserved — so JWT, flw_* keys, tenant scoping, rate limits, and sandbox simulators all work identically.
Endpoints
| Mode | Tools | Production | Sandbox |
|---|---|---|---|
| Curated (recommended) | 34 | https://back.p2p-flowie.com/exchange/mcp |
https://back.flowie.ink/exchange/mcp |
| Full | 94 | https://back.p2p-flowie.com/exchange/mcp/full |
https://back.flowie.ink/exchange/mcp/full |
The curated server exposes only the six tags an agent actually needs: Documents, Directory, Companies, Lifecycle, Compliance, Partners. Admin, sandbox control plane, AFNOR certification, and debug routes are hidden — fewer tokens spent on tool discovery, far fewer "wrong tool" misfires. Pick full only when the agent genuinely needs platform / white-label / certification surface.
Transport is streamable-HTTP (the modern MCP transport, MCP spec 2025-06-18). The legacy SSE transport is no longer mounted.
Authentication
Every request the agent makes is forwarded to the FastAPI handler with the original Authorization header preserved, so the same scoping rules apply: tenant isolation, per-key quotas, sandbox vs live partitioning.
Authorization: Bearer flw_test_your_key_here
Use a flw_test_… key against the sandbox host while you're developing the agent — every test recipient from the sandbox guide is reachable through MCP exactly as it is through REST. Need a key? Bootstrap one in one click, or — if the agent must request its own key on behalf of a real user — see the OAuth consent flow.
Quickstart — Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"flowie-exchange": {
"url": "https://back.flowie.ink/exchange/mcp",
"transport": "streamable-http",
"headers": {
"Authorization": "Bearer flw_test_your_key_here"
}
}
}
}
Restart Claude Desktop. The hammer icon shows 34 tools loaded. Try: "List my last 5 incoming invoices."
Quickstart — Claude Code
In the project directory, drop a .mcp.json file (Claude Code picks it up automatically per project):
{
"mcpServers": {
"flowie-exchange": {
"url": "https://back.flowie.ink/exchange/mcp",
"transport": "streamable-http",
"headers": { "Authorization": "Bearer flw_test_your_key_here" }
}
}
}
Or register globally so every project sees it:
claude mcp add flowie-exchange https://back.flowie.ink/exchange/mcp \
--transport streamable-http \
--header "Authorization: Bearer flw_test_your_key_here"
Quickstart — Cursor / VS Code
In Cursor: Settings → MCP → Add new server, paste the same JSON shape as Claude Desktop. In VS Code with the Continue extension: same JSON under continue.config.mcpServers. Both speak streamable-HTTP natively.
Quickstart — Python (mcp SDK)
For custom agents, the official mcp Python SDK speaks streamable-HTTP directly:
# pip install mcp
import asyncio, os
from mcp.client.streamable_http import streamablehttp_client
from mcp import ClientSession
URL = "https://back.flowie.ink/exchange/mcp"
KEY = os.environ["FLOWIE_KEY"]
async def main():
async with streamablehttp_client(
URL, headers={"Authorization": f"Bearer {KEY}"}
) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print(f"{len(tools.tools)} tools available")
# Call a tool by name with REST-style args
result = await session.call_tool(
"list_documents",
arguments={"direction": "incoming", "status": "unread", "limit": 5},
)
print(result.content[0].text)
asyncio.run(main())
Tool catalog (curated)
The curated server exposes one MCP tool per FastAPI operation tagged Documents, Directory, Companies, Lifecycle, Compliance, or Partners. The high-leverage ones for agents:
| Tool | What it does |
|---|---|
send_document | Send an e-invoice / credit note / order over Peppol. |
list_documents | Filter by direction, status, type, date range. |
search_documents | Full-text + structured search across all documents. |
get_document_structured | Flat, agent-friendly view — every field as a primitive. |
validate_document | Pre-flight a payload through BIS / EN-16931 rules. |
update_lifecycle | Approve, reject, mark as paid, dispute. |
search_directory | Find Peppol participants by name, VAT, or country. |
verify_recipient | Check a Peppol ID can receive a given document type. |
resolve_company | Look up by VAT / SIREN — get Peppol ID + enriched profile. |
create_company | Register a sender, auto-publish to the Peppol SMP. |
get_compliance_report | Latest PPF (FR) or SDI (IT) report status for a document. |
Run tools/list over MCP to enumerate the full set with input schemas and descriptions. Every tool's input schema mirrors the REST endpoint's request body — see the API Reference for the canonical shape.
Common workflow — "What invoices arrived this week?"
The agent picks the right tools from the prompt; you do nothing.
User: "What invoices arrived this week and which ones are still unpaid?"
Agent → list_documents({direction: "incoming", since: "2026-04-26"})
→ for each: get_document_structured({documentId})
→ for each unpaid: get_compliance_report({documentId})
→ summarises totals by supplier, flags the ones past dueDate
Common workflow — "Send an invoice to ACME"
Three tools, one chain. The agent verifies the recipient before sending.
User: "Bill ACME BVBA €4,500 + VAT for April consulting, due in 30 days."
Agent → search_directory({q: "ACME BVBA"}) # finds peppolId
→ verify_recipient({peppolId, documentType: "INVOICE"})
→ send_document({
type: "invoice",
from: "comp_abc123",
to: "0208:0123456789",
document: {
number: "INV-2026-0451",
issueDate: "2026-04-30",
dueDate: "2026-05-30",
currency: "EUR",
lines: [{
description: "Consulting — April 2026",
quantity: 1, unit: "lot",
unitPrice: 4500.00, vatRate: 21
}]
}
})
The agent sees the returned documentId + deliveryStatus and reports back. Pass an Idempotency-Key at the REST layer if you want retry safety — MCP forwards it as a tool argument.
Common workflow — "Mark INV-0417 as paid"
User: "INV-2026-0417 was paid yesterday — close the loop."
Agent → search_documents({number: "INV-2026-0417"}) # → documentId
→ update_lifecycle({
documentId,
status: "paid",
note: "Paid 2026-04-29 via SEPA"
})
The lifecycle change automatically triggers PPF (FR) or SDI (IT) reporting where applicable — the agent doesn't need to know about that. Watch compliance.reported on your webhook stream for confirmation. Belgian invoices skip this step (HERMES was decommissioned 2025-12-31).
Common workflow — "Onboard a new supplier"
User: "Add Globex SRL (VAT IT09876543210) as a partner and check they're on Peppol."
Agent → resolve_company({vatNumber: "IT09876543210"}) # enriched profile
→ verify_recipient({peppolId}) # canReceive: true?
→ save_partner({...}) # in your CRM/ERP
Direct HTTP (no SDK)
MCP is just JSON-RPC over an HTTP POST. If you don't want the SDK:
# 1. Initialize the session
curl -X POST https://back.flowie.ink/exchange/mcp \
-H "Authorization: Bearer $FLOWIE_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {"name": "curl", "version": "1.0"}
}
}'
# 2. List tools
curl -X POST https://back.flowie.ink/exchange/mcp \
-H "Authorization: Bearer $FLOWIE_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
# 3. Call a tool
curl -X POST https://back.flowie.ink/exchange/mcp \
-H "Authorization: Bearer $FLOWIE_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{
"name":"list_documents",
"arguments":{"direction":"incoming","limit":5}
}
}'
Errors
MCP errors mirror the underlying REST errors — same codes, same shape, wrapped in JSON-RPC. A 401 from REST surfaces as MCP error -32001 with the original Flowie error code in data.errorCode. See the error catalog for everything you might see.
The two MCP-specific errors:
tool not found— agent called a tool that's not in the curated set. Switch to/mcp/fullor rename.invalid arguments— input schema mismatch. Runtools/listand follow theinputSchemaexactly.
Rate limits & quotas
MCP calls inherit your REST quota — there's no separate budget. One MCP tools/call = one REST request. Use the same X-Flowie-RateLimit-Remaining header logic to back off; the header is surfaced on the JSON-RPC response envelope under _meta.
Sandbox
Point the agent at https://back.flowie.ink/exchange/mcp with a flw_test_… key and every sandbox feature works: forced errors via X-Sandbox-Force-Error, simulated recipients (0208:SIM_HAPPY, SIM_DISPUTE, TEST_AP_FAIL), lifecycle simulators, the lot. See the sandbox guide for the full menu.
mcpServers entries. Register both flowie-exchange-sandbox (test key, sandbox URL) and flowie-exchange-prod (live key, prod URL). Then prompt the agent explicitly: "Use the sandbox server to dry-run this."
When to use full vs curated
- Curated — agents that send, receive, search, and reconcile invoices. Default choice for 95% of integrations.
- Full — IDE integrations, ops scripts, AFNOR-certified flows, white-label admin, request inspector. Larger context cost; only when you genuinely need the extra surface.
You can mount both — agents pick the right one based on the host you point them at. There's no auth difference between the two, so the same key works against both URLs.
