ARDF Subagent Profile + MCP ↔ A2A Bridge
](https://www.npmjs.com/package/mcp-bridge-a2a) ](https://www.npmjs.com/package/mcp-bridge-a2a) ](https://github.com/MauricioPerera/ARDF-MCP-A2A/releases) 
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 rápido
- Notas de implementación
- Personalización
- Visión general
- Arquitectura
- Configuración
- Ejemplos de payloads
- Diagrama de notificaciones (SVG)
- Validación de esquemas (JSON Schema)
- Manifest avanzado (API Key + mTLS)
- FAQ (Preguntas frecuentes)
- Pruebas
- Troubleshooting
- Glosario
- Diagrama de arquitectura (ASCII)
- Manifest avanzado (OAuth2 + SSE)
- 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 (typesubagent) 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 tareasa2a://task/{id}y reenvía las llamadas A2A.
Uso
npm install
npm run dev
# Cliente MCP -> http://localhost:3000/mcpFlujo sugerido con un cliente MCP:
- Leer
a2a-agent://ventas-corepara obtener el manifiesto ARDF. - Invocar
subagent_startcon un mensaje (text: "Hola"); la respuesta incluye unresource_linkaa2a://task/{id}ystructuredContentcon{ taskUri, taskId }. - Suscríbase a ese recurso (
resources/subscribe) y continúe consubagent_sendo finalice consubagent_cancel.
Uso rápido
- Arranque el servidor:
npm install
npm run dev
# Servidor MCP en http://localhost:3000/mcp- Ejemplo de invocación directa a
/mcpparasubagent_start(usandocurl):
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):
{
"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 recibirnotifications/resources/updatedy continúe consubagent_sendo finalice consubagent_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.tsusaResourceTemplatepara publicara2a-agent://{id}ya2a://task/{taskId}, enviandonotifications/resources/updatedcuando 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.listdebe devolver un objeto{ resources: [...] }(no un array directo). - Las URIs de subagente usan el esquema
a2a-agent://{id}; elidse extrae delhostnamede la URI (y se admite comopathnamepara compatibilidad). - El transporte HTTP streamable está configurado con
enableJsonResponse: truey 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.jsonpor tus propios subagentes o carga varios enSUBAGENTS. - Añade autenticación según
security(OAuth2, API key, mTLS) ajustandojsonRpc. - Si el Agent Card anuncia
message/stream, incorpora un listener SSE y utilizanotifyTaskUpdatedpara 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/updatedcuando cambian los recursosa2a://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):
# 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):
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:
- Inicia sesión:
npm login - Dry run opcional:
npm publish --dry-run(ejecuta automáticamenteprepack→npm run build) - Publica:
npm publish - 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-a2ay luegomcp-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→ ejecutadist/server.js. - Requiere Node.js >= 18.
Ejemplos de payloads
- Petición
resources/list(cliente):
{
"type": "request",
"method": "resources/list",
"params": {},
"id": 1
}- Respuesta
resources/list(servidor):
{
"type": "response",
"result": {
"resources": [
{ "uri": "a2a-agent://ventas-core", "name": "Ventas Core", "mimeType": "application/json" }
]
},
"id": 1
}- Petición
resources/readde subagente:
{
"type": "request",
"method": "resources/read",
"params": { "uri": "a2a-agent://ventas-core" },
"id": 2
}- Respuesta
resources/readde subagente:
{
"type": "response",
"result": {
"contents": [
{ "type": "text", "text": "Manifiesto ARDF", "mimeType": "application/json" },
{ "type": "json", "json": { /* contenido del manifest */ } }
]
},
"id": 2
}- Petición
tools/callsubagent_start:
{
"type": "request",
"method": "tools/call",
"params": {
"name": "subagent_start",
"arguments": { "subagentUri": "a2a-agent://ventas-core", "text": "Hola" }
},
"id": 3
}- Respuesta
tools/callsubagent_start:
{
"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/callsubagent_send:
{
"type": "request",
"method": "tools/call",
"params": {
"name": "subagent_send",
"arguments": { "taskId": "abc-123", "text": "Siguiente mensaje" }
},
"id": 4
}- Respuesta
tools/callsubagent_send:
{
"type": "response",
"result": {
"content": [ { "type": "text", "text": "Mensaje enviado" } ],
"isError": false,
"structuredContent": { "ok": true }
},
"id": 4
}- Petición
tools/callsubagent_cancel:
{
"type": "request",
"method": "tools/call",
"params": {
"name": "subagent_cancel",
"arguments": { "taskId": "abc-123" }
},
"id": 5
}- Respuesta
tools/callsubagent_cancel:
{
"type": "response",
"result": {
"content": [ { "type": "text", "text": "Tarea cancelada" } ],
"isError": false,
"structuredContent": { "canceled": true }
},
"id": 5
}- Petición
resources/readde tarea:
{
"type": "request",
"method": "resources/read",
"params": { "uri": "a2a://task/abc-123" },
"id": 6
}- Respuesta
resources/readde tarea (ejemplo):
{
"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:
{
"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_startno devuelveresource_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.tscubre:
- resources/list + resources/read de subagentes. - tools/call para subagent_start, subagent_send, subagent_cancel.
- Mock de
fetchpara 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 Arrayenresources/list.
- Causa: El callback de ResourceTemplate.list devuelve un array en lugar de { resources }. - Solución: Devolver { resources: [...] }.
tools/callsubagent_startno devuelveresource_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/listyresources/read. - Orquestación:
tools/callejecutamessage/sendy publicaa2a://task/{id}. - Actualizaciones: El servidor envía
notifications/resources/updateda suscriptores.
Manifest avanzado (OAuth2 + SSE)
Ejemplo ilustrativo de un subagente con autenticación Bearer y message/stream para SSE:
{
"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/streamestá presente, el servidor puede abrir SSE y llamar anotifyTaskUpdatedcon 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):
# 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 recibirresources/updatedvia SSE. - Valida siempre
structuredContentcontraoutputSchemadel tool.
