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.
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.
local, project (.mcp.json), user (~/.claude.json), plugin o managed..mcp.json con HTTP/SSE/stdio/WebSocket, expansion ${ENV} o ${VAR:-default}, OAuth, headersHelper, timeout, alwaysLoad o limites de output.mcp__server__tool o mcp__server__*, sin confundir auto-approval con availability.mcp__plugin_<plugin-name>_<server-name>__<tool-name>.--bare, --mcp-config, --strict-mcp-config, --allowedTools y/o --disallowedTools.claude/channel que empuja eventos a una sesion abierta, no un tool normal de request/response.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).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.
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./mcp; tokens estaticos van por headers/env; headersHelper solo cuando necesitas headers dinamicos y aceptas ejecutar shell.${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}.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.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].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.createSdkMcpServer; sigue exponiendose como mcp__server__tool.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.isRetryable y respeta retryAfterSeconds; Claude puede leer el resultado, pero no inventa backoff.git filter-repo. Un .gitignore posterior NO borra lo ya commiteado.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).
// .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: 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.local, project, user, plugin o managed..mcp.json o config equivalente usa transporte correcto y JSON valido.${ENV}/headers/OAuth/keychain; cero secretos literales en archivos versionados./mcp; scopes, callback, authServerMetadataUrl o headersHelper se justifican cuando existen.mcp__server__tool o mcp__server__*; plugin MCP usa mcp__plugin_<plugin>_<server>__<tool>.--bare, --mcp-config, --strict-mcp-config y --permission-mode dontAsk (hard-deny de tools no listadas) cuando necesita determinismo.claude/channel, --channels, sender allowlist y permission relay solo si hay autenticacion de sender.allowedTools.isRetryable (+ retryAfterSeconds cuando aplica).filter-repo, no solo .gitignore.katas-mcp-structured-errors, katas-mcp-server-configuration, tool-use-design, custom-tooling-extension, plugin-builder, headless-sdk-automation, channels-integration, tool-permission-policy.katas-mcp-structured-errorskatas-mcp-server-configuration${ENV}). [EXPLICIT]CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT [SUPUESTO: nombre/limites exactos por confirmar contra la doc del cliente]. [EXPLICIT]mcp__* visibles en sesión (notebooklm/context7/playwright…) [CÓDIGO]. [SUPUESTO]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.
e8f986b
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.