Configurar MCP servers (project vs user scope, env-var expansion) y disenar contratos de error tipados con categoria y retryable.
Integrar servidores MCP de forma productiva: elegir el scope (project vs user), inyectar credenciales por expansión de variables de entorno y diseñar contratos de error tipados (isError, errorCategory, isRetryable, retryAfterSeconds) para que cliente y modelo sepan reintentar sin adivinar. [DOC]
El entregable es una config versionable por el equipo + un contrato de error consumible mecánicamente: la política de reintento vive en el código del cliente, nunca en el juicio del modelo. [INFERENCIA]
.mcp.json (compartida, versionada) o ~/.claude.json (personal). [DOC]Anti-scope (no la uses cuando):
Inputs: nombre del servidor + comando/args; alcance (equipo vs personal); nombres de env-vars (nunca valores); categorías de error a cubrir (auth/rate_limit/transient/fatal); límites de retry del cliente. [DOC]
Outputs: (1) bloque mcpServers con ${ENV_VAR} y cero literales; (2) función toolError(...) tipada; (3) lazo de reintento client-owned, acotado; (4) plan de remediación si hubo fuga. Si el entregable es JSON, debe pasar scripts/check.sh. [CÓDIGO]
.mcp.json versionado. Personal del dev → ~/.claude.json fuera del repo. Criterio: ¿quién debe heredar este servidor? [DOC]${ENV_VAR}; nunca el literal. El valor real vive en el entorno del proceso. [CÓDIGO]isError, errorCategory (auth/rate_limit/transient/fatal), isRetryable (bool) y retryAfterSeconds cuando aplica. El modelo lee campos, no infiere prosa. [CÓDIGO]isRetryable y respeta retryAfterSeconds; backoff acotado, no prosa del modelo. [CÓDIGO]git filter-repo. Un .gitignore posterior NO borra lo ya commiteado. [DOC]Decisión por defecto; el cliente puede endurecerla pero no relajarla. [INFERENCIA]
errorCategory | isRetryable | retryAfterSeconds | Razón |
|---|---|---|---|
auth | false | n/a | Reintentar no arregla credencial inválida; escala a rotación. |
rate_limit | true | server-provided | Respeta Retry-After; backoff si falta. |
transient | true | null → default 1s | Fallo de red/temporal; retry acotado. |
fatal | false | n/a | Error de contrato/lógica; reintentar amplifica el daño. |
Usa los assets de assets/ como contrato de validación: [CONFIG]
mcp-engineering-contract.json: campos obligatorios del reporte.scope-policy.json: team/personal ↔ .mcp.json / ~/.claude.json.secret-policy.json: formato ${ENV_VAR}, detección de literales, remediación por rotación + git filter-repo.typed-error-policy.json: categorías, retryability y retryAfterSeconds.client-retry-policy.json: límites de retry propiedad del cliente.evidence-policy.json: evidencia mínima para certificar.Cuando el entregable sea JSON, valida offline con scripts/validate_mcp_engineering.py. Para la smoke completa ejecuta scripts/check.sh, que acepta fixtures válidos y rechaza mutaciones inválidas. [CÓDIGO]
// .mcp.json — versionado para el equipo, secreto por env-var
{
"mcpServers": {
"billing": {
"command": "node",
"args": ["./servers/billing/index.js"],
"env": { "BILLING_API_KEY": "${BILLING_API_KEY}" }
}
}
}// Error contract returned by the server — typed, machine-readable
function toolError(category: ErrorCategory, retryAfter?: number) {
return {
isError: true,
errorCategory: category, // "auth" | "rate_limit" | "transient" | "fatal"
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, maxRetries = 3) {
for (let attempt = 0; ; attempt++) {
const res = await invoke(req);
if (!res.isError || !res.isRetryable || attempt >= maxRetries) return res;
await sleep((res.retryAfterSeconds ?? 2 ** attempt) * 1000); // bounded, client-owned
}
}// ANTI: token literal en archivo versionado — fuga garantizada
{
"mcpServers": {
"billing": { "env": { "BILLING_API_KEY": "sk-live-9f3c...a21" } }
}
}// ANTI: error como string genérico — 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.rate_limit sin Retry-After: marca isRetryable: true con retryAfterSeconds: null; el cliente aplica backoff exponencial acotado. [INFERENCIA]isError con content útil: prioriza isError; nunca infieras éxito de un payload presente. [SUPUESTO]~/.claude.json (personal) hace shadowing del .mcp.json del repo; documenta cuál gana para evitar drift. [SUPUESTO]maxRetries); sin tope, un transient persistente cuelga al agente. [INFERENCIA]Detente y rehaz si: [INFERENCIA]
sk-, ghp_, base64 largo) en un archivo versionado → conviértelo a ${ENV_VAR} y dispara remediación.errorCategory o isRetryable → el contrato es inválido, no lo certifiques..gitignore/git rm → insuficiente, exige rotación + filter-repo..mcp.json equipo / ~/.claude.json personal). [DOC]${ENV}; cero secretos literales en archivos versionados. [CÓDIGO]errorCategory + isRetryable (+ retryAfterSeconds si aplica). [CÓDIGO]maxRetries), no en el modelo. [CÓDIGO]filter-repo, no solo .gitignore. [DOC]scripts/check.sh cuando se requiere evidencia offline. [CÓDIGO]katas-mcp-structured-errors, katas-mcp-server-configuration, tool-use-design, custom-tooling-extension.fdad39c
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.