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.JaviMontano/jm-adk-beta
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.