MCPTypeScriptGitHub CopilotVS CodeAI

Crear un servidor MCP personalizado con TypeScript para ampliar GitHub Copilot

Sloth255
Sloth255
·12 min read·2,528 words

Introducción

MCP (Model Context Protocol) es un protocolo estándar definido por Anthropic para conectar modelos de IA con herramientas y fuentes de datos externas. GitHub Copilot, Claude, Cursor y otros clientes de IA ya lo admiten.

Un servidor MCP permite resolver necesidades como llamar a una API interna desde Copilot o ejecutar comandos específicos de un proyecto mediante IA.

Este artículo explica cómo implementar un servidor MCP con TypeScript y conectarlo a GitHub Copilot en VS Code. Incluye la configuración, el diseño de herramientas y las prácticas operativas esenciales.


Fundamentos de MCP

flowchart TD
  A[Agente de Copilot en VS Code] -->|MCP| B[Servidor MCP]
  B --> C[Herramientas externas]
  B --> D[API]
  B --> E[Base de datos]

Un servidor MCP puede exponer tres tipos principales de capacidades.

Tipo Descripción Ejemplos
Tools Funciones que el LLM puede invocar Llamadas a API, operaciones con archivos
Resources Datos disponibles para consulta Documentos, archivos de configuración
Prompts Plantillas de instrucciones reutilizables Revisión de código, investigación de incidentes

La comunicación se realiza con JSON-RPC mediante stdio o Streamable HTTP.


Principios y arquitectura de MCP

Por qué se creó MCP

Antes de MCP, cada cliente de IA integraba los sistemas externos por su cuenta. Para usar una misma API interna desde varias herramientas de IA había que implementar y mantener una integración independiente para cada cliente.

[Antes de MCP]
GitHub Copilot -> Integración personalizada A -> API interna
Claude         -> Integración personalizada B -> API interna (duplicada)
Cursor         -> Integración personalizada C -> API interna (duplicada)

[Con MCP]
GitHub Copilot -+
Claude         +-> Servidor MCP -> API interna (una implementación)
Cursor         -+

MCP resuelve esa fragmentación con un protocolo estándar. Una vez implementado el servidor, no es necesario volver a desarrollar la misma capacidad para cada cliente. De todos modos, las funciones disponibles dependen de la versión de MCP, del transporte y de los mecanismos de autenticación que admita cada cliente de IA.

Principio central: el LLM razona y el servidor ejecuta

El principio central de MCP es la separación de responsabilidades.

  • LLM (como Copilot): interpreta el contexto y decide qué herramienta llamar y qué argumentos enviar.
  • Servidor MCP: ejecuta la herramienta y devuelve el resultado.

Un LLM no accede directamente a sistemas externos. Cada solicitud pasa por una interfaz explícita de llamada a herramientas. Esto permite centralizar la autenticación, la validación y el registro en el servidor.

sequenceDiagram
  participant U as Usuario
  participant L as LLM (Copilot)
  participant S as Servidor MCP
  participant A as API externa

  U->>L: "Consulta los Issues abiertos"
  L->>S: tools/call search_github_issues
  S->>A: GET /search/issues
  A-->>S: JSON de resultado
  S-->>L: Resultado de la ejecución
  L-->>U: Respuesta con formato

Comunicación con JSON-RPC 2.0

La comunicación usa JSON-RPC 2.0. Tras establecer la conexión, el intercambio de mensajes sigue estos tres pasos.

Paso Método Descripción
1 initialize El cliente y el servidor intercambian versiones y capacidades admitidas
2 tools/list El cliente obtiene la lista de herramientas, sus nombres y esquemas
3 tools/call El cliente solicita ejecutar una herramienta

A continuación se muestra una solicitud y su respuesta para search_github_issues:

// Petición
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_github_issues",
    "arguments": {
      "query": "authentication",
      "repo": "owner/my-repo",
      "state": "open"
    }
  }
}
// Respuesta
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "[{\"number\": 42, \"title\": \"Fix OAuth token refresh\", ...}]"
      }
    ]
  }
}

El LLM recibe este content, lo transforma en una respuesta en lenguaje natural y la devuelve al usuario.

El papel de los transportes

MCP define dos transportes estándar: stdio y Streamable HTTP. El transporte HTTP+SSE anterior se conserva por compatibilidad, pero las implementaciones nuevas deben usar Streamable HTTP.

