Ir al contenido
Volver a Escritos

Construyendo servidores MCP personalizados: Extiende agentes de IA con herramientas específicas de dominio

Byte Smith · · 15 min de lectura

La mayoría de los equipos que usan agentes de codificación con IA comienzan con las herramientas que vienen incluidas. Eso te lleva sorprendentemente lejos: acceso a archivos, comandos de terminal, búsqueda web, consulta de documentación. Pero eventualmente chocas con el muro. Tus agentes no pueden consultar tus bases de datos internas. No pueden verificar tu sistema de tickets. No pueden activar tu pipeline de despliegue o buscar contexto de clientes desde tu API propietaria.

Ese es el vacío que llenan los servidores MCP personalizados. En lugar de esperar a que alguien más construya una integración para tus sistemas internos, la construyes tú mismo. Defines las herramientas, controlas los permisos, aplicas los límites de seguridad y le das a tus agentes exactamente las capacidades que necesitan para ser útiles dentro de los flujos de trabajo específicos de tu organización.

Esto no es teórico. Los equipos que conectan agentes a herramientas internas ven un cambio cualitativo en lo que esos agentes pueden lograr. Un agente que puede leer el esquema de tu base de datos, consultar despliegues recientes y verificar el estado de un ticket de Jira es fundamentalmente más útil que uno que solo puede ver archivos en disco. El valor compuesto viene de dar a los agentes acceso al mismo contexto que tus ingenieros usan al tomar decisiones.

Si ya estás familiarizado con cómo funciona MCP a alto nivel desde nuestra visión general de agentes de codificación con IA y MCP, esta guía va más profundo. Cubre decisiones de arquitectura, patrones de seguridad, preocupaciones de producción y una implementación de referencia que puedes bifurcar y adaptar.

Resumen de arquitectura MCP

Antes de construir, ayuda tener la arquitectura clara. MCP sigue un modelo cliente-servidor con tres capas:

  • Host: la aplicación con la que el usuario interactúa (un IDE como VS Code, Claude Desktop o un ejecutor de agentes personalizado)
  • MCP Client: vive dentro del host, gestiona conexiones a uno o más servidores MCP
  • MCP Server: expone capacidades al cliente, se conecta a tus sistemas backend

El servidor es donde vive tu código. Recibe solicitudes del cliente, ejecuta lógica contra tus herramientas y fuentes de datos, y devuelve resultados estructurados.

Opciones de transporte

MCP soporta dos mecanismos de transporte principales:

  • stdio: el servidor se ejecuta como un subproceso del host. Simple, sin configuración de red, ideal para desarrollo local y configuraciones de un solo usuario.
  • Streamable HTTP (anteriormente HTTP/SSE): el servidor se ejecuta como un servicio HTTP independiente. Requerido para despliegues multi-usuario, acceso remoto y entornos de producción donde necesitas autenticación adecuada y balanceo de carga.

Para desarrollo y pruebas locales, stdio es el camino más rápido. Para cualquier cosa que planees desplegar a un equipo, quieres transporte HTTP con autenticación adecuada delante.

Primitivas principales

Los servidores MCP exponen tres tipos de capacidades:

  • Tools: funciones que el agente puede llamar (consultar una base de datos, crear un ticket, ejecutar una compilación). Estos son la primitiva más común y más poderosa.
  • Resources: datos de solo lectura a los que el agente puede acceder (esquemas de base de datos, archivos de configuración, documentación). Piensa en estos como contexto que el agente puede obtener antes de decidir qué hacer.
  • Prompts: plantillas de prompts reutilizables que guían al agente hacia flujos de trabajo específicos.

Para la mayoría de proyectos de servidor personalizado, pasarás el 90% de tu tiempo definiendo herramientas y los límites de seguridad alrededor de ellas.

Si quieres experiencia práctica conectándote a servidores MCP existentes antes de construir el tuyo propio, comienza con nuestro tutorial sobre configurar agentes de codificación con MCP o extender GitHub Copilot con herramientas MCP.

Elegir tu patrón de servidor MCP

No todo servidor MCP necesita el mismo nivel de complejidad. El patrón correcto depende de lo que estás exponiendo y el perfil de riesgo de esas operaciones.

Acceso a datos de solo lectura

