@zy_zhou/vps-mcp-server 1.0.2 → 1.0.3

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/dist/server.js CHANGED
@@ -7,301 +7,324 @@ import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprot
7
7
  import express from 'express';
8
8
  import { handleRunCommand, handleRunScript, handleFileRead, handleFileWrite, handleListFiles, handleFileDelete, handleFileSearch, handleFilePatch, handleSessionStart, handleSessionExec, handleSessionRead, handleSessionStop, handleGetSystemStatus, handleKillProcess, handleHttpRequest, handleAgyAsk, } from './tools/index.js';
9
9
  const TOKEN = process.env.MCP_TOKEN || 'change-me';
10
- const server = new Server({
11
- name: 'vps-sandbox',
12
- version: '1.0.0',
13
- }, {
14
- capabilities: {
15
- tools: {},
16
- },
17
- });
18
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
19
- tools: [
20
- {
21
- name: 'run_command',
22
- description: '在 VPS 沙盒中执行 Shell 命令(一次性)',
23
- inputSchema: {
24
- type: 'object',
25
- properties: {
26
- command: { type: 'string', description: '要执行的命令' },
27
- cwd: { type: 'string', description: '工作目录,默认 /tmp' },
28
- timeout: { type: 'number', description: '超时(ms),默认 30000' },
10
+ function registerHandlers(server) {
11
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
12
+ tools: [
13
+ {
14
+ name: 'run_command',
15
+ description: '在 VPS 沙盒中执行 Shell 命令(一次性)',
16
+ inputSchema: {
17
+ type: 'object',
18
+ properties: {
19
+ command: { type: 'string', description: '要执行的命令' },
20
+ cwd: { type: 'string', description: '工作目录,默认 /tmp' },
21
+ timeout: { type: 'number', description: '超时(ms),默认 30000' },
22
+ },
23
+ required: ['command'],
29
24
  },
30
- required: ['command'],
31
25
  },
32
- },
33
- {
34
- name: 'run_script',
35
- description: '执行脚本代码 (python/bash/node)',
36
- inputSchema: {
37
- type: 'object',
38
- properties: {
39
- language: { type: 'string', enum: ['python', 'bash', 'node', 'sh'] },
40
- code: { type: 'string', description: '脚本源码' },
41
- timeout: { type: 'number', description: '超时(ms)' },
26
+ {
27
+ name: 'run_script',
28
+ description: '执行脚本代码 (python/bash/node)',
29
+ inputSchema: {
30
+ type: 'object',
31
+ properties: {
32
+ language: { type: 'string', enum: ['python', 'bash', 'node', 'sh'] },
33
+ code: { type: 'string', description: '脚本源码' },
34
+ timeout: { type: 'number', description: '超时(ms)' },
35
+ },
36
+ required: ['language', 'code'],
42
37
  },
43
- required: ['language', 'code'],
44
38
  },
45
- },
46
- {
47
- name: 'file_read',
48
- description: '读取文件内容',
49
- inputSchema: {
50
- type: 'object',
51
- properties: {
52
- filepath: { type: 'string' },
39
+ {
40
+ name: 'file_read',
41
+ description: '读取文件内容',
42
+ inputSchema: {
43
+ type: 'object',
44
+ properties: {
45
+ filepath: { type: 'string' },
46
+ },
47
+ required: ['filepath'],
53
48
  },
54
- required: ['filepath'],
55
49
  },
56
- },
57
- {
58
- name: 'file_write',
59
- description: '写入文件',
60
- inputSchema: {
61
- type: 'object',
62
- properties: {
63
- filepath: { type: 'string' },
64
- content: { type: 'string' },
50
+ {
51
+ name: 'file_write',
52
+ description: '写入文件',
53
+ inputSchema: {
54
+ type: 'object',
55
+ properties: {
56
+ filepath: { type: 'string' },
57
+ content: { type: 'string' },
58
+ },
59
+ required: ['filepath', 'content'],
65
60
  },
66
- required: ['filepath', 'content'],
67
61
  },
68
- },
69
- {
70
- name: 'file_patch',
71
- description: '增量编辑文件(搜索并替换)',
72
- inputSchema: {
73
- type: 'object',
74
- properties: {
75
- filepath: { type: 'string' },
76
- search: { type: 'string', description: '要搜索的原始文本' },
77
- replace: { type: 'string', description: '要替换成的新文本' },
62
+ {
63
+ name: 'file_patch',
64
+ description: '增量编辑文件(搜索并替换)',
65
+ inputSchema: {
66
+ type: 'object',
67
+ properties: {
68
+ filepath: { type: 'string' },
69
+ search: { type: 'string', description: '要搜索的原始文本' },
70
+ replace: { type: 'string', description: '要替换成的新文本' },
71
+ },
72
+ required: ['filepath', 'search', 'replace'],
78
73
  },
79
- required: ['filepath', 'search', 'replace'],
80
74
  },
81
- },
82
- {
83
- name: 'list_files',
84
- description: '列出目录内容或递归获取目录树',
85
- inputSchema: {
86
- type: 'object',
87
- properties: {
88
- dirpath: { type: 'string', description: '目录路径' },
89
- recursive: { type: 'boolean', description: '是否递归列出 (树状结构)' },
90
- depth: { type: 'number', description: '递归深度' },
91
- exclude: { type: 'array', items: { type: 'string' }, description: '排除的目录' },
75
+ {
76
+ name: 'list_files',
77
+ description: '列出目录内容或递归获取目录树',
78
+ inputSchema: {
79
+ type: 'object',
80
+ properties: {
81
+ dirpath: { type: 'string', description: '目录路径' },
82
+ recursive: { type: 'boolean', description: '是否递归列出 (树状结构)' },
83
+ depth: { type: 'number', description: '递归深度' },
84
+ exclude: { type: 'array', items: { type: 'string' }, description: '排除的目录' },
85
+ },
92
86
  },
93
87
  },
94
- },
95
- {
96
- name: 'file_delete',
97
- description: '删除文件或目录',
98
- inputSchema: {
99
- type: 'object',
100
- properties: {
101
- filepath: { type: 'string' },
88
+ {
89
+ name: 'file_delete',
90
+ description: '删除文件或目录',
91
+ inputSchema: {
92
+ type: 'object',
93
+ properties: {
94
+ filepath: { type: 'string' },
95
+ },
96
+ required: ['filepath'],
102
97
  },
103
- required: ['filepath'],
104
98
  },
105
- },
106
- {
107
- name: 'file_search',
108
- description: '搜索文件名或文件内容',
109
- inputSchema: {
110
- type: 'object',
111
- properties: {
112
- dirpath: { type: 'string', description: '起始目录' },
113
- query: { type: 'string', description: '搜索关键词' },
114
- type: { type: 'string', enum: ['name', 'content'], description: '搜索类型' },
115
- recursive: { type: 'boolean', description: '是否递归搜索' },
99
+ {
100
+ name: 'file_search',
101
+ description: '搜索文件名或文件内容',
102
+ inputSchema: {
103
+ type: 'object',
104
+ properties: {
105
+ dirpath: { type: 'string', description: '起始目录' },
106
+ query: { type: 'string', description: '搜索关键词' },
107
+ type: { type: 'string', enum: ['name', 'content'], description: '搜索类型' },
108
+ recursive: { type: 'boolean', description: '是否递归搜索' },
109
+ },
110
+ required: ['query'],
116
111
  },
117
- required: ['query'],
118
112
  },
119
- },
120
- {
121
- name: 'get_system_status',
122
- description: '获取 VPS 系统状态或进程列表',
123
- inputSchema: {
124
- type: 'object',
125
- properties: {
126
- type: { type: 'string', enum: ['summary', 'processes'], description: 'summary: 系统概览, processes: 进程列表' },
127
- sort: { type: 'string', enum: ['cpu', 'mem'], description: '进程排序方式' },
113
+ {
114
+ name: 'get_system_status',
115
+ description: '获取 VPS 系统状态或进程列表',
116
+ inputSchema: {
117
+ type: 'object',
118
+ properties: {
119
+ type: { type: 'string', enum: ['summary', 'processes'], description: 'summary: 系统概览, processes: 进程列表' },
120
+ sort: { type: 'string', enum: ['cpu', 'mem'], description: '进程排序方式' },
121
+ },
128
122
  },
129
123
  },
130
- },
131
- {
132
- name: 'kill_process',
133
- description: '终止指定 PID 的进程',
134
- inputSchema: {
135
- type: 'object',
136
- properties: {
137
- pid: { type: 'number' },
138
- force: { type: 'boolean' },
124
+ {
125
+ name: 'kill_process',
126
+ description: '终止指定 PID 的进程',
127
+ inputSchema: {
128
+ type: 'object',
129
+ properties: {
130
+ pid: { type: 'number' },
131
+ force: { type: 'boolean' },
132
+ },
133
+ required: ['pid'],
139
134
  },
140
- required: ['pid'],
141
135
  },
142
- },
143
- {
144
- name: 'http_request',
145
- description: '发起 HTTP 请求或下载文件',
146
- inputSchema: {
147
- type: 'object',
148
- properties: {
149
- url: { type: 'string' },
150
- method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'DELETE'] },
151
- headers: { type: 'object' },
152
- body: { type: 'any' },
153
- savePath: { type: 'string', description: '如果提供,则将响应保存到此文件路径' },
136
+ {
137
+ name: 'http_request',
138
+ description: '发起 HTTP 请求或下载文件',
139
+ inputSchema: {
140
+ type: 'object',
141
+ properties: {
142
+ url: { type: 'string' },
143
+ method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'DELETE'] },
144
+ headers: { type: 'object' },
145
+ body: { type: 'any' },
146
+ savePath: { type: 'string', description: '如果提供,则将响应保存到此文件路径' },
147
+ },
148
+ required: ['url'],
154
149
  },
155
- required: ['url'],
156
150
  },
157
- },
158
- {
159
- name: 'session_start',
160
- description: '启动一个持久 tmux 会话',
161
- inputSchema: {
162
- type: 'object',
163
- properties: {
164
- sessionId: { type: 'string' },
165
- command: { type: 'string' },
166
- cwd: { type: 'string' },
151
+ {
152
+ name: 'session_start',
153
+ description: '启动一个持久 tmux 会话',
154
+ inputSchema: {
155
+ type: 'object',
156
+ properties: {
157
+ sessionId: { type: 'string' },
158
+ command: { type: 'string' },
159
+ cwd: { type: 'string' },
160
+ },
161
+ required: ['sessionId'],
167
162
  },
168
- required: ['sessionId'],
169
163
  },
170
- },
171
- {
172
- name: 'session_exec',
173
- description: '向已有会话发送命令',
174
- inputSchema: {
175
- type: 'object',
176
- properties: {
177
- sessionId: { type: 'string' },
178
- command: { type: 'string' },
164
+ {
165
+ name: 'session_exec',
166
+ description: '向已有会话发送命令',
167
+ inputSchema: {
168
+ type: 'object',
169
+ properties: {
170
+ sessionId: { type: 'string' },
171
+ command: { type: 'string' },
172
+ },
173
+ required: ['sessionId', 'command'],
179
174
  },
180
- required: ['sessionId', 'command'],
181
175
  },
182
- },
183
- {
184
- name: 'session_read',
185
- description: '读取会话的最近输出',
186
- inputSchema: {
187
- type: 'object',
188
- properties: {
189
- sessionId: { type: 'string' },
190
- maxChars: { type: 'number' },
176
+ {
177
+ name: 'session_read',
178
+ description: '读取会话的最近输出',
179
+ inputSchema: {
180
+ type: 'object',
181
+ properties: {
182
+ sessionId: { type: 'string' },
183
+ maxChars: { type: 'number' },
184
+ },
185
+ required: ['sessionId'],
191
186
  },
192
- required: ['sessionId'],
193
187
  },
194
- },
195
- {
196
- name: 'session_stop',
197
- description: '停止并销毁会话',
198
- inputSchema: {
199
- type: 'object',
200
- properties: {
201
- sessionId: { type: 'string' },
188
+ {
189
+ name: 'session_stop',
190
+ description: '停止并销毁会话',
191
+ inputSchema: {
192
+ type: 'object',
193
+ properties: {
194
+ sessionId: { type: 'string' },
195
+ },
196
+ required: ['sessionId'],
202
197
  },
203
- required: ['sessionId'],
204
198
  },
205
- },
206
- {
207
- name: 'agy_ask',
208
- description: '向 agy 提问,自动维护上下文会话 ID。支持直接在普通沙盒中执行,或指定持久化的 tmux 会话执行并自动读取完毕后一次性返回。',
209
- inputSchema: {
210
- type: 'object',
211
- properties: {
212
- prompt: { type: 'string', description: '要提问的具体内容' },
213
- sessionId: { type: 'string', description: '(可选)绑定的持久 tmux 会话 ID。如果提供,将在该会话中执行;否则在常规沙盒环境中执行。' },
214
- clear: { type: 'boolean', description: '是否清空可见屏幕及历史滚动缓冲区,默认为 true' },
215
- dangerouslySkipPermissions: { type: 'boolean', description: '是否自动跳过权限询问,默认为 true' },
216
- model: { type: 'string', description: '透传给 agy 的模型参数' },
217
- agent: { type: 'string', description: '透传给 agy 的代理参数' },
218
- effort: { type: 'string', description: '透传给 agy 的思考深度参数' },
219
- mode: { type: 'string', description: '透传给 agy 的模式参数' },
220
- project: { type: 'string', description: '透传给 agy 的项目参数' },
221
- addDir: { type: 'string', description: '透传给 agy 的添加目录参数' }
222
- },
223
- required: ['prompt']
199
+ {
200
+ name: 'agy_ask',
201
+ description: '向 agy 提问,自动维护上下文会话 ID。支持直接在普通沙盒中执行,或指定持久化的 tmux 会话执行并自动读取完毕后一次性返回。',
202
+ inputSchema: {
203
+ type: 'object',
204
+ properties: {
205
+ prompt: { type: 'string', description: '要提问的具体内容' },
206
+ sessionId: { type: 'string', description: '(可选)绑定的持久 tmux 会话 ID。如果提供,将在该会话中执行;否则在常规沙盒环境中执行。' },
207
+ clear: { type: 'boolean', description: '是否清空可见屏幕及历史滚动缓冲区,默认为 true' },
208
+ dangerouslySkipPermissions: { type: 'boolean', description: '是否自动跳过权限询问,默认为 true' },
209
+ model: { type: 'string', description: '透传给 agy 的模型参数' },
210
+ agent: { type: 'string', description: '透传给 agy 的代理参数' },
211
+ effort: { type: 'string', description: '透传给 agy 的思考深度参数' },
212
+ mode: { type: 'string', description: '透传给 agy 的模式参数' },
213
+ project: { type: 'string', description: '透传给 agy 的项目参数' },
214
+ addDir: { type: 'string', description: '透传给 agy 的添加目录参数' }
215
+ },
216
+ required: ['prompt']
217
+ }
218
+ },
219
+ ],
220
+ }));
221
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
222
+ const { name, arguments: args } = request.params;
223
+ try {
224
+ let result;
225
+ switch (name) {
226
+ case 'run_command':
227
+ result = await handleRunCommand(args);
228
+ break;
229
+ case 'run_script':
230
+ result = await handleRunScript(args);
231
+ break;
232
+ case 'file_read':
233
+ result = await handleFileRead(args);
234
+ break;
235
+ case 'file_write':
236
+ result = await handleFileWrite(args);
237
+ break;
238
+ case 'file_patch':
239
+ result = await handleFilePatch(args);
240
+ break;
241
+ case 'list_files':
242
+ result = await handleListFiles(args);
243
+ break;
244
+ case 'file_delete':
245
+ result = await handleFileDelete(args);
246
+ break;
247
+ case 'file_search':
248
+ result = await handleFileSearch(args);
249
+ break;
250
+ case 'get_system_status':
251
+ result = await handleGetSystemStatus(args);
252
+ break;
253
+ case 'kill_process':
254
+ result = await handleKillProcess(args);
255
+ break;
256
+ case 'http_request':
257
+ result = await handleHttpRequest(args);
258
+ break;
259
+ case 'session_start':
260
+ result = await handleSessionStart(args);
261
+ break;
262
+ case 'session_exec':
263
+ result = await handleSessionExec(args);
264
+ break;
265
+ case 'session_read':
266
+ result = await handleSessionRead(args);
267
+ break;
268
+ case 'session_stop':
269
+ result = await handleSessionStop(args);
270
+ break;
271
+ case 'agy_ask':
272
+ result = await handleAgyAsk(args);
273
+ break;
274
+ default: throw new Error(`Unknown tool: ${name}`);
224
275
  }
225
- },
226
- ],
227
- }));
228
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
229
- const { name, arguments: args } = request.params;
230
- try {
231
- let result;
232
- switch (name) {
233
- case 'run_command':
234
- result = await handleRunCommand(args);
235
- break;
236
- case 'run_script':
237
- result = await handleRunScript(args);
238
- break;
239
- case 'file_read':
240
- result = await handleFileRead(args);
241
- break;
242
- case 'file_write':
243
- result = await handleFileWrite(args);
244
- break;
245
- case 'file_patch':
246
- result = await handleFilePatch(args);
247
- break;
248
- case 'list_files':
249
- result = await handleListFiles(args);
250
- break;
251
- case 'file_delete':
252
- result = await handleFileDelete(args);
253
- break;
254
- case 'file_search':
255
- result = await handleFileSearch(args);
256
- break;
257
- case 'get_system_status':
258
- result = await handleGetSystemStatus(args);
259
- break;
260
- case 'kill_process':
261
- result = await handleKillProcess(args);
262
- break;
263
- case 'http_request':
264
- result = await handleHttpRequest(args);
265
- break;
266
- case 'session_start':
267
- result = await handleSessionStart(args);
268
- break;
269
- case 'session_exec':
270
- result = await handleSessionExec(args);
271
- break;
272
- case 'session_read':
273
- result = await handleSessionRead(args);
274
- break;
275
- case 'session_stop':
276
- result = await handleSessionStop(args);
277
- break;
278
- case 'agy_ask':
279
- result = await handleAgyAsk(args);
280
- break;
281
- default: throw new Error(`Unknown tool: ${name}`);
276
+ return result;
282
277
  }
283
- return result;
284
- }
285
- catch (error) {
286
- return {
287
- content: [{ type: 'text', text: `Error: ${error.message}` }],
288
- isError: true,
289
- };
290
- }
291
- });
278
+ catch (error) {
279
+ return {
280
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
281
+ isError: true,
282
+ };
283
+ }
284
+ });
285
+ }
286
+ function createMcpServer() {
287
+ const s = new Server({
288
+ name: 'vps-sandbox',
289
+ version: '1.0.0',
290
+ }, {
291
+ capabilities: {
292
+ tools: {},
293
+ },
294
+ });
295
+ registerHandlers(s);
296
+ return s;
297
+ }
292
298
  const mode = process.env.MODE || 'stdio';
293
299
  if (mode === 'sse') {
294
300
  const app = express();
295
301
  app.use(express.json());
296
- let transport = null;
302
+ // 记录每个客户端的独立服务器和传输通道
303
+ const transports = new Map();
297
304
  app.get('/sse', async (req, res) => {
298
305
  const auth = (req.headers.authorization?.replace('Bearer ', '') || req.query.token);
299
306
  if (auth !== TOKEN) {
300
307
  res.status(401).json({ error: 'Unauthorized' });
301
308
  return;
302
309
  }
303
- transport = new SSEServerTransport('/messages', res);
304
- await server.connect(transport);
310
+ const clientServer = createMcpServer(); // 为当前连接动态实例化独立的 Server
311
+ // 创建 SSE Transport,客户端接收消息的端点为 /messages
312
+ const transport = new SSEServerTransport('/messages', res);
313
+ const sessionId = transport.sessionId;
314
+ transports.set(sessionId, { server: clientServer, transport });
315
+ console.error(`Client connected. Registered SSE transport for sessionId: ${sessionId}`);
316
+ // 连接断开时的自动垃圾清理与资源释放,防范内存泄漏
317
+ req.on('close', async () => {
318
+ transports.delete(sessionId);
319
+ console.error(`Client disconnected. Cleaned up SSE transport for sessionId: ${sessionId}`);
320
+ try {
321
+ await clientServer.close();
322
+ }
323
+ catch (err) {
324
+ // 忽略关闭可能抛出的已断连异常
325
+ }
326
+ });
327
+ await clientServer.connect(transport);
305
328
  });
306
329
  app.post('/messages', async (req, res) => {
307
330
  const auth = req.headers.authorization?.replace('Bearer ', '');
@@ -309,11 +332,17 @@ if (mode === 'sse') {
309
332
  res.status(401).json({ error: 'Unauthorized' });
310
333
  return;
311
334
  }
312
- if (transport) {
313
- await transport.handlePostMessage(req, res, req.body);
335
+ const sessionId = req.query.sessionId;
336
+ if (!sessionId) {
337
+ res.status(400).json({ error: 'Missing sessionId query parameter' });
338
+ return;
339
+ }
340
+ const entry = transports.get(sessionId);
341
+ if (entry) {
342
+ await entry.transport.handlePostMessage(req, res, req.body);
314
343
  }
315
344
  else {
316
- res.status(400).json({ error: 'No active SSE transport' });
345
+ res.status(404).json({ error: `Active SSE transport not found for sessionId: ${sessionId}` });
317
346
  }
318
347
  });
319
348
  app.listen(process.env.PORT || 8080, () => {
@@ -321,7 +350,8 @@ if (mode === 'sse') {
321
350
  });
322
351
  }
323
352
  else {
353
+ const stdioServer = createMcpServer();
324
354
  const transport = new StdioServerTransport();
325
- server.connect(transport);
355
+ stdioServer.connect(transport);
326
356
  console.error('MCP stdio server running');
327
357
  }
package/dist/tools/agy.js CHANGED
@@ -71,16 +71,16 @@ export async function handleAgyAsk(args) {
71
71
  // 4. 分情况执行
72
72
  if (sessionId) {
73
73
  // 【场景 A:传了 sessionId,需要实现上下文持续】
74
- // 获取 TTY 隔离键
75
- let ttyId = 'default';
74
+ // 获取 TTY 隔离键,默认使用专属 sessionId 兜底以防并发碰撞
75
+ let ttyId = `tmux_${sessionId}`;
76
76
  try {
77
77
  const ttyCheck = await executeCommand(`tmux display-message -p -t mcp-${sessionId} '#{pane_tty}'`);
78
- if (ttyCheck.success && ttyCheck.stdout) {
78
+ if (ttyCheck.success && ttyCheck.stdout && ttyCheck.stdout.trim()) {
79
79
  ttyId = ttyCheck.stdout.trim().replace(/^\/dev\//, '').replace(/\//g, '_');
80
80
  }
81
81
  }
82
82
  catch {
83
- ttyId = `tmux_${sessionId}`;
83
+ // 保持专属 sessionId 兜底
84
84
  }
85
85
  const logFile = `${TMP_DIR}/agy_conv_${ttyId}.log`;
86
86
  const sidStore = `${TMP_DIR}/agy_sid_${ttyId}.env`;
@@ -94,7 +94,9 @@ export async function handleAgyAsk(args) {
94
94
  }
95
95
  }
96
96
  catch (err) {
97
- console.error(`Failed to read sidStore (${sidStore}):`, err.message);
97
+ if (err.code !== 'ENOENT') {
98
+ console.error(`Failed to read sidStore (${sidStore}):`, err.message);
99
+ }
98
100
  }
99
101
  if (agySid) {
100
102
  extraArgs.push(`--conversation ${agySid}`);
@@ -48,15 +48,15 @@ export async function handleSessionStop(args) {
48
48
  if (!sessionId)
49
49
  throw new Error('sessionId required');
50
50
  // 1. 获取 TTY 隔离键以删除关联的 agy 缓存临时文件
51
- let ttyId = 'default';
51
+ let ttyId = `tmux_${sessionId}`;
52
52
  try {
53
53
  const ttyCheck = await executeCommand(`tmux display-message -p -t mcp-${sessionId} '#{pane_tty}'`);
54
- if (ttyCheck.success && ttyCheck.stdout) {
54
+ if (ttyCheck.success && ttyCheck.stdout && ttyCheck.stdout.trim()) {
55
55
  ttyId = ttyCheck.stdout.trim().replace(/^\/dev\//, '').replace(/\//g, '_');
56
56
  }
57
57
  }
58
58
  catch {
59
- ttyId = `tmux_${sessionId}`;
59
+ // 保持专属 sessionId 兜底
60
60
  }
61
61
  // 删除 TTY 关联和兜底命名的缓存与日志
62
62
  const filesToRemove = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zy_zhou/vps-mcp-server",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "MCP Server for VPS sandbox execution — run commands, scripts, and manage sessions remotely via MCP protocol. Supports stdio and SSE transports.",
5
5
  "type": "module",
6
6
  "main": "dist/server.js",