# Interoperating over A2A

Send a JSON-RPC `message/send` to `https://connectbyjbrh.com/a2a` with a text part. Connect answers as an agent message, not a task, using one of five documentation skills — named by `skillId`, or chosen from the shape of the question. Streaming and the authenticated extended card are refused explicitly. Nothing here requires authentication, because nothing here is private.

- **Status:** Available
- **Audience:** developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/developers/a2a-integration/

## A request and its reply

```json
{"jsonrpc": "2.0", "id": 1, "method": "message/send",
 "params": {"skillId": "describe-capability",
            "message": {"role": "user",
                        "parts": [{"kind": "text",
                                   "text": "Is call recording available?"}]}}}
```

```json
{"jsonrpc": "2.0", "id": 1, "result": {
  "kind": "message", "role": "agent",
  "messageId": "…", "contextId": "…",
  "parts": [{"kind": "text", "text": "Capability status vocabulary: …"},
            {"kind": "data", "data": {"statuses": {}, "matches": []}}],
  "metadata": {"skillId": "describe-capability",
               "source": "https://connectbyjbrh.com/docs/"}}}
```

The reply is a message, not a task. There is no task id to poll, no state machine to follow and no history to fetch, which is what the card means by `stateTransitionHistory: false`. A `contextId` you send is echoed back; if you send none, one is generated so a conversation can be threaded on your side.

## Which skill answers

Name one with `skillId` and that skill runs. Omit it and Connect picks from the wording, because an agent that found the card at a well-known URI often has not read the skill list first and refusing it would be the less useful behaviour.

| The question looks like | Skill chosen |
|---|---|
| *Can Connect…*, *does Connect…*, *status of*, *available* | `describe-capability` |
| *end to end*, *workflow*, *what happens when*, *step by step* | `explain-workflow` |
| *MCP*, *API*, *webhook*, *integrat…*, *protocol*, *OAuth* | `explain-integration` |
| *search*, *find pages*, *find docs*, *list* | `search-connect-docs` |
| Anything else | `explain-connect` |

Naming the skill is better than relying on the routing, and an unknown `skillId` is refused with the list of real ones rather than quietly falling back — a silent substitution would give you an answer from a skill you did not ask for.

## What is refused, and with which code

| Call | Code | HTTP | Meaning |
|---|---|---|---|
| `message/stream` | `-32004` | `200` | Streaming is not offered; the card declares `streaming: false` |
| `agent/getAuthenticatedExtendedCard` | `-32004` | `200` | There is no authenticated extended card; the public one is the whole surface |
| An unknown `skillId` | `-32004` | `200` | The five real skill ids are listed in the message |
| Any other method | `-32601` | `200` | This agent answers `message/send` |
| A message with no text part | `-32602` | `200` | Send at least one part with `kind: text` |
| `params` that is not an object | `-32602` | `200` | JSON-RPC params must be an object |
| Malformed JSON | `-32700` | `400` | The body could not be parsed |
| A body over 128 KiB | `-32600` | `413` | Smaller than the MCP limit, because a question is not a document |
| Over 60 requests a minute | `-32603` | `429` | Per client address, sliding over the last sixty seconds |

> **Note** `tasks/send` from an earlier draft is accepted and handled like `message/send`, so an older client still gets an answer. It receives the same message-shaped result — there is still no task object behind it.

## What A2A can and cannot get you

The five skills read the generated documentation corpus and nothing else. They can tell you what Connect does on each channel, the status of a named capability, how a workflow runs stage by stage, and which protocols are implemented at which version. They cannot look at a workspace, act on one, or hold anything on your behalf.

That is why the card carries no security schemes. There is no authenticated A2A surface to negotiate towards: not a private skill, not an extended card, not a scope. An agent that treats an unauthenticated answer as the free tier of something larger is reading a boundary as a step.

MCP and A2A are complementary rather than alternatives. MCP is how a model host reaches Connect's tools; A2A is how another agent discovers Connect and asks it something in one call. Both are served from the same module, so neither can answer differently from the website.

## Questions

### Do I have to fetch the card before calling?

Not technically — `/a2a` answers whether or not you read the card. Fetch it anyway: it tells you the five skill ids, and it tells you not to open a stream, which saves a failed call.

### How long is a reply?

A skill that returns a page truncates the body — 6,000 characters for `explain-connect`, 8,000 for the workflow and integration skills. Follow the `page` URL in the data part when you need the whole page.

### Can I send a file or an image?

No. `defaultInputModes` is `text/plain` only, and a message with no text part is refused. Replies may be plain text, Markdown or JSON.

## Related

- [The Connect Agent Card](https://connectbyjbrh.com/developers/agent-card/)
- [The Connect MCP server](https://connectbyjbrh.com/developers/mcp-server/)
- [The public API](https://connectbyjbrh.com/developers/public-api/)
- [A2A — the Agent2Agent protocol](https://connectbyjbrh.com/docs/protocols/a2a/)
- [A2A agent discovery](https://connectbyjbrh.com/docs/protocols/a2a-discovery/)
- [JSON-RPC 2.0](https://connectbyjbrh.com/docs/protocols/json-rpc/)

## What this page is based on

- `backend/app/a2a_server.py` — SKILLS, _ROUTES, a2a_endpoint
- `webapp/.well-known/agent-card.json`
- A2A specification 1.0.0 — https://a2a-protocol.org/latest/specification/