Este es el patrón más simple y de menor riesgo. Tu servidor expone herramientas que consultan bases de datos, obtienen respuestas de API o buscan documentación, pero nunca modifican nada.

Ejemplos:

  • consultar una réplica de solo lectura de tu base de datos de producción
  • buscar documentación interna o bases de conocimiento
  • obtener estado de despliegue de tu sistema CI/CD
  • buscar contexto de clientes desde una API de CRM

Los servidores de solo lectura son el mejor lugar para empezar. Entregan valor inmediato con riesgo mínimo, porque el peor caso es que el agente vea datos que no debería, lo cual controlas a través de autorización y filtrado de salida.

Operaciones con estado

Una vez que vas más allá de lecturas, las apuestas aumentan. Las operaciones con estado incluyen crear tickets, actualizar registros, activar despliegues o modificar configuración.

Estas requieren un diseño cuidadoso de permisos:

  • lista explícita de operaciones permitidas
  • flujos de confirmación para acciones destructivas
  • registro de auditoría para cada operación de escritura
  • capacidades de rollback donde sea posible

El principio clave es que tu servidor MCP debería aplicar límites más estrictos de los que aplicarías para un usuario humano. Un agente puede cometer errores más rápido y en mayor volumen que una persona. Tu servidor es la capa de aplicación.

Composición de múltiples herramientas

Los servidores MCP más útiles exponen un conjunto cohesivo de herramientas relacionadas, típicamente de cinco a diez, que trabajan juntas como un kit de herramientas. Un servidor de base de datos podría exponer list_tables, get_schema, query_database y explain_query. Un servidor de gestión de proyectos podría exponer search_tickets, get_ticket, create_ticket, update_status y add_comment.

La composición importa porque los agentes razonan mejor cuando las herramientas están lógicamente agrupadas y bien documentadas. Un servidor con tres herramientas enfocadas es más útil que uno con treinta vagamente relacionadas.

Marco de decisión

Comienza con solo lectura. Demuestra el valor. Luego agrega operaciones de escritura una a la vez, cada una con su propia verificación de permisos y rastro de auditoría. No se trata de ser cauteloso por el hecho de serlo. Se trata de construir confianza en el sistema incrementalmente para que puedas moverte más rápido después.

Arquitectura de seguridad para servidores MCP

La seguridad no es una preocupación adicional para servidores MCP. Es la restricción de diseño principal. Tu servidor MCP se sitúa entre un agente de IA y tus sistemas internos. Si el servidor es permisivo, el agente hereda esa permisividad, y el radio de impacto de un error o ataque escala en consecuencia.

Autenticación

Cómo el servidor verifica quién está haciendo una solicitud:

  • API keys: la opción más simple. Buena para herramientas internas, configuraciones de un solo inquilino y desarrollo. Almacena las claves en variables de entorno, nunca en código. Rota regularmente.
  • OAuth2: la elección correcta cuando tu servidor MCP necesita actuar en nombre de usuarios específicos con sus permisos. Más complejo de implementar pero necesario para despliegues de producción multi-usuario.
  • mTLS: TLS mutuo para comunicación servicio a servicio. Usa esto cuando tu servidor MCP es llamado por otros servicios en lugar de directamente por hosts de agentes.

Para la mayoría de los equipos que están comenzando, la autenticación por API key con alcance de permisos por clave es el balance correcto de seguridad y simplicidad.

Autorización

La autenticación te dice quién está llamando. La autorización te dice qué tienen permitido hacer.

Una autorización efectiva de servidor MCP incluye:

  • Permisos por herramienta: no todo usuario autenticado debería tener acceso a cada herramienta. Una API key de solo lectura no debería poder llamar herramientas de escritura.
  • Acceso basado en roles: mapear API keys o tokens OAuth a roles (lector, escritor, administrador) y aplicar verificaciones de roles en cada manejador de herramienta.
  • Aislamiento de inquilinos: si tu servidor sirve a múltiples equipos o clientes, asegura que las consultas estén limitadas a los datos del llamante. Nunca confíes en que el agente agregue la cláusula WHERE correcta.

Validación de entrada

Cada entrada de un agente debería tratarse como no confiable. Los agentes pueden alucinar parámetros, y un prompt comprometido puede intentar ataques de inyección.

import { z } from "zod";

