hono-mcp 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +249 -0
- package/dist/cli.js +80 -0
- package/dist/commands/math.js +105 -0
- package/dist/data/vscode-settings.json +188 -0
- package/dist/decorators/command.js +37 -0
- package/dist/index.js +40 -0
- package/dist/tools/decorator-tools.js +93 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Hono MCP Server
|
|
2
|
+
|
|
3
|
+
一个基于 [Hono](https://hono.dev/) 构建的 Model Context Protocol (MCP) 服务器,支持多种部署方式。
|
|
4
|
+
|
|
5
|
+
## ✨ 特性
|
|
6
|
+
|
|
7
|
+
- 🎯 **装饰器模式**:使用 TypeScript 装饰器优雅地定义 MCP 工具
|
|
8
|
+
- 🚀 **多种部署方式**:支持 Vercel 部署和本地 CLI 运行
|
|
9
|
+
- 🔧 **统一命令接口**:通过 `executeCommand` 和 `help` 两个工具管理所有命令
|
|
10
|
+
- 📦 **即插即用**:通过 `npx` 快速启动,无需复杂配置
|
|
11
|
+
- 🛡️ **类型安全**:使用 TypeScript 和 Zod 进行类型检查和验证
|
|
12
|
+
- 🎨 **易于扩展**:添加新命令只需创建新的装饰器类
|
|
13
|
+
|
|
14
|
+
## 📦 安装
|
|
15
|
+
|
|
16
|
+
### 通过 npx 直接运行(推荐)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx hono-mcp
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 全局安装
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g hono-mcp
|
|
26
|
+
hono-mcp
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 作为项目依赖安装
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install hono-mcp
|
|
33
|
+
npx hono-mcp
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 🚀 使用方法
|
|
37
|
+
|
|
38
|
+
### 命令行使用
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 使用默认配置(端口 3000)
|
|
42
|
+
npx hono-mcp
|
|
43
|
+
|
|
44
|
+
# 指定端口
|
|
45
|
+
npx hono-mcp --port 8080
|
|
46
|
+
|
|
47
|
+
# 指定主机
|
|
48
|
+
npx hono-mcp --host localhost
|
|
49
|
+
|
|
50
|
+
# 指定环境
|
|
51
|
+
npx hono-mcp --env development
|
|
52
|
+
|
|
53
|
+
# 组合使用
|
|
54
|
+
npx hono-mcp --port 8080 --host localhost --env development
|
|
55
|
+
|
|
56
|
+
# 查看帮助
|
|
57
|
+
npx hono-mcp --help
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 环境变量
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# 设置端口
|
|
64
|
+
export PORT=8080
|
|
65
|
+
npx hono-mcp
|
|
66
|
+
|
|
67
|
+
# 设置主机
|
|
68
|
+
export HOST=localhost
|
|
69
|
+
npx hono-mcp
|
|
70
|
+
|
|
71
|
+
# 设置环境
|
|
72
|
+
export NODE_ENV=development
|
|
73
|
+
npx hono-mcp
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 🌐 部署方式
|
|
77
|
+
|
|
78
|
+
### 方式 1:Vercel 部署
|
|
79
|
+
|
|
80
|
+
1. **克隆项目**
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
git clone https://github.com/Fromsko/hono-mcp-remote-server.git
|
|
84
|
+
cd hono-mcp-remote-server
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
2. **安装依赖**
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
bun install
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
3. **本地开发**
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
bun run dev:vercel
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
4. **部署到 Vercel**
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
vc deploy
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
或者通过 Vercel Dashboard 连接 GitHub 仓库进行自动部署。
|
|
106
|
+
|
|
107
|
+
### 方式 2:本地服务器
|
|
108
|
+
|
|
109
|
+
1. **克隆项目**
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
git clone https://github.com/Fromsko/hono-mcp-remote-server.git
|
|
113
|
+
cd hono-mcp-remote-server
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
2. **安装依赖**
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
bun install
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
3. **构建项目**
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
bun run build
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
4. **启动服务器**
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
bun run start
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
或者使用 npx:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npx hono-mcp --port 3000
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 📡 API 端点
|
|
141
|
+
|
|
142
|
+
- **GET `/`** - 服务器信息和可用工具列表
|
|
143
|
+
- **POST `/mcp/*`** - MCP 协议端点,用于工具执行
|
|
144
|
+
- **GET `/help`** - 查看所有可用命令的帮助信息
|
|
145
|
+
- **GET `/vscode`** - VS Code 设置示例
|
|
146
|
+
|
|
147
|
+
## 🔧 开发
|
|
148
|
+
|
|
149
|
+
### 本地开发
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# 安装依赖
|
|
153
|
+
bun install
|
|
154
|
+
|
|
155
|
+
# 开发模式(CLI)
|
|
156
|
+
bun run dev
|
|
157
|
+
|
|
158
|
+
# 开发模式(Vercel)
|
|
159
|
+
bun run dev:vercel
|
|
160
|
+
|
|
161
|
+
# 构建
|
|
162
|
+
bun run build
|
|
163
|
+
|
|
164
|
+
# 启动生产服务器
|
|
165
|
+
bun run start
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### 添加新命令
|
|
169
|
+
|
|
170
|
+
使用装饰器模式创建新命令非常简单:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { Command, Param } from '../decorators/command.js'
|
|
174
|
+
import { z } from 'zod'
|
|
175
|
+
import type { CommandHandler } from '../decorators/command.js'
|
|
176
|
+
|
|
177
|
+
@Command('math.square', 'Calculate the square of a number')
|
|
178
|
+
export class SquareCommand implements CommandHandler {
|
|
179
|
+
@Param(z.number().describe('The number to square'))
|
|
180
|
+
number!: number
|
|
181
|
+
|
|
182
|
+
async execute() {
|
|
183
|
+
const result = this.number * this.number
|
|
184
|
+
return {
|
|
185
|
+
content: [{ type: 'text', text: `${this.number}² = ${result}` }],
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
然后在 `src/index.ts` 中导入该命令:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import './commands/math.js'
|
|
195
|
+
import './commands/your-new-command.js'
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## 📖 使用 MCP 服务器
|
|
199
|
+
|
|
200
|
+
### 通过 executeCommand 工具
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"type": "math.add",
|
|
205
|
+
"params": {
|
|
206
|
+
"a": 5,
|
|
207
|
+
"b": 3
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### 通过 help 工具
|
|
213
|
+
|
|
214
|
+
查看所有可用命令及其描述。
|
|
215
|
+
|
|
216
|
+
## 🎯 可用命令
|
|
217
|
+
|
|
218
|
+
当前支持以下数学运算命令:
|
|
219
|
+
|
|
220
|
+
- `math.add` - 加法运算
|
|
221
|
+
- `math.subtract` - 减法运算
|
|
222
|
+
- `math.multiply` - 乘法运算
|
|
223
|
+
- `math.divide` - 除法运算
|
|
224
|
+
|
|
225
|
+
## 📚 设计文档
|
|
226
|
+
|
|
227
|
+
详细的设计文档请参考:[MCP 装饰器模式设计文档](./docs/MCP-装饰器模式设计文档.md)
|
|
228
|
+
|
|
229
|
+
## 🤝 贡献
|
|
230
|
+
|
|
231
|
+
欢迎提交 Issue 和 Pull Request!
|
|
232
|
+
|
|
233
|
+
## 📄 许可证
|
|
234
|
+
|
|
235
|
+
MIT License
|
|
236
|
+
|
|
237
|
+
## 🔗 相关链接
|
|
238
|
+
|
|
239
|
+
- [Hono 文档](https://hono.dev/)
|
|
240
|
+
- [MCP 协议规范](https://modelcontextprotocol.io/)
|
|
241
|
+
- [mcp-handler](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
242
|
+
|
|
243
|
+
## 📞 联系方式
|
|
244
|
+
|
|
245
|
+
作者: fromsko
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
**提示**: 使用 `npx hono-mcp --help` 查看完整的命令行选项。
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { serve } from '@hono/node-server';
|
|
3
|
+
import app from './index.js';
|
|
4
|
+
function parseArgs() {
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const options = {};
|
|
7
|
+
for (let i = 0; i < args.length; i++) {
|
|
8
|
+
const arg = args[i];
|
|
9
|
+
if (arg === '--port' || arg === '-p') {
|
|
10
|
+
options.port = parseInt(args[++i], 10);
|
|
11
|
+
}
|
|
12
|
+
else if (arg === '--host' || arg === '-h') {
|
|
13
|
+
options.host = args[++i];
|
|
14
|
+
}
|
|
15
|
+
else if (arg === '--env') {
|
|
16
|
+
options.env = args[++i];
|
|
17
|
+
}
|
|
18
|
+
else if (arg === '--help' || arg === '-help') {
|
|
19
|
+
console.log(`
|
|
20
|
+
Usage: hono-mcp [options]
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--port, -p <port> Port to listen on (default: 3000)
|
|
24
|
+
--host, -h <host> Host to bind to (default: 0.0.0.0)
|
|
25
|
+
--env <environment> Environment: development or production (default: production)
|
|
26
|
+
--help, -help Show this help message
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
hono-mcp # Start server on default port 3000
|
|
30
|
+
hono-mcp --port 8080 # Start server on port 8080
|
|
31
|
+
hono-mcp --port 8080 --host localhost # Start on localhost:8080
|
|
32
|
+
hono-mcp --env development # Start in development mode
|
|
33
|
+
`);
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return options;
|
|
38
|
+
}
|
|
39
|
+
async function main() {
|
|
40
|
+
const options = parseArgs();
|
|
41
|
+
const port = options.port || parseInt(process.env.PORT || '3000', 10);
|
|
42
|
+
const host = options.host || process.env.HOST || '0.0.0.0';
|
|
43
|
+
const env = options.env || process.env.NODE_ENV || 'production';
|
|
44
|
+
console.log(`🚀 Starting Hono MCP Server...`);
|
|
45
|
+
console.log(`📦 Environment: ${env}`);
|
|
46
|
+
console.log(`🌐 Host: ${host}`);
|
|
47
|
+
console.log(`🔌 Port: ${port}`);
|
|
48
|
+
console.log(`📡 MCP Endpoint: http://${host}:${port}/mcp`);
|
|
49
|
+
console.log(`📖 Help: http://${host}:${port}/help`);
|
|
50
|
+
console.log('');
|
|
51
|
+
try {
|
|
52
|
+
const server = await serve({
|
|
53
|
+
fetch: app.fetch,
|
|
54
|
+
port,
|
|
55
|
+
hostname: host,
|
|
56
|
+
});
|
|
57
|
+
console.log(`✅ Server is running!`);
|
|
58
|
+
console.log(``);
|
|
59
|
+
console.log(`Press Ctrl+C to stop the server`);
|
|
60
|
+
process.on('SIGINT', () => {
|
|
61
|
+
console.log('\n\n🛑 Shutting down server...');
|
|
62
|
+
server.close(() => {
|
|
63
|
+
console.log('✅ Server stopped');
|
|
64
|
+
process.exit(0);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
process.on('SIGTERM', () => {
|
|
68
|
+
console.log('\n\n🛑 Shutting down server...');
|
|
69
|
+
server.close(() => {
|
|
70
|
+
console.log('✅ Server stopped');
|
|
71
|
+
process.exit(0);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error('❌ Failed to start server:', error);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
main();
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { Command, Param } from '../decorators/command.js';
|
|
12
|
+
let AddCommand = class AddCommand {
|
|
13
|
+
a;
|
|
14
|
+
b;
|
|
15
|
+
async execute() {
|
|
16
|
+
const result = this.a + this.b;
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: 'text', text: `${this.a} + ${this.b} = ${result}` }],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
__decorate([
|
|
23
|
+
Param(z.number().describe('First number')),
|
|
24
|
+
__metadata("design:type", Number)
|
|
25
|
+
], AddCommand.prototype, "a", void 0);
|
|
26
|
+
__decorate([
|
|
27
|
+
Param(z.number().describe('Second number')),
|
|
28
|
+
__metadata("design:type", Number)
|
|
29
|
+
], AddCommand.prototype, "b", void 0);
|
|
30
|
+
AddCommand = __decorate([
|
|
31
|
+
Command('math.add', 'Add two numbers')
|
|
32
|
+
], AddCommand);
|
|
33
|
+
export { AddCommand };
|
|
34
|
+
let SubtractCommand = class SubtractCommand {
|
|
35
|
+
a;
|
|
36
|
+
b;
|
|
37
|
+
async execute() {
|
|
38
|
+
const result = this.a - this.b;
|
|
39
|
+
return {
|
|
40
|
+
content: [{ type: 'text', text: `${this.a} - ${this.b} = ${result}` }],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
__decorate([
|
|
45
|
+
Param(z.number().describe('First number')),
|
|
46
|
+
__metadata("design:type", Number)
|
|
47
|
+
], SubtractCommand.prototype, "a", void 0);
|
|
48
|
+
__decorate([
|
|
49
|
+
Param(z.number().describe('Second number')),
|
|
50
|
+
__metadata("design:type", Number)
|
|
51
|
+
], SubtractCommand.prototype, "b", void 0);
|
|
52
|
+
SubtractCommand = __decorate([
|
|
53
|
+
Command('math.subtract', 'Subtract two numbers')
|
|
54
|
+
], SubtractCommand);
|
|
55
|
+
export { SubtractCommand };
|
|
56
|
+
let MultiplyCommand = class MultiplyCommand {
|
|
57
|
+
a;
|
|
58
|
+
b;
|
|
59
|
+
async execute() {
|
|
60
|
+
const result = this.a * this.b;
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: 'text', text: `${this.a} × ${this.b} = ${result}` }],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
__decorate([
|
|
67
|
+
Param(z.number().describe('First number')),
|
|
68
|
+
__metadata("design:type", Number)
|
|
69
|
+
], MultiplyCommand.prototype, "a", void 0);
|
|
70
|
+
__decorate([
|
|
71
|
+
Param(z.number().describe('Second number')),
|
|
72
|
+
__metadata("design:type", Number)
|
|
73
|
+
], MultiplyCommand.prototype, "b", void 0);
|
|
74
|
+
MultiplyCommand = __decorate([
|
|
75
|
+
Command('math.multiply', 'Multiply two numbers')
|
|
76
|
+
], MultiplyCommand);
|
|
77
|
+
export { MultiplyCommand };
|
|
78
|
+
let DivideCommand = class DivideCommand {
|
|
79
|
+
a;
|
|
80
|
+
b;
|
|
81
|
+
async execute() {
|
|
82
|
+
if (this.b === 0) {
|
|
83
|
+
return {
|
|
84
|
+
content: [{ type: 'text', text: 'Error: Division by zero' }],
|
|
85
|
+
isError: true,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const result = this.a / this.b;
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: 'text', text: `${this.a} ÷ ${this.b} = ${result}` }],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
__decorate([
|
|
95
|
+
Param(z.number().describe('First number')),
|
|
96
|
+
__metadata("design:type", Number)
|
|
97
|
+
], DivideCommand.prototype, "a", void 0);
|
|
98
|
+
__decorate([
|
|
99
|
+
Param(z.number().describe('Second number')),
|
|
100
|
+
__metadata("design:type", Number)
|
|
101
|
+
], DivideCommand.prototype, "b", void 0);
|
|
102
|
+
DivideCommand = __decorate([
|
|
103
|
+
Command('math.divide', 'Divide two numbers')
|
|
104
|
+
], DivideCommand);
|
|
105
|
+
export { DivideCommand };
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
{
|
|
2
|
+
"security.workspace.trust.untrustedFiles": "open",
|
|
3
|
+
"workbench.colorTheme": "One Dark Modern",
|
|
4
|
+
"editor.fontFamily": "'JetBrains Mono', 'maple mono', 'Fira Code', 'Consolas', monospace",
|
|
5
|
+
"editor.tabSize": 4,
|
|
6
|
+
"editor.fontSize": 17,
|
|
7
|
+
"editor.fontLigatures": true,
|
|
8
|
+
"editor.fontWeight": 100,
|
|
9
|
+
"editor.lineNumbers": "on",
|
|
10
|
+
"editor.cursorStyle": "line-thin",
|
|
11
|
+
"editor.wordWrap": "on",
|
|
12
|
+
"editor.scrollBeyondLastLine": false,
|
|
13
|
+
"editor.renderControlCharacters": false,
|
|
14
|
+
"editor.linkedEditing": true,
|
|
15
|
+
"editor.mouseWheelZoom": true,
|
|
16
|
+
"editor.multiCursorModifier": "alt",
|
|
17
|
+
"editor.dragAndDrop": true,
|
|
18
|
+
"editor.showUnused": true,
|
|
19
|
+
"editor.emptySelectionClipboard": true,
|
|
20
|
+
"editor.accessibilityPageSize": 500,
|
|
21
|
+
"editor.minimap.maxColumn": 70,
|
|
22
|
+
"editor.minimap.renderCharacters": true,
|
|
23
|
+
"editor.minimap.showSlider": "always",
|
|
24
|
+
"editor.tokenColorCustomizations": {
|
|
25
|
+
"textMateRules": [
|
|
26
|
+
{
|
|
27
|
+
"name": "Comments",
|
|
28
|
+
"scope": "comment, punctuation.definition.comment",
|
|
29
|
+
"settings": {
|
|
30
|
+
"foreground": "#458b29"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"editor.unicodeHighlight.allowedLocales": {
|
|
36
|
+
"ja": true
|
|
37
|
+
},
|
|
38
|
+
"editor.formatOnSave": true,
|
|
39
|
+
"editor.formatOnPaste": true,
|
|
40
|
+
"editor.formatOnType": true,
|
|
41
|
+
"editor.formatOnSaveMode": "file",
|
|
42
|
+
"editor.foldingImportsByDefault": false,
|
|
43
|
+
"editor.codeActionsOnSave": {
|
|
44
|
+
"source.organizeImports": "always",
|
|
45
|
+
"source.fixAll.stylelint": "always",
|
|
46
|
+
"source.fixAll.tsserver": "always",
|
|
47
|
+
"source.unusedImports": "always"
|
|
48
|
+
},
|
|
49
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
50
|
+
"prettier.semi": true,
|
|
51
|
+
"editor.quickSuggestions": {
|
|
52
|
+
"other": "on",
|
|
53
|
+
"comments": "on",
|
|
54
|
+
"strings": "on"
|
|
55
|
+
},
|
|
56
|
+
"editor.quickSuggestionsDelay": 0,
|
|
57
|
+
"editor.suggest.matchOnWordStartOnly": true,
|
|
58
|
+
"editor.suggestFontSize": 14,
|
|
59
|
+
"editor.suggestLineHeight": 22,
|
|
60
|
+
"editor.suggest.preview": true,
|
|
61
|
+
"editor.suggest.showStatusBar": true,
|
|
62
|
+
"editor.suggest.filterGraceful": true,
|
|
63
|
+
"editor.suggest.shareSuggestSelections": true,
|
|
64
|
+
"editor.suggestSelection": "first",
|
|
65
|
+
"editor.suggest.showIcons": true,
|
|
66
|
+
"editor.suggest.showMethods": true,
|
|
67
|
+
"editor.suggest.showFunctions": true,
|
|
68
|
+
"editor.suggest.showConstructors": true,
|
|
69
|
+
"editor.suggest.showFields": true,
|
|
70
|
+
"editor.suggest.showVariables": true,
|
|
71
|
+
"editor.suggest.showUnits": true,
|
|
72
|
+
"editor.suggest.showValues": true,
|
|
73
|
+
"editor.suggest.showSnippets": true,
|
|
74
|
+
"editor.suggest.showKeywords": true,
|
|
75
|
+
"editor.suggest.showWords": true,
|
|
76
|
+
"editor.suggest.showOperators": true,
|
|
77
|
+
"editor.suggest.insertMode": "insert",
|
|
78
|
+
"editor.suggest.snippetsPreventQuickSuggestions": false,
|
|
79
|
+
"editor.suggest.localityBonus": true,
|
|
80
|
+
"editor.suggestOnTriggerCharacters": true,
|
|
81
|
+
"editor.acceptSuggestionOnCommitCharacter": true,
|
|
82
|
+
"editor.acceptSuggestionOnEnter": "smart",
|
|
83
|
+
"editor.parameterHints.enabled": true,
|
|
84
|
+
"editor.inlineSuggest.fontFamily": "'JetBrains Mono', 'Fira Code', 'Consolas', monospace",
|
|
85
|
+
"editor.inlineSuggest.suppressSuggestions": false,
|
|
86
|
+
"editor.autoClosingBrackets": "always",
|
|
87
|
+
"editor.autoClosingQuotes": "always",
|
|
88
|
+
"editor.autoSurround": "languageDefined",
|
|
89
|
+
"editor.largeFileOptimizations": false,
|
|
90
|
+
"tailwindCSS.includeLanguages": {
|
|
91
|
+
"plaintext": "html"
|
|
92
|
+
},
|
|
93
|
+
"files.autoSave": "afterDelay",
|
|
94
|
+
"workbench.editor.doubleClickTabToToggleEditorGroupSizes": "maximize",
|
|
95
|
+
"files.autoSaveDelay": 1000,
|
|
96
|
+
"files.refactoring.autoSave": true,
|
|
97
|
+
"files.associations": {
|
|
98
|
+
"*.vue": "vue",
|
|
99
|
+
"*.cjs": "javascript",
|
|
100
|
+
"*.mjs": "javascript",
|
|
101
|
+
"*.css": "tailwindcss"
|
|
102
|
+
},
|
|
103
|
+
"terminal.integrated.tabs.enabled": true,
|
|
104
|
+
"terminal.integrated.cursorStyle": "line",
|
|
105
|
+
"terminal.integrated.cursorBlinking": true,
|
|
106
|
+
"terminal.integrated.cursorStyleInactive": "line",
|
|
107
|
+
"terminal.integrated.fontFamily": "'JetBrains Mono', Consolas, 'Fira Code', monospace",
|
|
108
|
+
"terminal.integrated.enableMultiLinePasteWarning": "never",
|
|
109
|
+
"git.ignoreMissingGitWarning": true,
|
|
110
|
+
"git.confirmSync": false,
|
|
111
|
+
"git.enableSmartCommit": true,
|
|
112
|
+
"git.autofetch": true,
|
|
113
|
+
"git.openRepositoryInParentFolders": "never",
|
|
114
|
+
"debug.onTaskErrors": "debugAnyway",
|
|
115
|
+
"security.promptForLocalFileProtocolHandling": false,
|
|
116
|
+
"trae.preview.permission": {
|
|
117
|
+
"localhost": {
|
|
118
|
+
"fileSystem": "granted"
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"python.analysis.importFormat": "absolute",
|
|
122
|
+
"python.analysis.autoFormatStrings": false,
|
|
123
|
+
"javascript.updateImportsOnFileMove.enabled": "never",
|
|
124
|
+
"typescript.updateImportsOnFileMove.enabled": "always",
|
|
125
|
+
"extensions.ignoreRecommendations": false,
|
|
126
|
+
"code-runner.clearPreviousOutput": true,
|
|
127
|
+
"code-runner.runInTerminal": true,
|
|
128
|
+
"bracket-pair-colorizer-2.depreciation-notice": false,
|
|
129
|
+
"debug.inlineValues": "on",
|
|
130
|
+
"debug.openDebug": "openOnDebugBreak",
|
|
131
|
+
"workbench.startupEditor": "none",
|
|
132
|
+
"explorer.confirmDelete": false,
|
|
133
|
+
"explorer.confirmDragAndDrop": false,
|
|
134
|
+
"explorer.confirmPasteNative": false,
|
|
135
|
+
"notebook.output.scrolling": true,
|
|
136
|
+
"notebook.output.textLineLimit": 100,
|
|
137
|
+
"path-autocomplete.extensionOnImport": true,
|
|
138
|
+
"path-autocomplete.pathMappings": {
|
|
139
|
+
"./*": "${folder}/*"
|
|
140
|
+
},
|
|
141
|
+
"[markdown]": {
|
|
142
|
+
"editor.defaultFormatter": "yzhang.markdown-all-in-one"
|
|
143
|
+
},
|
|
144
|
+
"[html]": {
|
|
145
|
+
"editor.defaultFormatter": "vscode.html-language-features"
|
|
146
|
+
},
|
|
147
|
+
"[typescript]": {
|
|
148
|
+
"editor.rulers": [120],
|
|
149
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
150
|
+
},
|
|
151
|
+
"[typescriptreact]": {
|
|
152
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
153
|
+
},
|
|
154
|
+
"[jsonc]": {
|
|
155
|
+
"editor.defaultFormatter": "vscode.json-language-features"
|
|
156
|
+
},
|
|
157
|
+
"terminal.integrated.defaultProfile.windows": "Command Prompt",
|
|
158
|
+
"application.extensionMarketUrl": "https://marketplace.visualstudio.com/",
|
|
159
|
+
"[go]": {
|
|
160
|
+
"editor.defaultFormatter": "golang.go"
|
|
161
|
+
},
|
|
162
|
+
"[rust]": {
|
|
163
|
+
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
|
164
|
+
},
|
|
165
|
+
"workbench.secondarySideBar.defaultVisibility": "hidden",
|
|
166
|
+
"npm.packageManager": "bun",
|
|
167
|
+
"[python]": {
|
|
168
|
+
"editor.defaultFormatter": "charliermarsh.ruff"
|
|
169
|
+
},
|
|
170
|
+
"AI.toolcall.confirmMode": "autoRun",
|
|
171
|
+
"update.showReleaseNotes": false,
|
|
172
|
+
"explorer.compactFolders": false,
|
|
173
|
+
"rest-client.enableTelemetry": false,
|
|
174
|
+
"workbench.iconTheme": "material-icon-theme",
|
|
175
|
+
"workbench.view.showQuietly": {
|
|
176
|
+
"workbench.panel.output": false
|
|
177
|
+
},
|
|
178
|
+
"breadcrumbs.enabled": false,
|
|
179
|
+
"trae.tab.enableSummaryAutoDisplay": true,
|
|
180
|
+
"diffEditor.ignoreTrimWhitespace": false,
|
|
181
|
+
"go.lintTool": "golangci-lint-v2",
|
|
182
|
+
"liveServer.settings.donotVerifyTags": true,
|
|
183
|
+
"liveServer.settings.donotShowInfoMsg": true,
|
|
184
|
+
"cursor-infinity.autoOpenPanel": false,
|
|
185
|
+
"files.participants.timeout": 0,
|
|
186
|
+
"chat.detectParticipant.enabled": false,
|
|
187
|
+
"chat.disableAIFeatures": true
|
|
188
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
const PARAM_METADATA_KEY = "command:params";
|
|
3
|
+
const commandRegistry = new Map();
|
|
4
|
+
export function Command(type, description) {
|
|
5
|
+
return function (constructor) {
|
|
6
|
+
const instance = new constructor();
|
|
7
|
+
const params = new Map();
|
|
8
|
+
const paramMetadata = Reflect.getMetadata(PARAM_METADATA_KEY, constructor) || {};
|
|
9
|
+
Object.entries(paramMetadata).forEach(([key, schema]) => {
|
|
10
|
+
params.set(key, schema);
|
|
11
|
+
});
|
|
12
|
+
commandRegistry.set(type, {
|
|
13
|
+
type,
|
|
14
|
+
description,
|
|
15
|
+
params,
|
|
16
|
+
handler: instance.execute.bind(instance),
|
|
17
|
+
});
|
|
18
|
+
return constructor;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function Param(schema) {
|
|
22
|
+
return function (target, propertyKey) {
|
|
23
|
+
const constructor = target.constructor;
|
|
24
|
+
const existingParams = Reflect.getMetadata(PARAM_METADATA_KEY, constructor) || {};
|
|
25
|
+
existingParams[propertyKey] = schema;
|
|
26
|
+
Reflect.defineMetadata(PARAM_METADATA_KEY, existingParams, constructor);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function getCommandRegistry() {
|
|
30
|
+
return commandRegistry;
|
|
31
|
+
}
|
|
32
|
+
export function getCommand(type) {
|
|
33
|
+
return commandRegistry.get(type);
|
|
34
|
+
}
|
|
35
|
+
export function getAllCommands() {
|
|
36
|
+
return Array.from(commandRegistry.values());
|
|
37
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { createMcpHandler } from "mcp-handler";
|
|
4
|
+
import vscodeSettings from './data/vscode-settings.json' with { type: 'json' };
|
|
5
|
+
import { executeCommandTool, helpTool } from "./tools/decorator-tools.js";
|
|
6
|
+
import './commands/math.js';
|
|
7
|
+
const app = new Hono();
|
|
8
|
+
const tools = [executeCommandTool, helpTool];
|
|
9
|
+
var ToolName;
|
|
10
|
+
(function (ToolName) {
|
|
11
|
+
ToolName["ExecuteCommand"] = "executeCommand";
|
|
12
|
+
ToolName["Help"] = "help";
|
|
13
|
+
})(ToolName || (ToolName = {}));
|
|
14
|
+
const handler = createMcpHandler((server) => {
|
|
15
|
+
tools.forEach((tool) => {
|
|
16
|
+
tool(server);
|
|
17
|
+
});
|
|
18
|
+
}, {}, {
|
|
19
|
+
basePath: "/",
|
|
20
|
+
maxDuration: 60,
|
|
21
|
+
verboseLogs: true,
|
|
22
|
+
});
|
|
23
|
+
app.all("/mcp/*", async (c) => {
|
|
24
|
+
return await handler(c.req.raw);
|
|
25
|
+
});
|
|
26
|
+
app.get("/vscode", async (c) => {
|
|
27
|
+
return c.json(vscodeSettings);
|
|
28
|
+
});
|
|
29
|
+
app.get("/", async (c) => {
|
|
30
|
+
return c.json({
|
|
31
|
+
message: "Hono MCP Server - Math Operations",
|
|
32
|
+
endpoints: {
|
|
33
|
+
mcp: "/mcp",
|
|
34
|
+
testEdgeConfig: "/test-edge-config",
|
|
35
|
+
description: "MCP server with math operation tools (add, subtract, multiply, divide)",
|
|
36
|
+
},
|
|
37
|
+
tools: Object.values(ToolName),
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
export default app;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getCommandRegistry } from "../decorators/command.js";
|
|
3
|
+
export const executeCommandTool = (server) => {
|
|
4
|
+
server.tool("executeCommand", "Execute any command by specifying its type and parameters", {
|
|
5
|
+
type: {
|
|
6
|
+
type: "string",
|
|
7
|
+
description: "Command type (e.g., math.add, math.subtract, math.multiply, math.divide)",
|
|
8
|
+
},
|
|
9
|
+
params: {
|
|
10
|
+
type: "object",
|
|
11
|
+
description: "Command parameters",
|
|
12
|
+
},
|
|
13
|
+
}, async ({ type, params }) => {
|
|
14
|
+
const command = getCommandRegistry().get(type);
|
|
15
|
+
if (!command) {
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: `Unknown command type: ${type}\n\nAvailable commands:\n${Array.from(getCommandRegistry().keys())
|
|
21
|
+
.map((t) => `- ${t}`)
|
|
22
|
+
.join("\n")}`,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
isError: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const schema = z.object(Object.fromEntries(Array.from(command.params.entries()).map(([key, val]) => [key, val])));
|
|
29
|
+
const validationResult = schema.safeParse(params);
|
|
30
|
+
if (!validationResult.success) {
|
|
31
|
+
return {
|
|
32
|
+
content: [
|
|
33
|
+
{
|
|
34
|
+
type: "text",
|
|
35
|
+
text: `Invalid parameters: ${validationResult.error.message}`,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
isError: true,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return await command.handler(validationResult.data);
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
export const helpTool = (server) => {
|
|
45
|
+
server.tool("help", "Get help information about available commands", {
|
|
46
|
+
type: {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "Command type to get help for (optional)",
|
|
49
|
+
},
|
|
50
|
+
}, async ({ type }) => {
|
|
51
|
+
if (type) {
|
|
52
|
+
const command = getCommandRegistry().get(type);
|
|
53
|
+
if (!command) {
|
|
54
|
+
return {
|
|
55
|
+
content: [
|
|
56
|
+
{
|
|
57
|
+
type: "text",
|
|
58
|
+
text: `Unknown command type: ${type}\n\nAvailable commands:\n${Array.from(getCommandRegistry().keys())
|
|
59
|
+
.map((t) => `- ${t}`)
|
|
60
|
+
.join("\n")}`,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
isError: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const paramsInfo = Array.from(command.params.entries())
|
|
67
|
+
.map(([key, schema]) => {
|
|
68
|
+
const description = schema.description || "No description";
|
|
69
|
+
return `- ${key}: ${description}`;
|
|
70
|
+
})
|
|
71
|
+
.join("\n");
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{
|
|
75
|
+
type: "text",
|
|
76
|
+
text: `Command: ${command.type}\nDescription: ${command.description}\n\nParameters:\n${paramsInfo}`,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const commandList = Array.from(getCommandRegistry().values())
|
|
82
|
+
.map((cmd) => `- ${cmd.type}: ${cmd.description}`)
|
|
83
|
+
.join("\n");
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: `Available commands:\n${commandList}\n\nUse help with a specific command type for details.`,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hono-mcp",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "MCP server built with Hono - supports both Vercel deployment and npx CLI usage",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"hono-mcp": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"start": "node dist/cli.js",
|
|
17
|
+
"dev": "tsx src/cli.ts",
|
|
18
|
+
"dev:vercel": "tsx src/index.ts"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"hono",
|
|
23
|
+
"model-context-protocol",
|
|
24
|
+
"mcp-server",
|
|
25
|
+
"cli",
|
|
26
|
+
"npx",
|
|
27
|
+
"vercel",
|
|
28
|
+
"server",
|
|
29
|
+
"api",
|
|
30
|
+
"typescript",
|
|
31
|
+
"decorator"
|
|
32
|
+
],
|
|
33
|
+
"author": "fromsko",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@hono/node-server": "^1.19.7",
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.17.3",
|
|
38
|
+
"@vercel/edge-config": "^1.4.3",
|
|
39
|
+
"hono": "^4.9.2",
|
|
40
|
+
"mcp-handler": "^1.0.1",
|
|
41
|
+
"reflect-metadata": "^0.2.2",
|
|
42
|
+
"zod": "^3"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^20.11.17",
|
|
46
|
+
"tsx": "^4.7.0",
|
|
47
|
+
"typescript": "^5.9.3"
|
|
48
|
+
}
|
|
49
|
+
}
|