Einfach MCP
Ein TypeScript-Framework zum Erstellen von MCP -Servern, die Clientsitzungen verarbeiten können.
[!NOTIZ]
Eine Python-Implementierung finden Sie unter sova .
Merkmale
Related MCP server: MCP Framework
Installation
Schnellstart
[!NOTIZ]
Es gibt viele Beispiele für den Einsatz von Sova in der Praxis. Beispiele finden Sie im Showcase .
import { sova } from "simply";
import { z } from "zod"; // Or any validation library that supports Standard Schema
const server = new simply({
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",
});
Das war's! Sie haben einen funktionierenden MCP-Server.
Sie können den Server im Terminal mit folgendem testen:
git clone https://github.com/nonameguy9091/simply.git
cd simply
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
Server-Sent Events (SSE) bieten Servern einen Mechanismus, um Echtzeit-Updates über eine HTTPS-Verbindung an Clients zu senden. Im Kontext von MCP wird SSE hauptsächlich verwendet, um die Remote-MCP-Kommunikation zu ermöglichen. Dadurch kann auf ein auf einem Remote-Rechner gehostetes MCP zugegriffen werden und Updates über das Netzwerk weitergeleitet werden.
Sie können den Server auch mit SSE-Unterstützung betreiben:
server.start({
transportType: "sse",
sse: {
endpoint: "/sse",
port: 8080,
},
});
Dadurch wird der Server gestartet und wartet auf SSE-Verbindungen unter http://localhost:8080/sse .
Anschließend können Sie SSEClientTransport verwenden, um eine Verbindung zum Server herzustellen:
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);
Kernkonzepte
Werkzeuge
Tools in MCP ermöglichen es Servern, ausführbare Funktionen verfügbar zu machen, die von Clients aufgerufen und von LLMs zum Ausführen von Aktionen verwendet werden können.
Sova verwendet die Standardschemaspezifikation zur Definition von Werkzeugparametern. Dies ermöglicht Ihnen die Verwendung Ihrer bevorzugten Schemavalidierungsbibliothek (z. B. Zod, ArkType oder Valibot), sofern diese die Spezifikation implementiert.
Zod-Beispiel:
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-Beispiel:
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-Beispiel:
Valibot erfordert die Peer-Abhängigkeit @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);
},
});
Zurückgeben einer Zeichenfolge
execute kann eine Zeichenfolge zurückgeben:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return "Hello, world!";
},
});
Letzteres ist gleichbedeutend mit:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return {
content: [
{
type: "text",
text: "Hello, world!",
},
],
};
},
});
Zurückgeben einer Liste
Wenn Sie eine Liste von Nachrichten zurückgeben möchten, können Sie ein Objekt mit einer content zurückgeben:
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" },
],
};
},
});
Zurückgeben eines Bildes
Verwenden Sie imageContent , um ein Inhaltsobjekt für ein Bild zu erstellen:
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(...)
// ],
// };
},
});
Die Funktion imageContent akzeptiert die folgenden Optionen:
url : Die URL des Bildes.
path : Der Pfad zur Bilddatei.
buffer : Die Bilddaten als Puffer.
Es darf nur eine der Optionen url , path oder buffer angegeben werden.
Das obige Beispiel ist gleichbedeutend mit:
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",
},
],
};
},
});
Rückgabe eines Audios
Verwenden Sie audioContent , um ein Inhaltsobjekt für ein Audio zu erstellen:
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(...)
// ],
// };
},
});
Die Funktion audioContent akzeptiert die folgenden Optionen:
url : Die URL des Audios.
path : Der Pfad zur Audiodatei.
buffer : Die Audiodaten als Puffer.
Es darf nur eine der Optionen url , path oder buffer angegeben werden.
Das obige Beispiel ist gleichbedeutend mit:
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",
},
],
};
},
});
Rückgabekombinationstyp
Auf diese Weise können Sie verschiedene Typen kombinieren und an die KI zurücksenden
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,
// ],
// };
// },
});
Protokollierung
Tools können mithilfe des log im Kontextobjekt Nachrichten an den Client protokollieren:
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";
},
});
Das log verfügt über die folgenden Methoden:
debug(message: string, data?: SerializableValue)
error(message: string, data?: SerializableValue)
info(message: string, data?: SerializableValue)
warn(message: string, data?: SerializableValue)
Fehler
Die Fehler, die dem Benutzer angezeigt werden sollen, sollten als UserError -Instanzen ausgegeben werden:
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";
},
});
Fortschritt
Tools können den Fortschritt melden, indem sie reportProgress im Kontextobjekt aufrufen:
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";
},
});
Werkzeuganmerkungen
Ab der MCP-Spezifikation (26.03.2025) können Tools Anmerkungen enthalten, die durch Hinzufügen von Metadaten zum Verhalten eines Tools einen umfassenderen Kontext und mehr Kontrolle bieten:
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);
},
});
Die verfügbaren Anmerkungen sind:
Anmerkung | Typ | Standard | Beschreibung |
title
| Schnur | - | Ein für Menschen lesbarer Titel für das Tool, nützlich für die UI-Anzeige |
readOnlyHint
| Boolescher Wert | false
| Wenn „true“, bedeutet dies, dass das Tool seine Umgebung nicht ändert. |
destructiveHint
| Boolescher Wert | true
| Wenn „true“, kann das Tool destruktive Updates durchführen (nur sinnvoll, wenn readOnlyHint
„false“ ist). |
idempotentHint
| Boolescher Wert | false
| Wenn „true“, hat das wiederholte Aufrufen des Tools mit denselben Argumenten keine zusätzlichen Auswirkungen (nur sinnvoll, wenn readOnlyHint
“ „false“ ist). |
openWorldHint
| Boolescher Wert | true
| Wenn dies zutrifft, kann das Tool mit einer „offenen Welt“ externer Entitäten interagieren. |
Diese Anmerkungen helfen Kunden und LLMs dabei, besser zu verstehen, wie die Tools zu verwenden sind und was sie bei deren Aufruf erwartet.
Ressourcen
Ressourcen stellen alle Arten von Daten dar, die ein MCP-Server Clients zur Verfügung stellen möchte. Dies kann Folgendes umfassen:
Dateiinhalt
Screenshots und Bilder
Und mehr
Jede Ressource wird durch eine eindeutige URI identifiziert und kann entweder Text oder Binärdaten enthalten.
server.addResource({
uri: "file:///logs/app.log",
name: "Application Logs",
mimeType: "text/plain",
async load() {
return {
text: await readLogFile(),
};
},
});
[!NOTIZ]
load kann mehrere Ressourcen zurückgeben. Dies kann beispielsweise verwendet werden, um beim Lesen eines Verzeichnisses eine Liste der Dateien innerhalb des Verzeichnisses zurückzugeben.
async load() {
return [
{
text: "First file content",
},
{
text: "Second file content",
},
];
}
Sie können in load auch binäre Inhalte zurückgeben:
async load() {
return {
blob: 'base64-encoded-data'
};
}
Ressourcenvorlagen
Sie können auch Ressourcenvorlagen definieren:
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}`,
};
},
});
Automatische Vervollständigung von Ressourcenvorlagenargumenten
Stellen Sie complete Funktionen für Ressourcenvorlagenargumente bereit, um die automatische Vervollständigung zu ermöglichen:
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}`,
};
},
});
Eingabeaufforderungen
Mithilfe von Eingabeaufforderungen können Server wiederverwendbare Eingabeaufforderungsvorlagen und Workflows definieren, die Clients Benutzern und LLMs problemlos bereitstellen können. Sie bieten eine leistungsstarke Möglichkeit, gängige LLM-Interaktionen zu standardisieren und gemeinsam zu nutzen.
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}`;
},
});
Schnelle automatische Vervollständigung von Argumenten
Eingabeaufforderungen können eine automatische Vervollständigung ihrer Argumente bereitstellen:
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: [],
};
},
},
],
});
Schnelle automatische Vervollständigung von Argumenten mithilfe von enum
Wenn Sie ein enum Array für ein Argument angeben, stellt der Server automatisch Vervollständigungen für das Argument bereit.
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"],
},
],
});
Authentifizierung
Sova ermöglicht Ihnen die authenticate von Clients mithilfe einer benutzerdefinierten Funktion:
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,
};
},
});
Jetzt können Sie in Ihren Tools auf die authentifizierten Sitzungsdaten zugreifen:
server.addTool({
name: "sayHello",
execute: async (args, { session }) => {
return `Hello, ${session.id}!`;
},
});
Anweisungen geben
Mit der Option instructions können Sie dem Server Anweisungen geben:
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.',
});
Sitzungen
Das session ist eine Instanz von sovaSession und beschreibt aktive Clientsitzungen.
Wir weisen jeder Clientverbindung eine neue Serverinstanz zu, um eine 1:1-Kommunikation zwischen Client und Server zu ermöglichen.
Typisierte Serverereignisse
Sie können mit der Methode on auf vom Server ausgegebene Ereignisse warten:
server.on("connect", (event) => {
console.log("Client connected:", event.session);
});
server.on("disconnect", (event) => {
console.log("Client disconnected:", event.session);
});
sovaSession
sovaSession stellt eine Client-Sitzung dar und bietet Methoden zur Interaktion mit dem Client.
Beispiele zum Abrufen einer sovaSession Instanz finden Sie unter „Sitzungen“ .
requestSampling
requestSampling erstellt eine Sampling -Anforderung und gibt die Antwort zurück.
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
Die Eigenschaft clientCapabilities enthält die Clientfunktionen.
session.clientCapabilities;
loggingLevel
Die Eigenschaft loggingLevel beschreibt die vom Client festgelegte Protokollierungsebene.
roots
Die roots -Eigenschaft enthält die vom Client festgelegten Roots.
server
Die server enthält eine Instanz des MCP-Servers, der mit der Sitzung verknüpft ist.
Typisierte Sitzungsereignisse
Sie können mit der Methode on auf von der Sitzung ausgegebene Ereignisse warten:
session.on("rootsChanged", (event) => {
console.log("Roots changed:", event.roots);
});
session.on("error", (event) => {
console.error("Error:", event.error);
});
Ausführen Ihres Servers
Testen mit mcp-cli
Der schnellste Weg zum Testen und Debuggen Ihres Servers ist mit sova dev :
npx sova dev server.js
npx sova dev server.ts
Dadurch wird Ihr Server mit mcp-cli ausgeführt, um Ihren MCP-Server im Terminal zu testen und zu debuggen.
Prüfen mit MCP Inspector
Eine andere Möglichkeit besteht darin, den offiziellen MCP Inspector zu verwenden, um Ihren Server mit einer Web-Benutzeroberfläche zu überprüfen:
npx sova inspect server.ts
Häufig gestellte Fragen
Wie verwende ich es mit Claude Desktop?
Folgen Sie der Anleitung https://modelcontextprotocol.io/quickstart/user und fügen Sie die folgende Konfiguration hinzu:
{
"mcpServers": {
"my-mcp-server": {
"command": "npx",
"args": ["tsx", "/PATH/TO/YOUR_PROJECT/src/index.ts"],
"env": {
"YOUR_ENV_VAR": "value"
}
}
}
}
Danksagung