Agent Evidence API
[CI]
[M8ven Score]
[Listed on mcpservers.org]
[MCPRush]
Cited, source-grounded web evidence and claim verification, plus US weather from the
National Weather Service, for AI agents — over both HTTP and MCP. Paid per call in
USDC on Base over x402 — $0.01 a call, and nothing at all when nothing is retrieved.
No API key and no account for either service.
Discovery is free on every MCP route (tools/list, initialize, ping, and a
health tool), so an agent can read the schemas and probe for liveness before
paying for anything.
Optional: semantic ranking
Ranking is deterministic lexical matching by default, and it has a measured
ceiling. Asked "What is the population of Tokyo?" it returns passages that
mention "population" and "Tokyo" — 1603 history, wartime declines, municipal
taxes — and misses the sentence that answers the question, because that sentence
says "the city proper" instead of repeating "Tokyo". Two lexical heuristics
were tried and reverted; no keyword trick bridges it.
An optional Laya sidecar closes it. Laya
is an open-weight (Apache-2.0) typed-decision model: it scores a passage with
noul, P(this passage directly answers the question). No vendor, no API key, no
per-call fee, and your content never leaves the host.
Measured on the case above, single source:
The model is engaged only when the budget can afford the full pool. Cost is
~800 ms per candidate (a single call measures ~420 ms, but the sidecar
serialises, so ~700 ms under real concurrency). The budget is divided across
sources, so 1–2 sources can afford a 12-wide pool and larger requests cannot. A
narrower pool was measured to be worthless — it can only reshuffle passages that
already excluded the answer — so rather than spend ~20 s on that, those requests
fall back to lexical and stay fast. Lower REASONING_POOL_SIZE to engage on
larger requests, trading recall.
The budget is a hard ceiling, not a target: it is reserved before each call, so
concurrent sources cannot collectively overshoot it. (An earlier version checked
and then awaited, which let five sources each pass the check before any
decremented it — an 8 s budget produced a 50 s request.)
The trade is latency, not accuracy: ~420 ms per candidate, so a 12-candidate
pool costs ~5 s of inference per source. That is why it is off by default.
Enabling it changes only the ORDER of evidence — every excerpt, source, URL and
hash is identical — and if the sidecar is missing, slow, or returns anything
unexpected the service silently keeps its lexical result. The raw model
probability is never exposed (SPEC §21).
Note the pool matters: the first version of this ranked only the 5 candidates
lexical had already chosen, and the answer had been ranked 8th–20th, so it was
never available to promote. The wider pool is what makes it work.
Running the backend in Docker? The sidecar must be reachable from the container:
--add-host=host.docker.internal:host-gateway and point REASONING_URL at
http://host.docker.internal:8077, or put both on one Docker network.
You are only charged when the service delivers
The x402 middleware settles a payment only when the handler returns a status
below 400, so the service is built so that every way it can fail is a 4xx or
5xx:
- If no source could be retrieved — all 404s, all SSRF-blocked, all unsupported
content types — the request fails with
NO_SOURCES_RETRIEVED (502) and no
payment is taken. An earlier version returned 200 inconclusive here, which
would have billed a buyer $0.03 for an empty result.
- Partial success still settles. If one of five sources was retrieved, real
evidence was delivered and the per-source warnings explain the rest.
- On the MCP transport, a failed tool call is reported as
isError: true inside
an HTTP 200 — correct MCP behaviour, but invisible to the payment middleware.
The Worker rewrites that to 502 so the payment is not taken; the JSON-RPC body
is passed through unchanged.
Verified against the live deployment: a request whose sources all fail returns
502 NO_SOURCES_RETRIEVED, and a request with one good source plus one dead one
returns 200.
Try it without paying
Every paid route has a free preview that returns a real, unedited result — not a mock:
The evidence preview returns one source and up to three excerpts with source URL,
retrieval time and content hash, but no assessment. The paid call takes up to five
sources and adds the supported / contradicted / mixed / inconclusive verdict.
Twenty-five previews per caller address per day, no account, no wallet. The paid routes
are deliberately separate: they must keep answering 402, because that is what every x402
catalogue and validator checks, so previewing them in place would make the service
unlistable.
FAQ
What is Agent Evidence API?
An HTTP and MCP service for AI agents that returns cited, source-grounded evidence from
public web pages, plus US weather from the National Weather Service. You send a question
and up to five URLs; it returns the passages that bear on the question with their source
URL, retrieval time and content hash, and an assessment of supported, contradicted,
mixed or inconclusive.
Do I need an API key or an account?
No. There is nothing to sign up for. Payment is per call in USDC on Base over x402, so an
agent with a wallet can pay for exactly what it uses.
How much does it cost?
0.01 USDC per call. Nothing is charged when nothing is retrieved — if a source cannot be
fetched or no evidence is delivered, settlement is cancelled.
Can I see the output before paying?
Yes. GET /demo (HTML) and GET /demo.json serve one complete, unedited evidence
package from a real paid call. The MCP sample tool returns the same thing, free.
How is this different from a search engine or a scraper?
It is neither. You supply the URLs; the service does not discover sources for you. What
it adds is provenance: every excerpt is tied to a real retrieval, with a timestamp and a
content hash, so a claim can be traced back to what the page actually said.
What are its limits?
Matching is deterministic and lexical, not semantic. When a question asks more than one
thing and only one source contributed a matching passage, the service answers
inconclusive rather than guessing. Weather is US only, because the data is the US
National Weather Service's.
How do I call it from an agent?
Over HTTP (POST /v1/evidence) or MCP (POST /mcp). The buyer CLI is zero-install:
curl -fsSL https://agent-evidence.thx93.dev/buy.mjs -o buy.mjs, or
npx @thx93/x402-evidence.
Market analysis
docs/market-analysis.md — what the x402 economy
actually pays for, measured from the only catalogue that publishes real usage data
(30-day calls and unique payers per service). It is uncomfortable reading: the
category this service sits in is 1% of search by volume, and its price band is the
weakest of the five.
Engineering report
docs/ENGINEERING-REPORT.md — the report required
by SPEC.md §44: what was built, the architecture, deployment and test commands,
x402 and MCP status, registry readiness, remaining manual configuration, and known
limitations. It states plainly what is demonstrated and what is not — including
that no settlement has been exercised and that one §42 acceptance criterion is
unmet. It does not claim the project is production ready.
Actions only you can take
docs/CHECKLIST.md — every remaining step that needs a
human: the first payment, confirming the catalogue listing, posting, and the
decisions I deliberately left to you. Everything else is done.
Making the first sale
See docs/FIRST-SALE.md — the whole path from an empty
wallet to a settled payment and a catalogue entry, in one page.
How to see revenue
Every request the backend serves on a paid route has already passed the backend's
own x402 payment gate (the Worker only proxies). The log is append-only on the data
volume, so it survives container recreation and redeploys — unlike Docker logs.
A line means a payment proof was verified in this process. It is not proof of
settlement. The x402 order is verify → handler → settle, so the backend writes its
line before the facilitator settles, and a settlement that later fails still appears
here. The field is named payment_provided for that reason; it was previously
called settled, which was wrong.
For money, use the on-chain record. The buyer's client prints the settlement
transaction, and the authoritative ledger is the recipient address:
https://basescan.org/address/0x9c0e2B44180439294Fa30Ae2B2a94f8655455FD0
Each line carries the timestamp, request id, a salted hash of the question
(never the text), how many sources were retrieved, how many evidence items came
back, the assessment, and the server-side duration.
For the on-chain side, the payment response carries a settlement transaction
hash, and the recipient is 0x9c0e2B44180439294Fa30Ae2B2a94f8655455FD0 — check it
on basescan.
Live deployment
Buy something with no npm account and nothing to install — the buyer CLI is a
single self-contained file served by the service itself:
Or just look at the paywall first — this returns a 402 and charges nothing:
Evidence infrastructure for AI agents. Give it a question or a claim plus the
public web pages you want checked, and it returns structured, cited evidence:
source URLs, retrieval timestamps, short evidence excerpts, metadata, and an
explicit supported / contradicted / mixed / inconclusive assessment.
It is reachable by agents over both HTTP and MCP, and it is paid per
request with x402 + USDC on Base. There are no accounts, no passwords, and no
API keys — the payment is the authentication.
It provides evidence, not truth. The assessment is produced by deterministic
lexical matching, and the service says inconclusive rather than guessing. Do
not treat a supported status as proof.
Implementation status — read this first. Every package and both apps are
implemented, and pnpm typecheck passes across the whole workspace. pnpm test
covers ~147 cases in six suites, including a cross-package end-to-end flow test.
What is not done: no on-chain x402 payment has been demonstrated, there is
no automated x402 payment test, ROBOTS_POLICY is configured but inert, and two
committed literal test wallet addresses in apps/worker/wrangler.jsonc need
replacing before any deployment. See
Implementation status.
1. What the service does
An agent submits:
- a question or claim, and
- one or more public URLs to use as sources.
The service then:
- validates the URL and rejects anything unsafe (SSRF defences),
- fetches the page with bounded timeouts, redirects, and response size,
- detects the content type and parses the HTML,
- extracts metadata (title, description, canonical URL, Open Graph, JSON-LD,
language, publication and modification dates, headings, word count),
- normalises the main content and produces a SHA-256 content hash,
- extracts short evidence excerpts that match the question, with their position
in the document,
- compares the sources and reaches an assessment,
- returns a structured response with full provenance.
It is not a web scraper, an SEO tool, a generic crawler, a proxy, a
browser-automation service, or a search engine. It is evidence infrastructure:
the thing an agent calls when it needs to show its work.
What comes back
Every field is documented in docs/api.md.
2. Why an agent would use it
Because an agent that cites sources is more useful than one that asserts them.
- Grounding. Instead of generating a plausible sentence, the agent can attach
a real URL, a retrieval timestamp, a content hash, and the passage the claim
rests on.
- Verification. An agent can check what a specific page actually says, rather
than trusting a summary.
- Comparison. Multiple sources are compared, and disagreement is reported as
mixed instead of being averaged away.
- Honesty by construction. When nothing matches closely enough, the answer is
inconclusive. The service never invents a fact, never pretends a source says
something it does not, and never emits a confidence score.
- No onboarding. No signup, no key, no plan. Discover the tool, call it, pay
per request, get evidence.
3. Example use case
An agent has been asked: "Is Rotamech Industries actually a manufacturer of
centrifugal pumps, or just a reseller?"
Its own model knowledge is stale and unverifiable, so it gathers candidate URLs,
then calls this service:
The company site supports the claim; the registry page negates it. The service
returns:
with short cited excerpts from each side, their retrieval timestamps, and their
content hashes.
The agent now has something it did not have before: a defensible answer that
names both sides and shows where each came from, plus honest limitations. It can
tell the human "the company claims this; a registry record disputes it; here are
both sources", instead of picking one and sounding confident.
4. MCP usage
Transport: streamable HTTP. Endpoint: /mcp on the same host as the API.
Tools
Example client configuration
There is nothing to put in headers for authentication. There is no key.
initialize and tools/list are free
This matters for autonomous agents, and it is deliberate:
An agent must be able to discover the tools before it can decide to pay for
one. Only an actual invocation of the paid tool requires payment. A JSON-RPC
batch is charged if any element is a paid call.
Example tool call
Tool input schema (identical to the HTTP request body):
Full details, including what a payment-required response looks like to an MCP
client: docs/mcp.md.
5. HTTP usage
GET /health
GET /
A free capability description, useful for discovery before paying.
POST /v1/evidence
Returns the EvidenceResponse document shown in §1.
Without valid payment you get HTTP 402 with a PAYMENT_REQUIRED error envelope
and the x402 payment requirements:
Errors
Every failure uses one envelope. Codes are stable — switch on error.code.
Full table with meanings: docs/api.md §5.
A source that fails does not fail the request
If one of five URLs is unreachable, you still get the other four. The failed
source appears with status: null, empty evidence, and a warning carrying the
reason:
and limitations gains: "1 of 2 source(s) could not be retrieved; see per-source
warnings."
6. The x402 payment flow
- No API keys. No user accounts. By design (SPEC §11, §38). An agent should
never have to sign up, request a key, or subscribe.
- The recipient needs only a public address. Receiving USDC requires no
private key, because the service never signs anything — the paying client does.
- Never commit a private key. Not to
wrangler.jsonc, not to .env, not
anywhere. X402_TEST_PRIVATE_KEY is a disposable Base Sepolia key for local
testing only, must never hold mainnet funds, and must never be committed.
- Dev bypass is doubly guarded. Locally,
DEV_BYPASS_PAYMENT=true skips
gating — but only when the network is not Base mainnet. On eip155:8453
payment can never be bypassed, even if the flag leaks into production config.
Full detail: docs/x402.md.
7. Base and Base Sepolia
The settlement token is USDC in both cases.
Testnet and production configuration are kept in separate Worker environments and
must never be mixed. The safe default in packages/core/src/config.ts is Base
Sepolia, so an unconfigured deployment is on testnet rather than accidentally on
mainnet.
apps/worker/wrangler.jsonc provides three environments:
8. Pricing configuration
Pricing is environment-driven, never hard-coded.
The MVP default is approximately $0.03 per evidence request. That is a
starting point, not a fixed commercial price.
The price string is normalised before use: a leading $ is optional, and the
value is rendered to two decimals. Note that an unparseable value does not
stop the Worker — it silently falls back to $0.03. Verify the value after any
change.
9. Local development
Requires Node.js ≥ 22.6 (the cache uses the built-in node:sqlite module, so
there is no native dependency to compile) and pnpm.
The fixture server (tests/fixtures/server.ts) is offline and deterministic — no
dependency on live websites. It serves content pages, redirect chains,
redirect-to-private and redirect-to-metadata traps, a never-responding route, an
8 MiB streaming route, and 404/500 responses, so the SSRF and limit controls can
be exercised against a real socket.
Both dev commands work, with one caveat. pnpm dev:backend starts the
Fastify backend on :8080; pnpm dev:worker starts the Worker. Pointing the
Worker at the backend and calling /v1/evidence also requires the x402 leg to
settle, or DEV_BYPASS_PAYMENT=true with a non-mainnet network. See
Implementation status.
10. Deployment
-
Backend on the VPS. A Node.js + Fastify service built from docker/ and
run with docker compose, with restart: unless-stopped and a persistent
volume mounted at /app/data for the SQLite cache.
-
Cloudflare Worker. bash scripts/deploy-live.sh ships the production
environment, and refuses to report success unless the build exits cleanly, the
version ID actually changed, and the live service verifies from the outside.
(wrangler deploy alone pipes badly: wrangler deploy | tail exits 0 even when
the build fails, which hid a broken deploy twice.)
(Base mainnet).
-
The shared secret. BACKEND_AUTH_SECRET is deliberately absent from
wrangler.jsonc so it can never be committed:
It must match the backend's own BACKEND_AUTH_SECRET. Generate it with
openssl rand -hex 32.
-
Origin protection — the preferred option is a Cloudflare Tunnel, so
the backend has no public listener and no public DNS record. The alternative
is an authenticated origin behind a firewall restricted to Cloudflare's
egress ranges. In both cases the shared secret is validated on every internal
request, because network restriction and request authentication are
independent layers.
Use placeholders — PUBLIC_API_DOMAIN, BACKEND_ORIGIN_URL, MCP_PUBLIC_URL
— and never invent a real domain.
Full instructions, environment variable tables, health checks, and the manual
credential checklist: docs/deployment.md.
Deployable in principle, not yet deployed or exercised end to end. The
Docker artifacts, the backend, and the Worker all exist. Nothing has been
deployed from this repository, and two config defects must be fixed first (see
Implementation status). See
docs/deployment.md.
11. Security
The fetcher treats every URL as hostile input. Required defences (SPEC §18), all
enforced in the @aee/fetcher package:
- Scheme allowlist —
http and https only; file:// and everything else
rejected.
- Address rejection — localhost and its aliases, loopback, RFC1918/private
IPv4, link-local, multicast, unspecified, IPv6 loopback, IPv6 link-local,
private IPv6 ranges, and cloud metadata endpoints (
169.254.169.254 and
friends).
- Encoded evasion — decimal, octal, hexadecimal, short-form, and
IPv4-mapped-IPv6 representations are range-checked after parsing, never matched
as strings.
- DNS — resolve explicitly, validate every resolved address, and revalidate
at connect time to close the DNS-rebinding window.
- Redirects — validate every hop, not just the first URL, and enforce
MAX_REDIRECTS.
- Ports — allowlist only.
- Bounds — connect timeout, total request timeout, maximum response bytes
enforced while streaming, decompression-bomb defence, concurrency limits.
- No shell execution of URLs, ever. No
child_process in the fetch path.
- Logs — structured JSON with secret-key redaction. Never log payment or
origin secrets, private keys, or wallet secrets.
Separately, origin protection ensures a caller cannot skip the Worker and
call the backend directly to avoid paying: a Cloudflare Tunnel (preferred) or a
firewalled origin, plus server-to-server authentication on every internal
request. BACKEND_AUTH_SECRET has no default precisely so a misconfigured
backend fails closed instead of running unauthenticated.
And the access rules from SPEC §19: no bypassing access controls, no defeating
bot protections, no login automation, no paywall circumvention, no CAPTCHA
solving. A clearly identifiable User-Agent is used, robots.txt behaviour is
configurable via ROBOTS_POLICY, and only publicly accessible resources are in
scope. Submit only URLs you are permitted to access.
These controls are implemented in packages/fetcher and unit-tested in
tests/security/, which passes locally (92 security cases). No independent
audit has been performed. ROBOTS_POLICY is enforced. See
docs/security.md for the per-control status table, what
is enforced where, and the honest list of gaps.
12. Limitations
Stated plainly, because you should know these before you rely on the output.
And, right now
Implemented and typechecking: all six packages (schemas, core, fetcher,
extraction, cache, mcp), both apps (backend, worker), the Docker
artifacts, and server.json. pnpm typecheck passes across the workspace, and
six test suites cover roughly 147 cases, including an end-to-end flow test that
drives the real backend over HTTP against the fixture server.
Still open:
Verified locally: pnpm typecheck exits 0, pnpm test reports 199 passing
tests, and both scripts/smoke.sh and scripts/worker-smoke.sh pass against
live sockets. Re-run them and report the actual output rather than assuming a
pass — these results are from a single machine.
13. Registry publication
SPEC §28 requires the project to be registry-ready, not auto-published. That
means:
- a
server.json metadata file using the current official schema,
- validation of that file,
- documented exact publish steps,
- a namespace ownership and authentication checklist,
- publication by hand with the official
mcp-publisher workflow, and only when
credentials and authorization are explicitly available.
server.json now exists and declares this as a remote
streamable-HTTP server (no packages entry, because there is no npm package to
install). Two things remain before it is publishable:
remotes[].url is still https://mcp.example.invalid/mcp; replace it with your
real MCP_PUBLIC_URL.
- the
name and repository.url assert a GitHub namespace — confirm they are
genuinely yours, because namespace ownership is validated and a fabricated
handle fails.
Nothing has been published. The exact publish procedure, validation steps, and
namespace checklist are in
docs/registry-publication.md.
The discovery strategy — the MCP Registry, the GitHub repository, npm metadata
where appropriate, machine-readable metadata, documentation, and agent-oriented
tool descriptions — is in docs/discovery.md. It also
lists the truthful descriptive phrases worth using (web evidence, source
verification, claim verification, evidence extraction, source-grounded
research, compare sources, cited evidence, fresh web evidence), and states
plainly that descriptions must be truthful and must not be keyword-stuffed.
Do not publish until the service actually works. A registry entry listing a
tool that cannot be called is worse than no entry. Two concrete blockers remain:
the placeholder remotes[].url must be replaced with a live endpoint, and the
name/repository.url namespace must be confirmed as genuinely yours.
Project layout
Both the HTTP adapter and the MCP adapter call the same EvidenceService —
there is no duplicated business logic between them (SPEC §39):
x402 sits at the payment boundary in the Worker, never inside the core engine.
Implementation status
Snapshot taken while writing the documentation.
Implemented
packages/schemas — the complete wire contract (requests, responses, sources,
evidence items, warnings, errors, limits) with built output.
packages/fetcher — SSRF-hardened retrieval: scheme allowlist, IPv4/IPv6 range
classification (including decimal/octal/hex/127.1 forms and IPv4-mapped IPv6),
hostname rules, port allowlist, resolved-IP validation across all DNS records,
per-hop redirect validation, connect-time revalidation against DNS rebinding,
connect and total timeouts, streaming byte cap, and manual decompression.
packages/extraction — HTML parsing, metadata, main-content extraction,
normalisation, content hashing, deterministic lexical ranking, negation cues.
packages/cache — key normalisation, CacheProvider, bounded SQLite adapter.
packages/core — configuration, structured logger with secret redaction,
cross-source assessment, EvidenceService, typed ServiceError.
packages/mcp — the remote MCP adapter: streamable HTTP in stateless mode
(sessionIdGenerator: undefined, enableJsonResponse: true), a fresh
Server + transport pair per HTTP request, tools/list, and tools/call
delegating to the same EvidenceService the HTTP layer uses. Tool input
schemas are generated from zod via z.toJSONSchema.
apps/backend — Fastify service: fail-closed startup on a missing secret,
constant-time shared-secret verification on everything except /health,
GET /health, POST /internal/v1/evidence, and POST/GET/DELETE on /mcp.
apps/worker — the full edge: x402 gating, free /health and /,
POST /v1/evidence forwarding, MCP body inspection and forwarding, guarded dev
bypass, canonical error envelope.
docker/ — multi-stage Dockerfile, docker-compose.yml, separate
mainnet/testnet env templates, plus a root .dockerignore.
server.json, apps/worker/.dev.vars.example, scripts/smoke.sh.
Verified in this environment
pnpm typecheck — passes across the whole workspace (tsc --noEmit, exit 0).
- Six test suites exist totalling ~147 cases:
tests/security/ssrf.test.ts (30),
tests/security/limits.test.ts (16), tests/e2e/evidence-flow.test.ts (18),
packages/mcp/src/mcp.test.ts (15), packages/extraction/src/extraction.test.ts
(46), packages/cache/src/cache.test.ts (22).
Verified locally, not independently reproduced
pnpm typecheck → exit 0; pnpm test → 199 passing; scripts/smoke.sh and
scripts/worker-smoke.sh → all checks passed. No third party has reproduced
these results.
- No on-chain x402 payment has been demonstrated (the 402 gate is verified; the
settlement path needs a funded testnet wallet).
Open defects
Documentation
Contributing rules
AGENTS.md is the only project rule book. It defines the hard
constraints, the security rules, the coding standards, the testing bar, and the
definition of done. Do not create CLAUDE.md, .cursorrules, GEMINI.md, or any
competing instruction file.
Product requirements live in SPEC.md. Where this README and the
spec disagree, the spec is authoritative — and any contradiction between the
source code and the spec should be reported rather than silently resolved.
License
MIT. See package.json.