const QueryToolSchema = z.object({
  table: z
    .string()
    .refine((t) => ALLOWED_TABLES.includes(t), {
      message: "Table not in allowlist",
    }),
  columns: z
    .array(z.string())
    .max(20, "Too many columns requested"),
  where: z
    .record(z.string(), z.union([z.string(), z.number()]))
    .optional(),
  limit: z
    .number()
    .int()
    .min(1)
    .max(100)
    .default(25),
});

Patrones clave de validación:

  • aplicación de esquema en cada entrada de herramienta usando Zod o una biblioteca similar
  • prevención de inyección SQL a través de consultas parametrizadas (nunca concatenación de strings)
  • sanitización de parámetros para eliminar o rechazar patrones peligrosos
  • lista de permitidos de tablas y columnas para prevenir acceso a datos sensibles

Filtrado de salida

Lo que regresa de tus sistemas backend puede contener datos que el agente no debería ver o devolver al usuario.

  • Redacción de PII: enmascarar direcciones de correo electrónico, números de teléfono, números de seguro social y otros campos sensibles antes de devolver resultados
  • Exclusión de columnas sensibles: definir columnas que nunca se devuelven independientemente de la consulta
  • Seguridad a nivel de fila: filtrar resultados basándose en los permisos del llamante

Esto importa porque las respuestas de herramientas MCP se convierten en parte del contexto del agente, y ese contexto puede ser visible para el usuario final o registrado de maneras que no controlas completamente.

Limitación de tasa

Los agentes pueden hacer solicitudes mucho más rápido que los humanos. Sin límites de tasa, un agente mal configurado puede bombardear tu base de datos o agotar tus cuotas de API en minutos.

Implementa límites de tasa por usuario y por herramienta. Devuelve errores claros y estructurados cuando se alcancen los límites para que el agente pueda retroceder inteligentemente en lugar de reintentar en un ciclo cerrado.

Para un tratamiento más profundo de los patrones de seguridad para sistemas conectados a agentes, consulta nuestra guía para asegurar aplicaciones de IA agéntica y mejores prácticas de seguridad de API para aplicaciones integradas con IA.

Si tu servidor MCP se usa en flujos de trabajo de agentes de codificación, también consulta nuestra guía para asegurar pipelines de agentes de codificación con IA, que cubre detección, controles de políticas y puertas de revisión por niveles de riesgo a nivel de CI/CD.

Preocupaciones de producción

Hacer que una herramienta funcione localmente es la parte fácil. Hacerla confiable, observable y mantenible en producción es donde ocurre la ingeniería real.

Manejo de errores

Los agentes no pueden leer trazas de pila. Necesitan respuestas de error estructuradas que describan qué salió mal y qué, si algo, se puede hacer al respecto.

import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";

function handleToolError(error: unknown): never {
  if (error instanceof ValidationError) {
    throw new McpError(
      ErrorCode.InvalidParams,
      `Invalid input: ${error.message}. Check the tool schema for valid parameters.`
    );
  }

  if (error instanceof AuthorizationError) {
    throw new McpError(
      ErrorCode.InvalidRequest,
      `Permission denied: your API key does not have access to this operation.`
    );
  }

  if (error instanceof QueryTimeoutError) {
    throw new McpError(
      ErrorCode.InternalError,
      `Query timed out after ${error.timeoutMs}ms. Try a more specific query with fewer results.`
    );
  }

  // Never expose internal details
  throw new McpError(
    ErrorCode.InternalError,
    "An unexpected error occurred. Contact the platform team if this persists."
  );
}

Los mensajes de error deberían ayudar al agente a auto-corregirse. “Invalid table name” es mejor que “Error.” “Table not in allowlist, valid tables are: users, projects, deployments” es mejor aún.

Registro y rastros de auditoría

Cada invocación de herramienta MCP debería producir una entrada de registro estructurada que capture:

  • marca de tiempo
  • identidad autenticada (ID de API key, usuario o servicio)
  • nombre de herramienta y parámetros de entrada
  • resumen de respuesta (éxito/fallo, conteo de filas, duración)
  • cualquier fallo de validación u operaciones bloqueadas

