From a6fa58bf2bc96b58b5a2f29b2ca0117c43573821 Mon Sep 17 00:00:00 2001 From: wehub-resource-sync Date: Mon, 13 Jul 2026 12:36:40 +0800 Subject: [PATCH] chore: import upstream snapshot with attribution --- .bunfig.toml | 3 + .changelogrc.cjs | 1 + .commitlintrc.js | 1 + .editorconfig | 16 +++ .eslintignore | 30 +++++ .eslintrc.js | 14 +++ .github/workflows/release.yml | 28 +++++ .gitignore | 53 ++++++++ .npmrc | 11 ++ .prettierignore | 61 ++++++++++ .prettierrc.js | 3 + .releaserc.cjs | 1 + .stylelintrc.js | 9 ++ CHANGELOG.md | 127 +++++++++++++++++++ LICENSE | 21 ++++ README.md | 221 ++++++++++++++++++++++++++++++++++ README.wehub.md | 7 ++ README.zh-CN.md | 206 +++++++++++++++++++++++++++++++ package.json | 107 ++++++++++++++++ src/http.ts | 94 +++++++++++++++ src/index.ts | 1 + src/server.ts | 117 ++++++++++++++++++ src/stdio.ts | 22 ++++ tsconfig.json | 15 +++ 24 files changed, 1169 insertions(+) create mode 100644 .bunfig.toml create mode 100644 .changelogrc.cjs create mode 100644 .commitlintrc.js create mode 100644 .editorconfig create mode 100644 .eslintignore create mode 100644 .eslintrc.js create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .prettierrc.js create mode 100644 .releaserc.cjs create mode 100644 .stylelintrc.js create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 README.wehub.md create mode 100644 README.zh-CN.md create mode 100644 package.json create mode 100644 src/http.ts create mode 100644 src/index.ts create mode 100644 src/server.ts create mode 100644 src/stdio.ts create mode 100644 tsconfig.json diff --git a/.bunfig.toml b/.bunfig.toml new file mode 100644 index 0000000..d6bb75b --- /dev/null +++ b/.bunfig.toml @@ -0,0 +1,3 @@ +[install.lockfile] + +save = false diff --git a/.changelogrc.cjs b/.changelogrc.cjs new file mode 100644 index 0000000..9a2f5f9 --- /dev/null +++ b/.changelogrc.cjs @@ -0,0 +1 @@ +module.exports = require('@lobehub/lint').changelog; diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100644 index 0000000..9b8c6ac --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1 @@ +module.exports = require('@lobehub/lint').commitlint; diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7e3649a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..305d1ef --- /dev/null +++ b/.eslintignore @@ -0,0 +1,30 @@ +# Eslintignore for LobeHub +################################################################ + +# dependencies +node_modules + +# ci +coverage +.coverage + +# test +jest* +_test_ +__test__ + +# umi +.umi +.umi-production +.umi-test +.dumi/tmp* +!.dumirc.ts + +# production +dist +es +lib +logs + +# misc +# add other ignore file below diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..9928b9f --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,14 @@ +const config = require('@lobehub/lint').eslint; + +module.exports = { + ...config, + globals: { + HeadersInit: false, + }, + rules: { + ...config.rules, + 'unicorn/no-useless-undefined': 0, + 'unicorn/prefer-string-replace-all': 0, + 'unicorn/switch-case-braces': 0, + }, +}; diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9b7dac4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: Release CI +on: + push: + branches: + - master + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GH_TOKEN }} + + - name: Install bun + uses: oven-sh/setup-bun@v2 + + - name: Install deps + run: bun i + - name: Build + run: bun run build + + - name: Release + run: bun run release + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97f8145 --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +# Gitignore for LobeHub +################################################################ + +# general +.DS_Store +.idea +.vscode +.history +.temp +.env.local +venv +temp +tmp + +# dependencies +node_modules +*.log +*.lock +package-lock.json + +# ci +coverage +.coverage +.eslintcache +.stylelintcache + +# production +dist +es +lib +logs +test-output + +# umi +.umi +.umi-production +.umi-test +.dumi + + +# husky +.husky/prepare-commit-msg + +# misc +# add other ignore file below + +client.d.ts +client.js +openapi.d.ts +openapi.js +bun.lockb +schema.json +build diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..d9ed3d3 --- /dev/null +++ b/.npmrc @@ -0,0 +1,11 @@ +lockfile=false +resolution-mode=highest +public-hoist-pattern[]=*@umijs/lint* +public-hoist-pattern[]=*changelog* +public-hoist-pattern[]=*commitlint* +public-hoist-pattern[]=*eslint* +public-hoist-pattern[]=*postcss* +public-hoist-pattern[]=*prettier* +public-hoist-pattern[]=*remark* +public-hoist-pattern[]=*semantic-release* +public-hoist-pattern[]=*stylelint* diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..5d9a253 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,61 @@ +# Prettierignore for LobeHub +################################################################ + +# general +.DS_Store +.editorconfig +.idea +.vscode +.history +.temp +.env.local +.husky +.npmrc +.gitkeep +venv +temp +tmp +LICENSE + +# dependencies +node_modules +*.log +*.lock +package-lock.json + +# ci +coverage +.coverage +.eslintcache +.stylelintcache +test-output +tests/__snapshots__ +*.snap + +# production +dist +es +lib +logs + +# umi +.umi +.umi-production +.umi-test +.dumi/tmp* + +# ignore files +.*ignore + +# docker +docker +Dockerfile* + +# image +*.webp +*.gif +*.png +*.jpg + +# misc +# add other ignore file below diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..487640b --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,3 @@ +const {prettier}= require('@lobehub/lint'); + +export default prettier; diff --git a/.releaserc.cjs b/.releaserc.cjs new file mode 100644 index 0000000..3793001 --- /dev/null +++ b/.releaserc.cjs @@ -0,0 +1 @@ +module.exports = require('@lobehub/lint').semanticRelease; diff --git a/.stylelintrc.js b/.stylelintrc.js new file mode 100644 index 0000000..bcd7ba8 --- /dev/null +++ b/.stylelintrc.js @@ -0,0 +1,9 @@ +const config = require('@lobehub/lint').stylelint; + +module.exports = { + ...config, + rules: { + 'custom-property-pattern': null, + 'selector-pseudo-element-no-unknown': null, + }, +}; diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6092f18 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,127 @@ + + +# Changelog + +### [Version 1.1.2](https://github.com/lobehub/mcp-hello-world/compare/v1.1.1...v1.1.2) +Released on **2025-04-20** + + +#### 💄 Styles + +- **misc**: Improve output. + + +
+ + + +
+Improvements and Fixes + + + +#### Styles + +* **misc**: Improve output ([3dd383c](https://github.com/lobehub/mcp-hello-world/commit/3dd383c)) + +
+ + +
+ +[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top) + +
+ +### [Version 1.1.1](https://github.com/lobehub/mcp-hello-world/compare/v1.1.0...v1.1.1) +Released on **2025-04-20** + + +#### 🐛 Bug Fixes + +- **misc**: Fix output files. + + +
+ + + +
+Improvements and Fixes + + + +#### What's fixed + +* **misc**: Fix output files ([fe5c9d8](https://github.com/lobehub/mcp-hello-world/commit/fe5c9d8)) + +
+ + +
+ +[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top) + +
+ +## [Version 1.1.0](https://github.com/lobehub/mcp-hello-world/compare/v1.0.0...v1.1.0) +Released on **2025-04-20** + + +#### ✨ Features + +- **misc**: Bump version. + + +
+ + + +
+Improvements and Fixes + + + +#### What's improved + +* **misc**: Bump version ([fa7bdd5](https://github.com/lobehub/mcp-hello-world/commit/fa7bdd5)) + +
+ + +
+ +[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top) + +
+ +## Version 1.0.0 +Released on **2025-04-20** + + +#### ✨ Features + +- **misc**: Init mcp hello word. + + +
+ + + +
+Improvements and Fixes + + + +#### What's improved + +* **misc**: Init mcp hello word ([8a8f5c9](https://github.com/lobehub/mcp-hello-world/commit/8a8f5c9)) + +
+ + +
+ +[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top) + +
diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1dd53d2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 LobeHub + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9f511e9 --- /dev/null +++ b/README.md @@ -0,0 +1,221 @@ +# MCP Hello World - MCP Server Mock for Testing + +[![][npm-release-shield]][npm-release-link] +[![][npm-downloads-shield]][npm-downloads-link] +[![][github-action-test-shield]][github-action-test-link] +[![][github-action-release-shield]][github-action-release-link] + +[github-action-release-link]: https://github.com/lobehub/mcp-hello-world/actions/workflows/release.yml +[github-action-release-shield]: https://img.shields.io/github/actions/workflow/status/lobehub/mcp-hello-world/release.yml?label=Release&logo=githubactions&logoColor=white&style=flat-square +[github-action-test-link]: https://github.com/lobehub/mcp-hello-world/actions/workflows/test.yml +[github-action-test-shield]: https://img.shields.io/github/actions/workflow/status/lobehub/mcp-hello-world/test.yml?label=Test&logo=githubactions&logoColor=white&style=flat-square +[npm-downloads-link]: https://www.npmjs.com/package/mcp-hello-world +[npm-downloads-shield]: https://img.shields.io/npm/dt/mcp-hello-world?label=Downloads&logo=npm&style=flat-square +[npm-release-link]: https://www.npmjs.com/package/mcp-hello-world +[npm-release-shield]: https://img.shields.io/npm/v/mcp-hello-world?logo=npm&style=flat-square + +This is a **minimal Model Context Protocol (MCP) server** implemented in TypeScript, primarily intended to serve as a **Test Double / Mock Server**. + +**Core Purpose**: To provide a lightweight, controllable, and predictable MCP server environment for **unit testing** or **integration testing** client code that needs to interact with an MCP server. + +**Note:** This project is **not suitable for production environments** or deployment as a general-purpose MCP server. + +[![MCP Badge](https://lobehub.com/badge/mcp-full/lobehub-mcp-hello-world)](https://lobehub.com/mcp/lobehub-mcp-hello-world) + +## Why Use `mcp-hello-world` in Tests? + +When testing code related to MCP clients, you usually don't want to depend on a real, potentially complex, and unpredictably responsive AI backend service. Using `mcp-hello-world` as a test double offers several advantages: + +1. **Isolation**: Focus your tests on client logic without worrying about network issues or the availability of the real server. +2. **Predictability**: The provided `echo` and `debug` tools have simple, fixed behaviors, making it easy to write assertions. +3. **Speed**: Fast startup and response times, suitable for frequent use in unit tests. +4. **Lightweight**: Few dependencies, easy to integrate into test environments. +5. **Protocol Coverage**: Supports both `STDIO` and `HTTP/SSE` MCP transport protocols, allowing you to test client behavior under different connection methods. + +## Installation + +Add this package as a **dev dependency** to your project: + +```bash +# Using pnpm +pnpm add --save-dev mcp-hello-world + +# Or using bun +bun add --dev mcp-hello-world +``` + +## Manual Execution (for Debugging Tests) + +You might want to run the server manually sometimes to debug your tests or client behavior. + +### STDIO Mode + +This is the simplest way to run, especially during local development and debugging. + +```bash +# Ensure it's installed (globally or in the project) +# Using npx (universal) +npx mcp-hello-world + +# Or using pnpm dlx +pnpm dlx mcp-hello-world + +# Or using bunx +bunx mcp-hello-world +``` + +The server will listen on standard input and output MCP responses to standard output. You can use tools like [MCP Inspector](https://github.com/lobehub/mcp-inspector) to connect to the process. + +To configure this server in your MCP client, add the following to your configuration: + +```json +{ + "mcpServers": { + "mcp-hello-world": { + "command": "npx", + "args": ["mcp-hello-world"] + } + } +} +``` + +### HTTP/SSE Mode + +If you need to debug via a network interface or test HTTP-based MCP clients. + +```bash +# 1. Clone the repository (if not already installed in the project) +# git clone https://github.com/lobehub/mcp-hello-world.git +# cd mcp-hello-world +# pnpm install / bun install + +# 2. Build the project +# Using pnpm +pnpm build +# Or using bun +bun run build + +# 3. Start the HTTP server +# Using pnpm +pnpm start:http +# Or using bun +bun run start:http +``` + +The server will start on `http://localhost:3000` and provide: +- SSE endpoint: `/sse` +- Message endpoint: `/messages` + +## Usage in Tests + +You can programmatically start and stop the `mcp-hello-world` server within your test framework (like Jest, Vitest, Mocha, etc.) for automated testing. + +### Example: Testing with STDIO Mode (Node.js) + +```typescript +// test/my-mcp-client.test.ts (Example using Jest) +import { spawn } from 'child_process'; +import { MCPClient } from '../src/my-mcp-client'; // Assuming this is your client code + +describe('My MCP Client (STDIO)', () => { + let mcpServerProcess; + let client: MCPClient; + + beforeAll(() => { + // Start the mcp-hello-world process before tests + // Using npx (or pnpm dlx / bunx) ensures the command is found and executed + mcpServerProcess = spawn('npx', ['mcp-hello-world']); + + // Instantiate your client and connect to the subprocess's stdio + client = new MCPClient(mcpServerProcess.stdin, mcpServerProcess.stdout); + }); + + afterAll(() => { + // Shut down the mcp-hello-world process after tests + mcpServerProcess.kill(); + }); + + it('should receive echo response', async () => { + const request = { + jsonrpc: '2.0', + id: 1, + method: 'tools/invoke', + params: { name: 'echo', parameters: { message: 'test message' } }, + }; + + const response = await client.sendRequest(request); // Assuming your client has this method + + expect(response).toEqual({ + jsonrpc: '2.0', + id: 1, + result: { content: [{ type: 'text', text: 'Hello test message' }] }, + }); + }); + + it('should get greeting resource', async () => { + const request = { + jsonrpc: '2.0', + id: 2, + method: 'resources/get', + params: { uri: 'greeting://Alice' }, + }; + const response = await client.sendRequest(request); + expect(response).toEqual({ + jsonrpc: '2.0', + id: 2, + result: { data: 'Hello Alice!' }, // Confirm return format based on actual implementation + }); + }); + + // ... other test cases +}); +``` + +### Example: Testing with HTTP/SSE Mode + +For HTTP/SSE, you might need to: +1. Use `exec` or `spawn` in `beforeAll` to start `pnpm start:http` or `bun run start:http`. +2. Use an HTTP client (like `axios`, `node-fetch`, or your test framework's built-in client) to connect to `http://localhost:3000/sse` and `/messages` for testing. +3. Ensure you shut down the started server process in `afterAll`. + +## Provided MCP Capabilities (for Test Assertions) + +`mcp-hello-world` provides the following fixed capabilities for interaction and assertion in your tests: + +### Resources + +- **`hello://world`** + - Description: A static Hello World resource. + - Method: `resources/get` + - Parameters: None + - Returns: `{ data: 'Hello World!' }` +- **`greeting://{name}`** + - Description: A dynamic greeting resource. + - Method: `resources/get` + - Parameters: `name` included in the URI, e.g., `greeting://Bob`. + - Returns: `{ data: 'Hello {name}!' }` (e.g., `{ data: 'Hello Bob!' }`) + +### Tools + +- **`echo`** + - Description: Echoes the input message, prefixed with "Hello ". + - Method: `tools/invoke` + - Parameters: `{ name: 'echo', parameters: { message: string } }` + - Returns: `{ content: [{ type: 'text', text: 'Hello {message}' }] }` (e.g., `{ content: [{ type: 'text', text: 'Hello test' }] }`) +- **`debug`** + - Description: Lists all available MCP method definitions on the server. + - Method: `tools/invoke` + - Parameters: `{ name: 'debug', parameters: {} }` + - Returns: A JSON structure containing definitions for all registered resources, tools, and prompts. + +### Prompts + +- **`helpful-assistant`** + - Description: A basic assistant prompt definition. + - Method: `prompts/get` + - Parameters: None + - Returns: A JSON structure for the prompt with predefined `system` and `user` roles. + +## License + +MIT diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..af6e719 --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,7 @@ +# WeHub 来源说明 + +- 原始项目:`lobehub/mcp-hello-world` +- 原始仓库:https://github.com/lobehub/mcp-hello-world +- 导入方式:上游默认分支的最新快照 +- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准 +- 本文件仅用于记录来源,不代表 WeHub 是原项目作者 diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..6f83762 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,206 @@ +# MCP Hello World - 用于测试的 MCP 服务端模拟 + +[![][npm-release-shield]][npm-release-link] +[![][npm-downloads-shield]][npm-downloads-link] +[![][github-action-test-shield]][github-action-test-link] +[![][github-action-release-shield]][github-action-release-link] + +[github-action-release-link]: https://github.com/lobehub/mcp-hello-world/actions/workflows/release.yml +[github-action-release-shield]: https://img.shields.io/github/actions/workflow/status/lobehub/mcp-hello-world/release.yml?label=Release&logo=githubactions&logoColor=white&style=flat-square +[github-action-test-link]: https://github.com/lobehub/mcp-hello-world/actions/workflows/test.yml +[github-action-test-shield]: https://img.shields.io/github/actions/workflow/status/lobehub/mcp-hello-world/test.yml?label=Test&logo=githubactions&logoColor=white&style=flat-square +[npm-downloads-link]: https://www.npmjs.com/package/mcp-hello-world +[npm-downloads-shield]: https://img.shields.io/npm/dt/mcp-hello-world?label=Downloads&logo=npm&style=flat-square +[npm-release-link]: https://www.npmjs.com/package/mcp-hello-world +[npm-release-shield]: https://img.shields.io/npm/v/mcp-hello-world?logo=npm&style=flat-square + +这是一个基于 TypeScript 实现的 **极简模型上下文协议 (MCP) 服务端**,其**主要目的是作为测试替身 (Test Double) / 模拟服务器 (Mock Server)**。 + +**核心用途**:在针对需要与 MCP 服务端交互的客户端代码进行**单元测试**或**集成测试**时,提供一个轻量级、可控且行为可预测的 MCP 服务端环境。 + +**注意:** 本项目**不适用于生产环境**或作为通用的 MCP 服务端部署。 + +## 为什么在测试中使用 `mcp-hello-world`? + +在测试与 MCP 客户端相关的代码时,你通常不希望依赖一个真实的、可能复杂的、响应不确定的 AI 后端服务。使用 `mcp-hello-world` 作为测试替身有以下好处: + +1. **隔离性 (Isolation)**:让你的测试专注于客户端逻辑,而无需关心网络问题或真实服务端的可用性。 +2. **可预测性 (Predictability)**:提供的 `echo` 和 `debug` 工具行为简单且固定,方便编写断言。 +3. **速度 (Speed)**:启动和响应速度快,适合在单元测试中频繁使用。 +4. **轻量级 (Lightweight)**:依赖少,易于在测试环境中集成。 +5. **协议覆盖 (Protocol Coverage)**:同时支持 `STDIO` 和 `HTTP/SSE` 两种 MCP 传输协议,可以测试客户端在不同连接方式下的表现。 + +## 安装 + +将此包作为**开发依赖**添加到你的项目中: + +```bash +# 使用 pnpm +pnpm add --save-dev mcp-hello-world + +# 或者使用 bun +bun add --dev mcp-hello-world +``` + +## 手动运行 (用于调试测试) + +有时你可能想手动运行服务来调试你的测试或客户端行为。 + +### STDIO 模式 + +这是最简单的运行方式,尤其是在本地开发和调试时。 + +```bash +# 确保已安装 +# 使用 npx (通用) +npx mcp-hello-world + +# 或者使用 pnpm dlx +pnpm dlx mcp-hello-world + +# 或者使用 bunx +bunx mcp-hello-world +``` + +服务将监听标准输入,并将 MCP 响应输出到标准输出。你可以使用 [MCP Inspector](https://github.com/lobehub/mcp-inspector) 等工具连接到该进程。 + +### HTTP/SSE 模式 + +如果你需要通过网络接口进行调试,或者测试基于 HTTP 的 MCP 客户端。 + +```bash +# 1. 克隆仓库 (如果尚未在项目中安装) +# git clone https://github.com/lobehub/mcp-hello-world.git +# cd mcp-hello-world +# pnpm install / bun install + +# 2. 构建项目 +# 使用 pnpm +pnpm build +# 或者使用 bun +bun run build + +# 3. 启动 HTTP 服务 +# 使用 pnpm +pnpm start:http +# 或者使用 bun +bun run start:http +``` + +服务将在 `http://localhost:3000` 启动,并提供: +- SSE 端点: `/sse` +- 消息端点: `/messages` + +## 在测试中使用 + +你可以在你的测试框架(如 Jest, Vitest, Mocha 等)中,通过编程方式启动和停止 `mcp-hello-world` 服务,以便在自动化测试中使用。 + +### 示例:使用 STDIO 模式进行测试 (Node.js) + +```typescript +// test/my-mcp-client.test.ts (示例使用 Jest) +import { spawn } from 'child_process'; +import { MCPClient } from '../src/my-mcp-client'; // 假设这是你要测试的客户端 + +describe('My MCP Client (STDIO)', () => { + let mcpServerProcess; + let client: MCPClient; + + beforeAll(() => { + // 在测试开始前启动 mcp-hello-world 进程 + // 使用 npx (或 pnpm dlx / bunx) 确保能找到并执行命令 + mcpServerProcess = spawn('npx', ['mcp-hello-world']); + + // 实例化你的客户端,并连接到子进程的 stdio + client = new MCPClient(mcpServerProcess.stdin, mcpServerProcess.stdout); + }); + + afterAll(() => { + // 测试结束后关闭 mcp-hello-world 进程 + mcpServerProcess.kill(); + }); + + it('should receive echo response', async () => { + const request = { + jsonrpc: '2.0', + id: 1, + method: 'tools/invoke', + params: { name: 'echo', parameters: { message: 'test message' } }, + }; + + const response = await client.sendRequest(request); // 假设你的客户端有此方法 + + expect(response).toEqual({ + jsonrpc: '2.0', + id: 1, + result: { content: [{ type: 'text', text: 'Hello test message' }] }, + }); + }); + + it('should get greeting resource', async () => { + const request = { + jsonrpc: '2.0', + id: 2, + method: 'resources/get', + params: { uri: 'greeting://Alice' }, + }; + const response = await client.sendRequest(request); + expect(response).toEqual({ + jsonrpc: '2.0', + id: 2, + result: { data: 'Hello Alice!' }, // 根据实际实现确认返回格式 + }); + }); + + // ... 其他测试用例 +}); +``` + +### 示例:使用 HTTP/SSE 模式进行测试 + +对于 HTTP/SSE,你可能需要: +1. 在 `beforeAll` 中使用 `exec` 或 `spawn` 启动 `pnpm start:http` 或 `bun run start:http`。 +2. 使用 HTTP 客户端 (如 `axios`, `node-fetch`, 或测试框架内置的) 连接到 `http://localhost:3000/sse` 和 `/messages` 端点进行测试。 +3. 在 `afterAll` 中确保关闭启动的服务器进程。 + +## 提供的 MCP 能力 (用于测试断言) + +`mcp-hello-world` 提供以下固定的能力,供你在测试中进行交互和断言: + +### 资源 (Resources) + +- **`hello://world`** + - 描述: 一个静态的 Hello World 资源。 + - 方法: `resources/get` + - 参数: 无 + - 返回: `{ data: 'Hello World!' }` +- **`greeting://{name}`** + - 描述: 一个动态的问候资源。 + - 方法: `resources/get` + - 参数: URI 中包含 `name`,例如 `greeting://Bob`。 + - 返回: `{ data: 'Hello {name}!' }` (例如: `{ data: 'Hello Bob!' }`) + +### 工具 (Tools) + +- **`echo`** + - 描述: 回显输入的消息,并添加 "Hello " 前缀。 + - 方法: `tools/invoke` + - 参数: `{ name: 'echo', parameters: { message: string } }` + - 返回: `{ content: [{ type: 'text', text: 'Hello {message}' }] }` (例如: `{ content: [{ type: 'text', text: 'Hello test' }] }`) +- **`debug`** + - 描述: 列出服务端所有可用的 MCP 方法定义。 + - 方法: `tools/invoke` + - 参数: `{ name: 'debug', parameters: {} }` + - 返回: 包含所有已注册的 resources, tools, prompts 定义的 JSON 结构。 + +### 提示词 (Prompts) + +- **`helpful-assistant`** + - 描述: 一个基础的助手提示词定义。 + - 方法: `prompts/get` + - 参数: 无 + - 返回: 一个包含预定义 `system` 和 `user` 角色的提示词结构 JSON。 + +## 许可证 + +MIT \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..1c188ba --- /dev/null +++ b/package.json @@ -0,0 +1,107 @@ +{ + "name": "mcp-hello-world", + "version": "1.1.2", + "description": "A simple Hello World MCP server", + "keywords": [ + "lobehub", + "model-context-protocol", + "sdk" + ], + "homepage": "https://github.com/lobehub/mcp-hello-world", + "bugs": { + "url": "https://github.com/lobehub/mcp-hello-world/issues/new" + }, + "repository": { + "type": "git", + "url": "https://github.com/lobehub/mcp-hello-world.git" + }, + "license": "MIT", + "type": "module", + "author": "LobeHub ", + "bin": { + "mcp-hello-world": "./build/stdio.js" + }, + "sideEffects": false, + "main": "./build/index.js", + "types": "./build/index.d.ts", + "files": [ + "build" + ], + "scripts": { + "build": "tsc", + "ci": "npm run lint && npm run type-check", + "start": "node build/stdio.js", + "start:http": "node build/http.js", + "lint": "eslint \"{src,example}/**/*.{js,jsx,ts,tsx}\" --fix", + "lint:style": "stylelint \"{src,example}/**/*.{js,jsx,ts,tsx}\" --fix", + "prepack": "npm run build && clean-pkg-json", + "prepare": "husky install", + "prepublishOnly": "npm run build", + "prettier": "prettier -c --write --no-error-on-unmatched-pattern \"**/**\"", + "release": "semantic-release", + "test": "vitest --passWithNoTests", + "test:coverage": "vitest run --coverage --passWithNoTests", + "test:update": "vitest -u", + "type-check": "tsc -p tsconfig-check.json" + }, + "lint-staged": { + "*.json": [ + "prettier --write --no-error-on-unmatched-pattern" + ], + "*.{js,jsx}": [ + "prettier --write", + "stylelint --fix", + "eslint --fix" + ], + "*.{ts,tsx}": [ + "prettier --parser=typescript --write", + "stylelint --fix", + "eslint --fix" + ] + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.7.0", + "express": "^4.18.2", + "zod": "^3.22.4" + }, + "devDependencies": { + "@commitlint/cli": "^18", + "@lobehub/i18n-cli": "^1.10.0", + "@lobehub/lint": "latest", + "@testing-library/react": "^14.1.2", + "@types/lodash-es": "^4", + "@types/node": "^22", + "@types/react": "^18", + "@types/react-dom": "^18", + "@vitest/coverage-v8": "^1", + "clean-pkg-json": "^1", + "commitlint": "^18", + "dumi": "^2", + "dumi-theme-lobehub": "latest", + "eslint": "^8", + "husky": "^8", + "jsdom": "^22", + "leva": "^0", + "lint-staged": "^15", + "prettier": "^3", + "react": "^18", + "react-dom": "^18", + "remark": "^14", + "remark-cli": "^11", + "semantic-release": "^21", + "@types/express": "^4.17.21", + "rimraf": "^5.0.5", + "stylelint": "^15", + "ts-json-schema-generator": "^1.4.0", + "typescript": "^5", + "vitest": "^1" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + } +} diff --git a/src/http.ts b/src/http.ts new file mode 100644 index 0000000..39f83e6 --- /dev/null +++ b/src/http.ts @@ -0,0 +1,94 @@ +#!/usr/bin/env node + +import express from "express"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import { createServer } from "./server.js"; + +async function main() { + // Create the server + const server = createServer(); + + // Create Express app + const app = express(); + const port = process.env.PORT || 3000; + + // Server-side connections + const connections = new Map(); + + // Handle SSE connections + app.get("/sse", async (req, res) => { + // Generate a unique connection ID (UUID format) + const sessionId = req.query.sessionId as string || + `${Math.random().toString(36).substring(2, 15)}-${Date.now().toString(36)}`; + + try { + // Create a new SSE transport + const transport = new SSEServerTransport(`/messages?sessionId=${sessionId}`, res); + connections.set(sessionId, transport); + + // Connect the server to this transport + await server.connect(transport); + } catch (error: any) { + console.error('Error establishing SSE connection:', error); + // If headers already sent due to transport.start(), don't try to set them again + if (!res.headersSent) { + res.status(500).end(`Server error: ${error.message || 'Unknown error'}`); + } + } + + // Handle client disconnect + req.on("close", () => { + if (connections.has(sessionId)) { + connections.delete(sessionId); + console.error(`Client ${sessionId} disconnected`); + } + }); + }); + + // Handle messages from client + app.post("/messages", express.json(), async (req, res) => { + try { + // Extract sessionId from query parameters + const sessionId = req.query.sessionId as string; + + console.log(`Received message for session ${sessionId}, body:`, req.body); + + if (!sessionId || !connections.has(sessionId)) { + // If no sessionId or connection not found, try the first connection + if (connections.size === 0) { + return res.status(400).json({ error: "No active connections" }); + } + console.log('Using first available connection'); + const transport = connections.values().next().value; + await transport.handlePostMessage(req, res, req.body); + } else { + // Use the specific connection for this session + console.log(`Using connection for session ${sessionId}`); + const transport = connections.get(sessionId); + await transport.handlePostMessage(req, res, req.body); + } + } catch (error: any) { + console.error('Error handling message:', error); + if (!res.headersSent) { + res.status(500).json({ error: `Internal server error: ${error.message || 'Unknown error'}` }); + } + } + }); + + // Debug: log available methods + console.error('Available methods in the MCP server:'); + console.error('Server object keys:', Object.keys(server)); + console.error('Server constructor:', server.constructor.name); + + // Start the server + app.listen(port, () => { + console.error(`Hello World MCP Server running on http://localhost:${port}`); + console.error(`- Connect to /sse for server-sent events`); + console.error(`- Send messages to /messages endpoint`); + }); +} + +main().catch((error) => { + console.error("Fatal error in main():", error); + process.exit(1); +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..06bfcc2 --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ +export { createServer } from './server.js'; \ No newline at end of file diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..a88f667 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,117 @@ +import { + McpServer, + ResourceTemplate, +} from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +/** + * Creates and configures a Hello World MCP server + * with one resource, one prompt, and one tool + */ +export function createServer() { + // Create an MCP server + const server = new McpServer({ + name: "hello-world", + version: "1.0.0", + // Debug handler for incoming method calls + debug: true, + }); + + // Add a static resource + server.resource("hello", "hello://world", async (uri) => ({ + contents: [ + { + uri: uri.href, + text: "Hello, World from the resource!", + }, + ], + })); + + // Add a dynamic resource with parameters + server.resource( + "greeting", + new ResourceTemplate("greeting://{name}", { list: undefined }), + async (uri, { name }) => ({ + contents: [ + { + uri: uri.href, + text: `Hello, ${name} from the resource!`, + }, + ], + }) + ); + + // Add a prompt + server.prompt( + "helpful-assistant", + "A helpful assistant prompt", // Add description as second parameter + () => ({ + messages: [ + { + role: "assistant", + content: { + type: "text", + text: "You are a helpful assistant.", + }, + }, + ], + }) + ); + + // Add an echo tool + server.tool( + "echo", + "Echoes back a message with 'Hello' prefix", + { message: z.string().describe("The message to echo") }, + async ({ message }) => ({ + content: [ + { + type: "text", + text: `You said: ${message}`, + }, + ], + }) + ); + + // Add a debug tool to list available tools + server.tool( + "debug", + "Lists all available tools and methods", + {}, + async () => ({ + content: [ + { + type: "text", + text: JSON.stringify({ + tools: Object.keys(server), + methods: ["tools/invoke", "mcp/capabilities", "debug/info"] + }, null, 2), + }, + ], + }) + ); + + + server.tool( + 'add', + 'Adds two numbers', + { + a: z.number().describe('The first number'), + b: z.number().describe('The second number'), + }, + async ({ a, b }) => { + console.log(`[Mock SDK Server] Executing add tool with a=${a}, b=${b}`); + const sum = a + b; + return { + content: [ + { + text: `The sum is: ${sum}`, + type: 'text', + }, + ], + }; + }, + ); + + return server; +} diff --git a/src/stdio.ts b/src/stdio.ts new file mode 100644 index 0000000..577a806 --- /dev/null +++ b/src/stdio.ts @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createServer } from "./server.js"; + +async function main() { + // Create the server + const server = createServer(); + + // Create stdio transport + const transport = new StdioServerTransport(); + + // Connect the server to the transport + await server.connect(transport); + + console.error("Hello World MCP Server running on stdio"); +} + +main().catch((error) => { + console.error("Fatal error in main():", error); + process.exit(1); +}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a14bee0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "outDir": "./build", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +}