Ir al contenido

Autoría de servidores y herramientas MCP

Este documento explica cómo las definiciones de servidores MCP se convierten en herramientas mcp_* invocables en coding-agent, y qué deben esperar los operadores cuando las configuraciones son inválidas, duplicadas, deshabilitadas o protegidas por autenticación.

Config sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)
-> discovery providers normalize to canonical MCPServer
-> capability loader dedupes by server name (higher provider priority wins)
-> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false
-> MCPManager connects/listTools (with auth/header/env resolution)
-> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>
-> AgentSession.refreshMCPTools replaces live MCP tools immediately

1) Modelo de configuración del servidor y validación

Sección titulada «1) Modelo de configuración del servidor y validación»

src/mcp/types.ts define la forma de autoría utilizada por los escritores de configuración MCP y el entorno de ejecución:

  • stdio (valor por defecto cuando type está ausente): requiere command, opcionales args, env, cwd
  • http: requiere url, opcionales headers
  • sse: requiere url, opcionales headers (mantenido por compatibilidad)
  • campos compartidos: enabled, timeout, auth

validateServerConfig() (src/mcp/config.ts) aplica las reglas básicas de transporte:

  • rechaza configuraciones que definen tanto command como url
  • requiere command para stdio
  • requiere url para http/sse
  • rechaza type desconocido

config-writer.ts aplica esta validación para operaciones de agregar/actualizar y también valida los nombres de servidor:

  • no vacío
  • máximo 100 caracteres
  • solo [a-zA-Z0-9_.-]
  • type omitido significa stdio. Si su intención era HTTP/SSE pero omitió type, command se vuelve obligatorio.
  • sse todavía se acepta pero se trata internamente como transporte HTTP (createHttpTransport).
  • La validación es estructural, no de accesibilidad: una URL sintácticamente válida puede fallar al momento de la conexión.

2) Descubrimiento, normalización y precedencia

Sección titulada «2) Descubrimiento, normalización y precedencia»

loadAllMCPConfigs() (src/mcp/config.ts) carga elementos canónicos MCPServer mediante loadCapability(mcpCapability.id).

La capa de capacidades (src/capability/index.ts) entonces:

  1. carga proveedores en orden de prioridad
  2. elimina duplicados por server.name (el primero gana = mayor prioridad)
  3. valida los elementos sin duplicados

Resultado: los nombres de servidor duplicados entre distintas fuentes no se fusionan. Una definición gana; los duplicados de menor prioridad quedan ocultos.

El proveedor de respaldo dedicado en src/discovery/mcp-json.ts lee mcp.json y .mcp.json de la raíz del proyecto (baja prioridad).

En la práctica, los servidores MCP también provienen de proveedores de mayor prioridad (por ejemplo, directorios nativos .xcsh/... y directorios de configuración específicos de herramientas). Guía de autoría:

  • Prefiera .xcsh/mcp.json (proyecto) o ~/.xcsh/mcp.json (usuario) para control explícito.
  • Use mcp.json / .mcp.json en la raíz cuando necesite compatibilidad de respaldo.
  • Reutilizar el mismo nombre de servidor en múltiples fuentes causa ocultamiento por precedencia, no fusión.

convertToLegacyConfig() (src/mcp/config.ts) mapea el MCPServer canónico a MCPServerConfig de tiempo de ejecución.

Comportamiento clave:

  • transporte inferido como server.transport ?? (command ? "stdio" : url ? "http" : "stdio")
  • los servidores deshabilitados (enabled === false) se eliminan antes de la conexión
  • los campos opcionales se preservan cuando están presentes

Expansión de variables de entorno durante el descubrimiento

Sección titulada «Expansión de variables de entorno durante el descubrimiento»

mcp-json.ts expande marcadores de posición de variables de entorno en campos de texto con expandEnvVarsDeep():

  • soporta ${VAR} y ${VAR:-default}
  • los valores no resueltos permanecen como cadenas literales ${VAR}

mcp-json.ts también realiza verificaciones de tipo en tiempo de ejecución para JSON de usuario y registra advertencias para valores inválidos de enabled/timeout en lugar de hacer fallar todo el archivo.

3) Autenticación y resolución de valores en tiempo de ejecución

Sección titulada «3) Autenticación y resolución de valores en tiempo de ejecución»

MCPManager.prepareConfig()/#resolveAuthConfig() (src/mcp/manager.ts) es el paso final antes de la conexión.

Si la configuración tiene:

auth: { type: "oauth", credentialId: "..." }

y la credencial existe en el almacenamiento de autenticación:

  • http/sse: inyecta el encabezado Authorization: Bearer <access_token>
  • stdio: inyecta la variable de entorno OAUTH_ACCESS_TOKEN

Si la búsqueda de credenciales falla, el manager registra una advertencia y continúa con la autenticación sin resolver.

Resolución de valores de encabezados/variables de entorno

Sección titulada «Resolución de valores de encabezados/variables de entorno»

