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_decision

The 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.

/{country}/api/{source}/{tool}

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.