Un framework de TypeScript para construir MCP servers sin estado que pueden desplegarse en entornos serverless y edge.
También conocido como vitemcp/server, @vitemcp/server
- Stars
- 22
- Herramientas
- 1
- Descargas, 30 días
- —
- Última versión
- —
- Último commit
- 11 nov 2026
- Licencia
- MIT
¿Qué es ViteMCP MCP?
ViteMCP es un MCP server. Un framework de TypeScript para construir MCP servers sin estado que pueden desplegarse en entornos serverless y edge. Expone 1 herramienta, como title.
Resumen generado a partir del README del proyecto
Acerca del MCP server de ViteMCP
ViteMCP es un framework de TypeScript diseñado para desarrolladores que necesitan construir MCP servers sin estado. Está orientado específicamente a la revisión del protocolo del 2026-07-28, que elimina la necesidad de un handshake de inicialización o de IDs de sesión. Esta arquitectura hace que el framework sea ideal para el despliegue en entornos serverless y edge como Cloudflare Workers o Deno Deploy, ya que cada solicitud es independiente.
El framework simplifica el proceso de desarrollo al gestionar tareas repetitivas como la administración de conexiones, el manejo de herramientas y el formato de respuestas. Los desarrolladores pueden definir herramientas, recursos y prompts utilizando la especificación Standard Schema, lo que permite la integración con librerías como Zod, ArkType o Valibot. También permite generar servidores completos a partir de documentos OpenAPI y ofrece soporte nativo para contenido de imagen y audio. Para las pruebas, incluye un transporte en memoria y un CLI para inspeccionar y depurar la funcionalidad del servidor sin necesidad de vincularlo a un puerto local.
Qué puedes hacer con él
- 1Automatización de la creación de servidores desde APIsPuedes construir rápidamente un servidor completo proporcionando un documento OpenAPI existente.
Escrito a partir del README del proyecto. Cada caso de uso se verifica con una cita del mismo.
Cómo instalar ViteMCP MCP
Formatos verificados el 10 nov 2026Añadir a CursorAñadir a GooseInstalar ViteMCP MCP en Claude Code
$ claude mcp add server -- npx -y @vitemcp/serverInstalar ViteMCP MCP en Cursor
~/.cursor/mcp.json
{
"mcpServers": {
"server": {
"command": "npx",
"args": [
"-y",
"@vitemcp/server"
]
}
}
}Instalar ViteMCP MCP en VS Code
.vscode/mcp.json
{
"servers": {
"server": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@vitemcp/server"
]
}
}
}Instalar ViteMCP MCP en VS Code CLI
$ code --add-mcp '{"name":"server","type":"stdio","command":"npx","args":["-y","@vitemcp/server"]}'Instalar ViteMCP MCP en Claude Desktop
claude_desktop_config.json
{
"mcpServers": {
"server": {
"command": "npx",
"args": [
"-y",
"@vitemcp/server"
]
}
}
}Instalar ViteMCP MCP en Codex
$ codex mcp add server -- npx -y @vitemcp/serverInstalar ViteMCP MCP en Codex config.toml
~/.codex/config.toml
[mcp_servers.server] command = "npx" args = ["-y", "@vitemcp/server"]
Instalar ViteMCP MCP en Gemini CLI
$ gemini mcp add -s user server npx -- -y @vitemcp/serverInstalar ViteMCP MCP en Gemini settings.json
~/.gemini/settings.json
{
"mcpServers": {
"server": {
"command": "npx",
"args": [
"-y",
"@vitemcp/server"
]
}
}
}Herramientas de ViteMCP MCP 1
Detectado desde el READMEtitle
| title | — |
Configuración
YOUR_ENV_VAR
| Nombre | Descripción | Tipo |
|---|---|---|
| YOUR_ENV_VAR | — |
Verificaciones automáticas
Ejecutado 12 nov 2026Informe completo →Pasó nuestras verificaciones automáticas. Esto no es una auditoría de seguridad: lee el código y dale al servidor el acceso más limitado que necesite. Cómo funcionan las verificaciones
- Repositorio de origen vinculado y accesibleAprobado
- Licencia de código abierto (OSI)Aprobado
- Commit o lanzamiento en el último añoAprobado
- El nombre del paquete no es similar a un paquete popularAprobado
- El endpoint remoto usa HTTPSNo verificado
- Las herramientas destructivas están etiquetadasAprobado
- Sin instrucciones ocultas para el modeloAprobado
- No hay avisos críticos en el paquete ni en sus dependenciasNo verificado
README
Ver en GitHubViteMCP
A TypeScript framework for building stateless MCP servers.
[!IMPORTANT]
ViteMCP targets MCP revision 2026-07-28, which made the protocol stateless. There is no
initializehandshake and noMcp-Session-Id: every request is self-contained and carries its own protocol version and client capabilities.If you are coming from a session-based version, see Migrating from the session-based API.
Contents
- Features
- When to use ViteMCP over the official SDK?
- Installation
- Quickstart
- Core Concepts
- Server Features
- Deployment
- Testing and Debugging
- FAQ
- Showcase
- Acknowledgements
Authentication has a dedicated reference — see the OAuth guide. So does OpenAPI conversion — see the OpenAPI guide.
Features
- Simple Tool, Resource, Prompt definition
- Authentication
- Per-request auth context
- Image content
- Audio content
- Embedded
- Error handling
- HTTP Streaming
- HTTPS Support for secure connections
- Custom HTTP routes for REST APIs, webhooks, and admin interfaces
- Edge Runtime Support for Cloudflare Workers, Deno Deploy, and more
- Stateless by construction — every request stands alone, so serverless just works
- CORS (enabled by default)
- Progress notifications
- Multi round-trip requests for asking the client for more input
- Prompt argument auto-completion
- Cacheable list results (
ttlMs/cacheScope) - Health-check endpoint
- Generate a whole server from an OpenAPI document
- In-memory transport for unit testing without binding a port
- CLI for testing and debugging
When to use ViteMCP over the official SDK?
ViteMCP is built on top of the official SDK.
The official SDK provides foundational blocks for building MCPs, but leaves many implementation details to you — all of which src/ViteMCP.ts handles on your behalf:
- Initiating and configuring all the server components
- Handling of connections
- Handling of tools
- Handling of responses
- Handling of resources
- Adding prompts, resources and resource templates
- Embedding resource, image and audio content blocks
ViteMCP eliminates this complexity by providing an opinionated framework that:
- Handles all the boilerplate automatically
- Provides simple, intuitive APIs for common tasks
- Includes built-in best practices and error handling
- Lets you focus on your MCP's core functionality
When to choose ViteMCP: You want to build MCP servers quickly without dealing with low-level implementation details.
When to use the official SDK: You need maximum control or have specific architectural requirements. In this case, we encourage referencing ViteMCP's implementation to avoid common pitfalls.
Installation
npm install @vitemcp/server
Quickstart
[!NOTE]
There are many real-world examples of using ViteMCP in the wild. See the Showcase for examples.
import { ViteMCP } from "@vitemcp/server";
import { z } from "zod"; // Or any validation library that supports Standard Schema
const server = new ViteMCP({
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",
});
That's it! You have a working MCP server.
You can test the server in terminal with:
git clone https://github.com/vitemcp/server.git
cd server
pnpm install
pnpm build
# Test the addition server example using CLI:
npx @vitemcp/server dev src/examples/addition.ts
# Test the addition server example using MCP Inspector:
npx @vitemcp/server inspect src/examples/addition.ts
If you are looking for something to start from, src/examples/ has runnable servers covering tools, authentication, custom routes and edge deployment.
Core Concepts
Tools
Tools in MCP allow servers to expose executable functions that can be invoked by clients and used by LLMs to perform actions.
ViteMCP uses the Standard Schema specification for defining tool parameters. This allows you to use your preferred schema validation library (like Zod, ArkType, or Valibot) as long as it implements the spec.
Zod Example:
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);
},
});
ArkType Example:
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);
},
});
Valibot Example:
Valibot requires the peer dependency @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);
},
});
Plain JSON Schema Example:
If you already have a JSON Schema — from an OpenAPI document, a config file, or
another server — jsonSchemaAdapter wraps it so it can be used directly, with
no schema library in between.
It requires the peer dependency ajv, which does the validation, plus
ajv-formats if your schema uses format keywords such as email or uri.
Both are imported the first time a tool is called, so servers that don't use
this pay nothing for it.
npm install ajv ajv-formats
import { jsonSchemaAdapter } from "@vitemcp/server";
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);
},
});
Works for outputSchema too, and is advertised as written — as are Zod and
ArkType schemas, which emit their own JSON Schema. Where one of those holds
something JSON Schema cannot express — a z.date(), a z.bigint(), an ArkType
narrow() — that part alone is advertised as open, as {} or as the type a
narrow() narrows, and the library still enforces it. A schema from any other
library (Valibot, for one) is converted to JSON Schema for tools/list only;
arguments and results are still validated by the library itself, so a
v.transform() reaches execute and a v.check() is enforced even though
JSON Schema can express neither. Converted input schemas get
additionalProperties: false on every object, except where a record or rest
schema (v.record(), v.objectWithRest()) describes the extra keys, or where
the keys are declared in the branches of a v.union(), v.variant() or
v.intersect(), or behind the $ref a v.lazy() becomes; converted output
schemas are left as they are. A tool whose schema cannot be converted at all,
or whose parameters do not describe an object, is left out of the server and
reported through the logger.
A plain JSON Schema is validated in the dialect its $schema declares —
2020-12, 2019-09, draft-07 or draft-06 — and as 2020-12 when it declares none,
which is how MCP clients read it.
Unlike the schema libraries above, a plain JSON Schema carries no TypeScript
types, so execute receives unknown arguments. Cast or narrow them yourself.
Tools Without Parameters
When creating tools that don't require parameters, you have two options:
-
Omit the parameters property entirely:
server.addTool({ name: "sayHello", description: "Say hello", // No parameters property execute: async () => { return "Hello, world!"; }, }); -
Explicitly define empty parameters:
import { z } from "zod"; server.addTool({ name: "sayHello", description: "Say hello", parameters: z.object({}), // Empty object execute: async () => { return "Hello, world!"; }, });
[!NOTE]
Both approaches are fully compatible with all MCP clients, including Cursor. ViteMCP automatically generates the proper schema in both cases.
Structured Tool Output
Tools can declare an outputSchema and return structured data. ViteMCP exposes that value as MCP structuredContent, while also returning a JSON text fallback for clients that only render text content.
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,
};
},
});
You can also return explicit text content and structured content together:
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,
},
};
},
});
When outputSchema is provided, ViteMCP validates structuredContent before sending the tool result. Invalid structured output is returned to the client as a tool error instead of silently violating the advertised schema.
ViteMCP tells a structured payload apart from MCP content blocks by shape, so a payload that looks like one of the other shapes needs saying outright. Return it under structuredContent and there is nothing left to infer — the JSON text fallback is still written for you:
server.addTool({
name: "get-document",
parameters: z.object({ id: z.string() }),
outputSchema: z.object({
title: z.string(),
content: z.array(z.string()),
}),
execute: async ({ id }) => {
const doc = await getDocument(id);
return {
structuredContent: { title: doc.title, content: doc.paragraphs },
};
},
});
Three payload shapes need it: an array-valued content field (as above), a resultType of "input_required", and a lone structuredContent field of your own. Everything else is inferred correctly — including a payload that has a structuredContent field among others. In particular a top-level type field is safe — including type: "text" and the other content-block kinds — because a declared outputSchema turns off the single-content-block shorthand.
The wrapper must stand alone: { structuredContent }, optionally with isError or _meta. An object carrying anything else is read as a payload in its own right, so keys execute meant to keep never reach the client.
Restricting who can call a tool
A tool's optional canAccess receives the request's auth context and returns whether the caller may use it. Tools it rejects are filtered out of tools/list entirely. A caller who may use none of them gets an empty list rather than an error.
server.addTool({
name: "admin-tool",
description: "An admin-only tool",
canAccess: (auth) => auth?.role === "admin",
execute: async () => "Welcome, admin!",
});
Built-in helpers — requireAuth, requireScopes, requireRole, requireAll, requireAny — cover the usual cases; see Tool Authorization.
Return types
execute may return a plain string, a content object, or an array of content
blocks. A bare string is shorthand for a single text block — these two are
equivalent:
execute: async () => "Hello, world!";
execute: async () => ({ content: [{ type: "text", text: "Hello, world!" }] });
| Return value | Produces |
|---|---|
"some string" |
one text block |
{ content: [...] } |
any mix of text, image, audio and resource blocks |
imageContent({ ... }) |
one image block |
audioContent({ ... }) |
one audio block |
imageContent and audioContent build a block from a url, a path, or a
buffer — exactly one of the three. Both accept timeoutMs to bound a URL
download (30 seconds by default).
import { audioContent, imageContent } from "@vitemcp/server";
server.addTool({
name: "fetch-image",
description: "Fetch an image",
parameters: z.object({ url: z.string() }),
execute: async (args) => imageContent({ url: args.url }),
// ...or imageContent({ path: "/path/to/image.png" })
// ...or imageContent({ buffer: Buffer.from(base64Png, "base64") })
});
Each helper returns a single block, so combine them under content to send
more than one:
execute: async () => ({
content: [
{ type: "text", text: "Here is what I found:" },
await imageContent({ url: "https://example.com/image.png" }),
await audioContent({ url: "https://example.com/audio.mp3" }),
],
});
Raw blocks work too when you already hold base64 data:
execute: async () => ({
content: [
{ type: "image", data: base64Png, mimeType: "image/png" },
{ type: "audio", data: base64Mp3, mimeType: "audio/mpeg" },
],
});
Tool Annotations
Tools can include annotations that provide richer context and control by adding metadata about a tool's behavior:
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);
},
});
The available annotations are:
| Annotation | Type | Default | Description |
|---|---|---|---|
title |
string | - | A human-readable title for the tool, useful for UI display |
readOnlyHint |
boolean | false |
If true, indicates the tool does not modify its environment |
destructiveHint |
boolean | true |
If true, the tool may perform destructive updates (only meaningful when readOnlyHint is false) |
idempotentHint |
boolean | false |
If true, calling the tool repeatedly with the same arguments has no additional effect (only meaningful when readOnlyHint is false) |
openWorldHint |
boolean | true |
If true, the tool may interact with an "open world" of external entities |
These annotations help clients and LLMs better understand how to use the tools and what to expect when calling them.
Resources
Resources represent any kind of data that an MCP server wants to make available to clients. This can include:
- File contents
- Screenshots and images
- Log files
- And more
Each resource is identified by a unique URI and can contain either text or binary data.
server.addResource({
uri: "file:///logs/app.log",
name: "Application Logs",
mimeType: "text/plain",
async load() {
return {
text: await readLogFile(),
};
},
});
[!NOTE]
loadcan return multiple resources. This could be used, for example, to return a list of files inside a directory when the directory is read.async load() { return [ { text: "First file content", }, { text: "Second file content", }, ]; }
You can also return binary contents in load:
async load() {
return {
blob: 'base64-encoded-data'
};
}
load receives a context object mirroring what tool.execute gets — auth, log, requestId and the multi-round-trip helpers. reportProgress is not included, since it is tied to a tool call's progress token:
server.addResource({
uri: "file:///logs/app.log",
name: "Application Logs",
mimeType: "text/plain",
async load(context) {
context.log.info("loading application logs", { requestedBy: auth?.userId });
return {
text: await readLogFile(),
};
},
});
Resource templates
You can also define resource templates:
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}`,
};
},
});
Like plain resources, load also receives auth and context as its second and third arguments (see Resources).
Resource template argument auto-completion
Provide complete functions for resource template arguments to enable automatic completion:
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}`,
};
},
});
Embedded Resources
ViteMCP provides a convenient embedded() method that simplifies including resources in tool responses. This feature reduces code duplication and makes it easier to reference resources from within tools.
Basic Usage
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}`),
},
],
};
},
});
Working with Resource Templates
The embedded() method works seamlessly with resource templates. A URI reaches
the same template, with the same arguments, as it would through
resources/read:
// 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}`),
},
],
};
},
});
Working with Direct Resources
It also works with directly defined resources:
// 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"),
},
],
};
},
});
Preguntas sobre ViteMCP MCP
¿Cómo agrego ViteMCP MCP a Claude Code?
Ejecuta claude mcp add server -- npx -y @vitemcp/server en tu terminal. Añade --scope user para que esté disponible en todos los proyectos.
¿Cómo agrego ViteMCP MCP a Cursor?
Abre ~/.cursor/mcp.json y añade el servidor en mcpServers, usando la configuración de Cursor que se muestra en la sección de instalación de arriba.
¿Qué herramientas proporciona ViteMCP MCP?
Expone 1 herramienta, incluidas title.
¿Cómo usarlo con Claude Desktop?
El README incluye una sección de FAQ específica para Claude Desktop, aunque la respuesta detallada no se proporciona en el fragmento.
¿Puedo generar un servidor a partir de un documento OpenAPI?
Sí, ViteMCP te permite generar un servidor completo a partir de un documento OpenAPI.
¿Qué diferencia a ViteMCP del SDK oficial?
ViteMCP es un framework con una arquitectura definida que se construye sobre el SDK oficial y gestiona el código repetitivo como la administración de conexiones, el manejo de herramientas y la integración de recursos.