Esto no es opcional para uso en producción. Cuando algo salga mal, y lo hará, necesitas reconstruir exactamente lo que el agente pidió y lo que el servidor devolvió. Trata los registros del servidor MCP con el mismo rigor que aplicas a los registros de API gateway o rastros de auditoría de base de datos.

Pruebas

Los servidores MCP necesitan tres capas de pruebas:

  • Pruebas unitarias para manejadores de herramientas individuales: dadas estas entradas, ¿el manejador devuelve la salida correcta y aplica las restricciones correctas?
  • Pruebas de integración con un cliente MCP simulado: ¿el ciclo completo de solicitud/respuesta funciona correctamente, incluyendo autenticación, validación y manejo de errores?
  • Pruebas de seguridad: ¿el servidor rechaza correctamente solicitudes no autorizadas, bloquea intentos de inyección SQL y enmascara datos sensibles?

El SDK de MCP proporciona utilidades para crear clientes de prueba, lo que hace que las pruebas de integración sean sencillas.

Despliegue

Conteneriza tu servidor MCP desde el principio. Una compilación Docker multi-etapa mantiene la imagen pequeña, y Docker Compose te permite ejecutar el servidor junto con sus dependencias (base de datos, caché, etc.) para desarrollo local.

Los despliegues de producción deberían incluir:

  • endpoints de health check
  • manejo de apagado graceful
  • configuración basada en entorno (sin secretos hardcodeados)
  • límites de recursos (memoria, CPU)
  • escalado horizontal para despliegues de transporte HTTP

Versionado

A medida que tu servidor MCP evoluciona, agregarás herramientas, cambiarás esquemas y modificarás comportamiento. Los agentes que dependen de tu servidor se romperán si cambias esquemas de herramientas sin coordinación.

Mejores prácticas:

  • tratar los esquemas de herramientas como un contrato de API pública
  • agregar nuevas herramientas en lugar de modificar las existentes cuando sea posible
  • usar versionado semántico para tu servidor
  • documentar cambios que rompen compatibilidad y proporcionar guía de migración
  • considerar ejecutar múltiples versiones del servidor en paralelo durante transiciones

La implementación de referencia mcp-enterprise-starter

Para hacer todo esto concreto, construimos mcp-enterprise-starter, una implementación de referencia que demuestra cada patrón discutido en esta guía.

Arquitectura

El repositorio está estructurado como un servidor MCP en TypeScript que se conecta a PostgreSQL:

mcp-enterprise-starter/
  src/
    server.ts              # MCP server setup and transport config
    tools/
      query-database.ts    # Safe database query tool
      list-tables.ts       # Table listing tool
      get-schema.ts        # Schema inspection tool
    resources/
      schema.ts            # Database schema as MCP resource
    middleware/
      auth.ts              # API key authentication
      validation.ts        # Input validation and sanitization
      rate-limit.ts        # Per-user rate limiting
    utils/
      db.ts                # PostgreSQL connection pool
      sanitize.ts          # Query sanitization helpers
      errors.ts            # Structured error types
  tests/
  docker-compose.yml
  Dockerfile

Configuración del servidor

La inicialización del servidor conecta transporte, autenticación y registro de herramientas:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { requireAuth } from "./middleware/auth.js";
import { checkRateLimit } from "./middleware/rate-limit.js";

const server = new Server(
  { name: "mcp-enterprise-starter", version: "1.0.0" },
  { capabilities: { tools: {}, resources: {} } },
);

// Register tool listing
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [queryDatabaseTool, listTablesTool, getSchemaTool],
}));

// Handle tool calls with auth and rate limiting
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  const apiKey = process.env.MCP_API_KEY || process.env.API_KEYS?.split(",")[0];
  const authCtx = requireAuth(apiKey);
  checkRateLimit(authCtx.apiKey);

  switch (name) {
    case "query_database":
      return await handleQueryDatabase(args);
    case "list_tables":
      return await handleListTables();
    case "get_schema":
      return await handleGetSchema(args);
    default:
      throw new Error(`Unknown tool: ${name}`);
  }
});

// Start with stdio transport for local dev
const transport = new StdioServerTransport();
await server.connect(transport);

Consultas seguras a base de datos

