Sebuah framework TypeScript untuk membangun MCP server stateless yang dapat dideploy ke lingkungan serverless dan edge.
Juga dikenal sebagai vitemcp/server, @vitemcp/server
- Stars
- 22
- Tools
- 1
- Unduhan, 30 hari
- β
- Versi terbaru
- β
- Commit terakhir
- 11 Nov 2026
- Lisensi
- MIT
Apa itu ViteMCP MCP?
ViteMCP adalah MCP server. Sebuah framework TypeScript untuk membangun MCP server stateless yang dapat dideploy ke lingkungan serverless dan edge. Server ini menyediakan 1 tool, seperti title.
Ringkasan dibuat dari README proyek
Tentang ViteMCP MCP server
ViteMCP adalah framework TypeScript yang dirancang bagi developer yang perlu membangun MCP server yang bersifat stateless. Framework ini secara khusus menargetkan revisi protokol 2026-07-28, yang menghilangkan kebutuhan akan handshake inisialisasi atau session ID. Arsitektur ini membuat framework ini ideal untuk deployment di lingkungan serverless dan edge seperti Cloudflare Workers atau Deno Deploy, karena setiap request bersifat self-contained.
Framework ini menyederhanakan proses pengembangan dengan menangani tugas-tugas boilerplate seperti manajemen koneksi, penanganan tool, dan formatting response. Developer dapat mendefinisikan tool, resource, dan prompt menggunakan spesifikasi Standard Schema, sehingga memungkinkan integrasi dengan library seperti Zod, ArkType, atau Valibot. Framework ini juga mendukung pembuatan seluruh server dari dokumen OpenAPI dan menyediakan dukungan bawaan untuk konten gambar serta audio. Untuk keperluan testing, tersedia in-memory transport dan CLI untuk memeriksa serta melakukan debug pada fungsionalitas server tanpa harus melakukan binding ke local port.
Apa yang bisa Anda lakukan dengannya
- 1Otomatisasi pembuatan server dari APIAnda dapat membangun server yang lengkap dengan cepat hanya dengan menyediakan dokumen OpenAPI yang sudah ada.
Ditulis berdasarkan README proyek. Setiap use case diperiksa terhadap kutipan dari README tersebut.
Cara menginstal ViteMCP MCP
Format diverifikasi 10 Nov 2026Tambahkan ke CursorTambahkan ke GooseInstal ViteMCP MCP di Claude Code
$ claude mcp add server -- npx -y @vitemcp/serverInstal ViteMCP MCP di Cursor
~/.cursor/mcp.json
{
"mcpServers": {
"server": {
"command": "npx",
"args": [
"-y",
"@vitemcp/server"
]
}
}
}Instal ViteMCP MCP di VS Code
.vscode/mcp.json
{
"servers": {
"server": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@vitemcp/server"
]
}
}
}Instal ViteMCP MCP di VS Code CLI
$ code --add-mcp '{"name":"server","type":"stdio","command":"npx","args":["-y","@vitemcp/server"]}'Instal ViteMCP MCP di Claude Desktop
claude_desktop_config.json
{
"mcpServers": {
"server": {
"command": "npx",
"args": [
"-y",
"@vitemcp/server"
]
}
}
}Instal ViteMCP MCP di Codex
$ codex mcp add server -- npx -y @vitemcp/serverInstal ViteMCP MCP di Codex config.toml
~/.codex/config.toml
[mcp_servers.server] command = "npx" args = ["-y", "@vitemcp/server"]
Instal ViteMCP MCP di Gemini CLI
$ gemini mcp add -s user server npx -- -y @vitemcp/serverInstal ViteMCP MCP di Gemini settings.json
~/.gemini/settings.json
{
"mcpServers": {
"server": {
"command": "npx",
"args": [
"-y",
"@vitemcp/server"
]
}
}
}Tool ViteMCP MCP 1
Terdeteksi dari READMEtitle
| title | β |
Konfigurasi
YOUR_ENV_VAR
| Nama | Deskripsi | Tipe |
|---|---|---|
| YOUR_ENV_VAR | β |
Pengecekan otomatis
Berjalan 12 Nov 2026Laporan lengkap βLolos checks otomatis kami. Ini bukan audit keamanan: baca kodenya dan berikan server akses paling terbatas yang dibutuhkannya. Cara kerja checks
- Source repository terhubung dan dapat dijangkauLolos
- Lisensi open source (OSI)Lolos
- Ada commit atau release dalam satu tahun terakhirLolos
- Nama package tidak mirip dengan package populerLolos
- Remote endpoint menggunakan HTTPSTidak diperiksa
- Tools yang bersifat destruktif telah diberi labelLolos
- Tidak ada instruksi tersembunyi untuk modelLolos
- Tidak ada advisory kritis pada paket atau dependensinyaTidak diperiksa
README
Lihat di 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"),
},
],
};
},
});
Pertanyaan tentang ViteMCP MCP
Bagaimana cara menambahkan ViteMCP MCP ke Claude Code?
Jalankan claude mcp add server -- npx -y @vitemcp/server di terminal Anda. Tambahkan --scope user agar tersedia di semua proyek.
Bagaimana cara menambahkan ViteMCP MCP ke Cursor?
Buka ~/.cursor/mcp.json dan tambahkan server di bawah mcpServers, menggunakan config Cursor yang ditunjukkan di bagian instalasi di atas.
Tool apa yang disediakan ViteMCP MCP?
Server ini menyediakan 1 tool, termasuk title.
Bagaimana cara menggunakannya dengan Claude Desktop?
README menyertakan bagian FAQ khusus untuk Claude Desktop, meskipun jawaban detailnya tidak tersedia dalam cuplikan ini.
Bisakah saya membuat server dari dokumen OpenAPI?
Ya, ViteMCP memungkinkan Anda untuk men-generate seluruh server dari sebuah dokumen OpenAPI.
Apa perbedaan ViteMCP dengan SDK resmi?
ViteMCP adalah framework opinionated yang dibangun di atas SDK resmi untuk menangani boilerplate seperti manajemen koneksi, penanganan tool, dan embedding resource.