Puedes crear fácilmente flujos de trabajo de IA con Anakin AI sin ningún conocimiento de programación. Conéctate a API de LLM como: GPT-4, Claude 3.5 Sonnet, Uncensored Dolphin-Mixtral, Stable Diffusion, DALLE, Web Scraping.... en un solo flujo de trabajo!
¡Olvídate de la codificación complicada, automatiza tu trabajo mundano con Anakin AI!
Por tiempo limitado, ¡también puedes usar Google Gemini 1.5 y Stable Diffusion gratis!

Introducción
El Protocolo de Contexto del Modelo (MCP) representa un avance significativo en el ecosistema de IA, ofreciendo una forma estandarizada de comunicarse con modelos de lenguaje grandes. En lugar de que cada plataforma de IA implemente su propio formato único para los mensajes, MCP tiene como objetivo proporcionar una interfaz consistente para los prompts, respuestas y llamadas de funciones en varios modelos y plataformas.
Si bien el protocolo en sí está evolucionando, construir un servidor básico compatible con MCP puede ser sencillo. En esta guía, te guiaré a través de la creación de un servidor simple, pero funcional, que se adhiere a los principios fundamentales de manejo de mensajes en los sistemas de IA modernos. Nuestra implementación se centrará en crear una base que puedas extender más adelante con características más avanzadas.
¿Son 10 minutos tiempo suficiente? Para un sistema listo para producción, ciertamente no. Pero para un prototipo que demuestre los conceptos clave, ¡absolutamente! ¡Empecemos!
Requisitos previos
Antes de comenzar, necesitarás:
- Node.js (v16+) instalado en tu sistema
- Conocimientos básicos de JavaScript/TypeScript
- Familiaridad con Express.js o frameworks web similares
- Un editor de código (VS Code recomendado)
- Acceso a terminal/ línea de comandos
- npm o gestor de paquetes yarn
Paso 1: Configurar tu Proyecto (2 minutos)
Primero, creemos un nuevo directorio e inicialicemos nuestro proyecto:
mkdir mcp-server
cd mcp-server
npm init -y
Ahora, instala las dependencias necesarias:
npm install express cors typescript ts-node @types/node @types/express @types/cors
npm install --save-dev nodemon
Crea un archivo de configuración de TypeScript:
npx tsc --init
Edita el tsconfig.json
generado para incluir estas configuraciones esenciales:
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}
Actualiza la sección de scripts de tu package.json
:
"scripts": {
"start": "node dist/index.js",
"dev": "nodemon --exec ts-node src/index.ts",
"build": "tsc"
}
Paso 2: Crear el Servidor Principal (3 minutos)
Crea tu directorio de origen y el archivo del servidor principal:
mkdir -p src/handlers
touch src/index.ts
touch src/handlers/messageHandler.ts
touch src/types.ts
Primero definamos nuestros tipos en src/types.ts
:
// Estructura básica del mensaje
export interface Content {
type: string;
text?: string;
}
export interface Message {
role: "user" | "assistant" | "system";
content: Content[] | string;
}
// Estructuras de solicitud y respuesta
export interface ModelRequest {
messages: Message[];
max_tokens?: number;
temperature?: number;
stream?: boolean;
}
export interface ModelResponse {
message: Message;
}
// Interfaces de llamadas a herramientas
export interface Tool {
name: string;
description: string;
input_schema: {
type: string;
properties: Record;
required?: string[];
};
}
export interface ToolCall {
type: "tool_call";
id: string;
name: string;
input: Record;
}
export interface ToolResult {
type: "tool_result";
tool_call_id: string;
content: string;
}
Ahora, implementemos el servidor básico en src/index.ts
:
import express from 'express';
import cors from 'cors';
import { handleMessageRequest } from './handlers/messageHandler';
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json({ limit: '10mb' }));
// Punto final principal para el procesamiento de mensajes
app.post('/v1/messages', handleMessageRequest);
// Punto final de verificación de salud
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok' });
});
// Iniciar servidor
app.listen(PORT, () => {
console.log(`Servidor en funcionamiento en el puerto ${PORT}`);
console.log(`Verificación de salud: http://localhost:${PORT}/health`);
console.log(`Endpoint de mensajes: http://localhost:${PORT}/v1/messages`);
});
A continuación, implementemos el manejador de mensajes en src/handlers/messageHandler.ts
:
import { Request, Response } from 'express';
import { ModelRequest, ModelResponse, Message, Content } from '../types';
export async function handleMessageRequest(req: Request, res: Response) {
try {
const request = req.body as ModelRequest;
// Validación básica
if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
return res.status(400).json({ error: 'Formato de solicitud no válido. Se requiere un array de mensajes.' });
}
// Registrar la solicitud entrante (para depuración)
console.log('Solicitud recibida con', request.messages.length, 'mensajes');
// Procesar los mensajes
const response = processMessages(request.messages);
// Devolver la respuesta
return res.status(200).json(response);
} catch (error) {
console.error('Error procesando la solicitud:', error);
return res.status(500).json({ error: 'Error interno del servidor' });
}
}
function processMessages(messages: Message[]): ModelResponse {
// Extraer el último mensaje del usuario
const lastUserMessage = findLastUserMessage(messages);
if (!lastUserMessage) {
return createErrorResponse("No se encontró mensaje del usuario en la conversación");
}
const userQuery = extractTextContent(lastUserMessage);
// Lógica simple para la generación de respuestas
let responseText = "";
if (userQuery.toLowerCase().includes('hola') || userQuery.toLowerCase().includes('hi')) {
responseText = "¡Hola! ¿Cómo puedo ayudarte hoy?";
} else if (userQuery.toLowerCase().includes('tiempo')) {
responseText = "No tengo acceso a datos meteorológicos en tiempo real, pero puedo ayudarte a entender patrones climáticos en general.";
} else if (userQuery.toLowerCase().includes('hora')) {
responseText = `La hora actual en el servidor es ${new Date().toLocaleTimeString()}.`;
} else {
responseText = "Recibí tu mensaje. Esta es una respuesta simple del servidor modelo.";
}
// Construir y devolver la respuesta
return {
message: {
role: "assistant",
content: [{
type: "text",
text: responseText
}]
}
};
}
function findLastUserMessage(messages: Message[]): Message | undefined {
// Encontrar el último mensaje con rol 'user'
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i].role === 'user') {
return messages[i];
}
}
return undefined;
}
function extractTextContent(message: Message): string {
if (typeof message.content === 'string') {
return message.content;
} else if (Array.isArray(message.content)) {
return message.content
.filter(item => item.type === 'text' && item.text)
.map(item => item.text)
.join(' ');
}
return '';
}
function createErrorResponse(errorMessage: string): ModelResponse {
return {
message: {
role: "assistant",
content: [{
type: "text",
text: `Error: ${errorMessage}`
}]
}
};
}
Paso 3: Agregar Capacidad de Llamadas a Herramientas (3 minutos)
Crea un nuevo archivo para definiciones e implementaciones de herramientas:
touch src/tools.ts
Implementa algunas herramientas básicas en src/tools.ts
:
import { Tool } from './types';
// Definiciones de herramientas
export const availableTools: Tool[] = [
{
name: "get_current_time",
description: "Obtener la hora actual del servidor",
input_schema: {
type: "object",
properties: {
timezone: {
type: "string",
description: "Zona horaria opcional (por defecto a la zona horaria del servidor)"
}
}
}
},
{
name: "calculate",
description: "Realizar un cálculo matemático",
input_schema: {
type: "object",
properties: {
expression: {
type: "string",
description: "Expresión matemática a evaluar"
}
},
required: ["expression"]
}
}
];
// Implementaciones de herramientas
export function executeToolCall(name: string, params: Record): string {
switch (name) {
case "get_current_time":
return getTime(params.timezone);
case "calculate":
return calculate(params.expression);
default:
throw new Error(`Herramienta desconocida: ${name}`);
}
}
function getTime(timezone?: string): string {
const options: Intl.DateTimeFormatOptions = {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short'
};
try {
if (timezone) {
options.timeZone = timezone;
}
return new Date().toLocaleTimeString('es-ES', options);
} catch (error) {
return `${new Date().toLocaleTimeString()} (Hora del servidor)`;
}
}
function calculate(expression: string): string {
try {
// PRECAUCIÓN: En una aplicación real, deberías usar un método de evaluación más seguro
// Este es un ejemplo simplificado solo para demostración
const sanitizedExpression = expression.replace(/[^0-9+\-*/().\s]/g, '');
const result = eval(sanitizedExpression);
return `${expression} = ${result}`;
} catch (error) {
return `Error al calcular ${expression}: ${error instanceof Error ? error.message : String(error)}`;
}
}
Ahora, actualiza el manejador de mensajes para soportar llamadas a herramientas modificando src/handlers/messageHandler.ts
:
// Agrega estas importaciones al principio
import { availableTools, executeToolCall } from '../tools';
import { ToolCall, ToolResult } from '../types';
// Actualiza la función handleMessageRequest
export async function handleMessageRequest(req: Request, res: Response) {
try {
const request = req.body as ModelRequest;
// Validación básica
if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
return res.status(400).json({ error: 'Formato de solicitud no válido. Se requiere un array de mensajes.' });
}
// Verificar si esta es una solicitud con resultados de herramienta
const lastMessage = request.messages[request.messages.length - 1];
if (lastMessage.role === 'assistant' && Array.isArray(lastMessage.content)) {
const toolCalls = lastMessage.content.filter(item =>
item.type === 'tool_call') as ToolCall[];
if (toolCalls.length > 0) {
// Este es un seguimiento con resultados de herramientas
return handleToolResultsResponse(request, res);
}
}
// Procesar como un mensaje regular
const response = processMessages(request.messages);
// Devolver la respuesta
return res.status(200).json(response);
} catch (error) {
console.error('Error procesando la solicitud:', error);
return res.status(500).json({ error: 'Error interno del servidor' });
}
}
// Agrega esta función para manejar llamadas a herramientas
function handleToolResultsResponse(request: ModelRequest, res: Response) {
const messages = request.messages;
const lastAssistantMessage = messages[messages.length - 1];
if (lastAssistantMessage.role !== 'assistant' || !Array.isArray(lastAssistantMessage.content)) {
return res.status(400).json({ error: 'Formato de resultados de herramienta no válido' });
}
// Encontrar llamadas a herramientas y resultados
const toolCalls = lastAssistantMessage.content.filter(
item => item.type === 'tool_call'
) as ToolCall[];
const toolResults = lastAssistantMessage.content.filter(
item => item.type === 'tool_result'
) as ToolResult[];
// Procesar los resultados
let finalResponse = "He procesado la siguiente información:\n\n";
toolResults.forEach(result => {
const relatedCall = toolCalls.find(call => call.id === result.tool_call_id);
if (relatedCall) {
finalResponse += `- ${relatedCall.name}: ${result.content}\n`;
}
});
return res.status(200).json({
message: {
role: "assistant",
content: [{ type: "text", text: finalResponse }]
}
});
}
// Modifica la función processMessages para manejar posibles llamadas a herramientas
function processMessages(messages: Message[]): ModelResponse {
const lastUserMessage = findLastUserMessage(messages);
if (!lastUserMessage) {
return createErrorResponse("No se encontró mensaje del usuario en la conversación");
}
const userQuery = extractTextContent(lastUserMessage);
// Buscar palabras clave que podrían activar el uso de herramientas
if (userQuery.toLowerCase().includes('hora')) {
return {
message: {
role: "assistant",
content: [
{ type: "tool_call", id: "call_001", name: "get_current_time", input: {} },
{
type: "text",
text: "Voy a verificar la hora actual para ti."
}
]
}
};
} else if (userQuery.toLowerCase().match(/calcular|hacer cálculos|cuánto es \d+[\+\-\*\/]/)) {
// Extraer un cálculo potencial
const expression = userQuery.match(/(\d+[\+\-\*\/\(\)\.]*\d+)/)?.[0] || "1+1";
return {
message: {
role: "assistant",
content: [
{
type: "tool_call",
id: "call_002",
name: "calculate",
input: { expression }
},
{
type: "text",
text: `Voy a calcular ${expression} para ti.`
}
]
}
};
}
// Respuesta predeterminada para otras consultas
return {
message: {
role: "assistant",
content: [{
type: "text",
text: `Recibí tu mensaje: "${userQuery}". ¿Cómo puedo ayudarte más?`
}]
}
};
}
Paso 4: Pruebas y Despliegue (2 minutos)
Vamos a crear un script de prueba simple para verificar nuestro servidor. Crea un archivo test.js
en el directorio raíz:
const fetch = require('node-fetch');
async function testServer() {
const url = 'http://localhost:3000/v1/messages';
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
messages: [
{
role: "user",
content: [
{
type: "text",
text: "¿Qué hora es ahora mismo?"
}
]
}
]
})
});
const data = await response.json();
console.log(JSON.stringify(data, null, 2));
}
testServer().catch(console.error);
Para probar tu servidor:
# Inicia el servidor
npm run dev
# En otra terminal, ejecuta la prueba
node test.js
Para un despliegue rápido, vamos a crear un Dockerfile simple:
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
Construye y ejecuta el contenedor:
docker build -t mcp-server .
docker run -p 3000:3000 mcp-server
Conclusión
En solo 10 minutos, hemos construido un servidor básico que implementa los conceptos clave de los protocolos de mensajes de IA modernos. Nuestro servidor puede:
- Procesar solicitudes de mensajes estructurados
- Responder en un formato estándar
- Manejar funcionalidad básica de llamadas a herramientas
- Procesar y responder a resultados de herramientas
Aunque esta implementación es simplificada, proporciona una base sólida para un desarrollo posterior. Para extender este servidor para uso en producción, considera:
- Agregar autenticación y limitación de tasa
- Implementar un manejo de errores y validaciones adecuados
- Conectarse a modelos de IA reales para el procesamiento
- Agregar herramientas más sofisticadas
- Implementar respuestas en streaming
- Agregar registro y monitoreo comprensivos
Recuerda que, aunque nuestra implementación sigue principios generales de los protocolos de mensajería de IA modernos, implementaciones específicas como la API de OpenAI o la API de Claude de Anthropic pueden tener requisitos adicionales o ligeras variaciones en sus formatos esperados. Siempre consulta la documentación oficial del servicio específico con el que estés integrando.
El campo de los protocolos de mensajería de IA está evolucionando rápidamente, así que mantente actualizado con los últimos desarrollos y prepárate para adaptar tu implementación a medida que evolucionen los estándares. Al entender los conceptos básicos que se demuestran en esta guía, estarás bien equipado para trabajar con los nuevos avances que surjan en este emocionante campo.