Give your AI agent a way to say "I don't know": evidence envelopes over MCP An engineer has released FACTRAIL MCP, an open-source Apache-2.0 MCP server that returns structured "evidence envelopes" instead of bare answers, tying each fact to its source, retrieval time and support level so agents can cite evidence or report unknowns rather than inventing them. The hosted endpoint at mcp.factrail.online is stateless, read-only, free and rate-limited to 60 requests per minute per IP, exposing four current tools including factrail_verify for French company data by SIREN or SIRET and factrail_get_receipt for auditing prior results by content-addressed receipt ID. An AI agent is asked, "Is this French supplier still active, and what is its legal name?" It calls a tool, gets some text back, and replies confidently. What the user doesn't get is where the answer came from, when it was checked, or which parts the model filled in by itself. This tutorial is about a small pattern that closes that gap: instead of an answer, the tool returns an evidence envelope , and the agent is written to read it, cite it, and pass on what's missing instead of papering over it. The worked example is FACTRAIL MCP https://factrail.online , an open-source Apache-2.0 MCP server. We'll go through one real response line by line, including the field it could not resolve. Note on the name: this is FACTRAIL MCP factrail.online https://factrail.online , github.com/baronsigma/factrail https://github.com/baronsigma/factrail . An unrelated GitHub project uses the same name. An LLM predicts plausible text. For a question like "what is the legal name of company X?", a plausible answer and a correct one look the same from the inside. Tool calling helps, but only partly: "active" , with no source, no date, and no way of saying "I couldn't find this." What the agent needs is a structured way to say: this part is established, by this source, at this time; this other part is unknown. An evidence envelope is a response format in which every fact is tied to its evidence. In FACTRAIL MCP the envelope schema 1.2 contains: | Part | What it tells the agent | |---|---| | status | Overall outcome: supported , contradicted , insufficient evidence , stale , or conflicting sources . Not a boolean. | | facts | Each value, with a support level for example authoritative , derived provisional , caller input and the IDs of the evidence behind it. | | evidence | Each source: publisher, URL, retrieval time, source status. | | coverage | Which requested fields were resolved and which weren't, and why. | | conflicts | Competing claims from different sources, kept visible. | | freshness | When the evidence was retrieved and whether it's stale. | | receipt id | A content-addressed ID you can use to fetch the same envelope later and check it hasn't been altered. | The design rule behind it: unknown is better than invented. The hosted endpoint is https://mcp.factrail.online/mcp . It uses Streamable HTTP, is free and read-only, and needs no account or API key. Each client IP can make up to 60 MCP requests per minute full fair-use terms in section 7 . curl -s https://mcp.factrail.online/mcp \ -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":"0"}}}' Real response 29/09/2026 : {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"experimental":{},"tools":{"listChanged":false}},"protocolVersion":"2025-06-18","serverInfo":{"name":"factrail","version":"2.4.1"}}} The server is stateless, so tools/list works straight away with the same headers and {"jsonrpc":"2.0","id":2,"method":"tools/list"} . It returns 7 tools. Four are the current ones: | Tool | Title | Description starts with | |---|---|---| | factrail capabilities | FACTRAIL Capabilities | "Use when you need to discover what FACTRAIL can verify or assess before calling another tool." | | factrail verify | Verify French Company | "Use when you need verified, source-backed facts about a French company from its SIREN or SIRET." | | factrail assess | Assess EU Import Beta | "Use when you need a source-backed assessment of importing a product into the EU…" | | factrail get receipt | Get Evidence Receipt | "Use when you need to re-fetch or audit a previously returned FACTRAIL result by its receipt ID." | The other three verify french company , assess import , analyze company are older compatibility tools. Their titles start with Deprecated and their descriptions point to the replacement. Everything is annotated readOnlyHint: true . This matters for agents: the "Use when…" first sentence is what a model reads when it decides which tool to call, and the Deprecated marker steers it away from the legacy tools. The same metadata is published as a server card at https://mcp.factrail.online/.well-known/mcp/server-card.json , so directories and clients can read it without opening an MCP session. Tested with mcp 2.2.0 on Python 3.13: python3 -m venv .venv && . .venv/bin/activate && pip install mcp python import asyncio, json from mcp import ClientSession from mcp.client.streamable http import streamable http client URL = "https://mcp.factrail.online/mcp" async def main : async with streamable http client URL as read, write : async with ClientSession read, write as session: await session.initialize caps = await session.call tool "factrail capabilities", {} ver = await session.call tool "factrail verify", { "subject type": "company fr", "identifier": "552081317", "fields": "status", "legal name", "legal form", "head office", "naf code", "creation date" , } envelope = json.loads ver.content 0 .text also in ver.structuredContent rec = await session.call tool "factrail get receipt", {"receipt id": envelope "receipt id" } print json.dumps envelope, indent=2 asyncio.run main Older 1.x releases of the SDK call the helper streamablehttp client , and it yields three values. Check your installed version. mcp-remote bridge bridge tested { "mcpServers": { "factrail": { "command": "npx", "args": "-y", "mcp-remote", "https://mcp.factrail.online/mcp" } } } We tested the bridge itself mcp-remote 0.14.3, driven over stdio : initialize and tools/list came back with all 7 tools. Startup took about 16 seconds, because the bridge first probes for OAuth, which this server doesn't use. Claude Code: claude mcp add --transport http factrail https://mcp.factrail.online/mcp Cursor, in .cursor/mcp.json project or ~/.cursor/mcp.json global : { "mcpServers": { "factrail": { "url": "https://mcp.factrail.online/mcp" } } } VS Code, in .vscode/mcp.json : { "servers": { "factrail": { "type": "http", "url": "https://mcp.factrail.online/mcp" } } } Browser-based MCP clients work too: the endpoint accepts requests from any Origin and answers CORS preflight requests. We checked this with a request carrying an Origin header and a preflight OPTIONS request, and both came back 200 with access-control-allow-origin: . Step 1: ask what's covered. factrail capabilities returns the scope in machine-readable form. In our run it listed two capabilities: company fr operation verify : sources "INSEE Sirene" and "BODACC", limitation "French companies only". import operation assess : official taric.status is "not installed" . The tool itself is titled "Assess EU Import Beta ". An agent that reads this first knows not to use the tool for a German company, and not to treat any import result as official TARIC data. Step 2: verify. We asked for six fields about SIREN 552081317 , Électricité de France EDF , a large public company. Step 3: pass on the gaps, don't fill them. This is the part that belongs in your agent code or prompt. Here's a small helper that turns an envelope into text the model can quote, with an explicit "do not guess" line for every unresolved field: php def summarize for agent envelope: dict - str: """Turn an EvidenceEnvelope into text an agent can pass on without filling gaps.""" sources = {e "id" : e for e in envelope "evidence" } lines = f"Overall: {envelope 'status' } coverage: {envelope 'coverage' 'level' } " for fact in envelope "facts" : src = ", ".join f"{sources i 'publisher' } @ {sources i 'retrieved at' }" for i in fact "evidence ids" lines.append f"- {fact 'field' }: {fact 'value' } {fact 'support level' }; {src} " reasons = envelope "coverage" .get "metadata", {} .get "unresolved field reasons", {} for field in envelope "coverage" "fields unresolved" : lines.append f"- {field}: UNKNOWN {reasons.get field, 'no reason given' } . Do not guess." if envelope "conflicts" : lines.append f"- {len envelope 'conflicts' } source conflict s : report them, don't pick one silently." lines.append f"Receipt: {envelope 'receipt id' }" return "\n".join lines Run against the envelope below, it prints: Overall: insufficient evidence coverage: partial - status: active authoritative; INSEE Sirene 3.11 @ 2026-09-29T05:18:42.087910Z - legal name: ELECTRICITE DE FRANCE authoritative; INSEE Sirene 3.11 @ 2026-09-29T05:18:42.087910Z - head office: {'line 1': '22 AVENUE DE WAGRAM 22-30', 'line 2': None, 'locality': 'PARIS', 'postal code': '75008', 'country': 'FR'} authoritative; INSEE Sirene 3.11 @ 2026-09-29T05:18:42.249656Z - naf code: 35.11Z authoritative; INSEE Sirene 3.11 @ 2026-09-29T05:18:42.087910Z - creation date: 1955-01-01 authoritative; INSEE Sirene 3.11 @ 2026-09-29T05:18:42.087910Z - legal form: UNKNOWN unsupported fact . Do not guess. Receipt: fr 952a43c43fba95f2ceeef5040132dd3206c4fe6a7dd0c7b5e1a8140a32012cef Step 4: keep the receipt. Store receipt id next to whatever the agent decided. Any agent can re-fetch the same observation later with factrail get receipt . This envelope was captured on 29/09/2026 05:18 UTC from the hosted server, then at version 2.4.0. We re-ran the same call on 2.4.1 later that morning and got the same format and values; only generated at changed. It's trimmed only where marked … : { "schema version": "1.2", "status": "insufficient evidence", "subject": { "type": "company fr", "name": "ELECTRICITE DE FRANCE", "identifiers": { "siren": "552081317", "siret": "55208131766522" } }, "facts": { "field": "status", "value": "active", "evidence ids": "insee-siren" , "provenance type": "sourced", "support level": "authoritative", … }, { "field": "legal name", "value": "ELECTRICITE DE FRANCE", "evidence ids": "insee-siren" , "support level": "authoritative", … }, { "field": "head office", "value": { "line 1": "22 AVENUE DE WAGRAM 22-30", "line 2": null, "locality": "PARIS", "postal code": "75008", "country": "FR" }, "evidence ids": "insee-siret" , "support level": "authoritative", … }, { "field": "naf code", "value": "35.11Z", "evidence ids": "insee-siren" , "support level": "authoritative", … }, { "field": "creation date", "value": "1955-01-01", "evidence ids": "insee-siren" , "support level": "authoritative", … } , "evidence": { "id": "insee-siren", "source type": "government registry", "authority class": "primary official registry", "publisher": "INSEE Sirene 3.11", "url": "https://api.insee.fr/api-sirene/3.11/siren/552081317", "retrieved at": "2026-09-29T05:18:42.087910Z", "source status": "available", … }, { "id": "insee-siret", "publisher": "INSEE Sirene 3.11", "url": "https://api.insee.fr/api-sirene/3.11/siret/55208131766522", "retrieved at": "2026-09-29T05:18:42.249656Z", "source status": "available", … }, { "id": "bodacc", "source type": "government bulletin", "authority class": "official publication", "publisher": "DILA BODACC", "retrieved at": "2026-09-29T05:18:42.318612Z", "source status": "available", "metadata": { "source error": null, "truncated": true }, … } , "conflicts": , "coverage": { "level": "partial", "fields requested": "status", "legal name", "legal form", "head office", "naf code", "creation date" , "fields resolved": "status", "legal name", "head office", "naf code", "creation date" , "fields unresolved": "legal form" , "metadata": { "unresolved field reasons": { "legal form": "unsupported fact" }, "authority policies": { "policy id": "company fr.status.current registry over publication", "precedence": "primary official registry", "official publication" , "reason": "INSEE/Sirene reports current administrative status; BODACC notices are historical publication evidence.", … }, … } }, "freshness": { "generated at": "2026-09-29T05:18:42.961558Z", "oldest supporting retrieved at": "2026-09-29T05:18:42.087910Z", "newest supporting retrieved at": "2026-09-29T05:18:42.318612Z", "stale": false, "recheck after": null }, "receipt id": "fr 952a43c43fba95f2ceeef5040132dd3206c4fe6a7dd0c7b5e1a8140a32012cef", "state fingerprint": "fs 6a25339254aa8ac976fa5987c691fa5b42bcfde8269457d850de135a04dd575e", "generated at": "2026-09-29T05:18:42.961558Z" } How to read it: authoritative , and each points to an evidence entry insee-siren or insee-siret with a URL and a retrieval time. legal form . It's listed under fields unresolved with the reason unsupported fact , and there's no value for it. Because one requested field is missing, the top-level status is insufficient evidence and coverage.level is partial , rather than a success that quietly skips a field. An agent can pass that gap on to the user as it is. authority policies state that for current status and legal name, the INSEE register takes precedence over BODACC publications. BODACC was queried source status: "available" , truncated: true , but no fact in this envelope was taken from it, and conflicts is empty. stale is false . We then called factrail get receipt with the returned receipt id . It returned the same envelope, with the same receipt ID and the same state fingerprint , and no integrity error. What a receipt gives you: What it does not give you: a digital signature, a blockchain record, or proof that the facts are true. It proves the envelope hasn't changed since it was produced. Whether INSEE was right is a separate question. As of 29/09/2026 server 2.4.1, public beta : factrail assess , not installed , so there's no authoritative tariff coverage. Fair use of the public endpoint: FACTRAIL's public MCP endpoint https://mcp.factrail.online/mcp https://mcp.factrail.online/mcp is free, read-only, and needs no account. To keep it fair for everyone, each client IP may make up to 60 MCP requests per minute, while traffic arriving through known MCP gateways such as Smithery shares a separate allowance of 600 requests per minute. Requests over the limit receive HTTP 429 with a Retry-After header, and clients that keep sending requests after being limited are paused for 5 minutes. If you need more capacity, please get in touch via https://factrail.online/ https://factrail.online/ rather than working around the limits. In practice: an agent that gets a 429 should wait for the number of seconds in Retry-After before retrying, instead of looping. On Apify: if your agents run on Apify, the Actor spherical distinction/factrail-evidence https://apify.com/spherical distinction/factrail-evidence is a thin wrapper around this same endpoint, billed per event: $0.005 per successful verification and $0.03 per successful import assessment Apify pricing checked on 29/09/2026 . The direct MCP endpoint stays free. Call factrail capabilities first: it reports the live scope, which may be newer than this article. The server is a Python package. From the repository README: git clone https://github.com/baronsigma/factrail && cd factrail python3 -m venv .venv && . .venv/bin/activate pip install -e '. dev ' cp .env.example .env Set INSEE API KEY in .env for live French company lookups python3 -m factrail.mcp http server POST http://localhost:8765/mcp Notes from the README: FACTRAIL HOST and FACTRAIL PORT change the bind address and port. Behind a public hostname, set FACTRAIL ALLOWED HOSTS ; by default only localhost is accepted. FACTRAIL ALLOWED ORIGINS controls browser access. When it's unset, requests that carry an Origin header are rejected; allows any Origin with CORS this is what the hosted endpoint does . FACTRAIL RATE LIMIT PER MIN , default 60, and related variables . FACTRAIL CACHE PATH to a persistent path if you want receipts to survive restarts the default is /tmp . python3 -m factrail.mcp server . Links: factrail.online https://factrail.online · github.com/baronsigma/factrail https://github.com/baronsigma/factrail · endpoint https://mcp.factrail.online/mcp