dg-lab-mcp-server 1.0.0 → 1.0.1
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 +112 -45
- package/dist/cli.js +68 -79
- 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 +68 -79
- package/dist/index.js.map +3 -3
- 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
|
-
|
|
49
|
-
|
|
108
|
+
| `dg_get_device_status` | 获取指定设备的详细状态 |
|
|
109
|
+
| `dg_delete_device` | 删除设备会话 |
|
|
110
|
+
|
|
111
|
+
### 强度控制
|
|
112
|
+
| 工具 | 说明 |
|
|
113
|
+
|------|------|
|
|
114
|
+
| `dg_set_strength` | 设置 A/B 通道强度 (0-200) |
|
|
115
|
+
| `dg_adjust_strength` | 增量调整强度 |
|
|
116
|
+
| `dg_get_strength` | 获取当前强度值 |
|
|
50
117
|
|
|
51
|
-
###
|
|
118
|
+
### 波形控制
|
|
52
119
|
| 工具 | 说明 |
|
|
53
120
|
|------|------|
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
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,16 +154,19 @@ 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
|
|
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
|
}
|
|
@@ -2372,9 +2361,9 @@ function convertToHexWaveforms(sections, _playbackSpeed = 1) {
|
|
|
2372
2361
|
// src/tools/waveform-tools.ts
|
|
2373
2362
|
var waveformStorage = null;
|
|
2374
2363
|
var storagePath = "./data/waveforms.json";
|
|
2375
|
-
function initWaveformStorage(storage,
|
|
2364
|
+
function initWaveformStorage(storage, path) {
|
|
2376
2365
|
waveformStorage = storage || new WaveformStorage();
|
|
2377
|
-
if (
|
|
2366
|
+
if (path) storagePath = path;
|
|
2378
2367
|
}
|
|
2379
2368
|
function getWaveformStorage() {
|
|
2380
2369
|
if (!waveformStorage) {
|
|
@@ -3024,17 +3013,17 @@ function registerControlTools(toolManager, sessionManager, wsServer) {
|
|
|
3024
3013
|
|
|
3025
3014
|
// src/app.ts
|
|
3026
3015
|
function createApp() {
|
|
3027
|
-
const
|
|
3028
|
-
printConfigInfo(
|
|
3029
|
-
const server = createServer(
|
|
3016
|
+
const config = loadConfig();
|
|
3017
|
+
printConfigInfo(config);
|
|
3018
|
+
const server = createServer(config);
|
|
3030
3019
|
const toolManager = new ToolManager(() => {
|
|
3031
3020
|
broadcastNotification(server, "notifications/tools/list_changed");
|
|
3032
3021
|
});
|
|
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,
|
|
3022
|
+
const sessionManager = new SessionManager(config.connectionTimeoutMinutes);
|
|
3023
|
+
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`);
|
|
3024
|
+
const wsServer = createWSServer(config, sessionManager);
|
|
3025
|
+
const waveformStorage2 = initWaveforms(config);
|
|
3026
|
+
registerProtocolAndTools(server, toolManager, sessionManager, wsServer, config);
|
|
3038
3027
|
const shutdown = async () => {
|
|
3039
3028
|
console.log("\n[\u670D\u52A1\u5668] \u6B63\u5728\u5173\u95ED...");
|
|
3040
3029
|
wsServer.stop();
|
|
@@ -3044,7 +3033,7 @@ function createApp() {
|
|
|
3044
3033
|
console.log("[\u670D\u52A1\u5668] \u5DF2\u505C\u6B62");
|
|
3045
3034
|
};
|
|
3046
3035
|
return {
|
|
3047
|
-
config
|
|
3036
|
+
config,
|
|
3048
3037
|
server,
|
|
3049
3038
|
toolManager,
|
|
3050
3039
|
sessionManager,
|
|
@@ -3053,22 +3042,22 @@ function createApp() {
|
|
|
3053
3042
|
shutdown
|
|
3054
3043
|
};
|
|
3055
3044
|
}
|
|
3056
|
-
function printConfigInfo(
|
|
3045
|
+
function printConfigInfo(config) {
|
|
3057
3046
|
console.log("=".repeat(50));
|
|
3058
3047
|
console.log("DG-LAB MCP SSE \u670D\u52A1\u5668");
|
|
3059
3048
|
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(
|
|
3049
|
+
console.log(`[\u914D\u7F6E] \u7AEF\u53E3: ${config.port}`);
|
|
3050
|
+
console.log(`[\u914D\u7F6E] SSE \u8DEF\u5F84: ${config.ssePath}`);
|
|
3051
|
+
console.log(`[\u914D\u7F6E] POST \u8DEF\u5F84: ${config.postPath}`);
|
|
3052
|
+
const effectiveIP = getEffectiveIP(config);
|
|
3064
3053
|
const localIP = getLocalIP();
|
|
3065
3054
|
console.log(`[\u914D\u7F6E] \u672C\u5730 IP: ${localIP}`);
|
|
3066
|
-
console.log(`[\u914D\u7F6E] \u516C\u7F51 IP: ${
|
|
3055
|
+
console.log(`[\u914D\u7F6E] \u516C\u7F51 IP: ${config.publicIp || "(\u672A\u8BBE\u7F6E)"}`);
|
|
3067
3056
|
console.log(`[\u914D\u7F6E] \u5B9E\u9645\u4F7F\u7528 IP: ${effectiveIP}`);
|
|
3068
3057
|
}
|
|
3069
|
-
function createWSServer(
|
|
3058
|
+
function createWSServer(config, sessionManager) {
|
|
3070
3059
|
return new DGLabWSServer({
|
|
3071
|
-
heartbeatInterval:
|
|
3060
|
+
heartbeatInterval: config.heartbeatInterval,
|
|
3072
3061
|
onStrengthUpdate: (controllerId, a, b, limitA, limitB) => {
|
|
3073
3062
|
console.log(`[WS] ${controllerId} \u5F3A\u5EA6: A=${a}/${limitA}, B=${b}/${limitB}`);
|
|
3074
3063
|
const session = sessionManager.getSessionByClientId(controllerId);
|
|
@@ -3118,20 +3107,20 @@ function createWSServer(config2, sessionManager) {
|
|
|
3118
3107
|
}
|
|
3119
3108
|
});
|
|
3120
3109
|
}
|
|
3121
|
-
function initWaveforms(
|
|
3110
|
+
function initWaveforms(config) {
|
|
3122
3111
|
const waveformStorage2 = new WaveformStorage();
|
|
3123
|
-
if (loadWaveforms(waveformStorage2,
|
|
3112
|
+
if (loadWaveforms(waveformStorage2, config.waveformStorePath)) {
|
|
3124
3113
|
console.log(`[\u6CE2\u5F62] \u4ECE\u78C1\u76D8\u52A0\u8F7D\u4E86 ${waveformStorage2.list().length} \u4E2A\u6CE2\u5F62`);
|
|
3125
3114
|
}
|
|
3126
|
-
initWaveformStorage(waveformStorage2,
|
|
3115
|
+
initWaveformStorage(waveformStorage2, config.waveformStorePath);
|
|
3127
3116
|
return waveformStorage2;
|
|
3128
3117
|
}
|
|
3129
|
-
function registerProtocolAndTools(server, toolManager, sessionManager, wsServer,
|
|
3118
|
+
function registerProtocolAndTools(server, toolManager, sessionManager, wsServer, config) {
|
|
3130
3119
|
registerMCPProtocol(server.jsonRpcHandler, () => {
|
|
3131
3120
|
console.log("[MCP] \u5BA2\u6237\u7AEF\u5DF2\u521D\u59CB\u5316");
|
|
3132
3121
|
});
|
|
3133
3122
|
registerToolHandlers(server.jsonRpcHandler, toolManager);
|
|
3134
|
-
registerDeviceTools(toolManager, sessionManager, wsServer,
|
|
3123
|
+
registerDeviceTools(toolManager, sessionManager, wsServer, config.publicIp || void 0);
|
|
3135
3124
|
console.log("[\u5DE5\u5177] \u8BBE\u5907\u5DE5\u5177\u5DF2\u6CE8\u518C");
|
|
3136
3125
|
registerControlTools(toolManager, sessionManager, wsServer);
|
|
3137
3126
|
console.log("[\u5DE5\u5177] \u63A7\u5236\u5DE5\u5177\u5DF2\u6CE8\u518C");
|