CtrlK
BlogDocsLog inGet started
Tessl Logo

mcp-engineering

Integrar MCP en Claude Code y Agent SDK: .mcp.json, scopes, env-var expansion, auth, permissions, plugin MCP, headless strict config, channels y custom-tool boundary.

SKILL.md
Quality
Evals
Security

Mcp Engineering

Capacidad

Disenar e implementar integraciones MCP para Claude Code y Agent SDK: elegir el scope correcto (local, project, user, plugin o managed), escribir .mcp.json y configs equivalentes sin secretos, controlar auth y permisos, usar nombres de tools mcp__server__tool, distinguir MCP tools de channels, y fijar la frontera entre MCP externo e in-process custom tools del Agent SDK.

El entregable es una decision operable: configuracion MCP, matriz de scope/auth/permissions, contrato de error tipado, policy de ejecucion headless cuando aplica y checklist verificable. La politica de reintento vive en el cliente o caller, nunca en el juicio del modelo.

Cuando usarla

  • Conectar un servidor MCP a Claude Code y decidir si vive en scope local, project (.mcp.json), user (~/.claude.json), plugin o managed.
  • Disenar un .mcp.json con HTTP/SSE/stdio/WebSocket, expansion ${ENV} o ${VAR:-default}, OAuth, headersHelper, timeout, alwaysLoad o limites de output.
  • Escribir reglas de permiso para tools MCP con forma mcp__server__tool o mcp__server__*, sin confundir auto-approval con availability.
  • Empaquetar un MCP dentro de un plugin y nombrar correctamente tools mcp__plugin_<plugin-name>_<server-name>__<tool-name>.
  • Ejecutar Claude Code en headless/CI con --bare, --mcp-config, --strict-mcp-config, --allowedTools y/o --disallowedTools.
  • Separar channels de MCP tools: un channel es un servidor MCP stdio con claude/channel que empuja eventos a una sesion abierta, no un tool normal de request/response.
  • Integrar Agent SDK con servidores MCP externos o con custom tools in-process. Los wrappers in-process se llaman createSdkMcpServer (TS) y create_sdk_mcp_server (Python); envuelven tools definidas con tool()/@tool y se pasan a mcpServers en query() [DOC] (agent-sdk/custom-tools.md).
  • Un servidor MCP devuelve errores que el modelo "interpreta" como prosa y reintenta a ciegas.
  • Hay un token literal en un archivo versionado y necesitas rotarlo y purgar el historial.
  • Necesitas decidir si una capacidad debe ser un servidor MCP o si un tool built-in ya la cubre.

No la uses cuando un tool built-in (Read, Grep, Glob, Bash, etc.) ya resuelve la necesidad local. MCP existe para conectar sistemas, APIs, fuentes externas o procesos persistentes que no caben como built-in simple.

