Skip to main content
Glama

Myrcael

by noname9091

索瓦

用于构建能够处理客户端会话的MCP服务器的 TypeScript 框架。

[!笔记]

对于 Python 实现,请参阅sova

特征

安装

npm install 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

上交所

服务器发送事件(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 使用标准 Schema规范来定义工具参数。这意味着您可以使用您偏好的 Schema 验证库(例如 Zod、ArkType 或 Valibot),只要它们实现了该规范。

Zod示例:

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 :图像数据作为缓冲区。

只需指定urlpathbuffer中的一个。

上面的例子等价于:

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 :音频数据作为缓冲区。

只需指定urlpathbuffer中的一个。

上面的例子等价于:

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的一个实例,它描述活动的客户端会话。

server.sessions;

我们为每个客户端连接分配一个新的服务器实例,以实现客户端和服务器之间的 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实例的示例,请参阅Sessions

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属性描述客户端设置的日志记录级别。

session.loggingLevel;

roots

roots属性包含客户端设置的根。

session.roots;

server

server属性包含与会话关联的 MCP 服务器实例。

session.server;

类型化会话事件

您可以使用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-cli运行您的服务器,以便在终端中测试和调试您的 MCP 服务器。

使用MCP Inspector进行检查

另一种方法是使用官方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" } } } }

致谢

-
security - not tested
A
license - permissive license
-
quality - not tested

hybrid server

The server is able to function both locally and remotely, depending on the configuration or use case.

用于构建 MCP 服务器的 TypeScript 框架,具有客户端会话、身份验证、图像/音频内容和类型化服务器事件的功能。

  1. 特征
    1. 安装
      1. 快速入门
        1. 上交所
      2. 核心概念
        1. 工具
        2. 资源
        3. 资源模板
        4. 提示
        5. 验证
        6. 提供说明
        7. 会议
        8. 类型化服务器事件
      3. sovaSession
        1. requestSampling
        2. clientCapabilities
        3. loggingLevel
        4. roots
        5. server
        6. 类型化会话事件
      4. 运行您的服务器
        1. 使用mcp-cli进行测试
        2. 使用MCP Inspector进行检查
      5. 常问问题
        1. 如何与 Claude Desktop 一起使用?
      6. 致谢

        Related MCP Servers

        • A
          security
          A
          license
          A
          quality
          A TypeScript-based server that allows calling other MCP clients from your own MCP client, facilitating task delegation and context window offloading for enhanced multi-agent interactions.
          Last updated -
          3
          14
          JavaScript
          MIT License
          • Apple
        • A
          security
          F
          license
          A
          quality
          A TypeScript-based MCP server designed for experimentation and integration with Calude Desktop and Cursor IDE, offering a modular playground for extending server capabilities.
          Last updated -
          2
          1,133
          4
          JavaScript
        • -
          security
          F
          license
          -
          quality
          A TypeScript framework for building Model Context Protocol (MCP) servers with automatic discovery and loading of tools, resources, and prompts.
          Last updated -
          67
          TypeScript
          • Apple
        • -
          security
          F
          license
          -
          quality
          A simple TypeScript library for creating Model Context Protocol (MCP) servers with features like type safety, parameter validation, and a minimal code API.
          Last updated -
          1
          TypeScript
          MIT License

        View all related MCP servers

        MCP directory API

        We provide all the information about MCP servers via our MCP API.

        curl -X GET 'https://glama.ai/api/mcp/v1/servers/noname9091/myrcael'

        If you have feedback or need assistance with the MCP directory API, please join our Discord server