Skip to main content
Glama

Myrcael

by noname9091

소바

클라이언트 세션을 처리할 수 있는 MCP 서버를 구축하기 위한 TypeScript 프레임워크입니다.

[!메모]

Python 구현에 대해서는 sova 를 참조하세요.

특징

설치

지엑스피1

빠른 시작

[!메모]

실제로 소바를 활용하는 사례는 많습니다. 쇼케이스 에서 예시를 확인하세요.

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는 도구 매개변수를 정의하기 위해 표준 스키마 사양을 사용합니다. 이를 통해 해당 사양을 구현하는 한 선호하는 스키마 검증 라이브러리(예: 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/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 중 하나만 지정해야 합니다.

위의 예는 다음과 같습니다.

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 중 하나만 지정해야 합니다.

위의 예는 다음과 같습니다.

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부울falsetrue인 경우 도구가 환경을 수정하지 않음을 나타냅니다.
destructiveHint부울truetrue인 경우 도구는 파괴적인 업데이트를 수행할 수 있습니다( readOnlyHint 가 false인 경우에만 의미가 있음)
idempotentHint부울falsetrue인 경우 동일한 인수로 도구를 반복적으로 호출해도 추가 효과가 없습니다( 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 인스턴스를 얻는 방법에 대한 예는 세션을 참조하세요.

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 서버를 테스트하고 디버깅하기 위해 mcp-cli 로 서버가 실행됩니다.

MCP Inspector 으로 검사하세요

또 다른 방법은 공식 MCP Inspector 사용하여 웹 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