commit a6fa58bf2bc96b58b5a2f29b2ca0117c43573821 Author: wehub-resource-sync Date: Mon Jul 13 12:36:40 2026 +0800 chore: import upstream snapshot with attribution 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"] +}