Pular para o conteúdo

Criação de servidores e ferramentas MCP

Este documento explica como definições de servidores MCP se tornam ferramentas mcp_* invocáveis no coding-agent, e o que os operadores devem esperar quando as configurações são inválidas, duplicadas, desabilitadas ou protegidas por autenticação.

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 configuração do servidor e validação

Seção intitulada “1) Modelo de configuração do servidor e validação”

src/mcp/types.ts define a forma de autoria usada por escritores de configuração MCP e pelo runtime:

  • stdio (padrão quando type está ausente): requer command, opcionais args, env, cwd
  • http: requer url, opcionais headers
  • sse: requer url, opcionais headers (mantido para compatibilidade)
  • campos compartilhados: enabled, timeout, auth

validateServerConfig() (src/mcp/config.ts) valida os requisitos básicos de transporte:

  • rejeita configurações que definem tanto command quanto url
  • requer command para stdio
  • requer url para http/sse
  • rejeita type desconhecido

config-writer.ts aplica esta validação para operações de adição/atualização e também valida nomes de servidores:

  • não vazio
  • máximo de 100 caracteres
  • apenas [a-zA-Z0-9_.-]
  • type omitido significa stdio. Se você pretendia HTTP/SSE mas omitiu type, command se torna obrigatório.
  • sse ainda é aceito, mas tratado como transporte HTTP internamente (createHttpTransport).
  • A validação é estrutural, não de acessibilidade: uma URL sintaticamente válida ainda pode falhar no momento da conexão.

loadAllMCPConfigs() (src/mcp/config.ts) carrega itens canônicos MCPServer via loadCapability(mcpCapability.id).

A camada de capacidades (src/capability/index.ts) então:

  1. carrega provedores em ordem de prioridade
  2. desduplicar por server.name (primeira ocorrência vence = maior prioridade)
  3. valida os itens desduplicados

Resultado: nomes de servidores duplicados entre fontes não são mesclados. Uma definição vence; duplicatas de menor prioridade são ocultadas.

O provedor de fallback dedicado em src/discovery/mcp-json.tsmcp.json e .mcp.json da raiz do projeto (baixa prioridade).

Na prática, servidores MCP também vêm de provedores de maior prioridade (por exemplo, .xcsh/... nativo e diretórios de configuração específicos de ferramentas). Orientação de autoria:

  • Prefira .xcsh/mcp.json (projeto) ou ~/.xcsh/mcp.json (usuário) para controle explícito.
  • Use mcp.json / .mcp.json na raiz quando precisar de compatibilidade como fallback.
  • Reutilizar o mesmo nome de servidor em múltiplas fontes causa ocultação por precedência, não mesclagem.

convertToLegacyConfig() (src/mcp/config.ts) mapeia MCPServer canônico para MCPServerConfig de runtime.

Comportamento principal:

  • transporte inferido como server.transport ?? (command ? "stdio" : url ? "http" : "stdio")
  • servidores desabilitados (enabled === false) são descartados antes da conexão
  • campos opcionais são preservados quando presentes

Expansão de variáveis de ambiente durante a descoberta

Seção intitulada “Expansão de variáveis de ambiente durante a descoberta”

mcp-json.ts expande placeholders de variáveis de ambiente em campos de string com expandEnvVarsDeep():

  • suporta ${VAR} e ${VAR:-default}
  • valores não resolvidos permanecem como strings literais ${VAR}

mcp-json.ts também realiza verificações de tipo em runtime para JSON de usuário e registra avisos para valores inválidos de enabled/timeout em vez de falhar completamente o arquivo.

3) Autenticação e resolução de valores em runtime

Seção intitulada “3) Autenticação e resolução de valores em runtime”

MCPManager.prepareConfig()/#resolveAuthConfig() (src/mcp/manager.ts) é a passagem final pré-conexão.

Se a configuração possui:

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

e a credencial existe no armazenamento de autenticação:

  • http/sse: injeta header Authorization: Bearer <access_token>
  • stdio: injeta variável de ambiente OAUTH_ACCESS_TOKEN

Se a busca de credencial falhar, o manager registra um aviso e continua com autenticação não resolvida.

Antes de conectar, o manager resolve cada valor de header/env via resolveConfigValue() (src/config/resolve-config-value.ts):

  • valor começando com ! => executa comando shell, usa stdout com trim (em cache)
  • caso contrário, trata o valor como nome de variável de ambiente primeiro (process.env[name]), fallback para valor literal
  • valores de comando/env não resolvidos são omitidos do mapa final de headers/env

