A
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.
Created 10/11/2025
Updated 2 months ago
README
Repository documentation and setup instructions
ARDF Subagent Profile + MCP ↔ A2A Bridge
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/mcp
Flujo 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-Versionse negocia automáticamente si no se envía; no se requiereMcp-Session-Idpor defecto.
- El cliente DEBE incluir
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.
- Servidor MCP HTTP streamable en
- Flujo de datos:
- Descubrimiento →
resources/listyresources/readdel subagente. - Inicio →
tools/callsubagent_start→ creación y publicación dea2a://task/{taskId}. - Continuación/Cancelación →
subagent_send/subagent_cancel→ actualizaciones y notificaciones.
- Descubrimiento →
- 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
SUBAGENTSensrc/server.ts.
- Archivo de ejemplo:
- Seguridad (a nivel de Agent Card):
- Ajusta encabezados
Authorizationpara OAuth2 o API keys, o configura mTLS según elsecuritydel manifest.
- Ajusta encabezados
- Transporte MCP:
enableJsonResponse: truey sin sesión por defecto.- Requisitos del cliente:
Accept: application/json, text/event-stream. Mcp-Protocol-Versionse negocia si no se envía;Mcp-Session-Idno requerido por defecto.
- Streaming SSE (message/stream):
- Variables de entorno:
SSE_IDLE_TIMEOUT_MS(por defecto30000) tiempo de inactividad antes de abortar.SSE_RETRY_BASE_MS(por defecto1000) base del backoff exponencial.SSE_RETRY_MAX_MS(por defecto30000) máximo del backoff.SSE_MAX_ATTEMPTS(por defecto∞, en test1) límite de reconexiones del listener SSE.
- En modo test, el listener limita reconexiones para evitar bucles.
- Variables de entorno:
- Persistencia y directorio de datos:
DATA_DIR: ruta para almacenartasks.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 /healthzdevuelve{ 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 encabezadosAuthorization: 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.
- Define
- API Key:
- Define
FINANZAS_API_KEYy referencia en encabezadosX-API-Key: ${FINANZAS_API_KEY}. - Rotación: usa mecanismos de rotación segura y métricas para detectar uso indebido.
- Define
- 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.
- Evita incluir PEMs en el repositorio; usa inyección por archivo/volumen y variables como
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
3000y 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
-
- Uso rápido vía npx:
-
npx mcp-a2a-bridge(ejecuta el bin del paquete)
-
- o instala global:
npm i -g mcp-bridge-a2ay luegomcp-a2a-bridge
- o instala global:
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:
typedebe sersubagent.idúnico y estable.a2a.agentCardUriaccesible y válido.securityacorde (OAuth2, API Key, mTLS) y sin secretos hardcodeados.mcpBridge.toolsdeclara al menossubagent_start(y opcionalmentesubagent_send,subagent_cancel).taskResourceTemplatecon el patróna2a://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.listdevuelve{ resources: [...] }y no un array directo. - Confirma que hay manifiestos cargados en
SUBAGENTSy que sus URIsa2a-agent://{id}se publican.
- Asegúrate de que
- ¿Recibo error de protocolo o encabezados inválidos?
- Incluye
Accept: application/json, text/event-streamy permite que el servidor negocieMcp-Protocol-Versionsi falta.
- Incluye
- ¿
subagent_startno devuelveresource_link?- Revisa que el agente A2A devuelva
taskId; el bridge construyea2a://task/{taskId}a partir de ese valor.
- Revisa que el agente A2A devuelva
- ¿Se parsea mal
subagentUri?- Para URIs como
a2a-agent://ventas-core, el ID se extrae delhostname. Se admitepathnamepor compatibilidad.
- Para URIs como
- ¿No recibo actualizaciones de la tarea?
- Suscríbete al recurso
a2a://task/{id}conresources/subscribey mantén SSE activo paranotifications/resources/updated.
- Suscríbete al recurso
Pruebas
- Ejecutar:
npm test. - El archivo
test/mcp.spec.tscubre:resources/list + resources/readde subagentes.tools/callparasubagent_start,subagent_send,subagent_cancel.
- Mock de
fetchpara simular respuestas A2A. - Validaciones clave:
ResourceTemplate.listdevuelve{ resources: [...] }.Acceptincluyeapplication/json, text/event-stream.resource_link.uricorrecto ensubagent_start.
Troubleshooting
- Error:
expected undefined to be an instance of Arrayenresources/list.- Causa: El callback de
ResourceTemplate.listdevuelve un array en lugar de{ resources }. - Solución: Devolver
{ resources: [...] }.
- Causa: El callback de
tools/callsubagent_startno devuelveresource_link.- Causa: Falta
taskIden la respuesta del agente A2A. - Solución: Asegurar que la respuesta A2A incluya
taskIdy construira2a://task/{taskId}.
- Causa: Falta
- Identificador de subagente incorrecto.
- Causa: Extracción desde
pathnamecuando la URI esa2a-agent://{id}. - Solución: Extraer desde
hostnamey usarpathnamesolo por compatibilidad.
- Causa: Extracción desde
- Protocolo/encabezados.
- Causa:
Acceptincorrecto o versión MCP no negociada. - Solución: Incluir
Accept: application/json, text/event-stream; permitir negociación deMcp-Protocol-Version.
- Causa:
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-streamMcp-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.
Quick Setup
Installation guide for this server
Installation Command (package not published)
git clone https://github.com/MauricioPerera/ARDF-MCP-A2A
Manual Installation: Please check the README for detailed setup instructions and any additional dependencies required.
Cursor configuration (mcp.json)
{
"mcpServers": {
"mauricioperera-ardf-mcp-a2a": {
"command": "git",
"args": [
"clone",
"https://github.com/MauricioPerera/ARDF-MCP-A2A"
]
}
}
}