La herramienta query_database demuestra cómo dar a un agente acceso útil a base de datos sin darle ejecución SQL sin restricciones:

  • las consultas son strings SQL validados con valores parametrizados, no ejecución cruda sin filtrar
  • los nombres de tablas y columnas se validan contra una lista de permitidos
  • todos los valores se pasan como argumentos de consulta parametrizados
  • los resultados están limitados por defecto con un máximo configurable
  • las columnas sensibles se enmascaran en la salida
  • las palabras clave destructivas (DROP, DELETE, UPDATE, ALTER) se bloquean completamente para claves de solo lectura

Middleware de autenticación

La capa de autenticación mapea API keys a conjuntos de permisos:

import { McpToolError } from "../utils/errors.js";

export interface AuthContext {
  apiKey: string;
  permissions: "read" | "read-write";
}

function getApiKeys(): Map<string, AuthContext> {
  const keys = new Map<string, AuthContext>();
  const envKeys = process.env.API_KEYS || "";

  for (const key of envKeys.split(",").map((k) => k.trim()).filter(Boolean)) {
    keys.set(key, { apiKey: key, permissions: "read" });
  }
  return keys;
}

export function requireAuth(apiKey: string | undefined): AuthContext {
  if (!apiKey) {
    throw new McpToolError("auth_error", "Missing API key.", false);
  }
  const validKeys = getApiKeys();
  const context = validKeys.get(apiKey);
  if (!context) {
    throw new McpToolError("auth_error", "Invalid API key.", false);
  }
  return context;
}

Las API keys se cargan de la variable de entorno API_KEYS como una lista separada por comas (por ejemplo, dev-key-1,dev-key-2). Cada clave se mapea a un nivel de permisos. Para sistemas de producción, extenderías esto con restricciones de herramientas por clave, acceso basado en roles, o lo reemplazarías con OAuth2 completamente.

Cómo usarlo

El camino más rápido desde clonar hasta un servidor funcional:

  1. Clona el repositorio y copia .env.example a .env
  2. Ejecuta docker compose up para iniciar PostgreSQL con datos de muestra sembrados y el servidor MCP
  3. Copia el mcp-config.json proporcionado en tu configuración de MCP de Claude Desktop o VS Code
  4. Comienza a hacer preguntas a tu agente: “What tables are available?”, “Show me the schema for the projects table”, “Find all users in the engineering department”

La base de datos de muestra incluye tablas realistas (users, departments, projects) con suficientes datos para demostrar construcción de consultas, filtrado y enmascaramiento de columnas sensibles.

Adaptación para tus propios sistemas

El repositorio está diseñado para ser bifurcado y modificado. Para conectarlo a tus propias herramientas internas:

  1. Reemplaza la conexión de base de datos con tu fuente de datos
  2. Define nuevas herramientas en src/tools/ siguiendo los patrones existentes
  3. Actualiza las listas de tablas y columnas permitidas en tu configuración
  4. Agrega tus propias API keys y alcances de permisos
  5. Escribe pruebas para tu lógica de herramientas específica

Los patrones de autenticación, validación, manejo de errores y registro se trasladan independientemente del backend al que te conectes. Ese es el punto de la implementación de referencia: te da un shell de calidad productiva que llenas con tu propia lógica de dominio.

Qué sigue

Si quieres construir y desplegar un servidor MCP personalizado paso a paso, sigue nuestro tutorial complementario: Construye, asegura y despliega un servidor MCP personalizado. Te guía a través de cada etapa desde scaffolding hasta despliegue contenerizado con código funcional en cada paso.

Si aún no has configurado agentes con MCP, comienza con los fundamentos:

La trayectoria más amplia de MCP se está moviendo hacia orquestación multi-servidor, donde una sola sesión de agente se conecta a múltiples servidores MCP simultáneamente, cada uno proporcionando un dominio de capacidad diferente. Los registros de servidores y protocolos de descubrimiento están emergiendo para hacer esa coordinación manejable. Los equipos que construyan servidores MCP bien estructurados y seguros ahora estarán en la posición más fuerte a medida que ese ecosistema madure.

El cambio fundamental es directo: tus herramientas internas ya no son solo para humanos. Se están convirtiendo en capacidades que los agentes de IA pueden usar, bajo tu control, con tus límites de seguridad, dentro de tus flujos de trabajo. Construir un servidor MCP personalizado es cómo haces eso realidad.

Obtén el MCP Enterprise Starter Kit →