Content
# ARDF Subagent Profile + MCP ↔ A2A Bridge
[](https://github.com/MauricioPerera/ARDF-MCP-A2A/actions/workflows/ci.yml) [](https://github.com/MauricioPerera/ARDF-MCP-A2A/actions/workflows/build.yml) [](https://codecov.io/gh/MauricioPerera/ARDF-MCP-A2A)
[](https://www.npmjs.com/package/mcp-bridge-a2a) [](https://www.npmjs.com/package/mcp-bridge-a2a) [](https://github.com/MauricioPerera/ARDF-MCP-A2A/releases) [](https://github.com/MauricioPerera/ARDF-MCP-A2A/releases/latest)
[](https://sonarcloud.io/summary/new_code?id=MauricioPerera_ARDF-MCP-A2A) [](https://sonarcloud.io/summary/new_code?id=MauricioPerera_ARDF-MCP-A2A) [](https://sonarcloud.io/summary/new_code?id=MauricioPerera_ARDF-MCP-A2A)
Repositorio con el perfil ARDF `subagent@v0.1` y un servidor MCP en Node/TypeScript que actúa como puente hacia agentes A2A.
## Índice
- [Uso](#uso)
- [Uso rápido](#uso-rápido)
- [Notas de implementación](#notas-de-implementación)
- [Personalización](#personalización)
- [Visión general](#visión-general)
- [Arquitectura](#arquitectura)
- [Configuración](#configuración)
- [Ejemplos de payloads](#ejemplos-de-payloads)
- [Diagrama de notificaciones (SVG)](#diagrama-de-notificaciones-svg)
- [Validación de esquemas (JSON Schema)](#validación-de-esquemas-json-schema)
- [Manifest avanzado (API Key + mTLS)](#manifest-avanzado-api-key--mtls)
- [FAQ (Preguntas frecuentes)](#faq-preguntas-frecuentes)
- [Pruebas](#pruebas)
- [Troubleshooting](#troubleshooting)
- [Glosario](#glosario)
- [Diagrama de arquitectura (ASCII)](#diagrama-de-arquitectura-ascii)
- [Manifest avanzado (OAuth2 + SSE)](#manifest-avanzado-oauth2--sse)
- [Integración con clientes MCP (rápida)](#integración-con-clientes-mcp-rápida)
## Contenido
- `schemas/subagent.v0.1.json`: JSON Schema (draft 2020-12) que describe cómo publicar un subagente (type `subagent`) como recurso MCP.
- `examples/ventas-subagent.json`: Manifiesto de ejemplo que enlaza con un Agent Card A2A y declara los tools MCP (`subagent_start`, `subagent_send`, `subagent_cancel`).
- `src/server.ts`: Servidor MCP sobre HTTP streamable usando el SDK oficial; expone el recurso del subagente, materializa tareas `a2a://task/{id}` y reenvía las llamadas A2A.
## Uso
```sh
npm install
npm run dev
# Cliente MCP -> http://localhost:3000/mcp
```
Flujo sugerido con un cliente MCP:
1. Leer `a2a-agent://ventas-core` para obtener el manifiesto ARDF.
2. Invocar `subagent_start` con un mensaje (`text: "Hola"`); la respuesta incluye un `resource_link` a `a2a://task/{id}` y `structuredContent` con `{ taskUri, taskId }`.
3. Suscríbase a ese recurso (`resources/subscribe`) y continúe con `subagent_send` o finalice con `subagent_cancel`.
### Uso rápido
- Arranque el servidor:
```sh
npm install
npm run dev
# Servidor MCP en http://localhost:3000/mcp
```
- Ejemplo de invocación directa a `/mcp` para `subagent_start` (usando `curl`):
```sh
curl -s \
-X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"type":"request",
"method":"tools/call",
"params":{
"name":"subagent_start",
"arguments":{"subagentUri":"a2a-agent://ventas-core","text":"Hola"}
},
"id":1
}'
```
- Respuesta esperada (forma general):
```json
{
"type": "response",
"result": {
"content": [
{ "type": "resource_link", "uri": "a2a://task/abc-123" },
{ "type": "text", "text": "{\"taskUri\":\"a2a://task/abc-123\",\"taskId\":\"abc-123\"}" }
],
"isError": false,
"structuredContent": { "taskUri": "a2a://task/abc-123", "taskId": "abc-123" }
},
"id": 1
}
```
- Siguiente paso: suscríbase a `a2a://task/{id}` para recibir `notifications/resources/updated` y continúe con `subagent_send` o finalice con `subagent_cancel`.
## Notas de implementación
- El schema valida IDs, transporte preferido, mapping de skills A2A e identifica los tools MCP que orquestan la tarea.
- `server.ts` usa `ResourceTemplate` para publicar `a2a-agent://{id}` y `a2a://task/{taskId}`, enviando `notifications/resources/updated` cuando el estado cambia.
- Los tools consumen el Agent Card remoto y llaman a los endpoints JSON-RPC (`message/send`, `tasks/cancel`) del agente A2A, manteniendo un almacén en memoria de las tareas activas.
- La enumeración de subagentes implementada con `ResourceTemplate.list` debe devolver un objeto `{ resources: [...] }` (no un array directo).
- Las URIs de subagente usan el esquema `a2a-agent://{id}`; el `id` se extrae del `hostname` de la URI (y se admite como `pathname` para compatibilidad).
- El transporte HTTP streamable está configurado con `enableJsonResponse: true` y sin gestión de sesión (`sessionIdGenerator: undefined`):
- El cliente DEBE incluir `Accept: application/json, text/event-stream`.
- El encabezado `Mcp-Protocol-Version` se negocia automáticamente si no se envía; no se requiere `Mcp-Session-Id` por defecto.
## Personalización
- Sustituye `examples/ventas-subagent.json` por tus propios subagentes o carga varios en `SUBAGENTS`.
- Añade autenticación según `security` (OAuth2, API key, mTLS) ajustando `jsonRpc`.
- Si el Agent Card anuncia `message/stream`, incorpora un listener SSE y utiliza `notifyTaskUpdated` para reflejar eventos en tiempo real.
## Visión general
- Propósito: Puente entre MCP (Model Context Protocol) y agentes A2A, exponiendo subagentes como recursos MCP y orquestando tareas mediante tools.
- Contexto: MCP ofrece un contrato uniforme para recursos y herramientas; A2A define Agent Cards y endpoints JSON-RPC para mensajería y tareas.
- Resultado: Clientes MCP pueden descubrir, iniciar y manejar conversaciones/tareas con agentes A2A sin conocer los detalles internos.
## Arquitectura
- Componentes principales:
- Servidor MCP HTTP streamable en `src/server.ts`.
- Recursos:
- `a2a-agent://{id}`: manifiesto ARDF del subagente.
- `a2a://task/{taskId}`: estado de la tarea/conversación.
- Tools:
- `subagent_start`: inicia una tarea con un mensaje inicial.
- `subagent_send`: envía mensajes adicionales a una tarea existente.
- `subagent_cancel`: solicita cancelar una tarea.
- Flujo de datos:
- Descubrimiento → `resources/list` y `resources/read` del subagente.
- Inicio → `tools/call` `subagent_start` → creación y publicación de `a2a://task/{taskId}`.
- Continuación/Cancelación → `subagent_send`/`subagent_cancel` → actualizaciones y notificaciones.
- Notificaciones: El servidor envía `notifications/resources/updated` cuando cambian los recursos `a2a://task/{taskId}`.
## Configuración
- Subagentes:
- Archivo de ejemplo: `examples/ventas-subagent.json`.
- Puedes cargar múltiples manifiestos en una estructura `SUBAGENTS` en `src/server.ts`.
- Seguridad (a nivel de Agent Card):
- Ajusta encabezados `Authorization` para OAuth2 o API keys, o configura mTLS según el `security` del manifest.
- Transporte MCP:
- `enableJsonResponse: true` y sin sesión por defecto.
- Requisitos del cliente: `Accept: application/json, text/event-stream`.
- `Mcp-Protocol-Version` se negocia si no se envía; `Mcp-Session-Id` no requerido por defecto.
- Streaming SSE (message/stream):
- Variables de entorno:
- `SSE_IDLE_TIMEOUT_MS` (por defecto `30000`) tiempo de inactividad antes de abortar.
- `SSE_RETRY_BASE_MS` (por defecto `1000`) base del backoff exponencial.
- `SSE_RETRY_MAX_MS` (por defecto `30000`) máximo del backoff.
- `SSE_MAX_ATTEMPTS` (por defecto `∞`, en test `1`) límite de reconexiones del listener SSE.
- En modo test, el listener limita reconexiones para evitar bucles.
- Persistencia y directorio de datos:
- `DATA_DIR`: ruta para almacenar `tasks.json`. Por defecto `./data/`.
- Escritura atómica de tareas y limpieza periódica configurable: `TASK_TTL_MS` (TTL, por defecto 24h), `TASK_CLEANUP_INTERVAL_MS` (intervalo de compactación, por defecto 5min).
- Endpoint de salud:
- `GET /healthz` devuelve `{ ok, activeSse, tasks, uptime }` útil para probes.
- En modo test, el listener limita reconexiones para evitar bucles.
### Autenticación
- Recomendaciones generales:
- No commitees secretos en manifiestos; usa variables de entorno y mecanismos seguros de inyección.
- Si tus manifests usan placeholders (`${VAR}`), sustitúyelos antes de cargar el archivo o genera el manifest a partir de plantillas.
- OAuth2 (Bearer):
- Define `VENTAS_TOKEN` (u otro nombre según tu agente) y referencia en encabezados `Authorization: Bearer ${VENTAS_TOKEN}` dentro del manifest.
- Renueva el token fuera del bridge (cron, CI/CD) y recarga el manifest si cambia.
- Estrategia de refresh: mantén un job que obtenga tokens de corta duración y actualice la variable de entorno; evita tokens perpetuos.
- API Key:
- Define `FINANZAS_API_KEY` y referencia en encabezados `X-API-Key: ${FINANZAS_API_KEY}`.
- Rotación: usa mecanismos de rotación segura y métricas para detectar uso indebido.
- mTLS:
- Evita incluir PEMs en el repositorio; usa inyección por archivo/volumen y variables como `CLIENT_CERT_PEM`, `CLIENT_KEY_PEM`.
- Opción recomendada: Reverse proxy (Nginx/Envoy) con client cert del lado del proxy; el bridge habla HTTP(s) interno sin manejar certificados.
- Si necesitas mTLS desde el bridge, usa una librería HTTP que soporte client cert (axios/node-fetch) y configura cert/key a partir de secretos; valida que tu runtime soporte esta opción.
- Validación: verifica fecha de expiración y subject del cert al inicio y registra errores claros si faltan o son inválidos.
## Despliegue Docker/Cloud
- Variables de entorno típicas:
- `SSE_IDLE_TIMEOUT_MS`, `SSE_RETRY_BASE_MS`, `SSE_RETRY_MAX_MS`.
- Credenciales: `VENTAS_TOKEN`, `FINANZAS_API_KEY`, `CLIENT_CERT_PEM`, `CLIENT_KEY_PEM`.
- Docker (ejemplo de comandos):
```bash
# Construir imagen
docker build -t mcp-a2a-bridge .
# Ejecutar con variables de entorno y volumen para manifests
docker run --rm -p 3000:3000 \
-e SSE_IDLE_TIMEOUT_MS=30000 \
-e SSE_RETRY_BASE_MS=1000 \
-e SSE_RETRY_MAX_MS=30000 \
-e VENTAS_TOKEN=REDACTED \
-v %cd%/examples:/app/examples \
mcp-a2a-bridge
```
- Docker Compose (idea general):
```yaml
services:
bridge:
image: mcp-a2a-bridge:latest
ports:
- "3000:3000"
environment:
SSE_IDLE_TIMEOUT_MS: 30000
SSE_RETRY_BASE_MS: 1000
SSE_RETRY_MAX_MS: 30000
VENTAS_TOKEN: ${VENTAS_TOKEN}
volumes:
- ./examples:/app/examples
```
- Cloud (lineamientos):
- Usa secretos gestionados (Render, Fly.io, Railway, etc.) y mapea a variables de entorno.
- Expone el puerto `3000` y configura health checks básicos en `/` si tu plataforma lo requiere.
- Monta `examples/` como volumen o empaqueta manifests en la imagen si son públicos.
## Publicación en npm
Pasos típicos:
1. Inicia sesión: `npm login`
2. Dry run opcional: `npm publish --dry-run` (ejecuta automáticamente `prepack` → `npm run build`)
3. Publica: `npm publish`
4. Uso rápido vía npx:
- - `npx mcp-bridge-a2a@latest` (si el paquete expone un script de arranque) o instala global:
- - `npm i -g mcp-bridge-a2a` y luego `mcp-a2a-bridge`
+ 4. Uso rápido vía npx:
+ - `npx mcp-a2a-bridge` (ejecuta el bin del paquete)
+ - o instala global: `npm i -g mcp-bridge-a2a` y luego `mcp-a2a-bridge`
Notas:
- El bin del paquete es `mcp-a2a-bridge` → ejecuta `dist/server.js`.
- Requiere Node.js >= 18.
## Ejemplos de payloads
- Petición `resources/list` (cliente):
```json
{
"type": "request",
"method": "resources/list",
"params": {},
"id": 1
}
```
- Respuesta `resources/list` (servidor):
```json
{
"type": "response",
"result": {
"resources": [
{ "uri": "a2a-agent://ventas-core", "name": "Ventas Core", "mimeType": "application/json" }
]
},
"id": 1
}
```
- Petición `resources/read` de subagente:
```json
{
"type": "request",
"method": "resources/read",
"params": { "uri": "a2a-agent://ventas-core" },
"id": 2
}
```
- Respuesta `resources/read` de subagente:
```json
{
"type": "response",
"result": {
"contents": [
{ "type": "text", "text": "Manifiesto ARDF", "mimeType": "application/json" },
{ "type": "json", "json": { /* contenido del manifest */ } }
]
},
"id": 2
}
```
- Petición `tools/call` `subagent_start`:
```json
{
"type": "request",
"method": "tools/call",
"params": {
"name": "subagent_start",
"arguments": { "subagentUri": "a2a-agent://ventas-core", "text": "Hola" }
},
"id": 3
}
```
- Respuesta `tools/call` `subagent_start`:
```json
{
"type": "response",
"result": {
"content": [
{ "type": "resource_link", "uri": "a2a://task/abc-123" },
{ "type": "text", "text": "{\"taskUri\":\"a2a://task/abc-123\",\"taskId\":\"abc-123\"}" }
],
"isError": false,
"structuredContent": { "taskUri": "a2a://task/abc-123", "taskId": "abc-123" }
},
"id": 3
}
```
- Petición `tools/call` `subagent_send`:
```json
{
"type": "request",
"method": "tools/call",
"params": {
"name": "subagent_send",
"arguments": { "taskId": "abc-123", "text": "Siguiente mensaje" }
},
"id": 4
}
```
- Respuesta `tools/call` `subagent_send`:
```json
{
"type": "response",
"result": {
"content": [ { "type": "text", "text": "Mensaje enviado" } ],
"isError": false,
"structuredContent": { "ok": true }
},
"id": 4
}
```
- Petición `tools/call` `subagent_cancel`:
```json
{
"type": "request",
"method": "tools/call",
"params": {
"name": "subagent_cancel",
"arguments": { "taskId": "abc-123" }
},
"id": 5
}
```
- Respuesta `tools/call` `subagent_cancel`:
```json
{
"type": "response",
"result": {
"content": [ { "type": "text", "text": "Tarea cancelada" } ],
"isError": false,
"structuredContent": { "canceled": true }
},
"id": 5
}
```
- Petición `resources/read` de tarea:
```json
{
"type": "request",
"method": "resources/read",
"params": { "uri": "a2a://task/abc-123" },
"id": 6
}
```
- Respuesta `resources/read` de tarea (ejemplo):
```json
{
"type": "response",
"result": {
"contents": [
{ "type": "json", "json": { "taskId": "abc-123", "status": "running", "messages": [] } }
]
},
"id": 6
}
```
### Diagrama de notificaciones (SVG)
Para un diagrama detallado del flujo de suscripciones y eventos SSE, ver `docs/notifications.svg`.
## Validación de esquemas (JSON Schema)
- El esquema de subagentes está en `schemas/subagent.v0.1.json`.
- Recomendación: valida tus manifiestos con una librería de JSON Schema (p. ej., Ajv) antes de cargarlos en el bridge.
- Puntos clave a validar:
- `type` debe ser `subagent`.
- `id` único y estable.
- `a2a.agentCardUri` accesible y válido.
- `security` acorde (OAuth2, API Key, mTLS) y sin secretos hardcodeados.
- `mcpBridge.tools` declara al menos `subagent_start` (y opcionalmente `subagent_send`, `subagent_cancel`).
- `taskResourceTemplate` con el patrón `a2a://task/{taskId}`.
- Ejemplo de validación conceptual:
- Instala una librería de validación en tu pipeline y falla el build si el manifest no cumple el schema.
## Manifest avanzado (API Key + mTLS)
Ejemplo ilustrativo de un subagente que usa API Key y mTLS para llamadas JSON-RPC:
```json
{
"type": "subagent",
"id": "finanzas-core",
"name": "Finanzas Core",
"a2a": {
"agentCardUri": "https://finanzas.example.com/.well-known/agent-card.json"
},
"security": {
"auth": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key",
"value": "${FINANZAS_API_KEY}"
},
"mtls": {
"enabled": true,
"clientCert": "${CLIENT_CERT_PEM}",
"clientKey": "${CLIENT_KEY_PEM}"
}
},
"mcpBridge": {
"tools": [
{ "name": "subagent_start" },
{ "name": "subagent_send" },
{ "name": "subagent_cancel" }
]
},
"taskResourceTemplate": "a2a://task/{taskId}",
"capabilities": {
"message": {
"send": {
"endpoint": "https://finanzas.example.com/json-rpc/message/send",
"headers": { "X-API-Key": "${FINANZAS_API_KEY}" },
"mtls": true
}
},
"tasks": {
"get": {
"endpoint": "https://finanzas.example.com/json-rpc/tasks/get",
"headers": { "X-API-Key": "${FINANZAS_API_KEY}" },
"mtls": true
},
"cancel": {
"endpoint": "https://finanzas.example.com/json-rpc/tasks/cancel",
"headers": { "X-API-Key": "${FINANZAS_API_KEY}" },
"mtls": true
}
}
}
}
```
Notas:
- Usa variables de entorno para credenciales y claves mTLS; no las incluyas en el repositorio.
- Verifica certificados y claves en tiempo de arranque y reporta errores claros si faltan.
## FAQ (Preguntas frecuentes)
- ¿Por qué no veo recursos en `resources/list`?
- Asegúrate de que `ResourceTemplate.list` devuelve `{ resources: [...] }` y no un array directo.
- Confirma que hay manifiestos cargados en `SUBAGENTS` y que sus URIs `a2a-agent://{id}` se publican.
- ¿Recibo error de protocolo o encabezados inválidos?
- Incluye `Accept: application/json, text/event-stream` y permite que el servidor negocie `Mcp-Protocol-Version` si falta.
- ¿`subagent_start` no devuelve `resource_link`?
- Revisa que el agente A2A devuelva `taskId`; el bridge construye `a2a://task/{taskId}` a partir de ese valor.
- ¿Se parsea mal `subagentUri`?
- Para URIs como `a2a-agent://ventas-core`, el ID se extrae del `hostname`. Se admite `pathname` por compatibilidad.
- ¿No recibo actualizaciones de la tarea?
- Suscríbete al recurso `a2a://task/{id}` con `resources/subscribe` y mantén SSE activo para `notifications/resources/updated`.
## Pruebas
- Ejecutar: `npm test`.
- El archivo `test/mcp.spec.ts` cubre:
- `resources/list + resources/read` de subagentes.
- `tools/call` para `subagent_start`, `subagent_send`, `subagent_cancel`.
- Mock de `fetch` para simular respuestas A2A.
- Validaciones clave:
- `ResourceTemplate.list` devuelve `{ resources: [...] }`.
- `Accept` incluye `application/json, text/event-stream`.
- `resource_link.uri` correcto en `subagent_start`.
## Troubleshooting
- Error: `expected undefined to be an instance of Array` en `resources/list`.
- Causa: El callback de `ResourceTemplate.list` devuelve un array en lugar de `{ resources }`.
- Solución: Devolver `{ resources: [...] }`.
- `tools/call` `subagent_start` no devuelve `resource_link`.
- Causa: Falta `taskId` en la respuesta del agente A2A.
- Solución: Asegurar que la respuesta A2A incluya `taskId` y construir `a2a://task/{taskId}`.
- Identificador de subagente incorrecto.
- Causa: Extracción desde `pathname` cuando la URI es `a2a-agent://{id}`.
- Solución: Extraer desde `hostname` y usar `pathname` solo por compatibilidad.
- Protocolo/encabezados.
- Causa: `Accept` incorrecto o versión MCP no negociada.
- Solución: Incluir `Accept: application/json, text/event-stream`; permitir negociación de `Mcp-Protocol-Version`.
## Glosario
- ARDF: Esquema para describir recursos y capacidades de agentes.
- MCP: Protocolo para herramientas y recursos de contexto.
- A2A: Protocolo para agentes y sus interacciones JSON-RPC.
- Agent Card: Documento que describe endpoints y capacidades de un agente A2A.
- ResourceTemplate: Utilidad del SDK MCP para publicar familias de URIs.
## Diagrama de arquitectura (ASCII)
```
+--------------------+ HTTP (JSON/SSE) +-------------------------+
| Cliente MCP |-------------------------------------->| Servidor MCP (bridge) |
| (Inspector, IDE) | resources/*, tools/call | src/server.ts |
+--------------------+ +-----------+-------------+
| JSON-RPC
v
+------+----------------+
| Agente A2A remoto |
| (Agent Card: send, |
| stream, tasks/*) |
+----------------------+
```
Versión SVG (detallada): `docs/architecture.svg`
- Descubrimiento: El cliente llama `resources/list` y `resources/read`.
- Orquestación: `tools/call` ejecuta `message/send` y publica `a2a://task/{id}`.
- Actualizaciones: El servidor envía `notifications/resources/updated` a suscriptores.
## Manifest avanzado (OAuth2 + SSE)
Ejemplo ilustrativo de un subagente con autenticación Bearer y `message/stream` para SSE:
```json
{
"type": "subagent",
"id": "ventas-core",
"name": "Ventas Core",
"a2a": {
"agentCardUri": "https://ventas.example.com/.well-known/agent-card.json"
},
"security": {
"auth": {
"type": "oauth2",
"token": "${VENTAS_TOKEN}",
"scopes": ["message:send", "tasks:read", "tasks:cancel"]
}
},
"mcpBridge": {
"tools": [
{ "name": "subagent_start" },
{ "name": "subagent_send" },
{ "name": "subagent_cancel" }
]
},
"taskResourceTemplate": "a2a://task/{taskId}",
"capabilities": {
"message": {
"send": {
"endpoint": "https://ventas.example.com/json-rpc/message/send",
"headers": { "Authorization": "Bearer ${VENTAS_TOKEN}" }
},
"stream": {
"endpoint": "https://ventas.example.com/events/message/stream",
"sse": true,
"headers": { "Authorization": "Bearer ${VENTAS_TOKEN}" }
}
},
"tasks": {
"get": {
"endpoint": "https://ventas.example.com/json-rpc/tasks/get",
"headers": { "Authorization": "Bearer ${VENTAS_TOKEN}" }
},
"cancel": {
"endpoint": "https://ventas.example.com/json-rpc/tasks/cancel",
"headers": { "Authorization": "Bearer ${VENTAS_TOKEN}" }
}
}
}
}
```
Notas:
- Usa variables de entorno (`${VENTAS_TOKEN}`) para no commitear secretos.
- Si `message/stream` está presente, el servidor puede abrir SSE y llamar a `notifyTaskUpdated` con cada evento.
## Integración con clientes MCP (rápida)
- Encabezados recomendados:
- `Accept: application/json, text/event-stream`
- `Mcp-Protocol-Version: 2024-10-01` (opcional; el servidor negocia si falta)
- Endpoints típicos (ejemplo de curl):
```bash
# resources/list
curl -s -X POST \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
http://localhost:3000/mcp \
-d '{"type":"request","method":"resources/list","params":{},"id":1}'
# tools/call subagent_start
curl -s -X POST \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
http://localhost:3000/mcp \
-d '{"type":"request","method":"tools/call","params":{"name":"subagent_start","arguments":{"subagentUri":"a2a-agent://ventas-core","text":"Hola"}},"id":2}'
# resources/read de tarea
curl -s -X POST \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
http://localhost:3000/mcp \
-d '{"type":"request","method":"resources/read","params":{"uri":"a2a://task/abc-123"},"id":3}'
```
Sugerencias:
- Suscríbete a `a2a://task/{id}` (`resources/subscribe`) para recibir `resources/updated` via SSE.
- Valida siempre `structuredContent` contra `outputSchema` del tool.