sova
Un marco de TypeScript para crear servidores MCP capaces de manejar sesiones de cliente.
[!NOTA]
Para una implementación de Python, consulte sova .
Características
Related MCP server: MCP Framework
Instalación
Inicio rápido
[!NOTA]
Existen numerosos ejemplos reales del uso de sova en la naturaleza. Vea ejemplos en el Showcase .
import { sova } from "sova";
import { z } from "zod"; // Or any validation library that supports Standard Schema
const server = new sova({
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",
});
¡Listo! Ya tienes un servidor MCP funcionando.
Puedes probar el servidor en la terminal con:
git clone https://github.com/bounded1999/sova.git
cd sova
pnpm install
pnpm build
# Test the addition server example using CLI:
npx sova dev src/examples/addition.ts
# Test the addition server example using MCP Inspector:
npx sova inspect src/examples/addition.ts
SSE
Los eventos enviados por el servidor (SSE) proporcionan un mecanismo para que los servidores envíen actualizaciones en tiempo real a los clientes mediante una conexión HTTPS. En el contexto de MCP, SSE se utiliza principalmente para habilitar la comunicación remota de MCP, lo que permite acceder a un MCP alojado en una máquina remota y retransmitir actualizaciones a través de la red.
También puedes ejecutar el servidor con soporte SSE:
server.start({
transportType: "sse",
sse: {
endpoint: "/sse",
port: 8080,
},
});
Esto iniciará el servidor y escuchará conexiones SSE en http://localhost:8080/sse .
Luego puede usar SSEClientTransport para conectarse al servidor:
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);
Conceptos básicos
Herramientas
Las herramientas en MCP permiten a los servidores exponer funciones ejecutables que pueden ser invocadas por los clientes y utilizadas por los LLM para realizar acciones.
Sova utiliza la especificación del Esquema Estándar para definir los parámetros de la herramienta. Esto le permite usar su biblioteca de validación de esquemas preferida (como Zod, ArkType o Valibot) siempre que implemente la especificación.
Ejemplo de 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 de 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 de Valibot:
Valibot requiere la dependencia de pares @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);
},
});
Devolviendo 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!";
},
});
Esto último 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!",
},
],
};
},
});
Devolviendo una lista
Si desea devolver una lista de mensajes, puede 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" },
],
};
},
});
Devolviendo una imagen
Utilice imageContent para crear un objeto de contenido para una imagen:
import { imageContent } from "sova";
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 toma las siguientes opciones:
url : La URL de la imagen.
path : la ruta al archivo de imagen.
buffer : Los datos de la imagen como buffer.
Solo se debe especificar uno de los siguientes: 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",
},
],
};
},
});
Devolviendo un audio
Utilice audioContent para crear un objeto de contenido para un audio:
import { audioContent } from "sova";
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 toma las siguientes opciones:
Solo se debe especificar uno de los siguientes: 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",
},
],
};
},
});
Tipo de combinación de retorno
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 = imageContent({
// url: "https://example.com/image.png",
// });
// const audContent = audioContent({
// url: "https://example.com/audio.mp3",
// });
// return {
// content: [
// {
// type: "text",
// text: "Hello, world!",
// },
// imgContent,
// audContent,
// ],
// };
// },
});
Explotación florestal
Las herramientas pueden registrar mensajes en el cliente utilizando 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)
Errores
Los errores que se deben mostrar al usuario deben generarse como instancias UserError :
import { UserError } from "sova";
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 reportProgress en el objeto de contexto:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args, { reportProgress }) => {
reportProgress({
progress: 0,
total: 100,
});
// ...
reportProgress({
progress: 100,
total: 100,
});
return "done";
},
});
Anotaciones de herramientas
A partir de la Especificación MCP (26/03/2025), las herramientas pueden incluir anotaciones que brindan un contexto y un control más completos al agregar 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 | Por defecto | Descripción |
title
| cadena | - | Un título legible para humanos para la herramienta, útil para la visualización de la interfaz de usuario |
readOnlyHint
| booleano | false
| Si es verdadero, indica que la herramienta no modifica su entorno. |
destructiveHint
| booleano | true
| Si es verdadero, la herramienta puede realizar actualizaciones destructivas (solo tiene sentido cuando readOnlyHint
es falso) |
idempotentHint
| booleano | false
| Si es verdadero, llamar a la herramienta repetidamente con los mismos argumentos no tiene ningún efecto adicional (solo es significativo cuando readOnlyHint
es falso) |
openWorldHint
| booleano | true
| Si es cierto, la herramienta puede interactuar con un "mundo abierto" de entidades externas. |
Estas anotaciones ayudan a los clientes y a los LLM a comprender mejor cómo utilizar las herramientas y qué esperar al llamarlas.
Recursos
Los recursos representan cualquier tipo de datos que un servidor MCP desea poner a disposición de los clientes. Esto puede incluir:
Cada recurso se identifica mediante un URI único y puede contener texto o datos binarios.
server.addResource({
uri: "file:///logs/app.log",
name: "Application Logs",
mimeType: "text/plain",
async load() {
return {
text: await readLogFile(),
};
},
});
[!NOTA]
load puede devolver múltiples recursos. Esto podría usarse, por ejemplo, para devolver una lista de archivos dentro de un directorio al leerlo.
async load() {
return [
{
text: "First file content",
},
{
text: "Second file content",
},
];
}
También puedes devolver contenido binario en load :
async load() {
return {
blob: 'base64-encoded-data'
};
}
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}`,
};
},
});
Completado automático de argumentos de plantillas de recursos
Proporcionar funciones complete para los argumentos de la plantilla de recursos para permitir la finalización automática:
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}`,
};
},
});
Indicaciones
Los avisos permiten a los servidores definir plantillas de avisos y flujos de trabajo reutilizables que los clientes pueden mostrar fácilmente a los usuarios y a los LLM. Ofrecen una potente herramienta para estandarizar y compartir interacciones comunes de LLM.
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}`;
},
});
Completado automático de argumentos rápidos
Los mensajes 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: [],
};
},
},
],
});
Completado automático de argumentos mediante enum
Si proporciona una matriz enum para un argumento, el servidor proporcionará automáticamente compleciones 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"],
},
],
});
Autenticación
Sova le permite authenticate clientes mediante una función personalizada:
import { AuthError } from "sova";
const server = new sova({
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",
});
}
// Whatever you return here will be accessible in the `context.session` object.
return {
id: 1,
};
},
});
Ahora puedes acceder a los datos de la sesión autenticada en tus herramientas:
server.addTool({
name: "sayHello",
execute: async (args, { session }) => {
return `Hello, ${session.id}!`;
},
});
Proporcionar instrucciones
Puede proporcionar instrucciones al servidor utilizando la opción instructions :
const server = new sova({
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.',
});
Sesiones
El objeto session es una instancia de sovaSession y describe sesiones de cliente activas.
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 tipificados
Puedes escuchar los eventos emitidos por el servidor utilizando el método on :
server.on("connect", (event) => {
console.log("Client connected:", event.session);
});
server.on("disconnect", (event) => {
console.log("Client disconnected:", event.session);
});
sovaSession
sovaSession representa una sesión de cliente y proporciona métodos para interactuar con el cliente.
Consulte Sesiones para obtener ejemplos de cómo obtener una instancia sovaSession .
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,
});
clientCapabilities
La propiedad clientCapabilities contiene las capacidades del cliente.
session.clientCapabilities;
loggingLevel
La propiedad loggingLevel describe el nivel de registro establecido por el cliente.
roots
La propiedad roots contiene las raíces establecidas por el cliente.
server
La propiedad server contiene una instancia del servidor MCP que está asociado con la sesión.
Eventos de sesión tipificados
Puedes escuchar los eventos emitidos por la sesión utilizando el método on :
session.on("rootsChanged", (event) => {
console.log("Roots changed:", event.roots);
});
session.on("error", (event) => {
console.error("Error:", event.error);
});
Ejecución de su servidor
Prueba con mcp-cli
La forma más rápida de probar y depurar su servidor es con sova dev :
npx sova dev server.js
npx sova dev server.ts
Esto ejecutará su servidor con mcp-cli para probar y depurar su servidor MCP en la terminal.
Inspeccionar con MCP Inspector
Otra forma es utilizar el MCP Inspector para inspeccionar su servidor con una interfaz web:
npx sova inspect server.ts
Preguntas frecuentes
¿Cómo utilizar con Claude Desktop?
Siga la guía https://modelcontextprotocol.io/quickstart/user y agregue la siguiente configuración:
{
"mcpServers": {
"my-mcp-server": {
"command": "npx",
"args": ["tsx", "/PATH/TO/YOUR_PROJECT/src/index.ts"],
"env": {
"YOUR_ENV_VAR": "value"
}
}
}
}
Expresiones de gratitud