# I made my website callable by another AI agent — here's the actual JSON-RPC

> Source: <https://dev.to/andriy_pyvovarchuk/i-made-my-website-callable-by-another-ai-agent-heres-the-actual-json-rpc-42m>
> Published: 2026-08-04 06:01:22+00:00

Most "AI integration" still means a human reads your docs and writes a client. A2A flips that: another *agent* reads a small machine description of what your service does and calls it directly — no browser, no scraping, no human in the loop. I wired it up on a small tool I run (a free llms.txt validator) and it's simpler than the acronyms suggest. Here's the whole thing, with the real requests.

**A2A (Agent2Agent)** is an open protocol — now under the Linux Foundation — that lets AI agents *discover and call each other* over plain HTTP. Think of it as the agent-to-agent counterpart of a public API: instead of publishing OpenAPI docs for humans, you publish a machine description another agent can consume and invoke.

It has exactly two moving parts:

That's it. You can ship a useful A2A surface with one skill and a single method.

You serve a JSON file at `/.well-known/agent-card.json`

describing who you are and what you can do. The required fields are `name`

, `description`

, `version`

, `url`

(your endpoint) and at least one `skill`

:

```
{
  "protocolVersion": "0.3.0",
  "name": "llms.txt Validator",
  "description": "Validate a website's llms.txt and return a score with findings.",
  "url": "https://llms-txt-validator.dev/a2a",
  "skills": [{
    "id": "validate_llms_txt",
    "name": "Validate llms.txt",
    "description": "Given a domain or URL, fetch and validate its llms.txt."
  }]
}
```

The card is a **contract**, not a meta tag. An agent fetches it, sees a `validate_llms_txt`

skill, and knows both what you offer and where to call.

The card's `url`

points at a **JSON-RPC 2.0** endpoint. An agent invokes the `message/send`

method with a message; you do the work and return a `Task`

. Here's a real call to my agent:

```
POST /a2a
{ "jsonrpc": "2.0", "id": "1", "method": "message/send",
  "params": { "message": { "role": "user",
    "parts": [{ "kind": "text", "text": "validate llmstxt.org" }] } } }
```

…and the reply — a completed task carrying a human-readable summary *and* structured data the calling agent can use directly:

```
{ "result": { "kind": "task", "status": { "state": "completed" },
  "artifacts": [{ "parts": [
    { "kind": "text", "text": "Validated llmstxt.org: score 100/100..." },
    { "kind": "data", "data": { "ok": true, "report": { "scores": { "overall": 100 } } } }
  ] }] } }
```

No HTML, no parsing, no guessing. The agent asked a question in natural language and got back exactly the data it needed. You can `curl`

this yourself.

You don't need the whole spec to start. Pick *one* real thing your service does and:

`/.well-known/agent-card.json`

with one skill.`message/send`

method wrapping that capability.`Task`

with the result as an artifact.Streaming, task history, and push notifications are all optional — set `capabilities.streaming`

to `false`

and add them only when you actually need them.

They stack rather than compete: A2A reaches networked agents, WebMCP reaches in-browser ones, MCP wires tools into one model.

It's tempting to drop an agent card to look modern and leave the endpoint returning `501`

. Don't. An agent that fetches your card and calls a dead `url`

trusts you *less* afterward — you've spent its call for nothing. Every skill you advertise should resolve to real, working behavior. (That's why the [validator](https://llms-txt-validator.dev/) I built reports an A2A signal as "present" only when a live endpoint actually answers `message/send`

.)

If you ship one, drop your agent card in the comments — I'd like to call it.