Antes de conectar, el manager resuelve cada valor de encabezado/variable de entorno mediante resolveConfigValue() (src/config/resolve-config-value.ts):

  • valor que comienza con ! => ejecuta comando de shell, usa stdout recortado (en caché)
  • de lo contrario, trata el valor como nombre de variable de entorno primero (process.env[name]), con respaldo al valor literal
  • los valores de comando/variable de entorno no resueltos se omiten del mapa final de encabezados/variables de entorno

Advertencia operativa: esto significa que un comando secreto o clave de variable de entorno mal escrita puede eliminar silenciosamente esa entrada de encabezado/variable de entorno, produciendo errores 401/403 o fallos de inicio del servidor aguas abajo.

4) Puente de herramientas: MCP -> herramientas invocables por el agente

Sección titulada «4) Puente de herramientas: MCP -> herramientas invocables por el agente»

src/mcp/tool-bridge.ts convierte las definiciones de herramientas MCP en CustomTools.

Los nombres de herramientas se generan como:

mcp_<sanitized_server_name>_<sanitized_tool_name>

Reglas:

  • se convierte a minúsculas
  • caracteres que no son [a-z_] se convierten en _
  • guiones bajos repetidos se colapsan
  • el prefijo redundante <server>_ en el nombre de la herramienta se elimina una vez

Esto evita muchas colisiones, pero no todas. Diferentes nombres originales pueden sanitizarse al mismo identificador (por ejemplo my-server y my.server se sanitizan de forma similar), y la inserción en el registro es último-en-escribir-gana.

convertSchema() mantiene el JSON Schema de MCP mayormente tal cual, pero corrige esquemas de objetos que carecen de properties con {} para compatibilidad con proveedores.

MCPTool.execute() / DeferredMCPTool.execute():

  • llama a tools/call de MCP
  • aplana el contenido MCP en texto visualizable
  • devuelve detalles estructurados (serverName, mcpToolName, metadatos del proveedor)
  • mapea isError reportado por el servidor a resultado de texto Error: ...
  • mapea fallos de transporte/tiempo de ejecución lanzados a MCP error: ...
  • preserva la semántica de cancelación traduciendo AbortError en ToolAbortError

5) Ciclo de vida del operador: agregar/editar/eliminar y actualizaciones en vivo

Sección titulada «5) Ciclo de vida del operador: agregar/editar/eliminar y actualizaciones en vivo»

El modo interactivo expone /mcp en src/modes/controllers/mcp-command-controller.ts.

Operaciones soportadas:

  • add (asistente o adición rápida)
  • remove / rm
  • enable / disable
  • test
  • reauth / unauth
  • reload

Las escrituras de configuración son atómicas (writeMCPConfigFile: archivo temporal + renombrado).

Después de los cambios, el controlador llama a #reloadMCP():

  1. mcpManager.disconnectAll()
  2. mcpManager.discoverAndConnect()
  3. session.refreshMCPTools(mcpManager.getTools())

refreshMCPTools() reemplaza todas las entradas mcp_ del registro y reactiva inmediatamente el conjunto más reciente de herramientas MCP, de modo que los cambios toman efecto sin reiniciar la sesión.

  • Modo interactivo/TUI: /mcp proporciona UX dentro de la aplicación (asistente, flujo OAuth, texto de estado de conexión, reasociación inmediata en tiempo de ejecución).
  • Integración SDK/headless: discoverAndLoadMCPTools() (src/mcp/loader.ts) devuelve herramientas cargadas + errores por servidor; sin UX del comando /mcp.

6) Superficies de error visibles para el usuario

Sección titulada «6) Superficies de error visibles para el usuario»

Cadenas de error comunes que ven los usuarios/operadores:

  • fallos de validación al agregar/actualizar:
    • Invalid server config: ...
    • Server "<name>" already exists in <path>
  • problemas con argumentos de adición rápida:
    • Use either --url or -- <command...>, not both.
    • --token requires --url (HTTP/SSE transport).
  • fallos de conexión/prueba:
    • Failed to connect to "<name>": <message>
    • texto de ayuda sobre timeout sugiere aumentar el tiempo de espera
    • texto de ayuda de autenticación para 401/403
  • flujos de autenticación/OAuth:
    • Authentication required ... OAuth endpoints could not be discovered
    • OAuth flow timed out. Please try again.
    • OAuth authentication failed: ...
  • uso de servidor deshabilitado:
    • Server "<name>" is disabled. Run /mcp enable <name> first.

El JSON de fuente incorrecto en el descubrimiento generalmente se maneja como advertencias/registros; las rutas de config-writer lanzan errores explícitos.

Para una autoría MCP robusta en esta base de código:

  1. Mantenga los nombres de servidor globalmente únicos entre todas las fuentes de configuración compatibles con MCP.
  2. Prefiera nombres alfanuméricos/con guión bajo para evitar colisiones de nombres sanitizados en los nombres de herramientas mcp_* generados.
  3. Use type explícito para evitar valores por defecto accidentales de stdio.
  4. Trate enabled: false como apagado total: el servidor se omite del conjunto de conexión en tiempo de ejecución.
  5. Para configuraciones OAuth, almacene un credentialId válido; de lo contrario la inyección de autenticación se omite.
  6. Si usa resolución de secretos basada en comandos (!cmd), verifique que la salida del comando sea estable y no vacía.