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 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
- bun install
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
- ```bash
24
- bun run src/index.ts
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
- 服务器默认监听端口 `3323`,启动后会显示:
28
- - SSE 端点: `http://localhost:3323/sse`
29
- - POST 端点: `http://localhost:3323/message`
30
- - WebSocket: `ws://localhost:3323`
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. **创建连接**: 调用 `dg_connect` 获取二维码链接
96
+ 1. **创建设备**: 调用 `dg_create_device` 获取二维码内容
35
97
  2. **扫码绑定**: 用户使用 DG-LAB APP 扫描二维码
36
- 3. **检查状态**: 调用 `dg_get_status` 确认 `boundToApp: true`
37
- 4. **控制设备**: 使用 `dg_set_strength` 或 `dg_send_waveform` 控制设备
98
+ 3. **检查状态**: 调用 `dg_get_device_status` 确认 `boundToApp: true`
99
+ 4. **控制设备**: 使用强度控制或波形控制工具
38
100
 
39
- ## 可用工具
101
+ ## 可用工具 (16 个)
40
102
 
41
103
  ### 设备管理
42
104
  | 工具 | 说明 |
43
105
  |------|------|
44
- | `dg_connect` | 创建新的设备连接,返回二维码链接 |
106
+ | `dg_create_device` | 创建新设备会话,返回二维码内容 |
45
107
  | `dg_list_devices` | 列出所有设备及状态 |
46
- | `dg_get_status` | 获取指定设备的详细状态 |
47
- | `dg_set_alias` | 为设备设置别名 |
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` | 设置通道强度 (A/B, 0-200) |
55
- | `dg_send_waveform` | 发送波形数据到设备 |
56
- | `dg_clear_waveform` | 清空波形队列 |
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` | 获取波形的 hexWaveforms 数据 |
131
+ | `dg_get_waveform` | 获取波形详情和 hexWaveforms |
64
132
  | `dg_delete_waveform` | 删除已保存的波形 |
65
133
 
66
- ## 环境变量
134
+ ## 开发
67
135
 
68
- 可以通过创建 `.env` 文件配置服务器(参考 `.env.example`):
136
+ ### 从源码运行
69
137
 
70
- | 变量 | 默认值 | 说明 |
71
- |------|--------|------|
72
- | `PORT` | 3323 | 服务端口 (HTTP/WebSocket 共享) |
73
- | `PUBLIC_IP` | (空) | 公网IP地址,用于生成二维码。留空则自动检测本地IP。如果服务器部署在公网或需要远程访问,请填写公网IP |
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
- ```bash
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
- ├── server.ts # HTTP 服务器
95
- ├── ws-server.ts # WebSocket 服务器
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 config2 = {
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(config2);
108
- return config2;
96
+ validateConfig(config);
97
+ return config;
109
98
  }
110
- function validateConfig(config2) {
111
- if (config2.port < 1 || config2.port > 65535) {
112
- throw new ConfigError(`\u7AEF\u53E3\u65E0\u6548: ${config2.port}\uFF0C\u5FC5\u987B\u5728 1-65535 \u8303\u56F4\u5185`, {
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: config2.port }
103
+ context: { port: config.port }
115
104
  });
116
105
  }
117
- if (config2.publicIp) {
106
+ if (config.publicIp) {
118
107
  const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
119
- if (!ipv4Regex.test(config2.publicIp)) {
120
- console.warn(`[\u914D\u7F6E] \u26A0\uFE0F \u516C\u7F51IP\u683C\u5F0F\u65E0\u6548: ${config2.publicIp}\uFF0C\u5C06\u4F7F\u7528\u672C\u5730IP`);
121
- config2.publicIp = "";
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 = config2.publicIp.split(".");
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: ${config2.publicIp}\uFF0C\u6BCF\u6BB5\u5FC5\u987B\u57280-255\u8303\u56F4\u5185\uFF0C\u5C06\u4F7F\u7528\u672C\u5730IP`);
126
- config2.publicIp = "";
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 (!config2.ssePath.startsWith("/")) {
131
- throw new ConfigError(`SSE \u8DEF\u5F84\u65E0\u6548: ${config2.ssePath}\uFF0C\u5FC5\u987B\u4EE5 / \u5F00\u5934`, {
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: config2.ssePath, type: "ssePath" }
122
+ context: { path: config.ssePath, type: "ssePath" }
134
123
  });
135
124
  }
136
- if (!config2.postPath.startsWith("/")) {
137
- throw new ConfigError(`POST \u8DEF\u5F84\u65E0\u6548: ${config2.postPath}\uFF0C\u5FC5\u987B\u4EE5 / \u5F00\u5934`, {
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: config2.postPath, type: "postPath" }
128
+ context: { path: config.postPath, type: "postPath" }
140
129
  });
141
130
  }
