The same tools, without an agent.
Every tool the MCP servers mount also answers a plain HTTP request, at an address you can type. For a script, a notebook, a cron job or a backend: anything that needs an answer rather than a conversation. Same sources, same attribution, no key and no account.
An agent asks questions; a script needs an answer at three in the morning. This is the address it calls.
A first request
curl -G --data-urlencode 'ada=9ΓΓ346ΝΨ2Ν-Β5Τ' \
https://agoramcp-server.vercel.app/gr/api/diavgeia/get_decisionThe response, trimmed
{
"source": "diavgeia",
"country": "gr",
"tool": "get_decision",
"publisher": "Υπουργείο Ψηφιακής Διακυβέρνησης - Πρόγραμμα ΔΙΑΥΓΕΙΑ",
"license": "CC-BY-4.0",
"attribution": "Πρόγραμμα ΔΙΑΥΓΕΙΑ (diavgeia.gov.gr)",
"upstreamUrl": "https://diavgeia.gov.gr/opendata",
"data": {
"ada": "9ΓΓ346ΝΨ2Ν-Β5Τ",
"subject": "Μισθοδοσία μηνός Αυγούστου 2026",
"issuedOn": "2026-08-29",
"status": "PUBLISHED",
"organization": {
"id": "100076956",
"name": "ΑΝΑΠΤΥΞΙΑΚΟΣ ΟΡΓΑΝΙΣΜΟΣ ΔΗΜΟΥ ΛΑΡΙΣΑΙΩΝ ΜΟΝΟΠΡΟΣΩΠΗ Α Ε"
},
"expenses": { "totals": [{ "currency": "EUR", "amount": 2087.86 }] },
"documentUrl": "https://diavgeia.gov.gr/doc/9ΓΓ346ΝΨ2Ν-Β5Τ",
…
}
}The address describes itself.
One request, /gr/api, lists every source a country mounts and where each of its tools answers. One level down, /gr/api/diavgeia adds each tool's input schema and the example it ships with: enough to build a query string, or a form, for a tool nobody told you about. The live console on this site is built from the same description.
curl https://agoramcp-server.vercel.app/gr/api{
"country": "gr",
"endpoint": "/gr/api",
"sources": [
{
"id": "diavgeia",
"name": "Διαύγεια",
"license": "CC-BY-4.0",
"attribution": "Πρόγραμμα ΔΙΑΥΓΕΙΑ (diavgeia.gov.gr)",
"endpoint": "/gr/api/diavgeia",
"tools": [
{ "name": "search_decisions", "endpoint": "/gr/api/diavgeia/search_decisions", … },
{ "name": "get_decision", "endpoint": "/gr/api/diavgeia/get_decision", … },
…
]
},
…
]
}Arguments in, records out.
The address
Country, source, tool: the same three names the MCP mounts use, with the tool as the last segment because there is no session to choose it in.
The arguments
GET takes them as query parameters, coerced to what the schema declares: size=5 is a number, a bare includeInactive is true. POST takes one JSON object instead, for a long query or when you would rather not encode Greek into a URL (query values are URL-encoded, which every HTTP library does for you and curl does with --data-urlencode). Either way, a parameter the tool does not declare is a 400, so a typo cannot silently widen a search.
curl -G https://agoramcp-server.vercel.app/gr/api/diavgeia/search_decisions \
--data-urlencode 'subject=προμήθεια' \
--data-urlencode 'issuedFrom=2026-01-01' \
--data-urlencode 'issuedTo=2026-01-31' \
--data-urlencode 'size=5'curl https://agoramcp-server.vercel.app/gr/api/diavgeia/search_decisions \
-H 'content-type: application/json' \
-d '{ "subject": "προμήθεια", "issuedFrom": "2026-01-01", "issuedTo": "2026-01-31", "size": 5 }'The response
An envelope around data, the tool's own result: the same object an assistant reads over MCP. The rest is provenance, on every response: an MCP session is told the licence once when it connects; a stateless request has to be told every time. Show attribution wherever you show the data.
- source
- the source id, as in the address
- country
- its ISO 3166-1 alpha-2 code
- tool
- the tool that answered
- publisher
- the body that publishes the registry
- license
- the registry's licence, SPDX where one exists
- attribution
- the line the licence asks you to show with the data
- upstreamUrl
- where the registry itself lives
- data
- the tool's own result: what an assistant reads over MCP
When it goes wrong
One shape for every failure, and the status says which kind. The message is written for a person and details, where there is one, names the field, so a script can log the first and branch on code.
HTTP/1.1 400 Bad Request
{
"error": {
"code": "invalid_input",
"message": "✖ An ΑΔΑ looks like 9ΓΓ346ΝΨ2Ν-Β5Τ: capitals and digits, a hyphen, then three characters.\n → at ada",
"retryable": false,
"details": {
"issues": [{ "path": "ada", "message": "An ΑΔΑ looks like 9ΓΓ346ΝΨ2Ν-Β5Τ: …" }]
}
}
}- 400invalid_input
- an argument is missing, malformed, or not one the tool declares
- 404not_found
- no such source, tool or record
- 405method_not_allowed
- only GET and POST
- 429rate_limited
- over the per-source budget for this minute
- 500internal
- something else; the message says what
- 502upstream_error
- the registry answered with something unusable
- 503upstream_unavailable
- the registry is down; try again
- 503missing_credentials
- this deployment lacks a token the source needs
- 504upstream_timeout
- the registry did not answer in time
What it will and won't do.
No key
Nothing to sign up for and nothing to send. The address is the whole contract.
60 requests a minute
Per client, per source: a script hammering Διαύγεια does not spend another registry's allowance. Every response reports the budget in RateLimit-* headers; past it, a 429.
Cached where the registry is slow
Each tool caches its answer for a lifetime the source sets, and a successful GET says so in Cache-Control. Repeating a call costs the registry nothing.
Read-only
It cannot write to a registry, and it returns only what the registry publishes. The licence in every response is the registry's, not ours.