Cómo crear un servidor MCP en TypeScript: conecta tus propias herramientas con agentes de IA

Cuando desarrollo proyectos o escribo artículos, quiero que mis agentes de IA me ayuden a encontrar fotos reales en lugar de intentar generarlas. Los fotógrafos en Unsplash comparten un trabajo excelente de forma gratuita y merecen reconocimiento por sus imágenes.
Por esta razón, investigué el Model Context Protocol (MCP) y creé unsplashx.
Con unsplashx, mi asistente de IA puede buscar fotos en Unsplash, descargarlas con las dimensiones correctas y agregarlas a mis archivos con los créditos automáticos para el fotógrafo.
En este artículo, comparto lo que aprendí mientras construía unsplashx para que puedas crear tus propias herramientas personalizadas para agentes de IA usando TypeScript.
¿Qué es MCP y por qué es útil?
El Model Context Protocol funciona como un estándar abierto para herramientas de IA. Antes de MCP, conectar una API a un LLM significaba escribir adaptadores personalizados de llamadas a funciones (function calling) para OpenAI, reescribirlos para Anthropic y crear extensiones independientes para cada editor o IDE.
MCP estandariza esta integración mediante JSON-RPC 2.0:
| Componente | Rol | Ejemplos |
|---|---|---|
| Host Application | La aplicación que ejecuta la interfaz de usuario y el modelo de IA | Cursor, Antigravity IDE, Claude Desktop |
| MCP Client | El cliente del protocolo que gestiona la comunicación y los permisos de seguridad | Capa interna del protocolo integrada en el editor |
| MCP Server | El servicio independiente que expone herramientas, datos y recursos | unsplashx, GitHub MCP, PostgreSQL MCP |
Cuando ejecutas un servidor MCP de forma local, el host inicia tu script como un proceso secundario (child process) y se comunica con él a través de la entrada y salida estándar (stdio).
Ahora que entendemos este flujo de trabajo, pasemos a la configuración inicial del proyecto.
Paso 1: Configuración del proyecto y dependencias
Comencemos creando un nuevo directorio e inicializando un proyecto de Node.js:
mkdir unsplashx
cd unsplashx
npm init -ySolo necesitamos el paquete oficial @modelcontextprotocol/sdk:
npm install @modelcontextprotocol/sdkEn el archivo package.json, agregamos "type": "module" para habilitar ESM nativo y definimos el binario ejecutable:
{
"name": "unsplashx",
"version": "1.1.0",
"description": "Unsplash MCP Server",
"main": "index.js",
"type": "module",
"bin": {
"unsplashx": "./index.js"
},
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4"
}
}Ahora inicialicemos la instancia del servidor en index.js.
Paso 2: Crear la instancia del servidor
En index.js, importamos los módulos del SDK y leemos nuestra clave de API de Unsplash. Soportamos tanto una variable de entorno como un argumento en la línea de comandos:
#!/usr/bin/env node
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 fs from "fs/promises";
import path from "path";
const UNSPLASH_ACCESS_KEY = process.env.UNSPLASH_ACCESS_KEY || process.argv[2];
const API_BASE = "https://api.unsplash.com";
if (!UNSPLASH_ACCESS_KEY) {
console.error("Error: UNSPLASH_ACCESS_KEY is required.");
process.exit(1);
}
const server = new Server(
{ name: "unsplash-mcp", version: "1.1.0" },
{ capabilities: { tools: {} } }
);También creamos una pequeña función auxiliar para llamar a la API de Unsplash con nuestro encabezado de autorización:
async function fetchUnsplash(endpoint, params = {}) {
const url = new URL(`${API_BASE}${endpoint}`);
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) url.searchParams.append(key, value);
});
const response = await fetch(url, {
headers: {
Authorization: `Client-ID ${UNSPLASH_ACCESS_KEY}`,
"Accept-Version": "v1",
},
});
if (!response.ok) {
throw new Error(`Unsplash API error: ${response.statusText}`);
}
return response.json();
}Con nuestro servidor base listo, veamos cómo definimos el esquema de las herramientas.
Paso 3: Definir el esquema de las herramientas
Un servidor MCP comunica a los clientes de IA qué herramientas están disponibles escuchando el evento ListToolsRequestSchema.
En unsplashx, definí cuatro herramientas:
search_photos: busca fotos por palabra clave, orientación y color.download_to_project: guarda un archivo de imagen en una carpeta local.insert_to_page: agrega una imagen con créditos del fotógrafo a un archivo Markdown o HTML.get_photo: obtiene metadatos técnicos de una foto específica.
Así es como definimos el esquema de la herramienta search_photos, explicando cada parámetro al modelo de IA:
const searchPhotosTool = {
name: "search_photos",
description: "Search for high-quality photos on Unsplash.",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Search keyword (e.g. 'abstract dark network')" },
count: { type: "number", description: "Number of results (max 30)", default: 5 },
orientation: {
type: "string",
enum: ["landscape", "portrait", "squarish"],
description: "Filter by orientation",
},
},
required: ["query"],
},
};A continuación, definimos el esquema de la herramienta download_to_project. Esta herramienta permite al agente guardar archivos localmente:
const downloadToProjectTool = {
name: "download_to_project",
description: "Download a photo locally to your project directory. Automatically tracks the download.",
inputSchema: {
type: "object",
properties: {
photo_id: { type: "string", description: "The Unsplash photo ID" },
destination_dir: { type: "string", description: "Absolute path to destination directory" },
file_name: { type: "string", description: "Desired file name (e.g., 'cover.jpeg')" },
width: { type: "number", description: "Optional width in pixels" },
height: { type: "number", description: "Optional height in pixels" },
quality: { type: "number", description: "Optional quality (1-100)", default: 80 },
},
required: ["photo_id", "destination_dir", "file_name"],
},
};Ahora registramos estas herramientas en el manejador de nuestro servidor:
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [searchPhotosTool, downloadToProjectTool],
};
});Una vez que el cliente de IA sabe qué herramientas existen, implementemos el código que se ejecuta cuando se invoca una herramienta.
Paso 4: Ejecutar herramientas y dar crédito a los fotógrafos
Cuando el asistente de IA decide ejecutar una herramienta, el servidor recibe una solicitud CallToolRequestSchema.
Veamos cómo manejamos cada herramienta.
1. Búsqueda de fotos (search_photos)
Al buscar fotos, llamamos al endpoint /search/photos y devolvemos una lista limpia con los enlaces de atribución para cada fotógrafo:
async function handleSearchPhotos(args) {
const results = await fetchUnsplash("/search/photos", {
query: args.query,
per_page: args.count || 5,
orientation: args.orientation,
});
const photos = results.results.map((p) => ({
id: p.id,
description: p.description || p.alt_description,
preview_url: p.urls.small,
photographer: p.user.name,
attribution: `Photo by [${p.user.name}](${p.user.links.html}?utm_source=unsplashx&utm_medium=referral) on [Unsplash](https://unsplash.com/?utm_source=unsplashx&utm_medium=referral)`,
}));
return {
content: [{ type: "text", text: JSON.stringify(photos, null, 2) }],
};
}Observa cómo construimos el campo attribution. Esto le proporciona al asistente de IA el enlace exacto en Markdown para dar crédito al fotógrafo.
2. Descargas de imágenes (download_to_project)
Al descargar una imagen, existe una regla importante impuesta por Unsplash: debemos llamar al endpoint /photos/:id/download. Esto permite a Unsplash contabilizar la descarga y reconocer al fotógrafo.
Así es como descargamos la imagen y la guardamos en el disco local:
async function handleDownloadToProject(args) {
const photo = await fetchUnsplash(`/photos/${args.photo_id}`);
// Requerido por Unsplash: registrar la descarga de la foto
await fetchUnsplash(`/photos/${args.photo_id}/download`);
const url = new URL(photo.urls.regular);
if (args.width) url.searchParams.set("w", args.width);
if (args.height) url.searchParams.set("h", args.height);
if (args.quality) url.searchParams.set("q", args.quality);
const response = await fetch(url.toString());
const buffer = Buffer.from(await response.arrayBuffer());
const fullPath = path.join(args.destination_dir, args.file_name);
await fs.mkdir(args.destination_dir, { recursive: true });
await fs.writeFile(fullPath, buffer);
const attribution = `Photo by [${photo.user.name}](${photo.user.links.html}?utm_source=unsplashx&utm_medium=referral) on [Unsplash](https://unsplash.com/?utm_source=unsplashx&utm_medium=referral)`;
return {
content: [
{
type: "text",
text: `Successfully downloaded photo to ${fullPath}.\n\nREQUIRED ATTRIBUTION:\n${attribution}`,
},
],
};
}Ahora conectamos estos manejadores al evento CallToolRequestSchema:
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === "search_photos") return await handleSearchPhotos(args);
if (name === "download_to_project") return await handleDownloadToProject(args);
throw new Error(`Unknown tool: ${name}`);
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
});Ahora conectemos la capa de transporte para que el servidor pueda ejecutarse.
Paso 5: Conectar el transporte Stdio
Los servidores MCP locales se comunican con los editores de IA a través de la entrada y salida estándar (stdio).
Iniciamos el servidor al final de index.js:
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Unsplash MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});Nota importante: Nunca uses
console.log()en un servidor MCP que funcione sobre stdio. La salida estándar está reservada para los mensajes JSON-RPC. Utiliza siempreconsole.error()para registrar tus mensajes y logs.
Ahora conectemos unsplashx a tu editor.
Paso 6: Conectar unsplashx a tu editor
Puedes ejecutar unsplashx directamente con npx en cualquier cliente MCP.
Claude Desktop
Edita el archivo ~/Library/Application Support/Claude/claude_desktop_config.json (en macOS) o %APPDATA%\Claude\claude_desktop_config.json (en Windows):
{
"mcpServers": {
"unsplashx": {
"command": "npx",
"args": ["-y", "unsplashx"],
"env": {
"UNSPLASH_ACCESS_KEY": "YOUR_UNSPLASH_KEY"
}
}
}
}Cursor y Antigravity IDE
En el espacio de trabajo de tu proyecto o en el archivo global .agents/mcp_config.json:
{
"mcpServers": {
"unsplashx": {
"command": "npx",
"args": ["-y", "unsplashx"],
"env": {
"UNSPLASH_ACCESS_KEY": "YOUR_UNSPLASH_KEY"
}
}
}
}Una vez configurado, puedes pedirle a tu asistente de forma natural:
"Encuentra una foto abstracta y oscura de arquitectura en Unsplash, descárgala en public/images/posts/my-post/cover.jpeg a 1200x630 y muéstrame el crédito del fotógrafo."
El asistente buscará la foto, seleccionará la opción más adecuada, descargará la imagen y mostrará la atribución correspondiente.

