chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
[install.lockfile]
|
||||
|
||||
save = false
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@lobehub/lint').changelog;
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@lobehub/lint').commitlint;
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
@@ -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 }}
|
||||
+53
@@ -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
|
||||
@@ -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*
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
const {prettier}= require('@lobehub/lint');
|
||||
|
||||
export default prettier;
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@lobehub/lint').semanticRelease;
|
||||
@@ -0,0 +1,9 @@
|
||||
const config = require('@lobehub/lint').stylelint;
|
||||
|
||||
module.exports = {
|
||||
...config,
|
||||
rules: {
|
||||
'custom-property-pattern': null,
|
||||
'selector-pseudo-element-no-unknown': null,
|
||||
},
|
||||
};
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
<a name="readme-top"></a>
|
||||
|
||||
# Changelog
|
||||
|
||||
### [Version 1.1.2](https://github.com/lobehub/mcp-hello-world/compare/v1.1.1...v1.1.2)
|
||||
<sup>Released on **2025-04-20**</sup>
|
||||
|
||||
|
||||
#### 💄 Styles
|
||||
|
||||
- **misc**: Improve output.
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
|
||||
|
||||
#### Styles
|
||||
|
||||
* **misc**: Improve output ([3dd383c](https://github.com/lobehub/mcp-hello-world/commit/3dd383c))
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 1.1.1](https://github.com/lobehub/mcp-hello-world/compare/v1.1.0...v1.1.1)
|
||||
<sup>Released on **2025-04-20**</sup>
|
||||
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
|
||||
- **misc**: Fix output files.
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
|
||||
|
||||
#### What's fixed
|
||||
|
||||
* **misc**: Fix output files ([fe5c9d8](https://github.com/lobehub/mcp-hello-world/commit/fe5c9d8))
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## [Version 1.1.0](https://github.com/lobehub/mcp-hello-world/compare/v1.0.0...v1.1.0)
|
||||
<sup>Released on **2025-04-20**</sup>
|
||||
|
||||
|
||||
#### ✨ Features
|
||||
|
||||
- **misc**: Bump version.
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
|
||||
|
||||
#### What's improved
|
||||
|
||||
* **misc**: Bump version ([fa7bdd5](https://github.com/lobehub/mcp-hello-world/commit/fa7bdd5))
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## Version 1.0.0
|
||||
<sup>Released on **2025-04-20**</sup>
|
||||
|
||||
|
||||
#### ✨ Features
|
||||
|
||||
- **misc**: Init mcp hello word.
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
|
||||
|
||||
#### What's improved
|
||||
|
||||
* **misc**: Init mcp hello word ([8a8f5c9](https://github.com/lobehub/mcp-hello-world/commit/8a8f5c9))
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
[](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
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`lobehub/mcp-hello-world`
|
||||
- 原始仓库:https://github.com/lobehub/mcp-hello-world
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
+206
@@ -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
|
||||
+107
@@ -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 <i@lobehub.com>",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
+94
@@ -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);
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export { createServer } from './server.js';
|
||||
+117
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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"]
|
||||
}
|
||||
Reference in New Issue
Block a user