Como construir

  1. Decide el scope. local para tu proyecto privado en ~/.claude.json; project para equipo en .mcp.json; user para todos tus proyectos en ~/.claude.json; plugin para distribucion versionada; managed para control IT. Documenta precedencia y duplicados.
  2. Elige transporte y auth. HTTP es el default remoto; SSE es legacy (prefiere HTTP cuando exista) [DOC]; stdio para procesos locales; WebSocket solo para bidireccional persistente sin OAuth. OAuth de Claude Code se completa desde /mcp; tokens estaticos van por headers/env; headersHelper solo cuando necesitas headers dinamicos y aceptas ejecutar shell.
  3. Inyecta credenciales por env-var. Referencia ${ENV_VAR} o ${ENV_VAR:-default} en .mcp.json; nunca el secreto literal. Recuerda que ${CLAUDE_PROJECT_DIR} en project/user config necesita default si se expande en command o args; plugins sustituyen ${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA} y ${CLAUDE_PROJECT_DIR}.
  4. Controla permisos por nombre MCP. Tools normales usan mcp__<server>__<tool>. Plugin-bundled tools usan mcp__plugin_<plugin-name>_<server-name>__<tool-name>. Usa allowedTools o reglas permissions.allow con wildcards acotados por servidor (mcp__github__*), y disallowedTools/deny para bloqueo. No uses bypassPermissions como sustituto de allowlists.
  5. Haz headless determinista. En CI o scripts usa claude -p ... --mcp-config <file-or-json> --strict-mcp-config --allowedTools "mcp__server__tool" y un permission-mode no-interactivo. --strict-mcp-config ignora MCPs ambientales [DOC]; --tools no afecta MCP, así que bloquea MCP con --disallowedTools "mcp__*" o no cargues servidores. Para no-prompt headless, los modos confirmados son acceptEdits/bypassPermissions/plan [DOC]; --bare y --permission-mode dontAsk no se confirman contra cli-reference.md → [SUPUESTO: verificar valor exacto contra cli-reference antes de usar].
  6. Separa channels de tools. Un channel declara capabilities.experimental["claude/channel"], emite notifications/claude/channel y requiere --channels o development allowlist. Si tiene reply, ese reply si es un tool MCP normal y se gobierna con permisos.
  7. Define la frontera Agent SDK. Usa MCP externo para procesos/servicios compartidos, remotos o reutilizables. Usa custom tools del SDK cuando la funcion vive dentro de tu app y conviene un server in-process con createSdkMcpServer; sigue exponiendose como mcp__server__tool.
  8. Disena el contrato de error tipado. Del schema MCP solo isError y structuredContent son campos estandar; la categoria y los flags de reintento van dentro de structuredContent (errorCategory auth/rate_limit/transient/fatal, isRetryable, retryAfterSeconds cuando aplica). Si tambien los expones como campos top-level, es [SUPUESTO: convencion propia del server], no contrato MCP estandar.
  9. Coloca la politica de reintento en el cliente. El cliente decide reintentar segun isRetryable y respeta retryAfterSeconds; Claude puede leer el resultado, pero no inventa backoff.
  10. Si se filtro un secreto, rotalo y purga. Rotar la credencial comprometida + reescribir historia con git filter-repo. Un .gitignore posterior NO borra lo ya commiteado.
  11. Justifica MCP frente al built-in. Antes de anadir un servidor, confirma que ningun tool built-in cubre el caso.

Lee y aplica assets/mcp-integration-policy.json para la cobertura minima de decisiones y assets/source-map.md para fuentes primarias usadas al actualizar este skill. Nota de lectura: error_contract.required_fields en esa policy enumera el payload que va dentro de structuredContent; solo isError + structuredContent son schema MCP estandar, y errorCategory/isRetryable/retryAfterSeconds como campos top-level son [SUPUESTO: convencion propia del server], no contrato MCP (ver paso 8 y el comentario del bloque de codigo).

Patron correcto

// .mcp.json - scope project, versionado para el equipo, secreto por env-var
{
  "mcpServers": {
    "billing-api": {
      "type": "http",
      "url": "${BILLING_MCP_URL:-https://billing.example.com/mcp}",
      "headers": {
        "Authorization": "Bearer ${BILLING_API_KEY}"
      },
      "timeout": 600000
    }
  }
}
# Headless deterministic run: only the MCPs in this file load,
# and unlisted tools hard-deny instead of prompting.
claude --bare -p "Summarize overdue invoices" \
  --mcp-config ./mcp.ci.json \
  --strict-mcp-config \
  --allowedTools "mcp__billing-api__list_invoices" \
  --permission-mode dontAsk
// Error contract returned by the server - typed, machine-readable.
// isError + structuredContent son schema MCP estandar; los campos
// top-level son [SUPUESTO: convencion propia del server], no MCP.
function toolError(category: ErrorCategory, retryAfter?: number) {
  return {
    isError: true,
    content: [{ type: "text", text: `Tool failed: ${category}` }],
    structuredContent: {
      ok: false,
      errorCategory: category,          // "auth" | "rate_limit" | "transient" | "fatal"
      isRetryable: category === "rate_limit" || category === "transient",
      retryAfterSeconds: retryAfter ?? null
    },
    // Espejo top-level opcional, convencion del server (no schema MCP):
    errorCategory: category,
    isRetryable: category === "rate_limit" || category === "transient",
    retryAfterSeconds: retryAfter ?? null
  };
}