Resumen
Construir unsplashx me demostró lo sencillo que resulta crear herramientas personalizadas para asistentes de IA usando Model Context Protocol:
- Escribes tus herramientas en TypeScript y las expones con el SDK estándar de MCP.
- Los agentes de IA pueden buscar y descargar fotos reales sin salir del editor.
- Los fotógrafos reciben su debido crédito y el registro de descargas por su trabajo.
Si quieres usar el paquete o revisar el código completo, visita el repositorio en GitHub (danywalls/unsplashx) o instálalo directamente desde npm.
Bridging the gap between AI demos and real-world production applications.
Preguntas Frecuentes
¿Qué es unsplashx?
unsplashx es un servidor de código abierto para Model Context Protocol (MCP) que permite a asistentes de IA como Cursor, Claude y Antigravity buscar, descargar e insertar fotos reales de Unsplash con atribución automática al fotógrafo.
¿Por qué usar un servidor MCP de Unsplash en lugar de imágenes generadas por IA?
Permite a los desarrolladores acceder a fotografías de alta calidad tomadas por personas reales, respeta las directrices de seguimiento de descargas de Unsplash y garantiza enlaces de atribución correctos para los creadores.
¿Qué herramientas expone unsplashx a los agentes de IA?
Proporciona search_photos para filtrar por orientación o color, download_to_project para guardar archivos localmente, insert_to_page para actualizar archivos Markdown o HTML, y get_photo para obtener metadatos de las imágenes.
¿Cómo se comunican los editores de IA con el servidor MCP unsplashx?
Los clientes de IA inician el servidor como un proceso secundario local e intercambian mensajes JSON-RPC 2.0 a través de la entrada y salida estándar (stdio).
Artículos Relacionados
Cómo crear deep links de instalación en un clic para MCP en Cursor y VS Code
Deja de hacer que los usuarios editen archivos JSON a mano para instalar tu servidor MCP. Aprende a automatizar deep links de instalación en un clic para Cursor y VS Code.
WebMCP: Los agentes de IA son tus nuevos visitantes web
WebMCP es un nuevo estándar propuesto por Microsoft y Google. Convierte tu sitio web en una herramienta para agentes de IA. Aprende a usarlo hoy.
¿Qué es la Observabilidad de Agentes? Por qué y cuándo importa
Guía práctica sobre observabilidad para agentes de IA, OpenTelemetry, trazas, spans y la arquitectura en 3 capas para depurar sistemas de IA sin dar golpes a ciegas.
Compartir este artículo
Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.
Software real. Lecciones reales.
Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.
Únete a 13,800+ desarrolladores y lectores.
Cero spam. Cancela tu suscripción en cualquier momento.