Cap 6: MCP Servers
Qué es MCP
El Model Context Protocol (MCP) es un estándar abierto para conectar herramientas de IA con fuentes de datos externas. Con MCP, Claude Code puede leer documentos en Google Drive, actualizar tickets en Jira, interactuar con Slack, o usar tooling personalizado.
Tipos de transporte
stdio (local)
El servidor MCP se ejecuta como proceso local. Claude Code se comunica via stdin/stdout:
{
"mcpServers": {
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp@latest"]
}
}
}
HTTP (remoto)
El servidor MCP se accede via HTTP/SSE:
{
"mcpServers": {
"mi-servidor": {
"url": "https://mi-servidor.com/mcp",
"headers": {
"Authorization": "Bearer ${MCP_TOKEN}"
}
}
}
}
Configuración
Archivo .mcp.json (proyecto)
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@anthropic-ai/mcp-playwright@latest"]
},
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp@latest"]
}
}
}
Via CLI
# Agregar servidor
claude mcp add playwright npx @anthropic-ai/mcp-playwright@latest
# Listar servidores
claude mcp list
# Eliminar servidor
claude mcp remove playwright
Scopes
# Agregar a scope global (usuario)
claude mcp add --scope user context7 npx -y @upstash/context7-mcp@latest
# Agregar a scope proyecto
claude mcp add --scope project playwright npx @anthropic-ai/mcp-playwright@latest
OAuth
Algunos servidores MCP requieren autenticación OAuth:
{
"mcpServers": {
"google-drive": {
"url": "https://mcp-google.com/sse",
"oauth": {
"clientId": "${GOOGLE_CLIENT_ID}",
"scopes": ["drive.readonly"]
}
}
}
}
Servers recomendados
Context7 — Documentación actualizada
Consulta documentación de cualquier librería en tiempo real:
{
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp@latest"]
}
}
Playwright — Automatización de navegador
Testing y automatización web:
{
"playwright": {
"command": "npx",
"args": ["@anthropic-ai/mcp-playwright@latest"]
}
}
Chrome DevTools — Debugging web
Inspección y debugging de páginas web en Chrome:
{
"chrome-devtools": {
"command": "npx",
"args": ["@anthropic-ai/mcp-chrome-devtools@latest"]
}
}
Excalidraw — Diagramas
Crear y editar diagramas:
{
"excalidraw": {
"command": "npx",
"args": ["@anthropic-ai/mcp-excalidraw@latest"]
}
}
Permisos para herramientas MCP
Las herramientas MCP siguen el patrón mcp__servidor__herramienta:
{
"permissions": {
"allow": [
"mcp__context7__*",
"mcp__playwright__browser_navigate"
],
"deny": [
"mcp__*__delete_*"
]
}
}
Wildcard patterns
mcp__*— todas las herramientas de todos los servidores MCPmcp__playwright__*— todas las herramientas de Playwrightmcp__context7__query-docs— herramienta específica
MCP en sub-agents
Puedes restringir qué servidores MCP tiene disponible un agente:
<!-- .claude/agents/web-tester.md -->
---
mcpServers: ["playwright", "chrome-devtools"]
---
Diagnóstico
Si un servidor MCP no funciona:
# Verificar estado
claude mcp list
# Modo debug
claude --debug "mcp"
Crear un MCP Server desde cero
El examen pregunta sobre implementación, no solo consumo.
Estructura mínima en TypeScript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
const server = new Server(
{ name: "mi-servidor", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "get_weather",
description: "Obtiene el clima de una ciudad",
inputSchema: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
},
{
name: "search_products",
description: "Busca productos por nombre",
inputSchema: {
type: "object",
properties: { query: { type: "string" }, limit: { type: "number" } },
required: ["query"],
},
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "get_weather") {
const city = args?.city as string;
// Llamada real a API de clima
const data = await fetch(`https://wttr.in/${city}?format=3`);
const text = await data.text();
return { content: [{ type: "text", text }] };
}
if (name === "search_products") {
const { query, limit = 5 } = args as { query: string; limit?: number };
const results = await searchDB(query, limit);
return { content: [{ type: "text", text: JSON.stringify(results) }] };
}
return {
isError: true,
content: [{ type: "text", text: `Tool desconocida: ${name}` }],
};
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(console.error);
package.json mínimo:
{
"name": "mi-mcp-server",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0"
},
"devDependencies": {
"typescript": "^5.0.0"
}
}
Tipos de respuesta
| Tipo | Estructura | Cuándo usar |
|---|---|---|
| Éxito | content: [{ type: "text", text: "..." }] | Operación completada |
| Error manejado | isError: true, content: [...] | Error esperado (API caída, input inválido) |
| Throw | throw new Error(...) | Error de protocolo o bug interno |
Usar isError: true en lugar de throw permite que Claude razone sobre el error y decida qué hacer (reintentar, cambiar estrategia, informar al usuario). Con throw, Claude recibe un error de protocolo sin contexto.
Debugging de MCP servers
# Logs verbosos del protocolo MCP
MCP_DEBUG=1 claude
# Probar el server manualmente antes de integrar
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | node dist/index.js
# Ver qué servidores están conectados
claude mcp list
Errores comunes:
| Error | Causa | Solución |
|---|---|---|
| Timeout en startup | Server tarda en iniciar o crashea | Revisar logs, verificar dependencias |
| JSON malformado | Output extra en stdout (console.log) | Usar console.error para logs, nunca console.log |
| Tool no encontrada | Nombre incorrecto en tools/list | Verificar que el nombre coincide exactamente |
| Permission denied | Server sin permisos de ejecución | chmod +x dist/index.js |
Security model
Cada MCP server corre en su propio proceso aislado. Claude se comunica via protocolo MCP, no tiene acceso directo al filesystem ni variables de entorno del server.
flowchart LR
C["Claude Code"]
P["MCP Protocol\n(JSON-RPC)"]
S["Server Process"]
E["Variables de entorno"]
D["Base de datos"]
A["APIs externas"]
C <--> P <--> S
E --> S
D --> S
A --> S
style C fill:#1e3a5f,color:#fff
style S fill:#2d5a27,color:#fff
style P fill:#4a4a4a,color:#fff
- Claude NO puede leer las variables de entorno del server directamente
- Claude NO puede acceder a datos de un server que no está configurado
- El server decide qué exponer via sus tools — es la única superficie de ataque
OAuth en MCP
sequenceDiagram
participant U as Usuario
participant CC as Claude Code
participant S as MCP Server
participant O as OAuth Provider
U->>CC: Usa tool que requiere auth
CC->>U: Redirige a pantalla OAuth
U->>O: Autoriza acceso
O->>CC: Retorna token
CC->>CC: Almacena token en credentials store
CC->>S: Request con Authorization header
S->>S: Valida token
S->>CC: Respuesta de la tool
- Claude Code almacena el token, no el server
- El server recibe el token en cada request via
Authorization: Bearer <token> - Si el token expira, Claude Code inicia el flujo OAuth nuevamente
Siguiente: Settings