ソヴァ
クライアント セッションを処理できるMCPサーバーを構築するための TypeScript フレームワーク。
[!注記]
Python 実装については、 sovaを参照してください。
特徴
インストール
クイックスタート
[!注記]
sova の実際の使用例は数多くあります。例についてはショーケースをご覧ください。
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",
});
これで完了です。MCPサーバーが稼働しました。
ターミナルでサーバーをテストするには、次のコマンドを使用します。
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
南南東
Server-Sent Events (SSE) は、サーバーがHTTPS接続を介してクライアントにリアルタイム更新を送信するためのメカニズムを提供します。MCPのコンテキストでは、SSEは主にリモートMCP通信を可能にするために使用され、リモートマシンにホストされているMCPにアクセスし、ネットワーク経由で更新を中継することを可能にします。
SSE サポートを使用してサーバーを実行することもできます。
server.start({
transportType: "sse",
sse: {
endpoint: "/sse",
port: 8080,
},
});
これにより、サーバーが起動し、 http://localhost:8080/sse
で SSE 接続をリッスンします。
次に、 SSEClientTransport
を使用してサーバーに接続します。
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);
コアコンセプト
ツール
MCP のツールを使用すると、サーバーは、クライアントが呼び出して LLM がアクションを実行するために使用できる実行可能関数を公開できます。
sova はツールパラメータの定義に標準スキーマ仕様を使用します。これにより、仕様を実装している限り、お好みのスキーマ検証ライブラリ(Zod、ArkType、Valibot など)を使用できます。
ゾッドの例:
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の例:
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の例:
Valibot にはピア依存関係 @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);
},
});
文字列を返す
execute
文字列を返すことができます:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return "Hello, world!";
},
});
後者は以下と同等です:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return {
content: [
{
type: "text",
text: "Hello, world!",
},
],
};
},
});
リストを返す
メッセージのリストを返す場合は、 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" },
],
};
},
});
画像を返す
画像のコンテンツ オブジェクトを作成するには、 imageContent
を使用します。
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(...)
// ],
// };
},
});
imageContent
関数は次のオプションを受け取ります。
url
: 画像の URL。path
: 画像ファイルへのパス。buffer
: バッファとしてのイメージデータ。
url
、 path
、 buffer
のいずれか 1 つだけを指定する必要があります。
上記の例は次の例と同等です。
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",
},
],
};
},
});
オーディオを返す
audioContent
を使用してオーディオのコンテンツ オブジェクトを作成します。
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(...)
// ],
// };
},
});
audioContent
関数は次のオプションを受け取ります。
url
: オーディオの URL。path
: オーディオファイルへのパス。buffer
: バッファとしてのオーディオデータ。
url
、 path
、 buffer
のいずれか 1 つだけを指定する必要があります。
上記の例は次の例と同等です。
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",
},
],
};
},
});
組み合わせ型を返す
このように様々なタイプを組み合わせてAIに送り返すことができます
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,
// ],
// };
// },
});
ログ記録
ツールは、コンテキスト オブジェクト内のlog
オブジェクトを使用してクライアントにメッセージを記録できます。
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";
},
});
log
オブジェクトには次のメソッドがあります。
debug(message: string, data?: SerializableValue)
error(message: string, data?: SerializableValue)
info(message: string, data?: SerializableValue)
warn(message: string, data?: SerializableValue)
エラー
ユーザーに表示されるエラーは、 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";
},
});
進捗
ツールは、コンテキスト オブジェクトでreportProgress
を呼び出すことによって進行状況を報告できます。
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";
},
});
ツール注釈
MCP 仕様 (2025-03-26) の時点では、ツールの動作に関するメタデータを追加することで、より豊富なコンテキストと制御を提供する注釈をツールに含めることができます。
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);
},
});
利用可能な注釈は次のとおりです。
注釈 | タイプ | デフォルト | 説明 |
---|
title | 弦 | - | ツールの人間が読めるタイトル。UI 表示に役立ちます。 |
readOnlyHint | ブール値 | false | trueの場合、ツールは環境を変更しないことを示します。 |
destructiveHint | ブール値 | true | true の場合、ツールは破壊的な更新を実行する可能性があります ( readOnlyHint が false の場合にのみ意味があります) |
idempotentHint | ブール値 | false | true の場合、同じ引数でツールを繰り返し呼び出しても追加の効果はありません ( readOnlyHint が false の場合にのみ意味があります) |
openWorldHint | ブール値 | true | 真の場合、ツールは外部エンティティの「オープンワールド」と対話する可能性があります |
これらの注釈は、クライアントと LLM がツールの使用方法や、ツールを呼び出すときに何を期待できるかをよりよく理解するのに役立ちます。
リソース
リソースとは、MCPサーバーがクライアントに提供したいあらゆる種類のデータを指します。これには以下が含まれます。
各リソースは一意の URI によって識別され、テキストまたはバイナリ データのいずれかを含めることができます。
server.addResource({
uri: "file:///logs/app.log",
name: "Application Logs",
mimeType: "text/plain",
async load() {
return {
text: await readLogFile(),
};
},
});
[!注記]
load
複数のリソースを返すことができます。例えば、ディレクトリを読み込んだ際に、そのディレクトリ内のファイルのリストを返すなど、様々な用途に利用できます。
async load() {
return [
{
text: "First file content",
},
{
text: "Second file content",
},
];
}
load
でバイナリコンテンツを返すこともできます。
async load() {
return {
blob: 'base64-encoded-data'
};
}
リソーステンプレート
リソース テンプレートを定義することもできます。
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}`,
};
},
});
リソーステンプレート引数の自動補完
自動補完を有効にするには、リソース テンプレート引数にcomplete
関数を提供します。
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}`,
};
},
});
プロンプト
プロンプトを使用すると、サーバーは再利用可能なプロンプトテンプレートとワークフローを定義でき、クライアントはそれらをユーザーやLLMに簡単に表示できます。これにより、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}`;
},
});
プロンプト引数の自動補完
プロンプトでは引数の自動補完が可能です。
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: [],
};
},
},
],
});
enum
を使用したプロンプト引数の自動補完
引数にenum
配列を指定すると、サーバーは自動的に引数の補完を提供します。
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"],
},
],
});
認証
sova を使用すると、カスタム関数を使用してクライアントauthenticate
できます。
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,
};
},
});
これで、ツールで認証されたセッション データにアクセスできるようになります。
server.addTool({
name: "sayHello",
execute: async (args, { session }) => {
return `Hello, ${session.id}!`;
},
});
指示の提供
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.',
});
セッション
session
オブジェクトはsovaSession
のインスタンスであり、アクティブなクライアント セッションを記述します。
クライアントとサーバー間の 1:1 通信を可能にするために、クライアント接続ごとに新しいサーバー インスタンスを割り当てます。
型付きサーバーイベント
on
メソッドを使用して、サーバーによって発行されたイベントをリッスンできます。
server.on("connect", (event) => {
console.log("Client connected:", event.session);
});
server.on("disconnect", (event) => {
console.log("Client disconnected:", event.session);
});
sovaSession
sovaSession
クライアント セッションを表し、クライアントと対話するためのメソッドを提供します。
sovaSession
インスタンスを取得する方法の例については、 「セッション」を参照してください。
requestSampling
requestSampling
サンプリング要求を作成し、応答を返します。
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
clientCapabilities
プロパティにはクライアント機能が含まれます。
session.clientCapabilities;
loggingLevel
loggingLevel
プロパティは、クライアントによって設定されたログ記録レベルを表します。
roots
roots
プロパティには、クライアントによって設定されたルートが含まれます。
server
server
プロパティには、セッションに関連付けられた MCP サーバーのインスタンスが含まれます。
型付けされたセッションイベント
on
メソッドを使用して、セッションによって発行されたイベントをリッスンできます。
session.on("rootsChanged", (event) => {
console.log("Roots changed:", event.roots);
});
session.on("error", (event) => {
console.error("Error:", event.error);
});
サーバーの実行
mcp-cli
でテストする
サーバーをテストおよびデバッグする最も速い方法はsova dev
を使用することです。
npx sova dev server.js
npx sova dev server.ts
これにより、ターミナルで MCP サーバーをテストおよびデバッグするために、 mcp-cli
を使用してサーバーが実行されます。
MCP Inspector
で検査する
もう 1 つの方法は、公式のMCP Inspector
使用して Web UI でサーバーを検査することです。
npx sova inspect server.ts
よくある質問
Claude Desktop ではどのように使用しますか?
ガイドhttps://modelcontextprotocol.io/quickstart/userに従って、次の構成を追加します。
{
"mcpServers": {
"my-mcp-server": {
"command": "npx",
"args": ["tsx", "/PATH/TO/YOUR_PROJECT/src/index.ts"],
"env": {
"YOUR_ENV_VAR": "value"
}
}
}
}
謝辞