stdio Streamable HTTP
Mecanismo Inicia el servidor como subproceso y se comunica mediante entrada y salida estándar Envía y recibe JSON-RPC mediante HTTP POST/GET, y transmite mediante SSE cuando es necesario
Adecuado para Desarrollo local y uso individual Uso compartido por equipos, administración centralizada y varios clientes
Costo de inicio Bajo; puede iniciarse en cada invocación Requiere un servicio que permanezca en ejecución

Para el uso local desde VS Code, stdio es la opción habitual. Más adelante, la sección "Elegir entre stdio y Streamable HTTP" retoma esta decisión.


Cuándo crear un servidor MCP en lugar de usar Skills

GitHub Copilot en VS Code cuenta con dos mecanismos para extender su comportamiento mediante archivos Markdown. Los archivos de instrucciones personalizados (.github/copilot-instructions.md, .instructions.md y otros) definen pautas que se proporcionan a la IA. Las Skills del agente (SKILL.md) definen flujos de trabajo reutilizables que pueden incluir procedimientos, scripts y recursos de apoyo. En este artículo, ambos se agrupan bajo el término "personalización basada en instrucciones". Antes de crear un servidor MCP, conviene evaluar si estos mecanismos bastan.

Casos resueltos mediante personalización basada en instrucciones

  • Proporcionar a la IA información estática, como convenciones de código o la estructura del proyecto.
  • Reutilizar un procedimiento establecido como plantilla de Prompt.
  • Definir un flujo de trabajo que no requiera acceso a sistemas externos.

La personalización basada en instrucciones no requiere un servidor dedicado. Un agente puede ejecutar los scripts de una Skill mediante herramientas existentes, como la terminal, pero la posibilidad de ejecutarlos, las aprobaciones y el formato de salida dependen de las capacidades del entorno anfitrión. Conviene considerar primero esta alternativa.

Las Skills también pueden indicar cómo llamar a una API externa

Cuando el requisito es usar una API externa, puede documentar la invocación en una Skill y pedir a Copilot que genere código o un comando curl.

<!-- Ejemplo de SKILL.md -->
El endpoint de la API interna de estado es https://api.internal/status.
Para autenticarse, incluya un token Bearer en el encabezado Authorization.
Ejemplo de llamada con curl: curl -H "Authorization: Bearer $TOKEN" https://api.internal/status

Este enfoque sirve cuando el objetivo es que Copilot genere código. En entornos donde un agente puede llamar a una API mediante una terminal o extensión existente, el procedimiento de ejecución, la aprobación y el manejo de los resultados siguen dependiendo del anfitrión.

El punto en el que se necesita MCP

Las Skills dejan de ser suficientes en cualquiera de las siguientes situaciones.

Situación Personalización basada en instrucciones MCP
Enseñar a Copilot cómo llamar a una API
Definir un procedimiento para ejecutar una API con herramientas proporcionadas por el anfitrión
Exponer una API como una herramienta específica y tipada No
Estandarizar en el servidor la autenticación, la validación de entradas y el formato de los resultados No
Exponer la misma capacidad a varios clientes MCP No

Las Skills enseñan al agente qué hacer y en qué orden. MCP, en cambio, ofrece un contrato específico y tipado de herramienta que los clientes pueden invocar. Con MCP, el servidor ejecuta las llamadas y devuelve los resultados al contexto de Copilot en un formato definido. Ese control y esa portabilidad son los criterios decisivos.

Casos que requieren un servidor MCP

Cree su propio servidor MCP cuando requisitos como estos resulten difíciles de controlar solo con personalización basada en instrucciones.

Requisito Razón
Usar resultados de una API en el razonamiento de Copilot El resultado de la ejecución entra directamente en el contexto y permite hacer preguntas posteriores o transformaciones
Acceder a recursos autenticados Los tokens y las credenciales se pueden guardar con seguridad en variables de entorno sin exponerlos a los usuarios
Realizar operaciones de escritura o actualización Son operaciones con efectos secundarios, como crear Issues, actualizar etiquetas o modificar registros
Requerir datos en tiempo real Permite consultar el estado actual de CI o los valores más recientes de una base de datos, que no pueden residir en archivos estáticos
Manejar datos grandes o dinámicos Sirve para documentos demasiado extensos para incluirlos en Prompts o para datos que cambian con frecuencia
Realizar procesamiento o conversiones complejas Permite ejecutar en código análisis, agregaciones y conversiones de formato de archivos
Compartir entre proyectos o equipos Facilita administrar de forma centralizada las herramientas que usan varios desarrolladores y repositorios

Flujo de decisiones

