@ebowwa/terminal 0.2.0
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/client.d.ts +15 -0
- package/dist/client.js +45 -0
- package/dist/error.d.ts +8 -0
- package/dist/error.js +12 -0
- package/dist/exec.d.ts +47 -0
- package/dist/exec.js +107 -0
- package/dist/files.d.ts +124 -0
- package/dist/files.js +436 -0
- package/dist/fingerprint.d.ts +67 -0
- package/dist/index.d.ts +17 -0
- package/dist/pool.d.ts +143 -0
- package/dist/pool.js +554 -0
- package/dist/pty.d.ts +59 -0
- package/dist/scp.d.ts +30 -0
- package/dist/scp.js +74 -0
- package/dist/sessions.d.ts +98 -0
- package/dist/tmux-exec.d.ts +50 -0
- package/dist/tmux.d.ts +213 -0
- package/dist/tmux.js +528 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.js +5 -0
- package/ebowwa-terminal-0.2.0.tgz +0 -0
- package/mcp/README.md +181 -0
- package/mcp/package.json +34 -0
- package/mcp/test-fix.sh +273 -0
- package/package.json +118 -0
- package/src/api.ts +752 -0
- package/src/client.ts +55 -0
- package/src/config.ts +489 -0
- package/src/error.ts +13 -0
- package/src/exec.ts +128 -0
- package/src/files.ts +636 -0
- package/src/fingerprint.ts +263 -0
- package/src/index.ts +144 -0
- package/src/manager.ts +319 -0
- package/src/mcp/index.ts +467 -0
- package/src/mcp/stdio.ts +708 -0
- package/src/network-error-detector.ts +121 -0
- package/src/pool.ts +662 -0
- package/src/pty.ts +285 -0
- package/src/scp.ts +109 -0
- package/src/sessions.ts +861 -0
- package/src/tmux-exec.ts +96 -0
- package/src/tmux-local.ts +839 -0
- package/src/tmux-manager.ts +962 -0
- package/src/tmux.ts +711 -0
- package/src/types.ts +19 -0
package/src/mcp/stdio.ts
ADDED
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Terminal MCP Server (stdio protocol)
|
|
4
|
+
*
|
|
5
|
+
* Model Context Protocol server for terminal session management
|
|
6
|
+
* Uses stdio transport for Claude Code integration
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
TerminalSession,
|
|
11
|
+
SessionInfo,
|
|
12
|
+
SSHOptions,
|
|
13
|
+
SCPOptions,
|
|
14
|
+
} from "../index.js";
|
|
15
|
+
|
|
16
|
+
// Re-export terminal functions
|
|
17
|
+
import {
|
|
18
|
+
// Session management
|
|
19
|
+
getOrCreateSession,
|
|
20
|
+
getSession,
|
|
21
|
+
getAllSessions,
|
|
22
|
+
getAllSessionInfo,
|
|
23
|
+
getSessionInfo,
|
|
24
|
+
closeSession,
|
|
25
|
+
cleanupStaleSessions,
|
|
26
|
+
getSessionsByHost,
|
|
27
|
+
writeToSession,
|
|
28
|
+
resizeSession,
|
|
29
|
+
getSessionCount,
|
|
30
|
+
|
|
31
|
+
// PTY operations
|
|
32
|
+
createPTYSession,
|
|
33
|
+
writeToPTY,
|
|
34
|
+
setPTYSize,
|
|
35
|
+
readFromPTY,
|
|
36
|
+
closePTYSession,
|
|
37
|
+
getPTYSession,
|
|
38
|
+
getActivePTYSessions,
|
|
39
|
+
|
|
40
|
+
// Tmux operations
|
|
41
|
+
generateSessionName,
|
|
42
|
+
isTmuxInstalled,
|
|
43
|
+
ensureTmux,
|
|
44
|
+
listTmuxSessions,
|
|
45
|
+
hasTmuxSession,
|
|
46
|
+
createOrAttachTmuxSession,
|
|
47
|
+
killTmuxSession,
|
|
48
|
+
getTmuxSessionInfo,
|
|
49
|
+
cleanupOldTmuxSessions,
|
|
50
|
+
getTmuxResourceUsage,
|
|
51
|
+
sendCommandToPane,
|
|
52
|
+
splitPane,
|
|
53
|
+
capturePane,
|
|
54
|
+
getPaneHistory,
|
|
55
|
+
switchWindow,
|
|
56
|
+
switchPane,
|
|
57
|
+
renameWindow,
|
|
58
|
+
killPane,
|
|
59
|
+
getDetailedSessionInfo,
|
|
60
|
+
listSessionWindows,
|
|
61
|
+
listWindowPanes,
|
|
62
|
+
|
|
63
|
+
// SSH operations
|
|
64
|
+
execSSH,
|
|
65
|
+
execSSHParallel,
|
|
66
|
+
testSSHConnection,
|
|
67
|
+
|
|
68
|
+
// SCP operations
|
|
69
|
+
scpUpload,
|
|
70
|
+
scpDownload,
|
|
71
|
+
|
|
72
|
+
// File operations
|
|
73
|
+
listFiles,
|
|
74
|
+
previewFile,
|
|
75
|
+
sanitizePath,
|
|
76
|
+
|
|
77
|
+
// Fingerprint operations
|
|
78
|
+
getSSHFingerprint,
|
|
79
|
+
getLocalKeyFingerprint,
|
|
80
|
+
testSSHKeyConnection,
|
|
81
|
+
|
|
82
|
+
// Connection pool
|
|
83
|
+
getSSHPool,
|
|
84
|
+
closeGlobalSSHPool,
|
|
85
|
+
getActiveSSHConnections,
|
|
86
|
+
} from "../index.js";
|
|
87
|
+
|
|
88
|
+
// ==============
|
|
89
|
+
// MCP Protocol Types
|
|
90
|
+
//=============
|
|
91
|
+
|
|
92
|
+
interface JSONRPCMessage {
|
|
93
|
+
jsonrpc: "2.0";
|
|
94
|
+
id?: number | string;
|
|
95
|
+
method?: string;
|
|
96
|
+
params?: any;
|
|
97
|
+
result?: any;
|
|
98
|
+
error?: {
|
|
99
|
+
code: number;
|
|
100
|
+
message: string;
|
|
101
|
+
data?: any;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface MCPTool {
|
|
106
|
+
name: string;
|
|
107
|
+
description: string;
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: "object";
|
|
110
|
+
properties: Record<string, {
|
|
111
|
+
type: string;
|
|
112
|
+
description: string;
|
|
113
|
+
enum?: string[];
|
|
114
|
+
}>;
|
|
115
|
+
required: string[];
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ==============
|
|
120
|
+
// Tool Implementations
|
|
121
|
+
//=============
|
|
122
|
+
|
|
123
|
+
async function listSessionsTool(): Promise<string> {
|
|
124
|
+
const sessions = getAllSessionInfo();
|
|
125
|
+
const lines = [
|
|
126
|
+
"🖥️ Active Terminal Sessions",
|
|
127
|
+
"=".repeat(50),
|
|
128
|
+
"",
|
|
129
|
+
`Total sessions: ${sessions.length}`,
|
|
130
|
+
"",
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
for (const session of sessions) {
|
|
134
|
+
const active = session.active ? "🟢" : "⚪";
|
|
135
|
+
lines.push(`${active} ${session.id}`);
|
|
136
|
+
lines.push(` Host: ${session.host}`);
|
|
137
|
+
lines.push(` Type: ${session.type}`);
|
|
138
|
+
lines.push(` Created: ${new Date(session.createdAt).toLocaleString()}`);
|
|
139
|
+
if (session.lastActivity) {
|
|
140
|
+
lines.push(` Last activity: ${new Date(session.lastActivity).toLocaleString()}`);
|
|
141
|
+
}
|
|
142
|
+
lines.push("");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return lines.join("\n");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function getSessionInfoTool(sessionId: string): Promise<string> {
|
|
149
|
+
try {
|
|
150
|
+
const info = await getSessionInfo(sessionId);
|
|
151
|
+
const lines = [
|
|
152
|
+
`📋 Session: ${sessionId}`,
|
|
153
|
+
"=".repeat(50),
|
|
154
|
+
"",
|
|
155
|
+
`Host: ${info.host}`,
|
|
156
|
+
`Type: ${info.type}`,
|
|
157
|
+
`Active: ${info.active ? "Yes" : "No"}`,
|
|
158
|
+
`Created: ${new Date(info.createdAt).toLocaleString()}`,
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
if (info.lastActivity) {
|
|
162
|
+
lines.push(`Last activity: ${new Date(info.lastActivity).toLocaleString()}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const session = getSession(sessionId);
|
|
166
|
+
if (session) {
|
|
167
|
+
lines.push("");
|
|
168
|
+
lines.push(`PTY session: ${session.ptyId || "N/A"}`);
|
|
169
|
+
lines.push(`WebSocket attached: ${session.wsAttached ? "Yes" : "No"}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return lines.join("\n");
|
|
173
|
+
} catch (error) {
|
|
174
|
+
throw new Error(`Failed to get session info: ${error}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function createSessionTool(host: string, type: "ssh" | "pty" = "ssh", command?: string): Promise<string> {
|
|
179
|
+
try {
|
|
180
|
+
const session = await getOrCreateSession(host, {
|
|
181
|
+
type,
|
|
182
|
+
command,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return `✓ Created session ${session.id} for ${host} (${type})`;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
throw new Error(`Failed to create session: ${error}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function writeCommandTool(sessionId: string, command: string): Promise<string> {
|
|
192
|
+
try {
|
|
193
|
+
const session = getSession(sessionId);
|
|
194
|
+
if (!session) {
|
|
195
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await writeToSession(sessionId, command);
|
|
199
|
+
return `✓ Command sent to session ${sessionId}`;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
throw new Error(`Failed to write command: ${error}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function resizeSessionTool(sessionId: string, rows: number, cols: number): Promise<string> {
|
|
206
|
+
try {
|
|
207
|
+
await resizeSession(sessionId, rows, cols);
|
|
208
|
+
return `✓ Resized session ${sessionId} to ${rows}x${cols}`;
|
|
209
|
+
} catch (error) {
|
|
210
|
+
throw new Error(`Failed to resize session: ${error}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function closeSessionTool(sessionId: string): Promise<string> {
|
|
215
|
+
try {
|
|
216
|
+
await closeSession(sessionId);
|
|
217
|
+
return `✓ Closed session ${sessionId}`;
|
|
218
|
+
} catch (error) {
|
|
219
|
+
throw new Error(`Failed to close session: ${error}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function execSSHTool(host: string, command: string, options?: Partial<SSHOptions>): Promise<string> {
|
|
224
|
+
try {
|
|
225
|
+
const result = await execSSH(host, command, options);
|
|
226
|
+
return result.stdout || result.stderr || "Command executed";
|
|
227
|
+
} catch (error) {
|
|
228
|
+
throw new Error(`SSH command failed: ${error}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function testConnectionTool(host: string): Promise<string> {
|
|
233
|
+
try {
|
|
234
|
+
const result = await testSSHConnection(host);
|
|
235
|
+
if (result.success) {
|
|
236
|
+
return `✓ Connection to ${host} successful\nLatency: ${result.latency}ms`;
|
|
237
|
+
} else {
|
|
238
|
+
throw new Error(result.error || "Connection failed");
|
|
239
|
+
}
|
|
240
|
+
} catch (error) {
|
|
241
|
+
throw new Error(`Connection test failed: ${error}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function listTmuxSessionsTool(): Promise<string> {
|
|
246
|
+
try {
|
|
247
|
+
await ensureTmux();
|
|
248
|
+
const sessions = await listTmuxSessions();
|
|
249
|
+
const lines = [
|
|
250
|
+
"🎬 Tmux Sessions",
|
|
251
|
+
"=".repeat(50),
|
|
252
|
+
"",
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
if (sessions.length === 0) {
|
|
256
|
+
lines.push("No active tmux sessions");
|
|
257
|
+
} else {
|
|
258
|
+
for (const session of sessions) {
|
|
259
|
+
lines.push(`${session.name}`);
|
|
260
|
+
lines.push(` Windows: ${session.windows}`);
|
|
261
|
+
lines.push(` Created: ${new Date(session.created).toLocaleString()}`);
|
|
262
|
+
lines.push("");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return lines.join("\n");
|
|
267
|
+
} catch (error) {
|
|
268
|
+
throw new Error(`Failed to list tmux sessions: ${error}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function listFilesTool(host: string, path: string = "."): Promise<string> {
|
|
273
|
+
try {
|
|
274
|
+
const files = await listFiles(host, path);
|
|
275
|
+
const lines = [
|
|
276
|
+
`📁 Files on ${host}:${path}`,
|
|
277
|
+
"=".repeat(50),
|
|
278
|
+
"",
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
for (const file of files) {
|
|
282
|
+
const icon = file.type === "directory" ? "📁" : "📄";
|
|
283
|
+
const size = file.size ? ` (${file.size} bytes)` : "";
|
|
284
|
+
lines.push(`${icon} ${file.name}${size}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return lines.join("\n");
|
|
288
|
+
} catch (error) {
|
|
289
|
+
throw new Error(`Failed to list files: ${error}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function uploadFileTool(host: string, localPath: string, remotePath: string): Promise<string> {
|
|
294
|
+
try {
|
|
295
|
+
await scpUpload(host, { localPath, remotePath });
|
|
296
|
+
return `✓ Uploaded ${localPath} to ${host}:${remotePath}`;
|
|
297
|
+
} catch (error) {
|
|
298
|
+
throw new Error(`SCP upload failed: ${error}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function downloadFileTool(host: string, remotePath: string, localPath: string): Promise<string> {
|
|
303
|
+
try {
|
|
304
|
+
await scpDownload(host, { remotePath, localPath });
|
|
305
|
+
return `✓ Downloaded ${host}:${remotePath} to ${localPath}`;
|
|
306
|
+
} catch (error) {
|
|
307
|
+
throw new Error(`SCP download failed: ${error}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function getActiveConnectionsTool(): Promise<string> {
|
|
312
|
+
try {
|
|
313
|
+
const connections = getActiveSSHConnections();
|
|
314
|
+
const lines = [
|
|
315
|
+
"🔗 Active SSH Connections",
|
|
316
|
+
"=".repeat(50),
|
|
317
|
+
"",
|
|
318
|
+
`Total connections: ${connections.length}`,
|
|
319
|
+
"",
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
for (const conn of connections) {
|
|
323
|
+
lines.push(`${conn.host}`);
|
|
324
|
+
lines.push(` Connected: ${conn.connected ? "Yes" : "No"}`);
|
|
325
|
+
lines.push(` Port: ${conn.port || 22}`);
|
|
326
|
+
lines.push("");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return lines.join("\n");
|
|
330
|
+
} catch (error) {
|
|
331
|
+
throw new Error(`Failed to get connections: ${error}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ==============
|
|
336
|
+
// Tool Definitions
|
|
337
|
+
//=============
|
|
338
|
+
|
|
339
|
+
const TOOLS: MCPTool[] = [
|
|
340
|
+
{
|
|
341
|
+
name: "list_sessions",
|
|
342
|
+
description: "List all active terminal sessions",
|
|
343
|
+
inputSchema: {
|
|
344
|
+
type: "object",
|
|
345
|
+
properties: {},
|
|
346
|
+
required: [],
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
name: "get_session_info",
|
|
351
|
+
description: "Get detailed information about a specific terminal session",
|
|
352
|
+
inputSchema: {
|
|
353
|
+
type: "object",
|
|
354
|
+
properties: {
|
|
355
|
+
session_id: {
|
|
356
|
+
type: "string",
|
|
357
|
+
description: "The session ID to get info for",
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
required: ["session_id"],
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: "create_session",
|
|
365
|
+
description: "Create a new terminal session (SSH or PTY)",
|
|
366
|
+
inputSchema: {
|
|
367
|
+
type: "object",
|
|
368
|
+
properties: {
|
|
369
|
+
host: {
|
|
370
|
+
type: "string",
|
|
371
|
+
description: "SSH host in format user@host or just host",
|
|
372
|
+
},
|
|
373
|
+
type: {
|
|
374
|
+
type: "string",
|
|
375
|
+
description: "Session type: ssh or pty",
|
|
376
|
+
enum: ["ssh", "pty"],
|
|
377
|
+
},
|
|
378
|
+
command: {
|
|
379
|
+
type: "string",
|
|
380
|
+
description: "Optional command to run on session creation",
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
required: ["host"],
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: "write_command",
|
|
388
|
+
description: "Write a command to an active terminal session",
|
|
389
|
+
inputSchema: {
|
|
390
|
+
type: "object",
|
|
391
|
+
properties: {
|
|
392
|
+
session_id: {
|
|
393
|
+
type: "string",
|
|
394
|
+
description: "The session ID to write to",
|
|
395
|
+
},
|
|
396
|
+
command: {
|
|
397
|
+
type: "string",
|
|
398
|
+
description: "The command to write to the session",
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
required: ["session_id", "command"],
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: "resize_session",
|
|
406
|
+
description: "Resize a terminal session",
|
|
407
|
+
inputSchema: {
|
|
408
|
+
type: "object",
|
|
409
|
+
properties: {
|
|
410
|
+
session_id: {
|
|
411
|
+
type: "string",
|
|
412
|
+
description: "The session ID to resize",
|
|
413
|
+
},
|
|
414
|
+
rows: {
|
|
415
|
+
type: "number",
|
|
416
|
+
description: "Number of rows",
|
|
417
|
+
},
|
|
418
|
+
cols: {
|
|
419
|
+
type: "number",
|
|
420
|
+
description: "Number of columns",
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
required: ["session_id", "rows", "cols"],
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: "close_session",
|
|
428
|
+
description: "Close a terminal session",
|
|
429
|
+
inputSchema: {
|
|
430
|
+
type: "object",
|
|
431
|
+
properties: {
|
|
432
|
+
session_id: {
|
|
433
|
+
type: "string",
|
|
434
|
+
description: "The session ID to close",
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
required: ["session_id"],
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
name: "exec_ssh",
|
|
442
|
+
description: "Execute a command via SSH",
|
|
443
|
+
inputSchema: {
|
|
444
|
+
type: "object",
|
|
445
|
+
properties: {
|
|
446
|
+
host: {
|
|
447
|
+
type: "string",
|
|
448
|
+
description: "SSH host in format user@host or just host",
|
|
449
|
+
},
|
|
450
|
+
command: {
|
|
451
|
+
type: "string",
|
|
452
|
+
description: "The command to execute",
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
required: ["host", "command"],
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
name: "test_connection",
|
|
460
|
+
description: "Test SSH connection to a host",
|
|
461
|
+
inputSchema: {
|
|
462
|
+
type: "object",
|
|
463
|
+
properties: {
|
|
464
|
+
host: {
|
|
465
|
+
type: "string",
|
|
466
|
+
description: "SSH host to test connection to",
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
required: ["host"],
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "list_tmux_sessions",
|
|
474
|
+
description: "List all tmux sessions",
|
|
475
|
+
inputSchema: {
|
|
476
|
+
type: "object",
|
|
477
|
+
properties: {},
|
|
478
|
+
required: [],
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
name: "list_files",
|
|
483
|
+
description: "List files on a remote host",
|
|
484
|
+
inputSchema: {
|
|
485
|
+
type: "object",
|
|
486
|
+
properties: {
|
|
487
|
+
host: {
|
|
488
|
+
type: "string",
|
|
489
|
+
description: "SSH host to list files on",
|
|
490
|
+
},
|
|
491
|
+
path: {
|
|
492
|
+
type: "string",
|
|
493
|
+
description: "Path to list (default: current directory)",
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
required: ["host"],
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
name: "upload_file",
|
|
501
|
+
description: "Upload a file via SCP",
|
|
502
|
+
inputSchema: {
|
|
503
|
+
type: "object",
|
|
504
|
+
properties: {
|
|
505
|
+
host: {
|
|
506
|
+
type: "string",
|
|
507
|
+
description: "SSH host to upload to",
|
|
508
|
+
},
|
|
509
|
+
local_path: {
|
|
510
|
+
type: "string",
|
|
511
|
+
description: "Local file path to upload",
|
|
512
|
+
},
|
|
513
|
+
remote_path: {
|
|
514
|
+
type: "string",
|
|
515
|
+
description: "Remote destination path",
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
required: ["host", "local_path", "remote_path"],
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
name: "download_file",
|
|
523
|
+
description: "Download a file via SCP",
|
|
524
|
+
inputSchema: {
|
|
525
|
+
type: "object",
|
|
526
|
+
properties: {
|
|
527
|
+
host: {
|
|
528
|
+
type: "string",
|
|
529
|
+
description: "SSH host to download from",
|
|
530
|
+
},
|
|
531
|
+
remote_path: {
|
|
532
|
+
type: "string",
|
|
533
|
+
description: "Remote file path to download",
|
|
534
|
+
},
|
|
535
|
+
local_path: {
|
|
536
|
+
type: "string",
|
|
537
|
+
description: "Local destination path",
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
required: ["host", "remote_path", "local_path"],
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
name: "get_active_connections",
|
|
545
|
+
description: "Get all active SSH connections",
|
|
546
|
+
inputSchema: {
|
|
547
|
+
type: "object",
|
|
548
|
+
properties: {},
|
|
549
|
+
required: [],
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
];
|
|
553
|
+
|
|
554
|
+
// ==============
|
|
555
|
+
// MCP Server
|
|
556
|
+
//=============
|
|
557
|
+
|
|
558
|
+
async function handleToolCall(name: string, args: any): Promise<string> {
|
|
559
|
+
switch (name) {
|
|
560
|
+
case "list_sessions":
|
|
561
|
+
return await listSessionsTool();
|
|
562
|
+
case "get_session_info":
|
|
563
|
+
return await getSessionInfoTool(args.session_id);
|
|
564
|
+
case "create_session":
|
|
565
|
+
return await createSessionTool(args.host, args.type, args.command);
|
|
566
|
+
case "write_command":
|
|
567
|
+
return await writeCommandTool(args.session_id, args.command);
|
|
568
|
+
case "resize_session":
|
|
569
|
+
return await resizeSessionTool(args.session_id, args.rows, args.cols);
|
|
570
|
+
case "close_session":
|
|
571
|
+
return await closeSessionTool(args.session_id);
|
|
572
|
+
case "exec_ssh":
|
|
573
|
+
return await execSSHTool(args.host, args.command, args.options);
|
|
574
|
+
case "test_connection":
|
|
575
|
+
return await testConnection(args.host);
|
|
576
|
+
case "list_tmux_sessions":
|
|
577
|
+
return await listTmuxSessionsTool();
|
|
578
|
+
case "list_files":
|
|
579
|
+
return await listFilesTool(args.host, args.path);
|
|
580
|
+
case "upload_file":
|
|
581
|
+
return await uploadFileTool(args.host, args.local_path, args.remote_path);
|
|
582
|
+
case "download_file":
|
|
583
|
+
return await downloadFileTool(args.host, args.remote_path, args.local_path);
|
|
584
|
+
case "get_active_connections":
|
|
585
|
+
return await getActiveConnectionsTool();
|
|
586
|
+
default:
|
|
587
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function sendMessage(message: JSONRPCMessage): void {
|
|
592
|
+
console.log(JSON.stringify(message));
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
async function processMessage(message: JSONRPCMessage): Promise<void> {
|
|
596
|
+
const { id, method, params } = message;
|
|
597
|
+
|
|
598
|
+
try {
|
|
599
|
+
switch (method) {
|
|
600
|
+
case "initialize":
|
|
601
|
+
sendMessage({
|
|
602
|
+
jsonrpc: "2.0",
|
|
603
|
+
id,
|
|
604
|
+
result: {
|
|
605
|
+
protocolVersion: "2024-11-05",
|
|
606
|
+
capabilities: {
|
|
607
|
+
tools: {},
|
|
608
|
+
},
|
|
609
|
+
serverInfo: {
|
|
610
|
+
name: "terminal-mcp",
|
|
611
|
+
version: "1.0.0",
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
break;
|
|
616
|
+
|
|
617
|
+
case "tools/list":
|
|
618
|
+
sendMessage({
|
|
619
|
+
jsonrpc: "2.0",
|
|
620
|
+
id,
|
|
621
|
+
result: {
|
|
622
|
+
tools: TOOLS,
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
break;
|
|
626
|
+
|
|
627
|
+
case "tools/call":
|
|
628
|
+
const { name, arguments: args } = params;
|
|
629
|
+
const result = await handleToolCall(name, args || {});
|
|
630
|
+
sendMessage({
|
|
631
|
+
jsonrpc: "2.0",
|
|
632
|
+
id,
|
|
633
|
+
result: {
|
|
634
|
+
content: [
|
|
635
|
+
{
|
|
636
|
+
type: "text",
|
|
637
|
+
text: result,
|
|
638
|
+
},
|
|
639
|
+
],
|
|
640
|
+
},
|
|
641
|
+
});
|
|
642
|
+
break;
|
|
643
|
+
|
|
644
|
+
case "shutdown":
|
|
645
|
+
sendMessage({
|
|
646
|
+
jsonrpc: "2.0",
|
|
647
|
+
id,
|
|
648
|
+
result: {},
|
|
649
|
+
});
|
|
650
|
+
process.exit(0);
|
|
651
|
+
break;
|
|
652
|
+
|
|
653
|
+
default:
|
|
654
|
+
sendMessage({
|
|
655
|
+
jsonrpc: "2.0",
|
|
656
|
+
id,
|
|
657
|
+
error: {
|
|
658
|
+
code: -32601,
|
|
659
|
+
message: `Method not found: ${method}`,
|
|
660
|
+
},
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
} catch (error) {
|
|
664
|
+
sendMessage({
|
|
665
|
+
jsonrpc: "2.0",
|
|
666
|
+
id,
|
|
667
|
+
error: {
|
|
668
|
+
code: -32603,
|
|
669
|
+
message: `Internal error: ${error}`,
|
|
670
|
+
},
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ==============
|
|
676
|
+
// Main Loop
|
|
677
|
+
//=============
|
|
678
|
+
|
|
679
|
+
async function main() {
|
|
680
|
+
const decoder = new TextDecoder();
|
|
681
|
+
let buffer = "";
|
|
682
|
+
|
|
683
|
+
for await (const chunk of process.stdin) {
|
|
684
|
+
buffer += decoder.decode(chunk);
|
|
685
|
+
|
|
686
|
+
const lines = buffer.split("\n");
|
|
687
|
+
buffer = lines.pop() || "";
|
|
688
|
+
|
|
689
|
+
for (const line of lines) {
|
|
690
|
+
if (!line.trim()) continue;
|
|
691
|
+
|
|
692
|
+
try {
|
|
693
|
+
const message: JSONRPCMessage = JSON.parse(line);
|
|
694
|
+
await processMessage(message);
|
|
695
|
+
} catch (error) {
|
|
696
|
+
sendMessage({
|
|
697
|
+
jsonrpc: "2.0",
|
|
698
|
+
error: {
|
|
699
|
+
code: -32700,
|
|
700
|
+
message: `Parse error: ${error}`,
|
|
701
|
+
},
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
main().catch(console.error);
|