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 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` | 删除设备会话 |
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
- | `dg_set_strength` | 设置通道强度 (A/B, 0-200) |
55
- | `dg_send_waveform` | 发送波形数据到设备 |
56
- | `dg_clear_waveform` | 清空波形队列 |
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,16 +154,19 @@ 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
 
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
  }
@@ -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, path2) {
2364
+ function initWaveformStorage(storage, path) {
2376
2365
  waveformStorage = storage || new WaveformStorage();
2377
- if (path2) storagePath = path2;
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 config2 = loadConfig();
3028
- printConfigInfo(config2);
3029
- const server = createServer(config2);
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(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);
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: config2,
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(config2) {
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: ${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);
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: ${config2.publicIp || "(\u672A\u8BBE\u7F6E)"}`);
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(config2, sessionManager) {
3058
+ function createWSServer(config, sessionManager) {
3070
3059
  return new DGLabWSServer({
3071
- heartbeatInterval: config2.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(config2) {
3110
+ function initWaveforms(config) {
3122
3111
  const waveformStorage2 = new WaveformStorage();
3123
- if (loadWaveforms(waveformStorage2, config2.waveformStorePath)) {
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, config2.waveformStorePath);
3115
+ initWaveformStorage(waveformStorage2, config.waveformStorePath);
3127
3116
  return waveformStorage2;
3128
3117
  }
3129
- function registerProtocolAndTools(server, toolManager, sessionManager, wsServer, config2) {
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, config2.publicIp || void 0);
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");