dg-lab-mcp-server 1.0.0 → 1.0.2
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 +116 -45
- package/dist/cli.js +95 -108
- package/dist/cli.js.map +3 -3
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/index.js +95 -108
- package/dist/index.js.map +3 -3
- package/dist/waveform-parser.d.ts +34 -22
- package/dist/waveform-parser.d.ts.map +1 -1
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -8,81 +8,145 @@
|
|
|
8
8
|
- **内置 WebSocket 服务器**: 无需外部 WS 后端,直接与 DG-LAB APP 通信
|
|
9
9
|
- **单端口设计**: HTTP/SSE 和 WebSocket 共享同一端口
|
|
10
10
|
- **波形管理**: 支持解析、保存、发送 DG-LAB 波形数据
|
|
11
|
+
- **持续播放**: 支持波形持续循环播放
|
|
11
12
|
- **会话管理**: 支持设备别名、多设备管理
|
|
12
13
|
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# 全局安装
|
|
18
|
+
npm install -g dg-lab-mcp-server
|
|
19
|
+
|
|
20
|
+
# 或使用 npx 直接运行
|
|
21
|
+
npx dg-lab-mcp-server
|
|
22
|
+
```
|
|
23
|
+
|
|
13
24
|
## 快速开始
|
|
14
25
|
|
|
15
|
-
###
|
|
26
|
+
### 直接运行
|
|
16
27
|
|
|
17
28
|
```bash
|
|
18
|
-
|
|
29
|
+
# 使用默认配置
|
|
30
|
+
npx dg-lab-mcp-server
|
|
31
|
+
|
|
32
|
+
# 设置公网 IP
|
|
33
|
+
PUBLIC_IP=1.2.3.4 npx dg-lab-mcp-server
|
|
34
|
+
|
|
35
|
+
# 设置端口
|
|
36
|
+
PORT=8080 npx dg-lab-mcp-server
|
|
19
37
|
```
|
|
20
38
|
|
|
21
|
-
###
|
|
39
|
+
### 配置 MCP 客户端
|
|
40
|
+
|
|
41
|
+
在 Claude Desktop 或其他 MCP 客户端的配置文件中添加:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"dg-lab": {
|
|
47
|
+
"command": "npx",
|
|
48
|
+
"args": ["dg-lab-mcp-server"],
|
|
49
|
+
"env": {
|
|
50
|
+
"PUBLIC_IP": "你的公网IP"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
22
56
|
|
|
23
|
-
|
|
24
|
-
|
|
57
|
+
**Windows 配置文件位置**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
58
|
+
|
|
59
|
+
**macOS 配置文件位置**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
60
|
+
|
|
61
|
+
### 完整配置示例
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"dg-lab": {
|
|
67
|
+
"command": "npx",
|
|
68
|
+
"args": ["dg-lab-mcp-server"],
|
|
69
|
+
"env": {
|
|
70
|
+
"PUBLIC_IP": "162.14.116.58",
|
|
71
|
+
"PORT": "3323",
|
|
72
|
+
"CONNECTION_TIMEOUT_MINUTES": "10"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
25
77
|
```
|
|
26
78
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
79
|
+
## 环境变量
|
|
80
|
+
|
|
81
|
+
通过环境变量配置服务器:
|
|
82
|
+
|
|
83
|
+
| 变量 | 默认值 | 说明 |
|
|
84
|
+
|------|--------|------|
|
|
85
|
+
| `PORT` | 3323 | 服务端口 (HTTP/WebSocket 共享) |
|
|
86
|
+
| `PUBLIC_IP` | (自动检测) | 公网 IP 地址,用于生成二维码。留空则使用本地 IP |
|
|
87
|
+
| `SSE_PATH` | /sse | SSE 端点路径 |
|
|
88
|
+
| `POST_PATH` | /message | POST 端点路径 |
|
|
89
|
+
| `CONNECTION_TIMEOUT_MINUTES` | 5 | 未绑定设备的超时时间(分钟) |
|
|
90
|
+
| `HEARTBEAT_INTERVAL` | 30000 | WebSocket 心跳间隔 (ms) |
|
|
91
|
+
| `STALE_DEVICE_TIMEOUT` | 3600000 | 设备活跃超时 (ms),默认 1 小时 |
|
|
92
|
+
| `WAVEFORM_STORE_PATH` | ./data/waveforms.json | 波形存储路径 |
|
|
31
93
|
|
|
32
94
|
## 使用流程
|
|
33
95
|
|
|
34
|
-
1.
|
|
96
|
+
1. **创建设备**: 调用 `dg_create_device` 获取二维码内容
|
|
35
97
|
2. **扫码绑定**: 用户使用 DG-LAB APP 扫描二维码
|
|
36
|
-
3. **检查状态**: 调用 `
|
|
37
|
-
4. **控制设备**:
|
|
98
|
+
3. **检查状态**: 调用 `dg_get_device_status` 确认 `boundToApp: true`
|
|
99
|
+
4. **控制设备**: 使用强度控制或波形控制工具
|
|
38
100
|
|
|
39
|
-
## 可用工具
|
|
101
|
+
## 可用工具 (16 个)
|
|
40
102
|
|
|
41
103
|
### 设备管理
|
|
42
104
|
| 工具 | 说明 |
|
|
43
105
|
|------|------|
|
|
44
|
-
| `
|
|
106
|
+
| `dg_create_device` | 创建新设备会话,返回二维码内容 |
|
|
45
107
|
| `dg_list_devices` | 列出所有设备及状态 |
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `dg_find_device` | 按别名查找设备 |
|
|
49
|
-
| `dg_disconnect` | 断开并删除设备连接 |
|
|
108
|
+
| `dg_get_device_status` | 获取指定设备的详细状态 |
|
|
109
|
+
| `dg_delete_device` | 删除设备会话 |
|
|
50
110
|
|
|
51
|
-
###
|
|
111
|
+
### 强度控制
|
|
52
112
|
| 工具 | 说明 |
|
|
53
113
|
|------|------|
|
|
54
|
-
| `dg_set_strength` |
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
114
|
+
| `dg_set_strength` | 设置 A/B 通道强度 (0-200) |
|
|
115
|
+
| `dg_adjust_strength` | 增量调整强度 |
|
|
116
|
+
| `dg_get_strength` | 获取当前强度值 |
|
|
117
|
+
|
|
118
|
+
### 波形控制
|
|
119
|
+
| 工具 | 说明 |
|
|
120
|
+
|------|------|
|
|
121
|
+
| `dg_send_waveform` | 发送单次波形 |
|
|
122
|
+
| `dg_start_continuous_playback` | 开始持续播放波形 |
|
|
123
|
+
| `dg_stop_continuous_playback` | 停止持续播放 |
|
|
124
|
+
| `dg_get_playback_status` | 获取播放状态 |
|
|
57
125
|
|
|
58
126
|
### 波形管理
|
|
59
127
|
| 工具 | 说明 |
|
|
60
128
|
|------|------|
|
|
61
|
-
| `dg_parse_waveform` |
|
|
129
|
+
| `dg_parse_waveform` | 解析 DungeonLab+pulse 格式波形并保存 |
|
|
62
130
|
| `dg_list_waveforms` | 列出所有已保存的波形 |
|
|
63
|
-
| `dg_get_waveform` |
|
|
131
|
+
| `dg_get_waveform` | 获取波形详情和 hexWaveforms |
|
|
64
132
|
| `dg_delete_waveform` | 删除已保存的波形 |
|
|
65
133
|
|
|
66
|
-
##
|
|
134
|
+
## 开发
|
|
67
135
|
|
|
68
|
-
|
|
136
|
+
### 从源码运行
|
|
69
137
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
| `SSE_PATH` | /sse | SSE 端点路径 |
|
|
75
|
-
| `POST_PATH` | /message | POST 端点路径 |
|
|
76
|
-
| `HEARTBEAT_INTERVAL` | 30000 | 心跳间隔 (ms) |
|
|
77
|
-
| `STALE_DEVICE_TIMEOUT` | 3600000 | 设备过期超时 (ms) |
|
|
78
|
-
| `SESSION_STORE_PATH` | ./data/sessions.json | 会话存储路径 |
|
|
79
|
-
| `WAVEFORM_STORE_PATH` | ./data/waveforms.json | 波形存储路径 |
|
|
138
|
+
```bash
|
|
139
|
+
# 克隆仓库
|
|
140
|
+
git clone https://github.com/admilkjs/sse-dg-lab.git
|
|
141
|
+
cd sse-dg-lab/dg-lab-mcp-server
|
|
80
142
|
|
|
81
|
-
|
|
143
|
+
# 安装依赖
|
|
144
|
+
bun install
|
|
82
145
|
|
|
83
|
-
|
|
146
|
+
# 启动开发服务器
|
|
147
|
+
bun run dev
|
|
84
148
|
|
|
85
|
-
|
|
149
|
+
# 运行测试
|
|
86
150
|
bun test
|
|
87
151
|
```
|
|
88
152
|
|
|
@@ -90,19 +154,26 @@ bun test
|
|
|
90
154
|
|
|
91
155
|
```
|
|
92
156
|
src/
|
|
93
|
-
├── index.ts
|
|
94
|
-
├──
|
|
95
|
-
├──
|
|
157
|
+
├── index.ts # 入口文件
|
|
158
|
+
├── cli.ts # CLI 入口 (npx)
|
|
159
|
+
├── app.ts # 应用初始化
|
|
160
|
+
├── config.ts # 配置管理
|
|
161
|
+
├── server.ts # HTTP/SSE 服务器
|
|
162
|
+
├── ws-server.ts # WebSocket 服务器
|
|
96
163
|
├── session-manager.ts # 会话管理
|
|
97
|
-
├── tool-manager.ts
|
|
164
|
+
├── tool-manager.ts # MCP 工具管理
|
|
98
165
|
├── waveform-parser.ts # 波形解析
|
|
99
166
|
├── waveform-storage.ts # 波形存储
|
|
100
167
|
└── tools/
|
|
101
|
-
├── device-tools.ts
|
|
102
|
-
├── control-tools.ts
|
|
168
|
+
├── device-tools.ts # 设备管理工具
|
|
169
|
+
├── control-tools.ts # 设备控制工具
|
|
103
170
|
└── waveform-tools.ts # 波形管理工具
|
|
104
171
|
```
|
|
105
172
|
|
|
106
173
|
## 许可证
|
|
107
174
|
|
|
108
175
|
MIT
|
|
176
|
+
|
|
177
|
+
## 声明
|
|
178
|
+
|
|
179
|
+
本项目基于 [DG-LAB 开源协议](https://github.com/DG-LAB-OPENSOURCE/DG-LAB-OPENSOURCE) 实现设备通信功能。DG-LAB 开源协议仅供爱好者自由使用设备,未经授权请勿将相关内容用于任何商业用途。
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/config.ts
|
|
4
|
-
import * as dotenv from "dotenv";
|
|
5
|
-
import * as path from "path";
|
|
6
4
|
import * as os from "os";
|
|
7
5
|
|
|
8
6
|
// src/errors.ts
|
|
@@ -68,15 +66,6 @@ var WaveformError = class extends AppError {
|
|
|
68
66
|
};
|
|
69
67
|
|
|
70
68
|
// src/config.ts
|
|
71
|
-
var envPath = path.resolve(process.cwd(), ".env");
|
|
72
|
-
var dotenvResult = dotenv.config({ path: envPath });
|
|
73
|
-
if (dotenvResult.error) {
|
|
74
|
-
console.log(`[\u914D\u7F6E] .env \u6587\u4EF6\u52A0\u8F7D\u5931\u8D25: ${dotenvResult.error.message}`);
|
|
75
|
-
console.log(`[\u914D\u7F6E] \u5C1D\u8BD5\u52A0\u8F7D\u8DEF\u5F84: ${envPath}`);
|
|
76
|
-
} else {
|
|
77
|
-
console.log(`[\u914D\u7F6E] .env \u6587\u4EF6\u5DF2\u52A0\u8F7D: ${envPath}`);
|
|
78
|
-
}
|
|
79
|
-
console.log(`[\u914D\u7F6E] PUBLIC_IP \u73AF\u5883\u53D8\u91CF: "${process.env.PUBLIC_IP || "(\u672A\u8BBE\u7F6E)"}"`);
|
|
80
69
|
function getEnvString(key, defaultValue) {
|
|
81
70
|
return process.env[key] ?? defaultValue;
|
|
82
71
|
}
|
|
@@ -93,7 +82,7 @@ function getEnvNumber(key, defaultValue) {
|
|
|
93
82
|
return parsed;
|
|
94
83
|
}
|
|
95
84
|
function loadConfig() {
|
|
96
|
-
const
|
|
85
|
+
const config = {
|
|
97
86
|
port: getEnvNumber("PORT", 3323),
|
|
98
87
|
publicIp: getEnvString("PUBLIC_IP", ""),
|
|
99
88
|
ssePath: getEnvString("SSE_PATH", "/sse"),
|
|
@@ -104,57 +93,57 @@ function loadConfig() {
|
|
|
104
93
|
staleDeviceTimeout: getEnvNumber("STALE_DEVICE_TIMEOUT", 36e5),
|
|
105
94
|
connectionTimeoutMinutes: getEnvNumber("CONNECTION_TIMEOUT_MINUTES", 5)
|
|
106
95
|
};
|
|
107
|
-
validateConfig(
|
|
108
|
-
return
|
|
96
|
+
validateConfig(config);
|
|
97
|
+
return config;
|
|
109
98
|
}
|
|
110
|
-
function validateConfig(
|
|
111
|
-
if (
|
|
112
|
-
throw new ConfigError(`\u7AEF\u53E3\u65E0\u6548: ${
|
|
99
|
+
function validateConfig(config) {
|
|
100
|
+
if (config.port < 1 || config.port > 65535) {
|
|
101
|
+
throw new ConfigError(`\u7AEF\u53E3\u65E0\u6548: ${config.port}\uFF0C\u5FC5\u987B\u5728 1-65535 \u8303\u56F4\u5185`, {
|
|
113
102
|
code: "CONFIG_INVALID_PORT" /* CONFIG_INVALID_PORT */,
|
|
114
|
-
context: { port:
|
|
103
|
+
context: { port: config.port }
|
|
115
104
|
});
|
|
116
105
|
}
|
|
117
|
-
if (
|
|
106
|
+
if (config.publicIp) {
|
|
118
107
|
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
119
|
-
if (!ipv4Regex.test(
|
|
120
|
-
console.warn(`[\u914D\u7F6E] \u26A0\uFE0F \u516C\u7F51IP\u683C\u5F0F\u65E0\u6548: ${
|
|
121
|
-
|
|
108
|
+
if (!ipv4Regex.test(config.publicIp)) {
|
|
109
|
+
console.warn(`[\u914D\u7F6E] \u26A0\uFE0F \u516C\u7F51IP\u683C\u5F0F\u65E0\u6548: ${config.publicIp}\uFF0C\u5C06\u4F7F\u7528\u672C\u5730IP`);
|
|
110
|
+
config.publicIp = "";
|
|
122
111
|
} else {
|
|
123
|
-
const parts =
|
|
112
|
+
const parts = config.publicIp.split(".");
|
|
124
113
|
if (parts.some((part) => parseInt(part, 10) > 255)) {
|
|
125
|
-
console.warn(`[\u914D\u7F6E] \u26A0\uFE0F \u516C\u7F51IP\u683C\u5F0F\u65E0\u6548: ${
|
|
126
|
-
|
|
114
|
+
console.warn(`[\u914D\u7F6E] \u26A0\uFE0F \u516C\u7F51IP\u683C\u5F0F\u65E0\u6548: ${config.publicIp}\uFF0C\u6BCF\u6BB5\u5FC5\u987B\u57280-255\u8303\u56F4\u5185\uFF0C\u5C06\u4F7F\u7528\u672C\u5730IP`);
|
|
115
|
+
config.publicIp = "";
|
|
127
116
|
}
|
|
128
117
|
}
|
|
129
118
|
}
|
|
130
|
-
if (!
|
|
131
|
-
throw new ConfigError(`SSE \u8DEF\u5F84\u65E0\u6548: ${
|
|
119
|
+
if (!config.ssePath.startsWith("/")) {
|
|
120
|
+
throw new ConfigError(`SSE \u8DEF\u5F84\u65E0\u6548: ${config.ssePath}\uFF0C\u5FC5\u987B\u4EE5 / \u5F00\u5934`, {
|
|
132
121
|
code: "CONFIG_INVALID_PATH" /* CONFIG_INVALID_PATH */,
|
|
133
|
-
context: { path:
|
|
122
|
+
context: { path: config.ssePath, type: "ssePath" }
|
|
134
123
|
});
|
|
135
124
|
}
|
|
136
|
-
if (!
|
|
137
|
-
throw new ConfigError(`POST \u8DEF\u5F84\u65E0\u6548: ${
|
|
125
|
+
if (!config.postPath.startsWith("/")) {
|
|
126
|
+
throw new ConfigError(`POST \u8DEF\u5F84\u65E0\u6548: ${config.postPath}\uFF0C\u5FC5\u987B\u4EE5 / \u5F00\u5934`, {
|
|
138
127
|
code: "CONFIG_INVALID_PATH" /* CONFIG_INVALID_PATH */,
|
|
139
|
-
context: { path:
|
|
128
|
+
context: { path: config.postPath, type: "postPath" }
|
|
140
129
|
});
|
|
141
130
|
}
|
|
142
|
-
if (
|
|
143
|
-
throw new ConfigError(`\u5FC3\u8DF3\u95F4\u9694\u65E0\u6548: ${
|
|
131
|
+
if (config.heartbeatInterval < 1e3) {
|
|
132
|
+
throw new ConfigError(`\u5FC3\u8DF3\u95F4\u9694\u65E0\u6548: ${config.heartbeatInterval}\uFF0C\u5FC5\u987B\u81F3\u5C11 1000ms`, {
|
|
144
133
|
code: "CONFIG_LOAD_FAILED" /* CONFIG_LOAD_FAILED */,
|
|
145
|
-
context: { heartbeatInterval:
|
|
134
|
+
context: { heartbeatInterval: config.heartbeatInterval }
|
|
146
135
|
});
|
|
147
136
|
}
|
|
148
|
-
if (
|
|
149
|
-
throw new ConfigError(`\u8BBE\u5907\u8FC7\u671F\u8D85\u65F6\u65E0\u6548: ${
|
|
137
|
+
if (config.staleDeviceTimeout < 6e4) {
|
|
138
|
+
throw new ConfigError(`\u8BBE\u5907\u8FC7\u671F\u8D85\u65F6\u65E0\u6548: ${config.staleDeviceTimeout}\uFF0C\u5FC5\u987B\u81F3\u5C11 60000ms`, {
|
|
150
139
|
code: "CONFIG_LOAD_FAILED" /* CONFIG_LOAD_FAILED */,
|
|
151
|
-
context: { staleDeviceTimeout:
|
|
140
|
+
context: { staleDeviceTimeout: config.staleDeviceTimeout }
|
|
152
141
|
});
|
|
153
142
|
}
|
|
154
|
-
if (
|
|
155
|
-
throw new ConfigError(`\u8FDE\u63A5\u8D85\u65F6\u65F6\u95F4\u65E0\u6548: ${
|
|
143
|
+
if (config.connectionTimeoutMinutes < 1 || config.connectionTimeoutMinutes > 60) {
|
|
144
|
+
throw new ConfigError(`\u8FDE\u63A5\u8D85\u65F6\u65F6\u95F4\u65E0\u6548: ${config.connectionTimeoutMinutes}\uFF0C\u5FC5\u987B\u5728 1-60 \u5206\u949F\u8303\u56F4\u5185`, {
|
|
156
145
|
code: "CONFIG_LOAD_FAILED" /* CONFIG_LOAD_FAILED */,
|
|
157
|
-
context: { connectionTimeoutMinutes:
|
|
146
|
+
context: { connectionTimeoutMinutes: config.connectionTimeoutMinutes }
|
|
158
147
|
});
|
|
159
148
|
}
|
|
160
149
|
}
|
|
@@ -176,8 +165,8 @@ function getLocalIP() {
|
|
|
176
165
|
}
|
|
177
166
|
return "localhost";
|
|
178
167
|
}
|
|
179
|
-
function getEffectiveIP(
|
|
180
|
-
const cfg =
|
|
168
|
+
function getEffectiveIP(config) {
|
|
169
|
+
const cfg = config || getConfig();
|
|
181
170
|
return cfg.publicIp || getLocalIP();
|
|
182
171
|
}
|
|
183
172
|
|
|
@@ -511,7 +500,7 @@ var JsonRpcHandler = class {
|
|
|
511
500
|
};
|
|
512
501
|
|
|
513
502
|
// src/server.ts
|
|
514
|
-
function createServer(
|
|
503
|
+
function createServer(config) {
|
|
515
504
|
const app = express();
|
|
516
505
|
app.use((req, res, next) => {
|
|
517
506
|
res.header("Access-Control-Allow-Origin", "*");
|
|
@@ -525,18 +514,18 @@ function createServer(config2) {
|
|
|
525
514
|
});
|
|
526
515
|
app.use(express.json());
|
|
527
516
|
app.use(express.text({ type: "application/json" }));
|
|
528
|
-
const sseTransport = new SSETransport(
|
|
517
|
+
const sseTransport = new SSETransport(config.postPath);
|
|
529
518
|
const jsonRpcHandler = new JsonRpcHandler({
|
|
530
519
|
onError: (error) => {
|
|
531
520
|
console.error("[JSON-RPC \u9519\u8BEF]", error);
|
|
532
521
|
}
|
|
533
522
|
});
|
|
534
|
-
app.get(
|
|
523
|
+
app.get(config.ssePath, (req, res) => {
|
|
535
524
|
console.log("[SSE] \u65B0\u8FDE\u63A5");
|
|
536
525
|
const connection = sseTransport.connect(req, res);
|
|
537
526
|
console.log(`[SSE] \u8FDE\u63A5\u5DF2\u5EFA\u7ACB: ${connection.id}`);
|
|
538
527
|
});
|
|
539
|
-
app.post(
|
|
528
|
+
app.post(config.postPath, async (req, res) => {
|
|
540
529
|
const sessionId = req.query.sessionId;
|
|
541
530
|
if (!sessionId || !sseTransport.hasConnection(sessionId)) {
|
|
542
531
|
res.status(400).json({ error: "\u65E0\u6548\u6216\u7F3A\u5C11 sessionId" });
|
|
@@ -568,18 +557,18 @@ function createServer(config2) {
|
|
|
568
557
|
sseTransport,
|
|
569
558
|
jsonRpcHandler,
|
|
570
559
|
async start() {
|
|
571
|
-
return new Promise((
|
|
572
|
-
httpServer = app.listen(
|
|
573
|
-
console.log(`[\u670D\u52A1\u5668] MCP SSE \u670D\u52A1\u5668\u76D1\u542C\u7AEF\u53E3 ${
|
|
574
|
-
console.log(`[\u670D\u52A1\u5668] SSE \u7AEF\u70B9: ${
|
|
575
|
-
console.log(`[\u670D\u52A1\u5668] POST \u7AEF\u70B9: ${
|
|
576
|
-
|
|
560
|
+
return new Promise((resolve) => {
|
|
561
|
+
httpServer = app.listen(config.port, () => {
|
|
562
|
+
console.log(`[\u670D\u52A1\u5668] MCP SSE \u670D\u52A1\u5668\u76D1\u542C\u7AEF\u53E3 ${config.port}`);
|
|
563
|
+
console.log(`[\u670D\u52A1\u5668] SSE \u7AEF\u70B9: ${config.ssePath}`);
|
|
564
|
+
console.log(`[\u670D\u52A1\u5668] POST \u7AEF\u70B9: ${config.postPath}`);
|
|
565
|
+
resolve();
|
|
577
566
|
});
|
|
578
567
|
this.httpServer = httpServer;
|
|
579
568
|
});
|
|
580
569
|
},
|
|
581
570
|
async stop() {
|
|
582
|
-
return new Promise((
|
|
571
|
+
return new Promise((resolve) => {
|
|
583
572
|
if (httpServer && httpServer.listening) {
|
|
584
573
|
httpServer.close((err) => {
|
|
585
574
|
if (err) {
|
|
@@ -587,11 +576,11 @@ function createServer(config2) {
|
|
|
587
576
|
} else {
|
|
588
577
|
console.log("[\u670D\u52A1\u5668] \u5DF2\u505C\u6B62");
|
|
589
578
|
}
|
|
590
|
-
|
|
579
|
+
resolve();
|
|
591
580
|
});
|
|
592
581
|
} else {
|
|
593
582
|
console.log("[\u670D\u52A1\u5668] \u672A\u542F\u52A8\u6216\u5DF2\u5173\u95ED");
|
|
594
|
-
|
|
583
|
+
resolve();
|
|
595
584
|
}
|
|
596
585
|
});
|
|
597
586
|
}
|
|
@@ -2038,6 +2027,7 @@ function loadWaveforms(storage, filePath = "./data/waveforms.json") {
|
|
|
2038
2027
|
|
|
2039
2028
|
// src/waveform-parser.ts
|
|
2040
2029
|
var FREQUENCY_DATASET = [
|
|
2030
|
+
// (10..50) step 1 → 索引 0-40
|
|
2041
2031
|
10,
|
|
2042
2032
|
11,
|
|
2043
2033
|
12,
|
|
@@ -2048,29 +2038,42 @@ var FREQUENCY_DATASET = [
|
|
|
2048
2038
|
17,
|
|
2049
2039
|
18,
|
|
2050
2040
|
19,
|
|
2051
|
-
// 0-9
|
|
2052
2041
|
20,
|
|
2042
|
+
21,
|
|
2053
2043
|
22,
|
|
2044
|
+
23,
|
|
2054
2045
|
24,
|
|
2046
|
+
25,
|
|
2055
2047
|
26,
|
|
2048
|
+
27,
|
|
2056
2049
|
28,
|
|
2050
|
+
29,
|
|
2057
2051
|
30,
|
|
2052
|
+
31,
|
|
2058
2053
|
32,
|
|
2054
|
+
33,
|
|
2059
2055
|
34,
|
|
2056
|
+
35,
|
|
2060
2057
|
36,
|
|
2058
|
+
37,
|
|
2061
2059
|
38,
|
|
2062
|
-
|
|
2060
|
+
39,
|
|
2063
2061
|
40,
|
|
2062
|
+
41,
|
|
2064
2063
|
42,
|
|
2064
|
+
43,
|
|
2065
2065
|
44,
|
|
2066
|
+
45,
|
|
2066
2067
|
46,
|
|
2068
|
+
47,
|
|
2067
2069
|
48,
|
|
2070
|
+
49,
|
|
2068
2071
|
50,
|
|
2072
|
+
// (52..80) step 2 → 索引 41-55
|
|
2069
2073
|
52,
|
|
2070
2074
|
54,
|
|
2071
2075
|
56,
|
|
2072
2076
|
58,
|
|
2073
|
-
// 20-29
|
|
2074
2077
|
60,
|
|
2075
2078
|
62,
|
|
2076
2079
|
64,
|
|
@@ -2081,56 +2084,40 @@ var FREQUENCY_DATASET = [
|
|
|
2081
2084
|
74,
|
|
2082
2085
|
76,
|
|
2083
2086
|
78,
|
|
2084
|
-
// 30-39
|
|
2085
2087
|
80,
|
|
2088
|
+
// (85..100) step 5 → 索引 56-59
|
|
2086
2089
|
85,
|
|
2087
2090
|
90,
|
|
2088
2091
|
95,
|
|
2089
2092
|
100,
|
|
2093
|
+
// (110..200) step 10 → 索引 60-69
|
|
2090
2094
|
110,
|
|
2091
2095
|
120,
|
|
2092
2096
|
130,
|
|
2093
2097
|
140,
|
|
2094
2098
|
150,
|
|
2095
|
-
// 40-49
|
|
2096
2099
|
160,
|
|
2097
2100
|
170,
|
|
2098
2101
|
180,
|
|
2099
2102
|
190,
|
|
2100
2103
|
200,
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
280,
|
|
2104
|
+
// (233..400) step 33 → 索引 70-75
|
|
2105
|
+
233,
|
|
2106
|
+
266,
|
|
2105
2107
|
300,
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
340,
|
|
2109
|
-
360,
|
|
2110
|
-
380,
|
|
2108
|
+
333,
|
|
2109
|
+
366,
|
|
2111
2110
|
400,
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
460,
|
|
2115
|
-
480,
|
|
2111
|
+
// (450..600) step 50 → 索引 76-79
|
|
2112
|
+
450,
|
|
2116
2113
|
500,
|
|
2117
|
-
// 60-69
|
|
2118
2114
|
550,
|
|
2119
2115
|
600,
|
|
2120
|
-
|
|
2116
|
+
// (700..1000) step 100 → 索引 80-83
|
|
2121
2117
|
700,
|
|
2122
|
-
750,
|
|
2123
2118
|
800,
|
|
2124
|
-
850,
|
|
2125
2119
|
900,
|
|
2126
|
-
950,
|
|
2127
|
-
1e3,
|
|
2128
|
-
// 70-79
|
|
2129
|
-
1e3,
|
|
2130
|
-
1e3,
|
|
2131
|
-
1e3,
|
|
2132
2120
|
1e3
|
|
2133
|
-
// 80-83 (上限 1000)
|
|
2134
2121
|
];
|
|
2135
2122
|
var DURATION_DATASET = Array.from({ length: 100 }, (_, i) => i + 1);
|
|
2136
2123
|
function getFrequencyFromIndex(index) {
|
|
@@ -2372,9 +2359,9 @@ function convertToHexWaveforms(sections, _playbackSpeed = 1) {
|
|
|
2372
2359
|
// src/tools/waveform-tools.ts
|
|
2373
2360
|
var waveformStorage = null;
|
|
2374
2361
|
var storagePath = "./data/waveforms.json";
|
|
2375
|
-
function initWaveformStorage(storage,
|
|
2362
|
+
function initWaveformStorage(storage, path) {
|
|
2376
2363
|
waveformStorage = storage || new WaveformStorage();
|
|
2377
|
-
if (
|
|
2364
|
+
if (path) storagePath = path;
|
|
2378
2365
|
}
|
|
2379
2366
|
function getWaveformStorage() {
|
|
2380
2367
|
if (!waveformStorage) {
|
|
@@ -3024,17 +3011,17 @@ function registerControlTools(toolManager, sessionManager, wsServer) {
|
|
|
3024
3011
|
|
|
3025
3012
|
// src/app.ts
|
|
3026
3013
|
function createApp() {
|
|
3027
|
-
const
|
|
3028
|
-
printConfigInfo(
|
|
3029
|
-
const server = createServer(
|
|
3014
|
+
const config = loadConfig();
|
|
3015
|
+
printConfigInfo(config);
|
|
3016
|
+
const server = createServer(config);
|
|
3030
3017
|
const toolManager = new ToolManager(() => {
|
|
3031
3018
|
broadcastNotification(server, "notifications/tools/list_changed");
|
|
3032
3019
|
});
|
|
3033
|
-
const sessionManager = new SessionManager(
|
|
3034
|
-
console.log(`[\u4F1A\u8BDD] \u4EC5\u5185\u5B58\u6A21\u5F0F\uFF08\u8FDE\u63A5\u8D85\u65F6: ${
|
|
3035
|
-
const wsServer = createWSServer(
|
|
3036
|
-
const waveformStorage2 = initWaveforms(
|
|
3037
|
-
registerProtocolAndTools(server, toolManager, sessionManager, wsServer,
|
|
3020
|
+
const sessionManager = new SessionManager(config.connectionTimeoutMinutes);
|
|
3021
|
+
console.log(`[\u4F1A\u8BDD] \u4EC5\u5185\u5B58\u6A21\u5F0F\uFF08\u8FDE\u63A5\u8D85\u65F6: ${config.connectionTimeoutMinutes} \u5206\u949F\uFF0C\u6D3B\u8DC3\u8D85\u65F6: 1 \u5C0F\u65F6\uFF09`);
|
|
3022
|
+
const wsServer = createWSServer(config, sessionManager);
|
|
3023
|
+
const waveformStorage2 = initWaveforms(config);
|
|
3024
|
+
registerProtocolAndTools(server, toolManager, sessionManager, wsServer, config);
|
|
3038
3025
|
const shutdown = async () => {
|
|
3039
3026
|
console.log("\n[\u670D\u52A1\u5668] \u6B63\u5728\u5173\u95ED...");
|
|
3040
3027
|
wsServer.stop();
|
|
@@ -3044,7 +3031,7 @@ function createApp() {
|
|
|
3044
3031
|
console.log("[\u670D\u52A1\u5668] \u5DF2\u505C\u6B62");
|
|
3045
3032
|
};
|
|
3046
3033
|
return {
|
|
3047
|
-
config
|
|
3034
|
+
config,
|
|
3048
3035
|
server,
|
|
3049
3036
|
toolManager,
|
|
3050
3037
|
sessionManager,
|
|
@@ -3053,22 +3040,22 @@ function createApp() {
|
|
|
3053
3040
|
shutdown
|
|
3054
3041
|
};
|
|
3055
3042
|
}
|
|
3056
|
-
function printConfigInfo(
|
|
3043
|
+
function printConfigInfo(config) {
|
|
3057
3044
|
console.log("=".repeat(50));
|
|
3058
3045
|
console.log("DG-LAB MCP SSE \u670D\u52A1\u5668");
|
|
3059
3046
|
console.log("=".repeat(50));
|
|
3060
|
-
console.log(`[\u914D\u7F6E] \u7AEF\u53E3: ${
|
|
3061
|
-
console.log(`[\u914D\u7F6E] SSE \u8DEF\u5F84: ${
|
|
3062
|
-
console.log(`[\u914D\u7F6E] POST \u8DEF\u5F84: ${
|
|
3063
|
-
const effectiveIP = getEffectiveIP(
|
|
3047
|
+
console.log(`[\u914D\u7F6E] \u7AEF\u53E3: ${config.port}`);
|
|
3048
|
+
console.log(`[\u914D\u7F6E] SSE \u8DEF\u5F84: ${config.ssePath}`);
|
|
3049
|
+
console.log(`[\u914D\u7F6E] POST \u8DEF\u5F84: ${config.postPath}`);
|
|
3050
|
+
const effectiveIP = getEffectiveIP(config);
|
|
3064
3051
|
const localIP = getLocalIP();
|
|
3065
3052
|
console.log(`[\u914D\u7F6E] \u672C\u5730 IP: ${localIP}`);
|
|
3066
|
-
console.log(`[\u914D\u7F6E] \u516C\u7F51 IP: ${
|
|
3053
|
+
console.log(`[\u914D\u7F6E] \u516C\u7F51 IP: ${config.publicIp || "(\u672A\u8BBE\u7F6E)"}`);
|
|
3067
3054
|
console.log(`[\u914D\u7F6E] \u5B9E\u9645\u4F7F\u7528 IP: ${effectiveIP}`);
|
|
3068
3055
|
}
|
|
3069
|
-
function createWSServer(
|
|
3056
|
+
function createWSServer(config, sessionManager) {
|
|
3070
3057
|
return new DGLabWSServer({
|
|
3071
|
-
heartbeatInterval:
|
|
3058
|
+
heartbeatInterval: config.heartbeatInterval,
|
|
3072
3059
|
onStrengthUpdate: (controllerId, a, b, limitA, limitB) => {
|
|
3073
3060
|
console.log(`[WS] ${controllerId} \u5F3A\u5EA6: A=${a}/${limitA}, B=${b}/${limitB}`);
|
|
3074
3061
|
const session = sessionManager.getSessionByClientId(controllerId);
|
|
@@ -3118,20 +3105,20 @@ function createWSServer(config2, sessionManager) {
|
|
|
3118
3105
|
}
|
|
3119
3106
|
});
|
|
3120
3107
|
}
|
|
3121
|
-
function initWaveforms(
|
|
3108
|
+
function initWaveforms(config) {
|
|
3122
3109
|
const waveformStorage2 = new WaveformStorage();
|
|
3123
|
-
if (loadWaveforms(waveformStorage2,
|
|
3110
|
+
if (loadWaveforms(waveformStorage2, config.waveformStorePath)) {
|
|
3124
3111
|
console.log(`[\u6CE2\u5F62] \u4ECE\u78C1\u76D8\u52A0\u8F7D\u4E86 ${waveformStorage2.list().length} \u4E2A\u6CE2\u5F62`);
|
|
3125
3112
|
}
|
|
3126
|
-
initWaveformStorage(waveformStorage2,
|
|
3113
|
+
initWaveformStorage(waveformStorage2, config.waveformStorePath);
|
|
3127
3114
|
return waveformStorage2;
|
|
3128
3115
|
}
|
|
3129
|
-
function registerProtocolAndTools(server, toolManager, sessionManager, wsServer,
|
|
3116
|
+
function registerProtocolAndTools(server, toolManager, sessionManager, wsServer, config) {
|
|
3130
3117
|
registerMCPProtocol(server.jsonRpcHandler, () => {
|
|
3131
3118
|
console.log("[MCP] \u5BA2\u6237\u7AEF\u5DF2\u521D\u59CB\u5316");
|
|
3132
3119
|
});
|
|
3133
3120
|
registerToolHandlers(server.jsonRpcHandler, toolManager);
|
|
3134
|
-
registerDeviceTools(toolManager, sessionManager, wsServer,
|
|
3121
|
+
registerDeviceTools(toolManager, sessionManager, wsServer, config.publicIp || void 0);
|
|
3135
3122
|
console.log("[\u5DE5\u5177] \u8BBE\u5907\u5DE5\u5177\u5DF2\u6CE8\u518C");
|
|
3136
3123
|
registerControlTools(toolManager, sessionManager, wsServer);
|
|
3137
3124
|
console.log("[\u5DE5\u5177] \u63A7\u5236\u5DE5\u5177\u5DF2\u6CE8\u518C");
|