FastMCP
Un marco de trabajo en TypeScript para construir servidores MCP con manejo de sesiones de cliente.
Documentación
FastMCP
Un framework TypeScript para construir servidores MCP capaces de manejar sesiones de clientes.
[!IMPORTANT]
FastMCP implementa las revisiones heredadas de MCP basadas en handshake (
2025-11-25y anteriores). No soporta la especificación actual,2026-07-28, que hizo el protocolo sin estado — sin handshakeinitializey sinMcp-Session-Id. Para un framework orientado a la especificación actual, usa ViteMCP.
Características
- Definición simple de Herramientas, Recursos y Prompts
- Conversión de OpenAPI a MCP
- Autenticación
- Paso de cabeceras a través del contexto
- Seguimiento de ID de sesión e ID de solicitud
- Sesiones
- Contenido de imagen
- Contenido de audio
- Integrado
- Registro de eventos
- Manejo de errores
- HTTP Streaming (con compatibilidad con SSE)
- Soporte HTTPS para conexiones seguras
- Rutas HTTP personalizadas para APIs REST, webhooks e interfaces de administración
- Soporte de runtime perimetral para Cloudflare Workers, Deno Deploy y más
- Modo sin estado para despliegues serverless
- CORS (habilitado por defecto)
- Notificaciones de progreso
- Salida en streaming
- Eventos de servidor tipados
- Autocompletado de argumentos de prompts
- Muestreo
- Elicitación
- Comportamiento de ping configurable
- Endpoint de verificación de salud
- Raíces
- Transporte en memoria para pruebas unitarias sin vincular un puerto
- CLI para pruebas y depuración
¿Cuándo usar FastMCP en lugar del SDK oficial?
FastMCP está construido sobre el SDK oficial.
El SDK oficial proporciona bloques fundamentales para construir MCPs, pero deja muchos detalles de implementación a tu cargo:
- Iniciar y configurar todos los componentes del servidor
- Manejo de conexiones
- Manejo de herramientas
- Manejo de respuestas
- Manejo de recursos
- Añadir prompts, recursos, plantillas de recursos
- Incrustar recursos, bloques de contenido de imagen y audio
FastMCP elimina esta complejidad proporcionando un framework con opiniones que:
- Maneja todo el código repetitivo automáticamente
- Proporciona APIs simples e intuitivas para tareas comunes
- Incluye mejores prácticas integradas y manejo de errores
- Te permite centrarte en la funcionalidad principal de tu MCP
Cuándo elegir FastMCP: Quieres construir servidores MCP rápidamente sin lidiar con detalles de implementación de bajo nivel.
Cuándo usar el SDK oficial: Necesitas máximo control o tienes requisitos arquitectónicos específicos. En este caso, te animamos a consultar la implementación de FastMCP para evitar errores comunes.
Instalación
npm install fastmcp
Inicio rápido
[!NOTE]
Hay muchos ejemplos del mundo real de uso de FastMCP. Consulta el Escaparate para ver ejemplos.
import { FastMCP } from "fastmcp";
import { z } from "zod"; // Or any validation library that supports Standard Schema
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
});
server.addTool({
name: "add",
description: "Add two numbers",
parameters: z.object({
a: z.number(),
b: z.number(),
}),
execute: async (args) => {
return String(args.a + args.b);
},
});
server.start({
transportType: "stdio",
});
¡Eso es todo! Ya tienes un servidor MCP funcionando.
Puedes probar el servidor en la terminal con:
git clone https://github.com/punkpeye/fastmcp.git
cd fastmcp
pnpm install
pnpm build
# Test the addition server example using CLI:
npx fastmcp dev src/examples/addition.ts
# Test the addition server example using MCP Inspector:
npx fastmcp inspect src/examples/addition.ts
Si buscas un repositorio plantilla para construir tu propio servidor MCP, consulta fastmcp-boilerplate.
Opciones de servidor remoto
FastMCP soporta múltiples opciones de transporte para comunicación remota, permitiendo que un MCP alojado en una máquina remota sea accesible a través de la red.
HTTP Streaming
HTTP streaming proporciona una alternativa más eficiente a SSE en entornos que lo soportan, con potencialmente mejor rendimiento para cargas útiles más grandes.
Puedes ejecutar el servidor con soporte de HTTP streaming:
server.start({
transportType: "httpStream",
httpStream: {
port: 8080,
},
});
Esto iniciará el servidor y escuchará conexiones de HTTP streaming en http://localhost:8080/mcp.
Nota: También puedes personalizar la ruta del endpoint usando la opción
httpStream.endpoint(el valor predeterminado es/mcp).
Nota: Para servir HTTP streaming y rutas OAuth integradas bajo una ruta de emisor, establece
httpStream.basePath(por ejemplo,/issuer1). Esto expone los metadatos del servidor de autorización en/.well-known/oauth-authorization-server/issuer1según RFC 8414.
Nota: Esto también inicia un servidor SSE en
http://localhost:8080/sse.
Puedes conectarte a estos servidores usando el transporte de cliente apropiado.
Para conexiones HTTP streaming:
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const client = new Client(
{
name: "example-client",
version: "1.0.0",
},
{
capabilities: {},
},
);
const transport = new StreamableHTTPClientTransport(
new URL(`http://localhost:8080/mcp`),
);
await client.connect(transport);
Para conexiones SSE:
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
const client = new Client(
{
name: "example-client",
version: "1.0.0",
},
{
capabilities: {},
},
);
const transport = new SSEClientTransport(new URL(`http://localhost:8080/sse`));
await client.connect(transport);
Soporte HTTPS
FastMCP soporta HTTPS para conexiones seguras proporcionando opciones de certificado SSL:
server.start({
transportType: "httpStream",
httpStream: {
port: 8443,
sslCert: "./path/to/cert.pem",
sslKey: "./path/to/key.pem",
sslCa: "./path/to/ca.pem", // Optional: for client certificate authentication
},
});
Esto iniciará el servidor con HTTPS en https://localhost:8443/mcp.
Opciones SSL:
sslCert- Ruta al archivo de certificado SSLsslKey- Ruta al archivo de clave privada SSLsslCa- (Opcional) Ruta al certificado CA para autenticación mutua TLS
Para pruebas, puedes generar certificados autofirmados:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"
Para producción, obtén certificados de una CA de confianza como Let's Encrypt.
Consulta el ejemplo de servidor https para una demostración completa.
Configuración CORS
Por defecto, FastMCP habilita CORS con un conjunto estándar de cabeceras permitidas. Puedes personalizar el comportamiento de CORS pasando una opción cors:
server.start({
transportType: "httpStream",
httpStream: {
port: 8080,
cors: {
origin: "http://localhost:3000",
allowedHeaders: [
"Content-Type",
"Authorization",
"Accept",
"Mcp-Session-Id",
"Mcp-Protocol-Version",
"Last-Event-Id",
"X-Custom-Header",
],
credentials: true,
},
},
});
La opción cors acepta:
true(predeterminado) - habilita CORS con configuración predeterminadafalse- deshabilita CORS por completo- Un objeto con estos campos:
origin- una cadena, un array de cadenas o una función(origin: string) => booleanallowedHeaders- una cadena o un array de cadenasmethods- array de métodos HTTP permitidosexposedHeaders- array de cabeceras a exponercredentials- booleano para permitir credencialesmaxAge- duración de caché de preflight en segundos
El tipo CorsOptions se exporta desde fastmcp por conveniencia.
Rutas HTTP personalizadas
FastMCP te permite añadir rutas HTTP personalizadas junto a los endpoints MCP, permitiéndote construir servicios HTTP completos que incluyan APIs REST, webhooks, interfaces de administración y más, todo dentro del mismo proceso de servidor.
const app = server.getApp();
// Add REST API endpoints with Hono's native API
app.get("/api/users", async (c) => {
return c.json({ users: [] });
});
// Handle path parameters
app.get("/api/users/:id", async (c) => {
return c.json({
userId: c.req.param("id"),
query: c.req.query(), // Access query parameters
});
});
// Handle POST requests with body parsing
app.post("/api/users", async (c) => {
const body = await c.req.json();
return c.json({ created: body }, 201);
});
// Serve HTML content
app.get("/admin", async (c) => {
return c.html("<html><body><h1>Admin Panel</h1></body></html>");
});
// Handle webhooks
app.post("/webhook/github", async (c) => {
const payload = await c.req.json();
const event = c.req.header("x-github-event");
// Process webhook...
return c.json({ received: true });
});
Las rutas personalizadas usan la aplicación Hono subyacente devuelta por server.getApp() y soportan:
- Métodos HTTP de Hono:
get,post,put,delete,patch,optionsy más - Parámetros de ruta (
:param) y comodines (*) - Análisis de cadenas de consulta
- JSON, texto, formularios y otros ayudantes de cuerpo de
c.req - Códigos de estado y cabeceras personalizados
- Middleware y grupos de rutas a través de Hono
Las rutas se comparan en el orden en que se registran, permitiéndote definir rutas específicas antes de patrones de captura general.
Rutas públicas y protegidas
Las rutas Hono personalizadas son públicas a menos que añadas tu propio middleware de ruta o comprobaciones de autenticación. Para rutas personalizadas protegidas, coloca tu lógica de autenticación en un ayudante reutilizable y llámalo tanto desde la opción authenticate de FastMCP como desde tus manejadores de ruta Hono:
import type { IncomingMessage } from "node:http";
import type { Context } from "hono";
import { FastMCP } from "fastmcp";
async function authenticateRequest(request: IncomingMessage) {
const apiKey = request.headers["x-api-key"];
return apiKey === "123" ? { userId: "123" } : undefined;
}
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
authenticate: authenticateRequest,
});
const app = server.getApp();
async function requireAuth(c: Context) {
const auth = await authenticateRequest(c.env.incoming);
if (!auth) {
return c.json({ error: "Authentication required" }, 401);
}
return auth;
}
// Public route - no authentication required
app.get("/.well-known/openid-configuration", async (c) => {
return c.json({
issuer: "https://example.com",
authorization_endpoint: "https://example.com/auth",
token_endpoint: "https://example.com/token",
});
});
// Private route - requires authentication
app.get("/api/users", async (c) => {
const auth = await requireAuth(c);
if (auth instanceof Response) {
return auth;
}
return c.json({ users: [] });
});
// Public static files
app.get("/public/*", async (c) => {
return c.text(`File: ${c.req.path}`);
});
Las rutas públicas son perfectas para:
- Endpoints de descubrimiento OAuth (
.well-known/*) - Verificaciones de salud y páginas de estado
- Activos estáticos y documentación
- Endpoints de webhook de servicios externos
- APIs públicas que no requieren autenticación de usuario
Consulta el ejemplo de rutas personalizadas para una demostración completa.
Soporte de runtime perimetral
FastMCP soporta runtimes perimetrales como Cloudflare Workers, permitiendo el despliegue de servidores MCP en el borde con latencia mínima a nivel mundial.
Elegir entre FastMCP y EdgeFastMCP
| Caso de uso | Clase | Importación |
|---|---|---|
| Node.js, Express, Bun | FastMCP | import { FastMCP } from "fastmcp" |
| Cloudflare Workers, Deno Deploy | EdgeFastMCP | import { EdgeFastMCP } from "fastmcp/edge" |
| Característica | FastMCP | EdgeFastMCP |
|---|---|---|
| Runtime | Node.js | Edge (aislamientos V8) |
| Método de inicio | server.start({ port }) | export default server |
| Transporte | stdio, httpStream, SSE | Solo HTTP Streamable |
| Sesiones | Con estado o sin estado | Solo sin estado |
| Sistema de archivos | Sí | No |
| OAuth/Autenticación | Opción authenticate integrada | Usa middleware Hono (integrado planificado) |
| Rutas personalizadas | server.getApp() | server.getApp() |
Nota: La autenticación integrada para EdgeFastMCP está planificada para una versión futura. Tanto FastMCP como EdgeFastMCP usan Hono internamente, por lo que no hay barrera técnica: EdgeFastMCP simplemente se escribió antes de que se añadiera OAuth a FastMCP. Se aceptan PRs para añadir una opción
authenticateque acepteRequestweb en lugar dehttp.IncomingMessagede Node.js.Mientras tanto, usa middleware Hono:
const app = server.getApp(); app.use("/api/*", async (c, next) => { if (c.req.header("authorization") !== "Bearer secret") { return c.json({ error: "Unauthorized" }, 401); } await next(); });
Cloudflare Workers
Para desplegar FastMCP en Cloudflare Workers, usa la clase EdgeFastMCP de la subruta /edge:
import { EdgeFastMCP } from "fastmcp/edge";
import { z } from "zod";
const server = new EdgeFastMCP({
name: "My Edge Server",
version: "1.0.0",
description: "MCP server running on Cloudflare Workers",
});
// Add tools, resources, prompts as usual
server.addTool({
name: "greet",
description: "Greet someone",
parameters: z.object({
name: z.string(),
}),
execute: async ({ name }) => {
return `Hello, ${name}! Served from the edge.`;
},
});
// Export the server as the default (required for Cloudflare Workers)
export default server;
Diferencias de runtime perimetral
Al ejecutarse en runtimes perimetrales:
- Sin estado por defecto: Cada solicitud se maneja de forma independiente
- Sin acceso al sistema de archivos: Usa APIs fetch para datos externos
- Aislamientos V8: Inicios en frío rápidos y uso eficiente de recursos
- Despliegue global: Distribución automática a ubicaciones perimetrales
Rutas personalizadas en el borde
Puedes acceder a la aplicación Hono subyacente para añadir rutas HTTP personalizadas:
const app = server.getApp();
// Add a landing page
app.get("/", (c) => c.html("<h1>Welcome to my MCP server</h1>"));
// Add REST API endpoints
app.get("/api/status", (c) => c.json({ status: "ok" }));
Despliegue
Configura tu wrangler.toml:
name = "my-mcp-server"
main = "src/index.ts"
compatibility_date = "2024-01-01"
Despliega con:
wrangler deploy
Consulta el ejemplo edge-cloudflare-worker para una demostración completa.
Modo sin estado
FastMCP soporta operación sin estado para HTTP streaming, donde cada solicitud se maneja de forma independiente sin mantener sesiones persistentes. Esto es ideal para entornos serverless, despliegues con balanceo de carga o cuando no se requiere estado de sesión.
En modo sin estado:
- No se rastrean sesiones en el servidor
- Cada solicitud crea una sesión temporal que se descarta después de la respuesta
- Menor uso de memoria y mejor escalabilidad
- Perfecto para entornos de despliegue sin estado
Puedes habilitar el modo sin estado añadiendo la opción stateless: true:
server.start({
transportType: "httpStream",
httpStream: {
port: 8080,
stateless: true,
},
});
Nota: El modo sin estado solo está disponible con transporte HTTP streaming. Las características que dependen de sesiones persistentes (como estado específico de sesión) no estarán disponibles en modo sin estado.
También puedes habilitar el modo sin estado usando argumentos CLI o variables de entorno:
# Via CLI argument
npx fastmcp dev src/server.ts --transport http-stream --port 8080 --stateless true
# Via environment variable
FASTMCP_STATELESS=true npx fastmcp dev src/server.ts
El endpoint de verificación de salud /ready indicará cuando el servidor esté ejecutándose en modo sin estado:
{
"mode": "stateless",
"ready": 1,
"status": "ready",
"total": 1
}
Conceptos principales
Herramientas
Las herramientas en MCP permiten a los servidores exponer funciones ejecutables que pueden ser invocadas por clientes y usadas por LLMs para realizar acciones.
FastMCP usa la especificación Standard Schema para definir parámetros de herramientas. Esto te permite usar tu biblioteca de validación de esquemas preferida (como Zod, ArkType o Valibot) siempre que implemente la especificación.
Ejemplo con Zod:
import { z } from "zod";
server.addTool({
name: "fetch-zod",
description: "Fetch the content of a url (using Zod)",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return await fetchWebpageContent(args.url);
},
});
Ejemplo con ArkType:
import { type } from "arktype";
server.addTool({
name: "fetch-arktype",
description: "Fetch the content of a url (using ArkType)",
parameters: type({
url: "string",
}),
execute: async (args) => {
return await fetchWebpageContent(args.url);
},
});
Ejemplo con Valibot:
Valibot requiere la dependencia par @valibot/to-json-schema.
import * as v from "valibot";
server.addTool({
name: "fetch-valibot",
description: "Fetch the content of a url (using Valibot)",
parameters: v.object({
url: v.string(),
}),
execute: async (args) => {
return await fetchWebpageContent(args.url);
},
});
Ejemplo con JSON Schema plano:
Si ya tienes un JSON Schema — de un documento OpenAPI, un archivo de configuración u
otro servidor — jsonSchemaAdapter lo envuelve para que pueda usarse directamente, sin
ninguna librería de esquemas de por medio.
Requiere la dependencia par ajv, que realiza la validación, además de
ajv-formats si tu esquema usa palabras clave format como email o uri.
Ambas se importan la primera vez que se llama a una herramienta, por lo que los servidores
que no usan esto no pagan nada por ello.
npm install ajv ajv-formats
import { jsonSchemaAdapter } from "fastmcp";
server.addTool({
name: "fetch-json-schema",
description: "Fetch the content of a url (using plain JSON Schema)",
parameters: jsonSchemaAdapter({
type: "object",
properties: {
url: { type: "string", format: "uri" },
},
required: ["url"],
}),
execute: async (args) => {
const { url } = args as { url: string };
return await fetchWebpageContent(url);
},
});
Funciona también para outputSchema. Ten en cuenta que FastMCP anuncia los esquemas de entrada con
additionalProperties: false, sin importar lo que dijera tu esquema — el mismo tratamiento
que reciben los esquemas de Zod y Valibot. La excepción es un objeto compuesto solo por
propiedades adicionales: un diccionario (sin properties y con un
esquema additionalProperties, como z.record()) conserva su esquema de valores, y un
argumento de forma libre ({ type: "object" }, o additionalProperties: true)
permanece abierto. Los esquemas de salida conservan la regla de propiedades adicionales que tu esquema
declaró.
A diferencia de las librerías de esquemas anteriores, un JSON Schema plano no incluye tipos
de TypeScript, por lo que execute recibe argumentos unknown. Haz el cast o la reducción de tipos tú mismo.
Herramientas sin parámetros
Al crear herramientas que no requieren parámetros, tienes dos opciones:
-
Omitir la propiedad de parámetros por completo:
server.addTool({ name: "sayHello", description: "Say hello", // No parameters property execute: async () => { return "Hello, world!"; }, }); -
Definir explícitamente parámetros vacíos:
import { z } from "zod"; server.addTool({ name: "sayHello", description: "Say hello", parameters: z.object({}), // Empty object execute: async () => { return "Hello, world!"; }, });
[!NOTE]
Ambos enfoques son totalmente compatibles con todos los clientes MCP, incluido Cursor. FastMCP genera automáticamente el esquema adecuado en ambos casos.
Salida estructurada de herramientas
Las herramientas pueden declarar un outputSchema y devolver datos estructurados. FastMCP expone ese valor como structuredContent de MCP, al mismo tiempo que devuelve un respaldo de texto JSON para clientes que solo renderizan contenido de texto.
server.addTool({
name: "get-weather",
description: "Get weather for a city",
parameters: z.object({
city: z.string(),
}),
outputSchema: z.object({
temperature: z.number(),
humidity: z.number(),
}),
execute: async ({ city }) => {
const weather = await getWeather(city);
return {
temperature: weather.temperature,
humidity: weather.humidity,
};
},
});
También puedes devolver contenido de texto explícito y contenido estructurado juntos:
server.addTool({
name: "get-weather",
description: "Get weather for a city",
parameters: z.object({
city: z.string(),
}),
outputSchema: z.object({
temperature: z.number(),
humidity: z.number(),
}),
execute: async ({ city }) => {
const weather = await getWeather(city);
return {
content: [
{
type: "text",
text: `${city}: ${weather.temperature}F`,
},
],
structuredContent: {
temperature: weather.temperature,
humidity: weather.humidity,
},
};
},
});
Cuando se proporciona outputSchema, FastMCP valida structuredContent antes de enviar el resultado de la herramienta. La salida estructurada no válida se devuelve al cliente como un error de herramienta en lugar de violar silenciosamente el esquema anunciado.
Autorización de herramientas
Puedes controlar qué herramientas están disponibles para los usuarios autenticados añadiendo una función canAccess opcional a la definición de una herramienta. Esta función recibe el contexto de autenticación y debe devolver true si el usuario tiene permitido acceder a la herramienta.
server.addTool({
name: "admin-tool",
description: "An admin-only tool",
canAccess: (auth) => auth?.role === "admin",
execute: async () => "Welcome, admin!",
});
Un servidor sin una función authenticate no tiene autenticación que verificar, por lo que
cada sesión ve todas las herramientas. Con una, una sesión que termina sin autenticación —
en stdio, donde el servidor continúa cuando authenticate lanza una excepción o no devuelve
nada — no ve ninguna de las herramientas que tienen canAccess.
Devolver una cadena
execute puede devolver una cadena:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return "Hello, world!";
},
});
La última es equivalente a:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return {
content: [
{
type: "text",
text: "Hello, world!",
},
],
};
},
});
Devolver una lista
Si quieres devolver una lista de mensajes, puedes devolver un objeto con una propiedad content:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return {
content: [
{ type: "text", text: "First message" },
{ type: "text", text: "Second message" },
],
};
},
});
Devolver una imagen
Usa imageContent para crear un objeto de contenido para una imagen:
import { imageContent } from "fastmcp";
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return imageContent({
url: "https://example.com/image.png",
});
// or...
// return imageContent({
// path: "/path/to/image.png",
// });
// or...
// return imageContent({
// buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"),
// });
// or...
// return {
// content: [
// await imageContent(...)
// ],
// };
},
});
La función imageContent acepta las siguientes opciones:
url: La URL de la imagen.timeoutMs: Tiempo de espera opcional para la descarga de una URL en milisegundos (por defecto 30 segundos).path: La ruta al archivo de imagen.buffer: Los datos de la imagen como un buffer.
Solo se debe especificar uno de url, path o buffer.
El ejemplo anterior es equivalente a:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return {
content: [
{
type: "image",
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
mimeType: "image/png",
},
],
};
},
});
Comportamiento de ping configurable
FastMCP incluye un mecanismo de ping configurable para mantener la salud de la conexión. El comportamiento del ping se puede personalizar mediante las opciones del servidor:
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
ping: {
// Explicitly enable or disable pings (defaults vary by transport)
enabled: true,
// Configure ping interval in milliseconds (default: 5000ms)
intervalMs: 10000,
// Set log level for ping-related messages (default: 'debug')
logLevel: "debug",
},
});
Por defecto, el comportamiento del ping está optimizado para cada tipo de transporte:
- Habilitado para conexiones SSE y HTTP streaming (que se benefician del keep-alive)
- Deshabilitado para conexiones
stdio(donde los pings generalmente son innecesarios)
Este enfoque configurable ayuda a reducir la verbosidad de los registros y optimizar el rendimiento para diferentes escenarios de uso.
Mantener vivas las llamadas largas a herramientas (streamKeepalive)
Los pings viajan en el flujo independiente servidor-a-cliente, por lo que nunca mantienen abierta
la conexión de una llamada individual a una herramienta — y en modo stateless ese flujo
no existe en absoluto. Una herramienta que se ejecuta durante minutos sin producir salida deja
su conexión de respuesta en silencio, y un tiempo de espera de conexión inactiva frente al
servidor (el valor predeterminado de AWS ALB es 60 segundos) la cierra antes de que se escriba el resultado.
Esto aplica a ambos modos de sesión. Una implementación con estado mantiene su flujo independiente cálido con pings mientras la conexión que transporta la llamada a la herramienta queda inactiva y se cierra de todos modos; sin estado no tiene flujo independiente desde el principio.
streamKeepalive escribe periódicamente en el flujo de respuesta de la propia llamada a la herramienta
en curso, que es la conexión que de otro modo se cerraría. Es opcional:
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
streamKeepalive: {
// Opt in; disabled by default
enabled: true,
// Keep comfortably below the shortest idle timeout on the path
intervalMs: 20000,
},
});
Cada keepalive es un notifications/message (nivel configurable mediante logLevel,
por defecto debug) etiquetado con el registrador fastmcp-keepalive, relacionado con la llamada
a la herramienta que se está sirviendo. Los keepalives comienzan cuando una herramienta empieza a ejecutarse y se detienen cuando
la solicitud deja de esperarla — al tener éxito, error, tiempo de espera o cancelación
del cliente — por lo que las conexiones inactivas permanecen en silencio. Como los mensajes están relacionados
con la solicitud, esto funciona tanto en modo con estado como en modo stateless, y no necesita
soporte del cliente más allá de la notificación de registro estándar.
Límites que vale la pena conocer:
- No tiene efecto con
httpStream.enableJsonResponse, que almacena en búfer una única respuesta JSON en lugar de hacer streaming, por lo que no hay un flujo abierto en el que escribir. - Solo cubre llamadas a herramientas. Otras solicitudes de larga duración (
resources/read,prompts/get) siguen sin escribir nada hasta que terminan. - En
stdiono hay proxy que mantener vivo, por lo que habilitarlo allí solo añade tráfico de notificaciones.
Cancelar llamadas largas a herramientas (context.signal)
Cada execute recibe un AbortSignal que se dispara una vez que su resultado ya no puede
llegar a nadie. Reenvíalo a quien haga el trabajo real para que el trabajo se detenga
con la llamada en lugar de sobrevivirla:
server.addTool({
name: "fetch_report",
parameters: z.object({ url: z.string() }),
timeoutMs: 30000,
execute: async (args, { signal }) => {
const response = await fetch(args.url, { signal });
return await response.text();
},
});
Se aborta en cualquiera de estos tres eventos:
- El cliente canceló la llamada — envió
notifications/cancelled, que es lo que emite el SDK de MCP cuando un llamador aborta su propia solicitud. - La sesión terminó — el transporte se cerró, o la sesión se cerró explícitamente. Aquí no interviene ninguna notificación de cancelación.
- Transcurrió
timeoutMs—signal.reasones el mismoUserErrorque el llamador recibe, por lo que una herramienta puede distinguir un tiempo de espera de una cancelación.
La señal nunca se aborta después de que una llamada se completa normalmente, por lo que adjuntar una limpieza a ella es seguro.
Límites que vale la pena conocer:
- Nada se mata por ti. FastMCP deja de esperar a la herramienta, pero la
promesa que devolvió
executesigue ejecutándose hasta que se resuelve. Una herramienta que ignora la señal aún se ejecuta hasta completarse — solo que lo hace sin tener dónde informar. - Un cliente HTTP que desaparece a mitad de la solicitud no se detecta. El SDK de MCP solo
aborta la señal propia de una solicitud por un
notifications/cancelledexplícito, yStreamableHTTPServerTransportno trata un flujo de respuesta abandonado como un cierre de sesión. Un llamador que cuelga sin terminar su sesión (DELETE) deja la herramienta ejecutándose hasta que termina o agota el tiempo. EstablecetimeoutMsen cualquier cosa costosa en lugar de confiar en la detección de desconexión. loadno recibe uno. Los recursos, las plantillas de recursos y los avisos reciben un contexto más pequeño sinsignal.
Endpoint de verificación de salud
Cuando ejecutas FastMCP con el transporte httpStream puedes exponer opcionalmente un
endpoint HTTP simple que devuelve una respuesta de texto plano útil para verificaciones de
vitalidad del balanceador de carga o la orquestación de contenedores.
Habilita (o personaliza) el endpoint mediante la clave health en las opciones del servidor:
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
health: {
// Enable / disable (default: true)
enabled: true,
// Body returned by the endpoint (default: 'ok')
message: "healthy",
// Path that should respond (default: '/health')
path: "/healthz",
// HTTP status code to return (default: 200)
status: 200,
},
});
await server.start({
transportType: "httpStream",
httpStream: { port: 8080 },
});
Ahora una solicitud a http://localhost:8080/healthz devolverá:
HTTP/1.1 200 OK
content-type: text/plain
healthy
El endpoint se ignora cuando el servidor se inicia con el transporte stdio.
Gestión de raíces
FastMCP admite Roots — una característica que permite a los clientes proporcionar un conjunto de ubicaciones raíz similares a sistemas de archivos que se pueden enumerar y actualizar dinámicamente. La característica Roots se puede configurar o deshabilitar en las opciones del servidor:
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
roots: {
// Set to false to explicitly disable roots support
enabled: false,
// By default, roots support is enabled (true)
},
});
Esto proporciona los siguientes beneficios:
- Mejor compatibilidad con diferentes clientes que pueden no admitir Roots
- Menos registros de errores al conectarse a clientes que no implementan la capacidad de roots
- Control más explícito sobre las capacidades del servidor MCP
- Degradación gradual cuando la funcionalidad de roots no está disponible
Puedes escuchar los cambios de raíz en tu servidor:
server.on("connect", (event) => {
const session = event.session;
// Access the current roots
console.log("Initial roots:", session.roots);
// Listen for changes to the roots
session.on("rootsChanged", (event) => {
console.log("Roots changed:", event.roots);
});
});
Cuando un cliente no admite roots o cuando la funcionalidad de roots está explícitamente deshabilitada, estas operaciones manejarán la situación de manera elegante sin lanzar errores.
Devolver un audio
Usa audioContent para crear un objeto de contenido para un audio:
import { audioContent } from "fastmcp";
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return audioContent({
url: "https://example.com/audio.mp3",
});
// or...
// return audioContent({
// path: "/path/to/audio.mp3",
// });
// or...
// return audioContent({
// buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"),
// });
// or...
// return {
// content: [
// await audioContent(...)
// ],
// };
},
});
La función audioContent acepta las siguientes opciones:
url: La URL del audio.timeoutMs: Tiempo de espera opcional para la descarga de una URL en milisegundos (por defecto 30 segundos).path: La ruta al archivo de audio.buffer: Los datos del audio como un buffer.
Solo se debe especificar uno de url, path o buffer.
El ejemplo anterior es equivalente a:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return {
content: [
{
type: "audio",
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
mimeType: "audio/mpeg",
},
],
};
},
});
Devolver tipo combinado
Puedes combinar varios tipos de esta manera y enviarlos de vuelta a la IA
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return {
content: [
{
type: "text",
text: "Hello, world!",
},
{
type: "image",
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
mimeType: "image/png",
},
{
type: "audio",
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
mimeType: "audio/mpeg",
},
],
};
},
// or...
// execute: async (args) => {
// const imgContent = await imageContent({
// url: "https://example.com/image.png",
// });
// const audContent = await audioContent({
// url: "https://example.com/audio.mp3",
// });
// return {
// content: [
// {
// type: "text",
// text: "Hello, world!",
// },
// imgContent,
// audContent,
// ],
// };
// },
});
Registrador personalizado
FastMCP te permite proporcionar una implementación de registrador personalizada para controlar cómo el servidor registra mensajes. Esto es útil para integrarse con la infraestructura de registro existente o personalizar el formato de registro.
import { FastMCP, Logger } from "fastmcp";
class CustomLogger implements Logger {
debug(...args: unknown[]): void {
console.log("[DEBUG]", new Date().toISOString(), ...args);
}
error(...args: unknown[]): void {
console.error("[ERROR]", new Date().toISOString(), ...args);
}
info(...args: unknown[]): void {
console.info("[INFO]", new Date().toISOString(), ...args);
}
log(...args: unknown[]): void {
console.log("[LOG]", new Date().toISOString(), ...args);
}
warn(...args: unknown[]): void {
console.warn("[WARN]", new Date().toISOString(), ...args);
}
}
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
logger: new CustomLogger(),
});
Consulta src/examples/custom-logger.ts para ver ejemplos con Winston, Pino y registro basado en archivos.
Registro
Las herramientas pueden registrar mensajes al cliente usando el objeto log en el objeto de contexto:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args, { log }) => {
log.info("Downloading file...", {
url,
});
// ...
log.info("Downloaded file");
return "done";
},
});
El objeto log tiene los siguientes métodos:
debug(message: string, data?: SerializableValue)error(message: string, data?: SerializableValue)info(message: string, data?: SerializableValue)warn(message: string, data?: SerializableValue)
Un cliente puede elevar el nivel mínimo que desea con la solicitud logging/setLevel,
y FastMCP entonces descarta cualquier cosa menos severa en lugar de enviarla — por lo que
después de logging/setLevel con error, log.debug y log.info no llegan a nadie.
Hasta que un cliente lo solicite, se envía cada nivel. El nivel vigente se puede leer como
session.loggingLevel.
Errores
Los errores que están destinados a mostrarse al usuario deben lanzarse como instancias de UserError:
import { UserError } from "fastmcp";
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
if (args.url.startsWith("https://example.com")) {
throw new UserError("This URL is not allowed");
}
return "done";
},
});
Progreso
Las herramientas pueden informar el progreso llamando a reportProgress en el objeto de contexto:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args, { reportProgress }) => {
await reportProgress({
progress: 0,
total: 100,
});
// ...
await reportProgress({
progress: 100,
total: 100,
});
return "done";
},
});
reportProgress acepta un message legible por humanos opcional junto con los campos numéricos, que los clientes pueden mostrar junto al indicador de progreso:
await reportProgress({
progress: 40,
total: 100,
message: "Downloading chunk 4 of 10…",
});
Las notificaciones de progreso solo se emiten cuando el cliente opta por participar proporcionando un progressToken en la llamada a la herramienta; de lo contrario, reportProgress no hace nada. Como notifications/progress es parte de la especificación MCP (el campo message desde la revisión 2025-03-26), esta es la forma portátil de enviar actualizaciones incrementales durante una llamada a una herramienta de larga duración — consulta Salida en streaming a continuación para ver la diferencia.
Salida en streaming
FastMCP puede transmitir resultados parciales de las herramientas mientras aún se están ejecutando, lo que permite interfaces de usuario receptivas y comentarios en tiempo real. Esto es particularmente útil para:
- Operaciones de larga duración que generan contenido de forma incremental
- Generación progresiva de texto, imágenes u otros medios
- Operaciones donde los usuarios se benefician de ver resultados parciales inmediatos
[!IMPORTANT]
streamContentes una extensión de FastMCP, no parte de la especificación MCP. Emite una notificaciónnotifications/tool/streamContent, que la especificación MCP no define — a partir de la revisión2025-11-25no existe un mecanismo estándar para transmitir la salida de herramientas (SEP-2998 es la propuesta en curso para añadir uno).Los clientes descartan las notificaciones para las que no tienen un manejador registrado, de forma silenciosa y sin errores. Un cliente solo ve contenido transmitido si registra un manejador para el método (o establece un
fallbackNotificationHandler), y no se conoce ningún cliente que lo renderice como salida de herramienta — MCP Inspector, por ejemplo, lo registra en su panel de notificaciones mediante un manejador de respaldo, pero el resultado de la herramienta en sí solo muestra lo queexecutedevolvió. La transmisión es, por tanto, principalmente útil cuando también controlas el cliente — consulta Consumo de contenido transmitido a continuación. Si necesitas actualizaciones incrementales que funcionen en cualquier cliente, usareportProgresscon unmessageen su lugar.
Para transmitir desde una herramienta, usa el método streamContent:
server.addTool({
name: "generateText",
description: "Generate text incrementally",
parameters: z.object({
prompt: z.string(),
}),
annotations: {
streamingHint: true, // Advisory only; see below
readOnlyHint: true,
},
execute: async (args, { streamContent }) => {
// Send initial content immediately
await streamContent({ type: "text", text: "Starting generation...\n" });
// Simulate incremental content generation
const words = "The quick brown fox jumps over the lazy dog.".split(" ");
for (const word of words) {
await streamContent({ type: "text", text: word + " " });
await new Promise((resolve) => setTimeout(resolve, 300)); // Simulate delay
}
// Always return a final result. Returning nothing sends an empty tool
// result, so clients that ignore the streamed notifications see no output
// at all.
return "The quick brown fox jumps over the lazy dog.";
},
});
[!WARNING] Devolver
undefineddesdeexecuteproduce un resultado de herramienta concontentvacío. Si transmites todo y no devuelves nada, la llamada a la herramienta se resuelve con un resultado vacío sin indicación de que se haya perdido algo — incluso en clientes que registran la notificación. Devuelve también el resultado completo y trata el contenido transmitido puramente como una mejora de renderizado progresivo.
La anotación streamingHint es metadatos de asesoramiento. Se reenvía textualmente a los clientes en tools/list, pero no habilita ni limita streamContent, y FastMCP mismo nunca la lee. No se conoce ningún cliente que actúe sobre ella hoy, aunque SEP-2998 propone estandarizar el mismo nombre de anotación.
Consumo de contenido transmitido
Un cliente ve estas notificaciones solo si registra un manejador para el método (o establece un fallbackNotificationHandler):
import { z } from "zod";
const StreamContentNotificationSchema = z.object({
method: z.literal("notifications/tool/streamContent"),
params: z.object({
content: z.array(z.any()),
toolName: z.string(),
}),
});
client.setNotificationHandler(
StreamContentNotificationSchema,
(notification) => {
const { content, toolName } = notification.params;
// Render the partial content however you like.
},
);
Ten en cuenta que las notificaciones solo llevan toolName, no una solicitud o token de progreso, por lo que las llamadas concurrentes a la misma herramienta en una sesión no se pueden distinguir.
La transmisión funciona con todos los tipos de contenido (texto, imagen, audio) y se puede combinar con informes de progreso:
server.addTool({
name: "processData",
description: "Process data with streaming updates",
parameters: z.object({
datasetSize: z.number(),
}),
annotations: {
streamingHint: true,
},
execute: async (args, { streamContent, reportProgress }) => {
const total = args.datasetSize;
for (let i = 0; i < total; i++) {
// Standard progress notification: reaches every spec-compliant client
await reportProgress({
progress: i,
total,
message: `Processed ${i} of ${total} items`,
});
// Richer partial content: only reaches clients that opt in
if (i % 10 === 0) {
await streamContent({
type: "text",
text: `Processed ${i} of ${total} items...\n`,
});
}
await new Promise((resolve) => setTimeout(resolve, 50));
}
return "Processing complete!";
},
});
Elicitación
Las herramientas pueden solicitar información adicional al usuario durante la ejecución mediante elicitación, usando el método elicit en el objeto de contexto. El cliente debe anunciar el modo de capacidad elicitation correspondiente — elicitation: { form: {} } para solicitudes de formulario (el predeterminado) y/o elicitation: { url: {} } para solicitudes de URL.
server.addTool({
name: "delete-file",
description: "Delete a file",
parameters: z.object({
path: z.string(),
}),
execute: async (args, { elicit }) => {
const response = await elicit({
message: `Are you sure you want to delete ${args.path}?`,
requestedSchema: {
type: "object",
properties: {
confirmed: {
type: "boolean",
},
},
required: ["confirmed"],
},
});
if (response.action !== "accept" || !response.content?.confirmed) {
return "Deletion cancelled.";
}
// ...
return `Deleted ${args.path}`;
},
});
La respuesta action es "accept", "decline" o "cancel"; al aceptar, content contiene las respuestas del usuario que coinciden con requestedSchema. La elicitación también está disponible fuera de las herramientas mediante session.requestElicitation.
Anotaciones de herramientas
A partir de la Especificación MCP (2025-03-26), las herramientas pueden incluir anotaciones que proporcionan contexto y control más ricos al añadir metadatos sobre el comportamiento de una herramienta:
server.addTool({
name: "fetch-content",
description: "Fetch content from a URL",
parameters: z.object({
url: z.string(),
}),
annotations: {
title: "Web Content Fetcher", // Human-readable title for UI display
readOnlyHint: true, // Tool doesn't modify its environment
openWorldHint: true, // Tool interacts with external entities
},
execute: async (args) => {
return await fetchWebpageContent(args.url);
},
});
Las anotaciones disponibles son:
| Anotación | Tipo | Predeterminado | Descripción |
|---|---|---|---|
title | string | - | Un título legible para la herramienta, útil para la visualización en la interfaz de usuario |
readOnlyHint | boolean | false | Si es verdadero, indica que la herramienta no modifica su entorno |
destructiveHint | boolean | true | Si es verdadero, la herramienta puede realizar actualizaciones destructivas (solo tiene sentido cuando readOnlyHint es falso) |
idempotentHint | boolean | false | Si es verdadero, llamar a la herramienta repetidamente con los mismos argumentos no tiene efecto adicional (solo tiene sentido cuando readOnlyHint es falso) |
openWorldHint | boolean | true | Si es verdadero, la herramienta puede interactuar con un "mundo abierto" de entidades externas |
Estas anotaciones ayudan a los clientes y LLMs a comprender mejor cómo usar las herramientas y qué esperar al llamarlas.
Recursos
Recursos representan cualquier tipo de dato que un servidor MCP quiera poner a disposición de los clientes. Esto puede incluir:
- Contenido de archivos
- Capturas de pantalla e imágenes
- Archivos de registro
- Y más
Cada recurso se identifica mediante un URI único y puede contener datos de texto o binarios.
server.addResource({
uri: "file:///logs/app.log",
name: "Application Logs",
mimeType: "text/plain",
async load() {
return {
text: await readLogFile(),
};
},
});
[!NOTE]
loadpuede devolver múltiples recursos. Esto podría usarse, por ejemplo, para devolver una lista de archivos dentro de un directorio cuando se lee el directorio.async load() { return [ { text: "Contenido del primer archivo", }, { text: "Contenido del segundo archivo", }, ]; }
También puedes devolver contenido binario en load:
async load() {
return {
blob: 'base64-encoded-data'
};
}
load también recibe auth (el valor devuelto por tu función authenticate, si lo hay) y un objeto context como segundo y tercer argumento. context refleja los campos client, log, session y sessionId disponibles para tool.execute (consulta Seguimiento de ID de sesión y ID de solicitud); reportProgress y streamContent no se incluyen ya que están vinculados al token de progreso de una llamada de herramienta:
server.addResource({
uri: "file:///logs/app.log",
name: "Application Logs",
mimeType: "text/plain",
async load(auth, context) {
context.log.info("loading application logs", { requestedBy: auth?.userId });
return {
text: await readLogFile(),
};
},
});
Suscripción a actualizaciones de recursos
Los clientes pueden suscribirse a un recurso con el método MCP resources/subscribe para ser notificados cuando su contenido cambie. FastMCP anuncia la capacidad subscribe automáticamente para cualquier servidor que exponga recursos, rastrea las suscripciones de cada cliente y te permite emitir una actualización con sendResourceUpdated:
server.addResource({
uri: "file:///logs/app.log",
name: "Application Logs",
mimeType: "text/plain",
async load() {
return { text: await readLogFile() };
},
});
// Whenever the underlying data changes, notify subscribed clients:
await server.sendResourceUpdated("file:///logs/app.log");
sendResourceUpdated solo notifica a los clientes que se han suscrito al URI dado, por lo que es seguro llamarlo siempre que tus datos cambien. FastMCP también anuncia la capacidad listChanged para herramientas, recursos y prompts, y emite notifications/tools/list_changed / notifications/resources/list_changed / notifications/prompts/list_changed automáticamente cuando agregas o eliminas herramientas, recursos, plantillas de recursos o prompts en tiempo de ejecución.
Una sesión no puede mostrar un tipo de primitiva que nunca negoció. MCP establece las capacidades durante initialize y se mantienen durante toda la vida de la sesión, por lo que un cliente que se conectó mientras el servidor no tenía recursos no verá los recursos agregados posteriormente — FastMCP registra una advertencia nombrando la capacidad y deja esa sesión intacta. Registra uno de cada tipo que planees agregar antes de start(), o haz que el cliente se reconecte; las sesiones que se conecten después verán todo.
Plantillas de recursos
También puedes definir plantillas de recursos:
server.addResourceTemplate({
uriTemplate: "file:///logs/{name}.log",
name: "Application Logs",
mimeType: "text/plain",
arguments: [
{
name: "name",
description: "Name of the log",
required: true,
},
],
async load({ name }) {
return {
text: `Example log content for ${name}`,
};
},
});
Al igual que los recursos simples, load también recibe auth y context como segundo y tercer argumento (consulta Recursos).
Autocompletado de argumentos de plantillas de recursos
Proporciona funciones complete para los argumentos de plantillas de recursos para habilitar el autocompletado automático:
server.addResourceTemplate({
uriTemplate: "file:///logs/{name}.log",
name: "Application Logs",
mimeType: "text/plain",
arguments: [
{
name: "name",
description: "Name of the log",
required: true,
complete: async (value) => {
if (value === "Example") {
return {
values: ["Example Log"],
};
}
return {
values: [],
};
},
},
],
async load({ name }) {
return {
text: `Example log content for ${name}`,
};
},
});
Recursos integrados
FastMCP proporciona un método conveniente embedded() que simplifica la inclusión de recursos en las respuestas de herramientas. Esta característica reduce la duplicación de código y facilita la referencia a recursos desde dentro de las herramientas.
Uso básico
server.addTool({
name: "get_user_data",
description: "Retrieve user information",
parameters: z.object({
userId: z.string(),
}),
execute: async (args) => {
return {
content: [
{
type: "resource",
resource: await server.embedded(`user://profile/${args.userId}`),
},
],
};
},
});
Trabajo con plantillas de recursos
El método embedded() funciona perfectamente con plantillas de recursos:
// Define a resource template
server.addResourceTemplate({
uriTemplate: "docs://project/{section}",
name: "Project Documentation",
mimeType: "text/markdown",
arguments: [
{
name: "section",
required: true,
},
],
async load(args) {
const docs = {
"getting-started": "# Getting Started\n\nWelcome to our project!",
"api-reference": "# API Reference\n\nAuthentication is required.",
};
return {
text: docs[args.section] || "Documentation not found",
};
},
});
// Use embedded resources in a tool
server.addTool({
name: "get_documentation",
description: "Retrieve project documentation",
parameters: z.object({
section: z.enum(["getting-started", "api-reference"]),
}),
execute: async (args) => {
return {
content: [
{
type: "resource",
resource: await server.embedded(`docs://project/${args.section}`),
},
],
};
},
});
Trabajo con recursos directos
También funciona con recursos definidos directamente:
// Define a direct resource
server.addResource({
uri: "system://status",
name: "System Status",
mimeType: "text/plain",
async load() {
return {
text: "System operational",
};
},
});
// Use in a tool
server.addTool({
name: "get_system_status",
description: "Get current system status",
parameters: z.object({}),
execute: async () => {
return {
content: [
{
type: "resource",
resource: await server.embedded("system://status"),
},
],
};
},
});
Prompts
Prompts permiten a los servidores definir plantillas de prompts reutilizables y flujos de trabajo que los clientes pueden mostrar fácilmente a usuarios y LLMs. Proporcionan una forma potente de estandarizar y compartir interacciones comunes con LLMs.
server.addPrompt({
name: "git-commit",
description: "Generate a Git commit message",
arguments: [
{
name: "changes",
description: "Git diff or description of changes",
required: true,
},
],
load: async (args) => {
return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`;
},
});
Al igual que los recursos, load también recibe auth y context como segundo y tercer argumento (consulta Recursos):
server.addPrompt({
name: "git-commit",
description: "Generate a Git commit message",
arguments: [
{
name: "changes",
description: "Git diff or description of changes",
required: true,
},
],
load: async (args, auth, context) => {
context.log.debug("generating git commit prompt", { user: auth?.userId });
return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`;
},
});
Autocompletado de argumentos de prompts
Los prompts pueden proporcionar autocompletado para sus argumentos:
server.addPrompt({
name: "countryPoem",
description: "Writes a poem about a country",
load: async ({ name }) => {
return `Hello, ${name}!`;
},
arguments: [
{
name: "name",
description: "Name of the country",
required: true,
complete: async (value) => {
if (value === "Germ") {
return {
values: ["Germany"],
};
}
return {
values: [],
};
},
},
],
});
Autocompletado de argumentos de prompts usando enum
Si proporcionas un array enum para un argumento, el servidor proporcionará automáticamente completados para el argumento.
server.addPrompt({
name: "countryPoem",
description: "Writes a poem about a country",
load: async ({ name }) => {
return `Hello, ${name}!`;
},
arguments: [
{
name: "name",
description: "Name of the country",
required: true,
enum: ["Germany", "France", "Italy"],
},
],
});
OpenAPI
fromOpenAPI() (de fastmcp/openapi) convierte un documento OpenAPI 3.x en un servidor FastMCP, una herramienta por operación — manejando $refs externos (especificaciones de múltiples archivos), resolución de servers[0].url relativos y colisiones de aplanamiento de parámetros en el proceso:
import { fromOpenAPI } from "fastmcp/openapi";
const server = await fromOpenAPI({
spec: "https://petstore3.swagger.io/api/v3/openapi.json",
include: (operation) => operation.tags.includes("pet"),
});
await server.start({ transportType: "stdio" });
Consulta OpenAPI a MCP para la referencia completa de opciones, autenticación y limitaciones conocidas.
Autenticación
FastMCP admite autenticación OAuth 2.1 con proveedores preconfigurados, lo que te permite asegurar tu servidor con una configuración mínima.
OAuth con proveedores preconfigurados
Usa la opción auth con un proveedor para habilitar la autenticación OAuth:
import { FastMCP, getAuthSession, GoogleProvider, requireAuth } from "fastmcp";
const server = new FastMCP({
auth: new GoogleProvider({
baseUrl: "https://your-server.com",
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
name: "My Server",
version: "1.0.0",
});
server.addTool({
canAccess: requireAuth,
description: "Get user profile",
execute: async (_args, { session }) => {
const { accessToken } = getAuthSession(session);
const response = await fetch(
"https://www.googleapis.com/oauth2/v2/userinfo",
{
headers: { Authorization: `Bearer ${accessToken}` },
},
);
return JSON.stringify(await response.json());
},
name: "get-profile",
});
Proveedores disponibles:
| Proveedor | Importación | Caso de uso |
|---|---|---|
GoogleProvider | fastmcp | Google OAuth |
GitHubProvider | fastmcp | GitHub OAuth |
AzureProvider | fastmcp | Azure/Entra ID |
OAuthProvider | fastmcp | Cualquier proveedor OAuth 2.0 |
Proveedor OAuth genérico (para SAP, Auth0, Okta, etc.):
import { FastMCP, OAuthProvider } from "fastmcp";
const server = new FastMCP({
auth: new OAuthProvider({
authorizationEndpoint: process.env.OAUTH_AUTH_ENDPOINT!,
baseUrl: "https://your-server.com",
clientId: process.env.OAUTH_CLIENT_ID!,
clientSecret: process.env.OAUTH_CLIENT_SECRET!,
scopes: ["openid", "profile"],
tokenEndpoint: process.env.OAUTH_TOKEN_ENDPOINT!,
}),
name: "My Server",
version: "1.0.0",
});
Autorización de herramientas
Controla el acceso a las herramientas usando la propiedad canAccess con funciones auxiliares integradas:
import {
requireAuth,
requireScopes,
requireRole,
requireAll,
requireAny,
getAuthSession,
} from "fastmcp";
// Require any authenticated user
server.addTool({
canAccess: requireAuth,
name: "user-tool",
// ...
});
// Require specific OAuth scopes
server.addTool({
canAccess: requireScopes("read:user", "write:data"),
name: "scoped-tool",
// ...
});
// Require specific role
server.addTool({
canAccess: requireRole("admin"),
name: "admin-tool",
// ...
});
// Combine with AND logic
server.addTool({
canAccess: requireAll(requireAuth, requireRole("admin")),
name: "admin-only",
// ...
});
// Combine with OR logic
server.addTool({
canAccess: requireAny(requireRole("admin"), requireRole("moderator")),
name: "staff-tool",
// ...
});
Autorización personalizada:
Para lógica personalizada, pasa una función directamente:
server.addTool({
name: "custom-auth-tool",
canAccess: (auth) =>
auth?.role === "admin" && auth?.department === "engineering",
execute: async () => "Access granted!",
});
Extracción de datos de sesión:
Usa getAuthSession para acceso seguro por tipos a la sesión OAuth en tus funciones de ejecución de herramientas:
import { getAuthSession, GoogleSession } from "fastmcp";
server.addTool({
canAccess: requireAuth,
name: "get-profile",
execute: async (_args, { session }) => {
// Type-safe destructuring (throws if not authenticated)
const { accessToken } = getAuthSession(session);
// Or with provider-specific typing:
// const { accessToken } = getAuthSession<GoogleSession>(session);
const response = await fetch("https://api.example.com/user", {
headers: { Authorization: `Bearer ${accessToken}` },
});
return JSON.stringify(await response.json());
},
});
Nota: También puedes acceder a
session.accessTokendirectamente, pero debes manejar el caso en quesessionno esté definido. El auxiliargetAuthSessionlanza un error claro si la sesión no está autenticada, lo que lo hace más seguro cuando se usa concanAccess: requireAuth.
Autenticación personalizada
Para escenarios que no son OAuth (claves API, tokens personalizados), usa la opción authenticate:
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
authenticate: (request) => {
const apiKey = request.headers["x-api-key"];
if (apiKey !== "123") {
throw new Response(null, {
status: 401,
statusText: "Unauthorized",
});
}
return { id: 1, role: "user" };
},
});
server.addTool({
name: "sayHello",
execute: async (args, { session }) => {
return `Hello, ${session.id}!`;
},
});
Proxy OAuth
La opción auth usa el Proxy OAuth integrado de FastMCP que actúa como intermediario seguro entre clientes MCP y proveedores OAuth ascendentes. El proxy maneja el flujo completo de autorización OAuth 2.1, incluido el Registro Dinámico de Clientes (DCR), PKCE, gestión de consentimiento y gestión de tokens con cifrado y patrones de intercambio de tokens habilitados por defecto.
Características clave:
- 🔐 Seguro por defecto: Cifrado automático (AES-256-GCM) y patrón de intercambio de tokens
- 🚀 Configuración cero: Genera claves automáticamente y maneja flujos OAuth automáticamente
- 🔌 Proveedores preconfigurados: Soporte integrado para Google, GitHub y Azure
- 🎯 Cumplimiento RFC: Implementa DCR (RFC 7591), PKCE y OAuth 2.1
- 🔑 JWKS opcional: Soporte para verificación de tokens RS256/ES256 (mediante dependencia opcional
jose)
Inicio rápido:
import { FastMCP, getAuthSession, GoogleProvider, requireAuth } from "fastmcp";
const server = new FastMCP({
auth: new GoogleProvider({
baseUrl: "https://your-server.com",
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
name: "My Server",
version: "1.0.0",
});
server.addTool({
canAccess: requireAuth,
name: "protected-tool",
execute: async (_args, { session }) => {
const { accessToken } = getAuthSession(session);
// Use accessToken to call upstream APIs
return "Authenticated!";
},
});
Configuración avanzada:
Para más control sobre el comportamiento de OAuth, puedes usar la opción oauth directamente:
import { FastMCP } from "fastmcp";
import { GoogleProvider } from "fastmcp/auth";
const authProvider = new GoogleProvider({
baseUrl: "https://your-server.com",
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
scopes: ["openid", "profile", "email"],
});
const server = new FastMCP({
name: "My Server",
oauth: {
authorizationServer: authProvider
.getProxy()
.getAuthorizationServerMetadata(),
enabled: true,
proxy: authProvider.getProxy(),
},
version: "1.0.0",
});
Documentación:
- Características del proxy OAuth - Lista completa de características y capacidades
- Guía de implementación del proxy OAuth - Configuración y ajustes
- Comparación Python vs TypeScript - Comparación de características
Endpoints de descubrimiento OAuth
FastMCP también admite endpoints de descubrimiento OAuth para la integración directa con proveedores OAuth, compatible con Especificación MCP 2025-03-26 y Especificación MCP 2025-06-18. Esto proporciona endpoints de descubrimiento estándar que cumplen con RFC 8414 (Metadatos del servidor de autorización OAuth 2.0) y RFC 9470 (Metadatos de recursos protegidos OAuth 2.0):
import { FastMCP } from "fastmcp";
import buildGetJwks from "get-jwks";
import fastJwt, { type DecodedJwt } from "fast-jwt";
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
oauth: {
enabled: true,
authorizationServer: {
issuer: "https://auth.example.com",
authorizationEndpoint: "https://auth.example.com/oauth/authorize",
tokenEndpoint: "https://auth.example.com/oauth/token",
jwksUri: "https://auth.example.com/.well-known/jwks.json",
responseTypesSupported: ["code"],
},
protectedResource: {
resource: "mcp://my-server",
authorizationServers: ["https://auth.example.com"],
},
},
authenticate: async (request) => {
const authHeader = request.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
throw new Response(null, {
status: 401,
statusText: "Missing or invalid authorization header",
});
}
const token = authHeader.slice(7); // Remove 'Bearer ' prefix
// Validate OAuth JWT access token using OpenID Connect discovery
try {
// Create JWKS client for token verification
const getJwks = buildGetJwks();
// Create JWT verifier
const verify = fastJwt.createVerifier({
async key({ header }: DecodedJwt) {
const publicKey = await getJwks.getPublicKey({
kid: header.kid,
alg: header.alg,
domain: "https://auth.example.com",
});
return publicKey;
},
algorithms: ["RS256"],
});
// Verify the JWT token
const payload = await verify(token);
return {
userId: payload.sub,
scope: payload.scope,
email: payload.email,
// Include other claims as needed
};
} catch (error) {
throw new Response(null, {
status: 401,
statusText: "Invalid OAuth token",
});
}
},
});
Si tu servidor MCP se publica bajo una ruta de emisor, configura también la ruta base de transmisión HTTP:
server.start({
transportType: "httpStream",
httpStream: {
basePath: "/issuer1",
endpoint: "/mcp",
port: 8080,
},
});
Con esta configuración, FastMCP sirve los metadatos del servidor de autorización de la ruta del emisor en /.well-known/oauth-authorization-server/issuer1, mientras que los metadatos de recursos protegidos permanecen disponibles para el endpoint MCP en /.well-known/oauth-protected-resource/issuer1/mcp.
Esta configuración expone automáticamente los endpoints de descubrimiento OAuth:
/.well-known/oauth-authorization-server- Metadatos del servidor de autorización (RFC 8414)/.well-known/oauth-authorization-server<basePath>- Metadatos del servidor de autorización cuandohttpStream.basePathestá configurado (RFC 8414 Sección 3)/.well-known/oauth-protected-resource- Metadatos de recursos protegidos (RFC 9728)/.well-known/oauth-protected-resource<endpoint>- Metadatos de recursos protegidos en sub-ruta (MCP 2025-11-25)
Mecanismo de descubrimiento (Especificación MCP 2025-11-25):
Los clientes descubren los metadatos de recursos protegidos utilizando el siguiente orden de búsqueda:
- Encabezado WWW-Authenticate - Método principal (manejado automáticamente por mcp-proxy)
- Sub-ruta well-known -
/.well-known/oauth-protected-resource<endpoint>(por ejemplo,/.well-known/oauth-protected-resource/mcp) - Raíz well-known -
/.well-known/oauth-protected-resource(respaldo)
Tanto los endpoints de sub-ruta como los de raíz devuelven metadatos idénticos, garantizando compatibilidad con todas las implementaciones de clientes MCP.
Para la validación de tokens JWT, puedes usar bibliotecas como get-jwks y fast-jwt para tokens JWT OAuth.
Pasar encabezados a través del contexto
Si estás exponiendo tu servidor MCP a través de HTTP, es posible que desees permitir que los clientes proporcionen claves sensibles mediante encabezados, que luego se pueden pasar a las APIs con las que interactúan tus herramientas, permitiendo que cada cliente proporcione sus propias claves de API. Esto se puede hacer capturando los encabezados HTTP en la sección authenticate y almacenándolos en la sesión para que las herramientas los referencien más adelante.
import { FastMCP } from "fastmcp";
import { IncomingHttpHeaders } from "http";
// Define the session data type
interface SessionData {
headers: IncomingHttpHeaders;
[key: string]: unknown; // Add index signature to satisfy Record<string, unknown>
}
// Create a server instance
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
authenticate: async (request: any): Promise<SessionData> => {
// Authentication logic
return {
headers: request.headers,
};
},
});
// Tool to display HTTP headers
server.addTool({
name: "headerTool",
description: "Reads HTTP headers from the request",
execute: async (args: any, context: any) => {
const session = context.session as SessionData;
const headers = session?.headers ?? {};
const getHeaderString = (header: string | string[] | undefined) =>
Array.isArray(header) ? header.join(", ") : (header ?? "N/A");
const userAgent = getHeaderString(headers["user-agent"]);
const authorization = getHeaderString(headers["authorization"]);
return `User-Agent: ${userAgent}\nAuthorization: ${authorization}\nAll Headers: ${JSON.stringify(headers, null, 2)}`;
},
});
// Start the server
server.start({
transportType: "httpStream",
httpStream: {
port: 8080,
},
});
Un cliente que se conecte a esto podría verse así:
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
const transport = new StreamableHTTPClientTransport(
new URL(`http://localhost:8080/mcp`),
{
requestInit: {
headers: {
Authorization: "Test 123",
},
},
},
);
const client = new Client({
name: "example-client",
version: "1.0.0",
});
(async () => {
await client.connect(transport);
// Call a tool
const result = await client.callTool({
name: "headerTool",
arguments: {
arg1: "value",
},
});
console.log("Tool result:", result);
})().catch(console.error);
Lo que aparecería en la consola después de que el cliente se ejecute sería algo como esto:
Tool result: {
content: [
{
type: 'text',
text: 'User-Agent: node\n' +
'Authorization: Test 123\n' +
'All Headers: {\n' +
' "host": "localhost:8080",\n' +
' "connection": "keep-alive",\n' +
' "authorization": "Test 123",\n' +
' "content-type": "application/json",\n' +
' "accept": "application/json, text/event-stream",\n' +
' "accept-language": "*",\n' +
' "sec-fetch-mode": "cors",\n' +
' "user-agent": "node",\n' +
' "accept-encoding": "gzip, deflate",\n' +
' "content-length": "163"\n' +
'}'
}
]
}
Seguimiento de ID de sesión y ID de solicitud
FastMCP expone automáticamente los IDs de sesión y solicitud a los manejadores de herramientas a través del parámetro de contexto. Esto permite la gestión de estado por sesión y el seguimiento de solicitudes.
ID de sesión (context.sessionId):
- Disponible solo para transportes basados en HTTP (HTTP Stream, SSE)
- Extraído del encabezado
Mcp-Session-Id - Permanece constante en múltiples solicitudes del mismo cliente
- Útil para mantener estado por sesión, contadores o datos específicos del usuario
ID de solicitud (context.requestId):
- Disponible para todos los transportes cuando el cliente lo proporciona
- Único para cada solicitud individual
- Útil para el rastreo y depuración de solicitudes
import { FastMCP } from "fastmcp";
import { z } from "zod";
const server = new FastMCP({
name: "Session Counter Server",
version: "1.0.0",
});
// Per-session counter storage
const sessionCounters = new Map<string, number>();
server.addTool({
name: "increment_counter",
description: "Increment a per-session counter",
parameters: z.object({}),
execute: async (args, context) => {
if (!context.sessionId) {
return "Session ID not available (requires HTTP transport)";
}
const counter = sessionCounters.get(context.sessionId) || 0;
const newCounter = counter + 1;
sessionCounters.set(context.sessionId, newCounter);
return `Counter for session ${context.sessionId}: ${newCounter}`;
},
});
server.addTool({
name: "show_ids",
description: "Display session and request IDs",
parameters: z.object({}),
execute: async (args, context) => {
return `Session ID: ${context.sessionId || "N/A"}
Request ID: ${context.requestId || "N/A"}`;
},
});
server.start({
transportType: "httpStream",
httpStream: {
port: 8080,
},
});
Casos de uso:
- Gestión de estado por sesión: Mantener contadores, cachés o datos temporales únicos para cada sesión de cliente
- Autenticación y autorización de usuarios: Rastrear usuarios autenticados a través de solicitudes
- Gestión de recursos específicos de sesión: Asignar y gestionar recursos por sesión
- Implementaciones multi-tenant: Aislar datos y operaciones por sesión
- Rastreo de solicitudes: Rastrear solicitudes individuales para depuración y monitoreo
Ejemplo:
Consulta src/examples/session-id-counter.ts para un ejemplo completo que demuestra la gestión de contadores basada en sesión.
Notas:
- Los IDs de sesión se generan automáticamente por la capa de transporte MCP
- En modo sin estado, los IDs de sesión no se persisten entre solicitudes
- Para el transporte stdio,
sessionIdseráundefinedya que no existe el concepto de sesión HTTP
Proporcionar instrucciones
Puedes proporcionar instrucciones al servidor usando la opción instructions:
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
instructions:
'Instructions describing how to use the server and its features.\n\nThis can be used by clients to improve the LLM\'s understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt.',
});
Iconos y metadatos del servidor
Puedes anunciar iconos y metadatos relacionados para tu servidor. Los clientes que admiten iconos pueden mostrarlos en su interfaz:
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
websiteUrl: "https://example.com",
icons: [
{
src: "https://example.com/icon.png",
mimeType: "image/png",
sizes: ["48x48"],
},
],
});
Un title opcional también se pasa a MCP initialize (serverInfo), para clientes que prefieren un nombre para mostrar en lugar del name del servidor.
Sesiones
El objeto session es una instancia de FastMCPSession y describe las sesiones de cliente activas.
server.sessions;
Asignamos una nueva instancia de servidor para cada conexión de cliente para permitir la comunicación 1:1 entre un cliente y el servidor.
Eventos de servidor tipados
Puedes escuchar eventos emitidos por el servidor usando el método on:
server.on("connect", (event) => {
console.log("Client connected:", event.session);
});
server.on("disconnect", (event) => {
console.log("Client disconnected:", event.session);
});
FastMCPSession
FastMCPSession representa una sesión de cliente y proporciona métodos para interactuar con el cliente.
Consulta Sesiones para ejemplos de cómo obtener una instancia de FastMCPSession.
requestElicitation
requestElicitation crea una solicitud de elicitación para recopilar información adicional del usuario a través del cliente y devuelve la respuesta. El cliente debe anunciar el modo de capacidad elicitation correspondiente: elicitation: { form: {} } para solicitudes de formulario (el predeterminado) y/o elicitation: { url: {} } para solicitudes de URL.
await session.requestElicitation({
message: "What is your name?",
requestedSchema: {
type: "object",
properties: {
name: { type: "string" },
},
required: ["name"],
},
});
Dentro de una herramienta, prefiere el método elicit del objeto de contexto.
requestSampling
requestSampling crea una solicitud de muestreo y devuelve la respuesta.
await session.requestSampling({
messages: [
{
role: "user",
content: {
type: "text",
text: "What files are in the current directory?",
},
},
],
systemPrompt: "You are a helpful file system assistant.",
includeContext: "thisServer",
maxTokens: 100,
});
Opciones
requestSampling acepta un segundo parámetro opcional para opciones de solicitud:
await session.requestSampling(
{
messages: [
{
role: "user",
content: {
type: "text",
text: "What files are in the current directory?",
},
},
],
systemPrompt: "You are a helpful file system assistant.",
includeContext: "thisServer",
maxTokens: 100,
},
{
// Progress callback - called when progress notifications are received
onprogress: (progress) => {
console.log(`Progress: ${progress.progress}/${progress.total}`);
},
// Abort signal for cancelling the request
signal: abortController.signal,
// Request timeout in milliseconds (default: DEFAULT_REQUEST_TIMEOUT_MSEC)
timeout: 30000,
// Whether progress notifications reset the timeout (default: false)
resetTimeoutOnProgress: true,
// Maximum total timeout regardless of progress (no default)
maxTotalTimeout: 60000,
},
);
Opciones:
onprogress?: (progress: Progress) => void- Callback para notificaciones de progreso del extremo remotosignal?: AbortSignal- Señal de aborto para cancelar la solicitudtimeout?: number- Tiempo de espera de solicitud en milisegundosresetTimeoutOnProgress?: boolean- Si las notificaciones de progreso restablecen el tiempo de esperamaxTotalTimeout?: number- Tiempo de espera máximo total independientemente de las notificaciones de progreso
clientCapabilities
La propiedad clientCapabilities contiene las capacidades del cliente.
session.clientCapabilities;
loggingLevel
La propiedad loggingLevel describe el nivel de registro establecido por el cliente. Lee info hasta que el cliente envía logging/setLevel; los mensajes menos severos que el nivel que el cliente estableció se descartan en lugar de enviarse.
session.loggingLevel;
roots
La propiedad roots contiene las raíces establecidas por el cliente.
session.roots;
server
La propiedad server contiene una instancia del servidor MCP asociada con la sesión.
session.server;
Eventos de sesión tipados
Puedes escuchar eventos emitidos por la sesión usando el método on:
session.on("rootsChanged", (event) => {
console.log("Roots changed:", event.roots);
});
session.on("error", (event) => {
console.error("Error:", event.error);
});
Ejecutar tu servidor
Pruebas unitarias con transporte en memoria
server.connect(transport) adjunta el servidor a un transporte que construyes tú mismo, en lugar de permitir que start() cree uno. Combinado con el InMemoryTransport del SDK, esto te permite ejecutar un servidor en proceso: sin puerto que vincular, sin subproceso que generar, que es generalmente lo que deseas para probar un servidor stdio:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
async function createTestClient(server: FastMCP) {
const [clientTransport, serverTransport] =
InMemoryTransport.createLinkedPair();
const client = new Client({ name: "test-client", version: "0.0.0" });
const [session] = await Promise.all([
server.connect(serverTransport),
client.connect(clientTransport),
]);
return { client, session };
}
test("adds two numbers", async () => {
const { client } = await createTestClient(server);
expect(
await client.callTool({ arguments: { a: 2, b: 3 }, name: "add" }),
).toEqual({
content: [{ text: "5", type: "text" }],
});
await client.close();
});
La sesión se construye a partir de las herramientas, recursos y prompts registrados en la instancia, exactamente como start() la construye, por lo que tus pruebas ejercitan la misma conexión que usa el servidor real, incluido el filtrado de canAccess y los eventos connect/disconnect.
Pasa la autenticación de sesión como segundo argumento, equivalente a lo que tu función authenticate devolvería:
await server.connect(serverTransport, { id: 7, role: "admin" });
connect devuelve el FastMCPSession, por lo que puedes hacer afirmaciones sobre session.clientCapabilities, session.roots y el resto. El ciclo de vida del transporte te pertenece: stop() no cierra los transportes pasados a connect, así que cierra el cliente (y la sesión, si necesitas que disconnect se active) cuando la prueba termine.
Probar con mcp-cli
La forma más rápida de probar y depurar tu servidor es con fastmcp dev:
npx fastmcp dev server.js
npx fastmcp dev server.ts
Esto ejecutará tu servidor con mcp-cli para probar y depurar tu servidor MCP en la terminal.
Para llamar a una herramienta de forma no interactiva (por ejemplo, en scripts o pruebas automatizadas), pasa --tool y JSON opcional --args:
npx fastmcp dev server.ts --tool add --args '{"a":1,"b":2}'
Esto imprime el resultado de la herramienta como JSON y sale, en lugar de abrir el inspector interactivo. --watch no tiene efecto en este modo, ya que el servidor se inicia para una sola llamada.
Inspeccionar con MCP Inspector
Otra forma es usar el MCP Inspector oficial para inspeccionar tu servidor con una interfaz web:
npx fastmcp inspect server.ts
Preguntas frecuentes
¿Cómo usar con Claude Desktop?
Sigue la guía https://modelcontextprotocol.io/quickstart/user y agrega la siguiente configuración:
{
"mcpServers": {
"my-mcp-server": {
"command": "npx",
"args": ["tsx", "/PATH/TO/YOUR_PROJECT/src/index.ts"],
"env": {
"YOUR_ENV_VAR": "value"
}
}
}
}
¿Cómo ejecutar FastMCP detrás de un proxy?
Consulta este problema para un ejemplo de uso de FastMCP con express y http-proxy-middleware.
Vitrina
[!NOTA]
Si has desarrollado un servidor usando FastMCP, ¡por favor envía un PR para mostrarlo aquí!
[!NOTA]
Si estás buscando un repositorio de plantilla para construir tu propio servidor MCP, consulta fastmcp-boilerplate.
- apinetwork/piapi-mcp-server - genera medios usando Midjourney/Flux/Kling/LumaLabs/Udio/Chrip/Trellis
- domdomegg/computer-use-mcp - controla tu computadora
- LiterallyBlah/Dradis-MCP – gestiona proyectos y vulnerabilidades en Dradis
- Meeting-Baas/meeting-mcp - crea bots de reuniones, busca transcripciones y gestiona datos de grabación
- drumnation/unsplash-smart-mcp-server – permite a los agentes de IA buscar, recomendar y entregar fotos de stock profesionales de Unsplash sin problemas
- ssmanji89/halopsa-workflows-mcp - integración de flujos de trabajo HaloPSA con asistentes de IA
- aiamblichus/mcp-chat-adapter – proporciona una interfaz limpia para que los LLM usen finalización de chat
- eyaltoledano/claude-task-master – gestor avanzado de proyectos/tareas de IA impulsado por FastMCP
- cswkim/discogs-mcp-server - se conecta a la API de Discogs para interactuar con tu colección de música
- Panzer-Jack/feuse-mcp - Herramientas MCP útiles para frontend - Utilidades esenciales para desarrolladores web para automatizar la integración de API y la generación de código
- sunra-ai/sunra-clients - Sunra.ai es una plataforma de medios generativos construida para desarrolladores, que proporciona capacidades de inferencia de modelos de IA de alto rendimiento.
- foxtrottwist/shortcuts-mcp - conecta Claude a Atajos de macOS para automatización del sistema, integración de aplicaciones y flujos de trabajo interactivos
Agradecimientos
- FastMCP está inspirado en la implementación en Python de Jonathan Lowin.
- Partes del código base fueron adoptadas de LiteMCP.
- Partes del código base fueron adoptadas de Model Context protocolでSSEをやってみる.
Este proyecto se prueba con BrowserStack.