Ressalva operacional: isso significa que um comando/chave de env de segredo digitado incorretamente pode silenciosamente remover aquela entrada de header/env, produzindo falhas 401/403 downstream ou falhas na inicialização do servidor.

4) Ponte de ferramentas: MCP -> ferramentas invocáveis pelo agente

Seção intitulada “4) Ponte de ferramentas: MCP -> ferramentas invocáveis pelo agente”

src/mcp/tool-bridge.ts converte definições de ferramentas MCP em CustomTools.

Os nomes de ferramentas são gerados como:

mcp_<sanitized_server_name>_<sanitized_tool_name>

Regras:

  • converte para minúsculas
  • caracteres não [a-z_] tornam-se _
  • underscores repetidos são colapsados
  • prefixo redundante <server>_ no nome da ferramenta é removido uma vez

Isso evita muitas colisões, mas não todas. Nomes brutos diferentes ainda podem ser sanitizados para o mesmo identificador (por exemplo my-server e my.server são sanitizados de forma similar), e a inserção no registro é última-escrita-vence.

convertSchema() mantém o JSON Schema do MCP praticamente como está, mas corrige esquemas de objetos sem properties com {} para compatibilidade com provedores.

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

  • chama MCP tools/call
  • achata conteúdo MCP em texto exibível
  • retorna detalhes estruturados (serverName, mcpToolName, metadados do provedor)
  • mapeia isError reportado pelo servidor para resultado de texto Error: ...
  • mapeia falhas de transporte/runtime lançadas para MCP error: ...
  • preserva semântica de cancelamento traduzindo AbortError em ToolAbortError

5) Ciclo de vida do operador: adicionar/editar/remover e atualizações ao vivo

Seção intitulada “5) Ciclo de vida do operador: adicionar/editar/remover e atualizações ao vivo”

O modo interativo expõe /mcp em src/modes/controllers/mcp-command-controller.ts.

Operações suportadas:

  • add (assistente ou adição rápida)
  • remove / rm
  • enable / disable
  • test
  • reauth / unauth
  • reload

As escritas de configuração são atômicas (writeMCPConfigFile: arquivo temporário + renomeação).

Após as alterações, o controller chama #reloadMCP():

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

refreshMCPTools() substitui todas as entradas mcp_ do registro e imediatamente reativa o conjunto mais recente de ferramentas MCP, então as alterações entram em vigor sem reiniciar a sessão.

  • Modo interativo/TUI: /mcp fornece UX dentro do aplicativo (assistente, fluxo OAuth, texto de status de conexão, revinculação imediata em runtime).
  • Integração SDK/headless: discoverAndLoadMCPTools() (src/mcp/loader.ts) retorna ferramentas carregadas + erros por servidor; sem UX do comando /mcp.

Strings de erro comuns que usuários/operadores veem:

  • falhas de validação ao adicionar/atualizar:
    • Invalid server config: ...
    • Server "<name>" already exists in <path>
  • problemas de argumentos na adição rápida:
    • Use either --url or -- <command...>, not both.
    • --token requires --url (HTTP/SSE transport).
  • falhas de conexão/teste:
    • Failed to connect to "<name>": <message>
    • texto de ajuda sobre timeout sugere aumentar o tempo limite
    • texto de ajuda de autenticação para 401/403
  • fluxos de auth/OAuth:
    • Authentication required ... OAuth endpoints could not be discovered
    • OAuth flow timed out. Please try again.
    • OAuth authentication failed: ...
  • uso de servidor desabilitado:
    • Server "<name>" is disabled. Run /mcp enable <name> first.

JSON de fonte com problemas na descoberta é geralmente tratado como avisos/logs; os caminhos do config-writer lançam erros explícitos.

Para autoria MCP robusta neste codebase:

  1. Mantenha nomes de servidores globalmente únicos em todas as fontes de configuração compatíveis com MCP.
  2. Prefira nomes alfanuméricos/underscore para evitar colisões de nomes sanitizados nos nomes de ferramentas mcp_* gerados.
  3. Use type explícito para evitar padrões stdio acidentais.
  4. Trate enabled: false como desligamento definitivo: o servidor é omitido do conjunto de conexão em runtime.
  5. Para configurações OAuth, armazene um credentialId válido; caso contrário, a injeção de autenticação é ignorada.
  6. Se usar resolução de segredos baseada em comando (!cmd), verifique se a saída do comando é estável e não vazia.