flowchart TD
  A[Hay una tarea por resolver] --> B{Se necesita acceso a un<br/>sistema externo?}
  B -->|No| C[La personalización basada en instrucciones puede resolverlo]
  B -->|Sí| D{Copilot debe recibir el resultado<br/>y razonar a partir de él?}
  D -->|No: basta generar código| C
  D -->|Sí| E{Intervienen autenticación, efectos secundarios<br/>o datos dinámicos?}
  E -->|No| F[Compruebe si un servidor MCP existente<br/>puede cubrir la necesidad]
  E -->|Sí| G[Crear un servidor MCP]
  F -->|No existe| G

La diferencia clave entre la personalización basada en instrucciones y MCP es si se necesita controlar en el servidor un contrato específico de herramienta, la autenticación y la validación de entradas.


Configuración

mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/server zod
npm install -D typescript @types/node tsx
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "strict": true
  },
  "include": ["src/**/*"]
}
// package.json (extracto)
{
  "type": "module",
  "scripts": {
    "dev": "tsx src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

Implementación de un servidor MCP básico

// src/index.ts
import { McpServer } from '@modelcontextprotocol/server'
import { serveStdio } from '@modelcontextprotocol/server/stdio'
import * as z from 'zod/v4'
import { fetchWeather } from './tools/weather.js'

interface GithubIssueSearchResponse {
  items: Array<{
    number: number
    title: string
    state: string
    html_url: string
  }>
}

function createServer(): McpServer {
  const server = new McpServer({
    name: 'my-custom-tools',
    version: '1.0.0',
  })

  server.registerTool(
    'get_weather',
    {
      description: 'Consulta el tiempo actual de la ciudad indicada',
      inputSchema: z.object({
        city: z.string().describe('Ciudad cuyo tiempo se quiere consultar (p. ej., Tokyo, Osaka)'),
      }),
    },
    async ({ city }) => {
      const weather = await fetchWeather(city)
      return {
        content: [{
          type: 'text',
          text: `Tiempo actual en ${city}: ${weather.description}, temperatura: ${weather.temp}°C`,
        }],
      }
    }
  )

  server.registerTool(
    'search_github_issues',
    {
      description: 'Busca issues en un repositorio de GitHub',
      inputSchema: z.object({
        query: z.string().describe('Términos de búsqueda'),
        repo: z.string().regex(/^[^/]+\/[^/]+$/).describe('Nombre del repositorio (formato owner/repo)'),
        state: z.enum(['open', 'closed', 'all']).default('open').describe('Estado del issue'),
      }),
    },
    async ({ query, repo, state }) => {
      const token = process.env.GITHUB_TOKEN
      if (!token) {
        return {
          content: [{ type: 'text', text: 'GITHUB_TOKEN no está configurado' }],
          isError: true,
        }
      }

      const stateQualifier = state === 'all' ? '' : ` state:${state}`
      const url = new URL('https://api.github.com/search/issues')
      url.searchParams.set(
        'q',
        `${query} is:issue repo:${repo}${stateQualifier}`
      )

      const response = await fetch(url.toString(), {
        headers: {
          Authorization: `Bearer ${token}`,
          Accept: 'application/vnd.github.v3+json',
        },
      })

      if (!response.ok) {
        return {
          content: [{ type: 'text', text: `Error de la API de GitHub: ${response.status}` }],
          isError: true,
        }
      }

      const data = await response.json() as GithubIssueSearchResponse
      const issues = data.items.slice(0, 5).map((issue) => ({
        number: issue.number,
        title: issue.title,
        state: issue.state,
        url: issue.html_url,
      }))

      return {
        content: [{ type: 'text', text: JSON.stringify(issues, null, 2) }],
      }
    }
  )

  return server
}

void serveStdio(createServer)
console.error('El servidor MCP se inició')

Ejemplos prácticos de implementación de herramientas

API meteorológica (llamada a una API externa)

// src/tools/weather.ts
interface WeatherData {
  description: string
  temp: number
  humidity: number
}

export async function fetchWeather(city: string): Promise<WeatherData> {
  const apiKey = process.env.OPENWEATHER_API_KEY
  if (!apiKey) throw new Error('OPENWEATHER_API_KEY no está configurada')

  const url = `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(city)}&appid=${apiKey}&units=metric&lang=ja`

  const response = await fetch(url)
  if (!response.ok) {
    throw new Error(`No fue posible obtener los datos meteorológicos: ${response.statusText}`)
  }

  const data = await response.json()
  return {
    description: data.weather[0].description,
    temp: Math.round(data.main.temp),
    humidity: data.main.humidity,
  }
}

Búsqueda en documentación interna (archivos locales)

// src/tools/docs-search.ts
import * as fs from 'fs/promises'
import * as path from 'path'
import { pathToFileURL } from 'url'

interface DocResult {
  title: string
  excerpt: string
  url: string
}

export async function searchDocs(
  query: string,
  limit: number
): Promise<DocResult[]> {
  const docsDir = process.env.DOCS_DIR ?? './docs'
  const files = await fs.readdir(docsDir)
  const results: DocResult[] = []

  for (const file of files.filter((f) => f.endsWith('.md'))) {
    const content = await fs.readFile(path.join(docsDir, file), 'utf-8')

    if (content.toLowerCase().includes(query.toLowerCase())) {
      const lines = content.split('\n')
      // Omite el front matter (el primer bloque ---) y usa el primer H1 como título
      let bodyStart = 0
      if (lines[0]?.trim() === '---') {
        const end = lines.findIndex((l, i) => i > 0 && l.trim() === '---')
        bodyStart = end >= 0 ? end + 1 : 0
      }
      const h1 = lines.slice(bodyStart).find((l) => /^#\s/.test(l))
      const title = h1 ? h1.replace(/^#\s*/, '') : path.basename(file, '.md')
      const matchIndex = content.toLowerCase().indexOf(query.toLowerCase())
      const excerpt = content.slice(
        Math.max(0, matchIndex - 50),
        matchIndex + 150
      )

      results.push({
        title,
        excerpt,
        url: pathToFileURL(path.resolve(docsDir, file)).toString(),
      })

      if (results.length >= limit) break
    }
  }

  return results
}

Implementación de Resources

Los Resources proporcionan datos de solo lectura que la IA puede consultar. Son adecuados para información que el modelo debe poder recuperar de forma confiable, como políticas internas, procedimientos operativos y documentación de API.

// Agregar al inicio de src/index.ts
import { readFile } from 'node:fs/promises'
import path from 'node:path'

// Agregar antes de return server dentro de createServer
server.registerResource(
  'project-guide',
  'docs://project-guide',
  {
    title: 'Guía del proyecto',
    description: 'Guía del proyecto como referencia durante el desarrollo',
    mimeType: 'text/markdown',
  },
  async (uri) => {
  const docsDir = process.env.DOCS_DIR ?? path.join(process.cwd(), 'docs')
  const content = await readFile(
    path.join(docsDir, 'project-guide.md'),
    'utf-8'
  )

  return {
    contents: [
      {
        uri: uri.href,
        mimeType: 'text/markdown',
        text: content,
      },
    ],
  }
  }
)

Buenos candidatos para Resources:

  • Guías de diseño y convenciones de código.
  • Runbooks operativos.
  • Resúmenes de especificaciones de API.
  • Preguntas frecuentes internas.

La principal ventaja es que esta información puede consultarse sin pegarla en cada conversación.


Uso de Prompts

MCP puede exponer Prompts además de herramientas. Son útiles para reutilizar procedimientos y criterios de revisión.

  • Prompts para revisiones de seguridad.
  • Prompts para generar descripciones de PR.
  • Prompts para la investigación inicial de incidentes.

Las herramientas son los medios de ejecución; los Prompts, los patrones de razonamiento. Esta distinción ayuda a detectar casos de uso útiles.


Conexión con VS Code

// .vscode/mcp.json
{
  "servers": {
    "my-custom-tools": {
      "type": "stdio",
      "command": "node",
      "args": ["${workspaceFolder}/dist/index.js"],
      "env": {
        "GITHUB_TOKEN": "${env:GITHUB_TOKEN}",
        "OPENWEATHER_API_KEY": "${env:OPENWEATHER_API_KEY}"
      }
    }
  }
}

Durante el desarrollo, puede ejecutarse directamente con tsx, sin pasar por la compilación.

{
  "servers": {
    "my-custom-tools-dev": {
      "type": "stdio",
      "command": "npx",
      "args": ["tsx", "${workspaceFolder}/src/index.ts"]
    }
  }
}

Uso desde Copilot Chat

Después de reiniciar VS Code, las herramientas MCP estarán disponibles en Copilot Chat.

"Dime el tiempo en Tokyo"
  -> Copilot llama automáticamente a la herramienta get_weather

"Busca Issues abiertos sobre \"memory leak\""
  -> Copilot llama automáticamente a la herramienta search_github_issues

También es posible filtrar herramientas por nombre de servidor.

#my-custom-tools Muestra los Issues abiertos sobre "authentication" en owner/my-repo
  -> Llama a la herramienta search_github_issues mediante este servidor

Consejos de depuración

# Probar herramientas de forma interactiva con MCP Inspector
npx @modelcontextprotocol/inspector npx tsx src/index.ts

Abra en el navegador la URL que muestra Inspector para revisar visualmente las llamadas a herramientas y los valores devueltos.


Buenas prácticas para diseñar herramientas

Empiece por decidir qué no permitir

Las herramientas MCP son útiles, pero concentrar demasiada capacidad en una sola herramienta aumenta el riesgo.

Mal ejemplo Buen ejemplo
manage_project list_open_issues, update_issue_labels
run_anything get_pipeline_status
execute_sql search_customers

Una herramienta es más confiable cuando su nombre deja claros los efectos secundarios, recibe pocos parámetros de entrada y su forma de fallar es fácil de explicar.

Diseñe el esquema inputSchema con cuidado

El esquema de entrada de una herramienta no solo sirve para validar: también es la interfaz del LLM. Las descripciones vagas reducen la precisión al inferir argumentos.

const updateIssueInput = {
  type: 'object',
  properties: {
    repo: { type: 'string', pattern: '^[^/]+/[^/]+$' },
    issueNumber: { type: 'integer', minimum: 1 },
    labels: {
      type: 'array',
      items: { type: 'string' },
      maxItems: 10,
    },
  },
  required: ['repo', 'issueNumber', 'labels'],
  additionalProperties: false,
}

Puntos clave:

  • Escriba valores description específicos.
  • Use enum siempre que sea posible.
  • Declare required explícitamente.
  • Use additionalProperties: false para evitar parámetros no deseados.

Separe las responsabilidades de Zod y JSON Schema

inputSchema es el contrato externo, mientras que Zod vuelve a validar los datos en tiempo de ejecución. En especial, no omita la validación del lado del servidor en herramientas que llamen a una API o a una base de datos externa.

case 'update_issue': {
  // Define con JSON Schema la estructura que se muestra al modelo
  // y valida con Zod los valores que realmente se aceptan
  const input = z.object({
    repo: z.string().regex(/^[^/]+\/[^/]+$/),
    issueNumber: z.number().int().positive(),
    labels: z.array(z.string()).max(10),
  }).parse(args)
  // ...
}

Manejo de errores

Es más fácil gestionar los errores si se separan, como mínimo, en estas tres categorías:

  • Errores de entrada (fallos de validación).
  • Errores de autenticación y autorización.
  • Fallos del servicio externo.
function toToolErrorMessage(error: unknown): string {
  if (error instanceof z.ZodError) {
    return 'La entrada no es válida. Comprueba los campos obligatorios y los formatos de los valores.'
  }

  if (error instanceof Error) {
    return `No fue posible ejecutar la herramienta: ${error.message}`
  }

  return 'No fue posible ejecutar la herramienta.'
}

Los mensajes devueltos no deben revelar detalles internos innecesarios y deben indicar cuál es el siguiente paso.


Manejo de credenciales

Nunca envíe secretos en Prompts o argumentos; léalos desde variables de entorno o desde un entorno de ejecución seguro.

Hacer Evitar
Leer tokens de API desde env Incluir secretos en los argumentos de la herramienta
Usar tokens con privilegios mínimos Escribir tokens en los registros
Separar los entornos de producción y desarrollo Incluir secretos en mensajes de error

Mantenga los permisos al mínimo

  • Use un token de solo lectura si solo busca Issues de GitHub.
  • Use un usuario exclusivo de SELECT para una herramienta de lectura de bases de datos.
  • Use únicamente permiso de lectura de flujos de trabajo para una herramienta que verifique implementaciones.

En equipos, es más seguro separar las herramientas de solo lectura de las de actualización y aislar las herramientas orientadas a producción.


Diseño operativo

Tiempos de espera, reintentos y fallos parciales

Un experimento local puede funcionar de forma confiable y, aun así, fallar en producción. Evite escribir código bajo el supuesto de que todo saldrá bien.

async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
  return await Promise.race([
    promise,
    new Promise<T>((_, reject) => {
      setTimeout(() => reject(new Error(`Se agotó el tiempo de espera después de ${ms} ms`)), ms)
    }),
  ])
}

Elementos mínimos:

  • Tiempos de espera.
  • Límites de reintento.
  • Límites de tasa.
  • Normalización de mensajes de error.

Observabilidad: diseño de registros

Durante la depuración no basta con saber qué se invocó. Registre estos datos:

  • Nombre de la herramienta.
  • Hora de invocación y duración de la ejecución.
  • Categoría de éxito o fallo, y motivo del fallo.

No registre más de lo necesario: mantenga fuera de los registros los datos personales, los tokens de acceso, las sentencias SQL completas y las respuestas completas de API externas.

Estrategia de prueba

Separar las implementaciones de las herramientas en funciones permite probar su lógica fuera del protocolo MCP.

// Separar la lógica de negocio para que pueda probarse
export async function searchGithubIssues(input: SearchIssuesInput) {
  // Implementación real
}

server.registerTool(
  'search_github_issues',
  { inputSchema: SearchIssuesInputSchema },
  async (input) => formatResult(await searchGithubIssues(input))
)

Capas de prueba recomendadas:

  1. Pruebas unitarias de validación de entradas.
  2. Pruebas unitarias de los manejadores de herramientas.
  3. Pruebas de integración que incluyan el transporte.
  4. Verificación manual con MCP Inspector.

Elegir entre stdio y Streamable HTTP

Perspectiva stdio Streamable HTTP
Configuración local Muy sencilla Requiere más preparación
Auditoría central Limitada Sólida
Administración de autenticación Depende de cada usuario Más sencilla
Depuración Sencilla Intervienen más factores de red
Distribución npm o binario URL del servicio

Empiece por comprobar la utilidad con stdio. Cuando necesite compartir el servidor en un equipo o administrarlo de forma centralizada, considere Streamable HTTP. Si lo expone mediante HTTP, implemente la validación de origen, la vinculación a localhost y la autenticación.


División de servidores MCP

Concentrar todo en un servidor tiende a ampliar demasiado sus responsabilidades. Dividirlo en unidades como estas simplifica la operación:

  • Herramientas relacionadas con GitHub.
  • Herramientas para API internas.
  • Herramientas de lectura de bases de datos.
  • Servidores que solo exponen Resources de documentación.

La división también facilita organizar los permisos y las variables de entorno.


Errores comunes

1. Crear una herramienta general que pueda hacer de todo

Diseños como run_anything o execute_sql (SQL arbitrario) suelen reducir tanto la seguridad como la reproducibilidad.

2. Dejar inputSchema demasiado abierto

El modelo tiene dificultades para construir argumentos confiables, lo que provoca más llamadas fallidas.

3. Recibir secretos mediante argumentos

Pueden filtrarse con facilidad mediante registros o rutas de conversación.

4. Combinar operaciones de lectura y actualización bajo el mismo permiso

Esto aumenta la probabilidad de incidentes operativos.

5. Implementar herramientas de escritura directamente en los manejadores

Esto dificulta las pruebas y el mantenimiento.


Una secuencia práctica de adopción

Para un primer servidor MCP, esta secuencia es realista:

  1. Cree un servidor local con stdio.
  2. Añada solo una o dos herramientas de solo lectura.
  3. Agregue Resources y Prompts.
  4. Mejore los registros y el manejo de errores.
  5. Añada herramientas de actualización con cuidado y solo cuando sea necesario.

Esta secuencia facilita equilibrar la seguridad y la utilidad.

Buenos candidatos para empezar:

  • Consulta de configuración del proyecto.
  • Búsqueda de documentación interna.
  • Comprobaciones de estado de CI.
  • Búsqueda de PR e Issues.

Añada herramientas de actualización solo después de contar con un diseño de auditoría y con la confianza de quienes las usarán.


Resumen

  • Los servidores MCP se comunican mediante JSON-RPC sobre stdio o Streamable HTTP, y el @modelcontextprotocol/server actual simplifica su implementación en TypeScript.
  • La combinación de Tools (medios de ejecución), Resources (datos de referencia) y Prompts (patrones de razonamiento) amplía las capacidades de Copilot.
  • Registrar un servidor en .vscode/mcp.json permite invocarlo desde GitHub Copilot en VS Code.
  • El diseño de herramientas debe comenzar por definir qué no se permitirá; un inputSchema bien diseñado mejora la precisión del razonamiento del LLM.
  • Administre los secretos mediante variables de entorno y mantenga los permisos al mínimo.
  • Empiece con stdio y una o dos herramientas de solo lectura; amplíe el servidor después de acumular experiencia operativa.

Referencias