Gatefee over MCP
One URL, one API key: an agent can search licensed local news, retrieve a document with a signed receipt, look a receipt up, and see its own usage. Everything that happens over MCP is the same transaction as the HTTP API — same rights decision, same metering, same receipts.
Licensed and metered.
gatefee_searchandgatefee_get_documentare recorded against your account and may be charged under your plan.gatefee_get_receiptandgatefee_usageare free. An agent in a loop spends money. What stops it today is your license's hard spend cap — the most you can be charged in a month, all-in — and the key's rate limit. Give the key you hand to an agent its own spend cap — in the console, on the key: the most that key can add to your bill in a month. Setting or lowering it takes effect at once; raising it asks for your second factor. (It bounds metered spend; the Free plan meters nothing.)
Connect
Endpoint: POST https://<your Gatefee host>/mcp — Streamable HTTP, stateless. Credential: your API key (scope retrieve) as
Authorization: Bearer gp_live_…. There is no OAuth yet (it is planned with the self-serve pages, Phase 5.5).
The server speaks MCP revision 2026-07-28 and also serves clients that still open with the older initialize handshake
(the 2025-11-25 family) — with no session in either case.
Claude Code
claude mcp add --transport http gatefee https://<host>/mcp --header "Authorization: Bearer gp_live_…"
Claude Desktop — use the mcp-remote bridge in claude_desktop_config.json (the form below — no space after the colon, the
value in env — is the one mcp-remote's own README gives, because some hosts mangle spaces inside args; it also documents
--header-file, which keeps the key out of the process list):
{ "mcpServers": { "gatefee": { "command": "npx", "args": ["-y", "mcp-remote", "https://<host>/mcp", "--header", "Authorization:${GATEFEE_AUTH}"], "env": { "GATEFEE_AUTH": "Bearer gp_live_…" } } } }
MCP Inspector (CLI)
npx @modelcontextprotocol/inspector --cli https://<host>/mcp --transport http \
--header "Authorization: Bearer gp_live_…" --method tools/list
TypeScript SDK
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
const client = new Client({ name: 'my-agent', version: '1.0.0' }, { versionNegotiation: { mode: 'auto' } });
await client.connect(new StreamableHTTPClientTransport(new URL('https://<host>/mcp'), { requestInit: { headers: { authorization: `Bearer ${process.env.GATEFEE_KEY}` } } }));
Tools
| Tool | Arguments | You get |
|---|---|---|
gatefee_search |
query, purpose, geo?, publisher_ids?, sections?, published_after?, published_before?, language?, limit?, cursor?, idempotency_key? |
hits: title, publisher, date, snippet — never a body |
gatefee_get_document |
document_id, purpose, geo?, version_id?, if_none_match?, idempotency_key? |
the document, receipt_id, receipt_jws, license_summary, attribution, license |
gatefee_get_receipt |
receipt_id |
one of your receipts |
gatefee_usage |
from?, to?, group_by? |
your own usage and spend (micro-USD, as strings) |
purposeis required and never defaulted —rag,displayorsummarize. It decides rights and price. Training is not licensed.geois the country of use as you state it (ISO 3166-1 alpha-2); it is never taken from your connection.- Paying once, not twice. Each call is its own request. If your client may retry — after a timeout, say — pass an
idempotency_key: the same key with the same arguments returns the same response and no new charge. Without one, a retry is a second charge. To re-read a document you already hold, pass itscontent_hashasif_none_match: if it has not changed you get "not modified" — nothing served, no unit consumed. - The receipt (
receipt_jws) is a compact JWS anyone can verify offline against/.well-known/jwks.json— seedocs/receipts.md. It shows the publisher's list price, never what you paid, your plan or your usage. - Refusals are results you can read (
isError: true, withstructuredContent.error):denied(withdeny_reasonand, where one exists, anoffer),not_found,idempotency_mismatch,busy,unavailable,invalid_request. Authentication and rate-limit failures are plain HTTP401/403/429.
Resources
Two templates; resources/list is always empty, so there is nothing for a client to fetch by itself, and no tool result links to one.
gatefee://document/{id}{?purpose,geo}— the same licensed, metered retrieval as the tool. It must state a purpose (?purpose=rag); without one it is refused before anything is looked up or charged. It returns three items: the license, attribution and signed receipt (JSON); the publisher's own title, byline and section (JSON, third-party); then the body. Every read is a new request. A resource read cannot carry anidempotency_keyorif_none_match, so a retry — by your client, or after a timeout — is a second charge. For anything that may retry, use thegatefee_get_documenttool.gatefee://receipt/{id}— one of your receipts. Free.
The document is untrusted input
A retrieved article is third-party text about to be read by a model. Gatefee returns it exactly as published (what is served is what the receipt commits to), so it cannot be made safe on the way through. What Gatefee does: its own words — the license summary, the attribution, the receipt id — come first, each in its own block, and never quote the title or body; then a fixed notice; then the body, last and alone. Treat the body, titles and snippets as data, never as instructions — the same care you would give any web page.
Not served
subscriptions/listen (the tool list never changes), prompts, sampling, elicitation, JSON-RPC batches (one POST is one message and
one rate-limit token), and any request carrying a browser Origin header (403): this endpoint is for servers and desktop clients.