Ein TypeScript-Framework zum Erstellen von stateless MCP servern, die für Serverless- und Edge-Umgebungen bereitgestellt werden können.
Auch bekannt als vitemcp/server, @vitemcp/server
- Stars
- 22
- Tools
- 1
- Downloads, 30 Tage
- —
- Neueste Version
- —
- Letzter Commit
- 11. Nov. 2026
- Lizenz
- MIT
Was ist ViteMCP MCP?
ViteMCP ist ein MCP server. Ein TypeScript-Framework zum Erstellen von stateless MCP servern, die für Serverless- und Edge-Umgebungen bereitgestellt werden können. Er stellt 1 Tool bereit, etwa title.
Zusammenfassung generiert aus dem README des Projekts
Über ViteMCP MCP server
ViteMCP ist ein TypeScript-Framework für Entwickler, die stateless MCP server bauen müssen. Es zielt speziell auf die Protokoll-Revision vom 2026-07-28 ab, die den Initialize-Handshake oder Session-IDs überflüssig macht. Diese Architektur macht das Framework ideal für das Deployment in Serverless- und Edge-Umgebungen wie Cloudflare Workers oder Deno Deploy, da jede Anfrage in sich abgeschlossen ist.
Das Framework vereinfacht den Entwicklungsprozess, indem es Boilerplate-Aufgaben wie Connection Management, Tool-Handling und die Formatierung von Responses übernimmt. Entwickler können Tools, Resources und Prompts mithilfe der Standard Schema Spezifikation definieren, was die Integration mit Libraries wie Zod, ArkType oder Valibot ermöglicht. Es unterstützt zudem das Generieren kompletter Server aus OpenAPI-Dokumenten und bietet integrierten Support für Bild- und Audioinhalte. Für Tests sind ein In-Memory-Transport sowie ein CLI enthalten, mit dem du die Server-Funktionalität inspizieren und debuggen kannst, ohne einen lokalen Port binden zu müssen.
Was du damit machen kannst
- 1Automatisierung der Server-Erstellung aus APIsDu kannst schnell einen kompletten Server erstellen, indem du einfach ein bestehendes OpenAPI-Dokument bereitstellst.
Geschrieben aus dem README des Projekts. Jeder Use Case wurde gegen ein Zitat daraus geprüft.
So installierst du ViteMCP MCP
Formate geprüft am 10. Nov. 2026Zu Cursor hinzufügenZu Goose hinzufügenViteMCP MCP in Claude Code installieren
$ claude mcp add server -- npx -y @vitemcp/serverViteMCP MCP in Cursor installieren
~/.cursor/mcp.json
{
"mcpServers": {
"server": {
"command": "npx",
"args": [
"-y",
"@vitemcp/server"
]
}
}
}ViteMCP MCP in VS Code installieren
.vscode/mcp.json
{
"servers": {
"server": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@vitemcp/server"
]
}
}
}ViteMCP MCP in VS Code CLI installieren
$ code --add-mcp '{"name":"server","type":"stdio","command":"npx","args":["-y","@vitemcp/server"]}'ViteMCP MCP in Claude Desktop installieren
claude_desktop_config.json
{
"mcpServers": {
"server": {
"command": "npx",
"args": [
"-y",
"@vitemcp/server"
]
}
}
}ViteMCP MCP in Codex installieren
$ codex mcp add server -- npx -y @vitemcp/serverViteMCP MCP in Codex config.toml installieren
~/.codex/config.toml
[mcp_servers.server] command = "npx" args = ["-y", "@vitemcp/server"]
ViteMCP MCP in Gemini CLI installieren
$ gemini mcp add -s user server npx -- -y @vitemcp/serverViteMCP MCP in Gemini settings.json installieren
~/.gemini/settings.json
{
"mcpServers": {
"server": {
"command": "npx",
"args": [
"-y",
"@vitemcp/server"
]
}
}
}ViteMCP MCP tools 1
Erkannt aus dem READMEtitle
| title | — |
Konfiguration
YOUR_ENV_VAR
| Name | Beschreibung | Typ |
|---|---|---|
| YOUR_ENV_VAR | — |
Automatisierte Checks
Geluften am 12. Nov. 2026Vollständiger Bericht →Unsere automatisierten Checks bestanden. Das ist kein Security-Audit: Lies den Code und gib dem Server nur den minimal notwendigen Zugriff. Wie die Checks funktionieren
- Source Repository verknüpft und erreichbarBestanden
- Open Source Lizenz (OSI)Bestanden
- Commit oder Release im letzten JahrBestanden
- Package-Name ähnelt keinem populären PackageBestanden
- Remote Endpoint nutzt HTTPSNicht geprüft
- Destruktive Tools sind markiertBestanden
- Keine versteckten Anweisungen an das ModellBestanden
- Keine kritischen Advisories im Paket oder seinen AbhängigkeitenNicht geprüft
README
Auf GitHub ansehenViteMCP
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"),
},
],
};
},
});
ViteMCP MCP Fragen
Wie füge ich ViteMCP MCP zu Claude Code hinzu?
Führe claude mcp add server -- npx -y @vitemcp/server in deinem Terminal aus. Füge --scope user hinzu, damit der Server in jedem Projekt verfügbar ist.
Wie füge ich ViteMCP MCP zu Cursor hinzu?
Öffne ~/.cursor/mcp.json und füge den Server unter mcpServers hinzu, mit der Cursor-Config aus dem Installationsabschnitt oben.
Welche Tools bietet ViteMCP MCP?
Er stellt 1 Tool bereit, darunter title.
Wie nutze ich es mit Claude Desktop?
Das README enthält einen speziellen FAQ-Bereich für Claude Desktop, auch wenn die detaillierte Antwort im Snippet nicht enthalten ist.
Kann ich einen Server aus einem OpenAPI-Dokument generieren?
Ja, ViteMCP ermöglicht es dir, einen kompletten Server aus einem OpenAPI-Dokument zu generieren.
Was unterscheidet ViteMCP vom offiziellen SDK?
ViteMCP ist ein opinionated Framework, das auf dem offiziellen SDK aufbaut und Boilerplate-Aufgaben wie Connection Management, Tool-Handling und das Einbetten von Resources übernimmt.