142
- if (config2.heartbeatInterval < 1e3) {
143
- throw new ConfigError(`\u5FC3\u8DF3\u95F4\u9694\u65E0\u6548: ${config2.heartbeatInterval}\uFF0C\u5FC5\u987B\u81F3\u5C11 1000ms`, {
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: config2.heartbeatInterval }
134
+ context: { heartbeatInterval: config.heartbeatInterval }
146
135
  });
147
136
  }
148
- if (config2.staleDeviceTimeout < 6e4) {
149
- throw new ConfigError(`\u8BBE\u5907\u8FC7\u671F\u8D85\u65F6\u65E0\u6548: ${config2.staleDeviceTimeout}\uFF0C\u5FC5\u987B\u81F3\u5C11 60000ms`, {
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: config2.staleDeviceTimeout }
140
+ context: { staleDeviceTimeout: config.staleDeviceTimeout }
152
141
  });
153
142
  }
154
- if (config2.connectionTimeoutMinutes < 1 || config2.connectionTimeoutMinutes > 60) {
155
- throw new ConfigError(`\u8FDE\u63A5\u8D85\u65F6\u65F6\u95F4\u65E0\u6548: ${config2.connectionTimeoutMinutes}\uFF0C\u5FC5\u987B\u5728 1-60 \u5206\u949F\u8303\u56F4\u5185`, {
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: config2.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(config2) {
180
- const cfg = config2 || getConfig();
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(config2) {
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(config2.postPath);
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(config2.ssePath, (req, res) => {
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(config2.postPath, async (req, res) => {
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((resolve2) => {
572
- httpServer = app.listen(config2.port, () => {
573
- console.log(`[\u670D\u52A1\u5668] MCP SSE \u670D\u52A1\u5668\u76D1\u542C\u7AEF\u53E3 ${config2.port}`);
574
- console.log(`[\u670D\u52A1\u5668] SSE \u7AEF\u70B9: ${config2.ssePath}`);
575
- console.log(`[\u670D\u52A1\u5668] POST \u7AEF\u70B9: ${config2.postPath}`);
576
- resolve2();
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((resolve2) => {
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
- resolve2();
579
+ resolve();
591
580
  });
592
581
  } else {
593
582
  console.log("[\u670D\u52A1\u5668] \u672A\u542F\u52A8\u6216\u5DF2\u5173\u95ED");
594
- resolve2();
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
- // 10-19
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
- 220,
2102
- 240,
2103
- 260,
2104
- 280,
2104
+ // (233..400) step 33 → 索引 70-75
2105
+ 233,
2106
+ 266,
2105
2107
  300,
2106
- // 50-59
2107
- 320,
2108
- 340,
2109
- 360,
2110
- 380,
2108
+ 333,
2109
+ 366,
2111
2110
  400,
2112
- 420,
2113
- 440,
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
- 650,
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, path2) {
2362
+ function initWaveformStorage(storage, path) {
2376
2363
  waveformStorage = storage || new WaveformStorage();
2377
- if (path2) storagePath = path2;
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 config2 = loadConfig();
3028
- printConfigInfo(config2);
3029
- const server = createServer(config2);
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(config2.connectionTimeoutMinutes);
3034
- console.log(`[\u4F1A\u8BDD] \u4EC5\u5185\u5B58\u6A21\u5F0F\uFF08\u8FDE\u63A5\u8D85\u65F6: ${config2.connectionTimeoutMinutes} \u5206\u949F\uFF0C\u6D3B\u8DC3\u8D85\u65F6: 1 \u5C0F\u65F6\uFF09`);
3035
- const wsServer = createWSServer(config2, sessionManager);
3036
- const waveformStorage2 = initWaveforms(config2);
3037
- registerProtocolAndTools(server, toolManager, sessionManager, wsServer, config2);
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: config2,
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(config2) {
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: ${config2.port}`);
3061
- console.log(`[\u914D\u7F6E] SSE \u8DEF\u5F84: ${config2.ssePath}`);
3062
- console.log(`[\u914D\u7F6E] POST \u8DEF\u5F84: ${config2.postPath}`);
3063
- const effectiveIP = getEffectiveIP(config2);
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: ${config2.publicIp || "(\u672A\u8BBE\u7F6E)"}`);
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(config2, sessionManager) {
3056
+ function createWSServer(config, sessionManager) {
3070
3057
  return new DGLabWSServer({
3071
- heartbeatInterval: config2.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(config2) {
3108
+ function initWaveforms(config) {
3122
3109
  const waveformStorage2 = new WaveformStorage();
3123
- if (loadWaveforms(waveformStorage2, config2.waveformStorePath)) {
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, config2.waveformStorePath);
3113
+ initWaveformStorage(waveformStorage2, config.waveformStorePath);
3127
3114
  return waveformStorage2;
3128
3115
  }
3129
- function registerProtocolAndTools(server, toolManager, sessionManager, wsServer, config2) {
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, config2.publicIp || void 0);
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");