// Retry policy lives in the CLIENT, not in the model's judgement.
async function callTool(req: Req) {
  const res = await invoke(req);
  if (res.isError && res.isRetryable) {
    await sleep((res.retryAfterSeconds ?? 1) * 1000);
    return invoke(req); // bounded retry, client-owned
  }
  return res;
}

Anti-patron

// ANTI: token literal en archivo versionado - fuga garantizada
{
  "mcpServers": {
    "billing": { "env": { "BILLING_API_KEY": "sk-live-9f3c...a21" } }
  }
}
// ANTI: error como string generico - el modelo debe adivinar si reintenta
function toolError() {
  return { content: "Something went wrong, please try again" };
}
// El modelo reintenta a ciegas un fatal, o no reintenta un transient.
// Y "git rm + gitignore" NO purga el secreto del historial.

Checklist de validacion

  • Scope elegido y documentado: local, project, user, plugin o managed.
  • .mcp.json o config equivalente usa transporte correcto y JSON valido.
  • Credenciales por ${ENV}/headers/OAuth/keychain; cero secretos literales en archivos versionados.
  • OAuth remoto se activa via /mcp; scopes, callback, authServerMetadataUrl o headersHelper se justifican cuando existen.
  • Permisos usan mcp__server__tool o mcp__server__*; plugin MCP usa mcp__plugin_<plugin>_<server>__<tool>.
  • Headless/CI usa --bare, --mcp-config, --strict-mcp-config y --permission-mode dontAsk (hard-deny de tools no listadas) cuando necesita determinismo.
  • Channels se tratan como MCP stdio con claude/channel, --channels, sender allowlist y permission relay solo si hay autenticacion de sender.
  • Agent SDK separa MCP externo de custom tools in-process y ambos se gobiernan con allowedTools.
  • Cada error expone categoria + isRetryable (+ retryAfterSeconds cuando aplica).
  • Politica de reintento vive en el cliente, no en el modelo.
  • MCP esta justificado frente a built-ins.
  • Ante fuga de secreto el plan es rotar + filter-repo, no solo .gitignore.

Katas y skills relacionadas

  • Katas: 06, 22.
  • Skills relacionadas: katas-mcp-structured-errors, katas-mcp-server-configuration, tool-use-design, custom-tooling-extension, plugin-builder, headless-sdk-automation, channels-integration, tool-permission-policy.

Related katas (toolkit)

  • katas-mcp-structured-errors
  • katas-mcp-server-configuration

Contract

  • Aceptación: servidor/tool MCP con scope, auth y errores tipados; sin secretos inline (usa ${ENV}). [EXPLICIT]
  • Límites: integra MCP; no implementa el servicio remoto. [EXPLICIT]
  • Casos borde: servidores con auth interactiva pueden faltar en runs headless/cron. Transporte remoto: el cliente reconecta con backoff exponencial pero cierra conexiones MCP por idle-timeout y aborta si el server tarda en el primer byte; errores de auth/not-found no se reintentan. Para servers HTTP lentos, sube el idle-timeout via env-var CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT [SUPUESTO: nombre/limites exactos por confirmar contra la doc del cliente]. [EXPLICIT]
  • Supuestos: mcp__* visibles en sesión (notebooklm/context7/playwright…) [CÓDIGO]. [SUPUESTO]
  • Trade-off: MCP amplía capacidades a cambio de superficie de confianza y latencia de red. [EXPLICIT]

Packet

Capas del packet, cargables bajo demanda (disciplina ICM: una capa por vez, nunca todas juntas): knowledge/ cuerpo de conocimiento · prompts/ prompts listos · examples/ salida de ejemplo · agents/ subagentes del packet · templates/ plantilla de output · assets/ recursos estáticos.

Repository
JaviMontano/claude-plugins
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.