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 formatoComunicació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 | Sí | — |
| Definir un procedimiento para ejecutar una API con herramientas proporcionadas por el anfitrión | Sí | — |
| Exponer una API como una herramienta específica y tipada | No | Sí |
| Estandarizar en el servidor la autenticación, la validación de entradas y el formato de los resultados | No | Sí |
| Exponer la misma capacidad a varios clientes MCP | No | Sí |
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| GLa 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
descriptionespecíficos. - Use
enumsiempre que sea posible. - Declare
requiredexplícitamente. - Use
additionalProperties: falsepara 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:
- Pruebas unitarias de validación de entradas.
- Pruebas unitarias de los manejadores de herramientas.
- Pruebas de integración que incluyan el transporte.
- 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:
- Cree un servidor local con stdio.
- Añada solo una o dos herramientas de solo lectura.
- Agregue Resources y Prompts.
- Mejore los registros y el manejo de errores.
- 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/serveractual 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.jsonpermite invocarlo desde GitHub Copilot en VS Code. - El diseño de herramientas debe comenzar por definir qué no se permitirá; un
inputSchemabien 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.
