seahorse-bash-client 1.0.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.
@@ -0,0 +1,318 @@
1
+ "use strict";
2
+ /**
3
+ * WebSocket Client - Connects to Seahorse Agent and handles MCP tool calls
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.WebSocketClient = void 0;
10
+ const ws_1 = __importDefault(require("ws"));
11
+ const events_1 = require("events");
12
+ const os_1 = __importDefault(require("os"));
13
+ const https_1 = __importDefault(require("https"));
14
+ const http_1 = __importDefault(require("http"));
15
+ const pty_manager_1 = require("./pty-manager");
16
+ const tools_1 = require("./tools");
17
+ class WebSocketClient extends events_1.EventEmitter {
18
+ constructor(config) {
19
+ super();
20
+ this.ws = null;
21
+ this.reconnectTimer = null;
22
+ this.heartbeatTimer = null;
23
+ this.isConnected = false;
24
+ this.isShuttingDown = false;
25
+ this.config = {
26
+ reconnectInterval: 5000,
27
+ heartbeatInterval: 30000,
28
+ maxOutputLines: 50000,
29
+ ...config,
30
+ };
31
+ this.ptyManager = new pty_manager_1.PTYManager({
32
+ maxOutputLines: this.config.maxOutputLines,
33
+ defaultShell: this.config.defaultShell,
34
+ });
35
+ // Forward session exit events
36
+ this.ptyManager.on('session:exited', (data) => {
37
+ this.sendNotification('session/exited', data);
38
+ });
39
+ }
40
+ /**
41
+ * Connect to the Seahorse Agent
42
+ */
43
+ async connect() {
44
+ if (this.isShuttingDown)
45
+ return;
46
+ return new Promise((resolve, reject) => {
47
+ try {
48
+ const url = new URL(this.config.serverUrl);
49
+ const isSecure = url.protocol === 'wss:';
50
+ // Create agent that forces HTTP/1.1 (no ALPN for HTTP/2)
51
+ const agent = isSecure
52
+ ? new https_1.default.Agent({
53
+ rejectUnauthorized: !this.config.insecure,
54
+ // Force HTTP/1.1 by not advertising HTTP/2
55
+ ALPNProtocols: ['http/1.1'],
56
+ })
57
+ : new http_1.default.Agent();
58
+ const wsOptions = {
59
+ agent,
60
+ perMessageDeflate: false,
61
+ };
62
+ this.ws = new ws_1.default(this.config.serverUrl, wsOptions);
63
+ this.ws.on('open', () => {
64
+ this.isConnected = true;
65
+ this.emit('connected');
66
+ this.register();
67
+ this.startHeartbeat();
68
+ resolve();
69
+ });
70
+ this.ws.on('message', (data) => {
71
+ this.handleMessage(data);
72
+ });
73
+ this.ws.on('close', (code, reason) => {
74
+ this.isConnected = false;
75
+ this.stopHeartbeat();
76
+ this.emit('disconnected', { code, reason: reason.toString() });
77
+ if (!this.isShuttingDown) {
78
+ this.scheduleReconnect();
79
+ }
80
+ });
81
+ this.ws.on('error', (error) => {
82
+ this.emit('error', error);
83
+ if (!this.isConnected) {
84
+ reject(error);
85
+ }
86
+ });
87
+ }
88
+ catch (error) {
89
+ reject(error);
90
+ }
91
+ });
92
+ }
93
+ /**
94
+ * Disconnect from the server
95
+ */
96
+ async disconnect() {
97
+ this.isShuttingDown = true;
98
+ if (this.reconnectTimer) {
99
+ clearTimeout(this.reconnectTimer);
100
+ this.reconnectTimer = null;
101
+ }
102
+ this.stopHeartbeat();
103
+ // Shutdown all PTY sessions
104
+ await this.ptyManager.shutdown();
105
+ if (this.ws) {
106
+ this.ws.close(1000, 'Client shutdown');
107
+ this.ws = null;
108
+ }
109
+ }
110
+ /**
111
+ * Register tools with the agent
112
+ */
113
+ register() {
114
+ // Note: Server expects "registration" type, not "register"
115
+ const message = {
116
+ type: 'registration',
117
+ tools: tools_1.TOOL_DEFINITIONS,
118
+ metadata: {
119
+ version: '1.0.0',
120
+ hostname: os_1.default.hostname(),
121
+ platform: process.platform,
122
+ shell: this.config.defaultShell || process.env.SHELL || '/bin/bash',
123
+ },
124
+ };
125
+ this.send(message);
126
+ }
127
+ /**
128
+ * Handle incoming WebSocket messages
129
+ */
130
+ handleMessage(data) {
131
+ try {
132
+ const message = JSON.parse(data.toString());
133
+ switch (message.type) {
134
+ case 'tool_call':
135
+ this.handleToolCall(message);
136
+ break;
137
+ case 'heartbeat':
138
+ case 'heartbeat_ack':
139
+ // Respond to heartbeat, ignore ack
140
+ if (message.type === 'heartbeat') {
141
+ this.send({ type: 'heartbeat' });
142
+ }
143
+ break;
144
+ case 'registration_ack':
145
+ // Server acknowledged registration
146
+ const ackData = message;
147
+ this.emit('registered', {
148
+ serverName: ackData.server_name,
149
+ tools: tools_1.TOOL_DEFINITIONS,
150
+ registeredCount: ackData.registered_tools,
151
+ });
152
+ break;
153
+ case 'error':
154
+ const errorData = message;
155
+ this.emit('error', new Error(`Server error: ${errorData.error}`));
156
+ break;
157
+ default:
158
+ this.emit('message', message);
159
+ }
160
+ }
161
+ catch (error) {
162
+ this.emit('error', new Error(`Failed to parse message: ${error}`));
163
+ }
164
+ }
165
+ /**
166
+ * Handle a tool call from the agent
167
+ */
168
+ async handleToolCall(message) {
169
+ const { request_id, params } = message;
170
+ const { name, arguments: args } = params;
171
+ this.emit('tool:call', { name, args, requestId: request_id });
172
+ try {
173
+ const result = await this.executeTool(name, args);
174
+ this.sendToolResponse(request_id, result, false);
175
+ this.emit('tool:success', { name, requestId: request_id });
176
+ }
177
+ catch (error) {
178
+ const errorMessage = error instanceof Error ? error.message : String(error);
179
+ this.sendToolResponse(request_id, { error: errorMessage }, true, errorMessage);
180
+ this.emit('tool:error', { name, requestId: request_id, error: errorMessage });
181
+ }
182
+ }
183
+ /**
184
+ * Execute a tool
185
+ */
186
+ async executeTool(name, args) {
187
+ // Validate allowed commands if configured
188
+ if (this.config.allowedCommands && name.startsWith('bash_')) {
189
+ const command = args.command || '';
190
+ const isAllowed = this.config.allowedCommands.some((allowed) => command.startsWith(allowed) || command === allowed);
191
+ if (!isAllowed) {
192
+ throw new Error(`Command not in allowed list: ${command}`);
193
+ }
194
+ }
195
+ // Validate blocked commands if configured
196
+ if (this.config.blockedCommands && name.startsWith('bash_')) {
197
+ const command = args.command || '';
198
+ const isBlocked = this.config.blockedCommands.some((blocked) => command.includes(blocked) || command.startsWith(blocked));
199
+ if (isBlocked) {
200
+ throw new Error(`Command is blocked: ${command}`);
201
+ }
202
+ }
203
+ // Validate cwd if restrictions are configured
204
+ if (this.config.allowedCwdPaths && args.cwd) {
205
+ const cwd = args.cwd;
206
+ const isAllowed = this.config.allowedCwdPaths.some((allowed) => cwd.startsWith(allowed));
207
+ if (!isAllowed) {
208
+ throw new Error(`Working directory not allowed: ${cwd}`);
209
+ }
210
+ }
211
+ switch (name) {
212
+ case 'bash_spawn':
213
+ return this.ptyManager.spawn(args);
214
+ case 'bash_exec':
215
+ return this.ptyManager.exec(args);
216
+ case 'bash_write':
217
+ return this.ptyManager.write(args);
218
+ case 'bash_read':
219
+ return this.ptyManager.read(args);
220
+ case 'bash_list':
221
+ return this.ptyManager.list(args);
222
+ case 'bash_kill':
223
+ return this.ptyManager.kill(args);
224
+ default:
225
+ throw new Error(`Unknown tool: ${name}`);
226
+ }
227
+ }
228
+ /**
229
+ * Send a tool response
230
+ */
231
+ sendToolResponse(requestId, result, isError, errorMessage) {
232
+ const response = {
233
+ type: 'tool_response',
234
+ request_id: requestId,
235
+ content: [
236
+ {
237
+ type: 'text',
238
+ text: JSON.stringify(result, null, 2),
239
+ },
240
+ ],
241
+ isError,
242
+ ...(errorMessage && { error: errorMessage }),
243
+ };
244
+ this.send(response);
245
+ }
246
+ /**
247
+ * Send a notification to the agent
248
+ */
249
+ sendNotification(method, params) {
250
+ if (!this.isConnected)
251
+ return;
252
+ const notification = {
253
+ type: 'notification',
254
+ method,
255
+ params,
256
+ };
257
+ this.send(notification);
258
+ this.emit('notification:sent', { method, params });
259
+ }
260
+ /**
261
+ * Send a message through the WebSocket
262
+ */
263
+ send(message) {
264
+ if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
265
+ this.ws.send(JSON.stringify(message));
266
+ }
267
+ }
268
+ /**
269
+ * Schedule a reconnection attempt
270
+ */
271
+ scheduleReconnect() {
272
+ if (this.reconnectTimer)
273
+ return;
274
+ this.emit('reconnecting', { interval: this.config.reconnectInterval });
275
+ this.reconnectTimer = setTimeout(async () => {
276
+ this.reconnectTimer = null;
277
+ try {
278
+ await this.connect();
279
+ }
280
+ catch (error) {
281
+ // Will retry via close handler
282
+ }
283
+ }, this.config.reconnectInterval);
284
+ }
285
+ /**
286
+ * Start heartbeat timer
287
+ */
288
+ startHeartbeat() {
289
+ this.heartbeatTimer = setInterval(() => {
290
+ if (this.isConnected) {
291
+ this.send({ type: 'heartbeat' });
292
+ }
293
+ }, this.config.heartbeatInterval);
294
+ }
295
+ /**
296
+ * Stop heartbeat timer
297
+ */
298
+ stopHeartbeat() {
299
+ if (this.heartbeatTimer) {
300
+ clearInterval(this.heartbeatTimer);
301
+ this.heartbeatTimer = null;
302
+ }
303
+ }
304
+ /**
305
+ * Get connection status
306
+ */
307
+ get connected() {
308
+ return this.isConnected;
309
+ }
310
+ /**
311
+ * Get PTY manager for direct access
312
+ */
313
+ get pty() {
314
+ return this.ptyManager;
315
+ }
316
+ }
317
+ exports.WebSocketClient = WebSocketClient;
318
+ //# sourceMappingURL=websocket-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-client.js","sourceRoot":"","sources":["../src/websocket-client.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "seahorse-bash-client",
3
+ "version": "1.0.0",
4
+ "description": "Reverse MCP client for providing bash/PTY tools to Seahorse Agent",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "seahorse-bash": "./bin/cli.js"
9
+ },
10
+ "scripts": {
11
+ "postinstall": "cd node_modules/node-pty && npm run install",
12
+ "build": "tsc",
13
+ "watch": "tsc -w",
14
+ "start": "node dist/cli.js",
15
+ "dev": "ts-node src/cli.ts",
16
+ "prepublishOnly": "npm run build",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest"
19
+ },
20
+ "keywords": [
21
+ "seahorse",
22
+ "mcp",
23
+ "bash",
24
+ "pty",
25
+ "shell",
26
+ "reverse-connection",
27
+ "agent"
28
+ ],
29
+ "author": "Seahorse Team",
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "chalk": "^4.1.2",
33
+ "commander": "^11.0.0",
34
+ "node-pty": "^1.0.0",
35
+ "ora": "^5.4.1",
36
+ "uuid": "^9.0.0",
37
+ "ws": "^8.14.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^20.19.30",
41
+ "@types/uuid": "^9.0.0",
42
+ "@types/ws": "^8.5.0",
43
+ "ts-node": "^10.9.0",
44
+ "typescript": "^5.0.0",
45
+ "vitest": "^4.0.18"
46
+ },
47
+ "engines": {
48
+ "node": ">=18.0.0"
49
+ },
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/dn-inc/seahorse-mcp-agent-server.git"
53
+ },
54
+ "homepage": "https://github.com/dn-inc/seahorse-mcp-agent-server#readme"
55
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Seahorse Bash Client CLI
4
+ *
5
+ * Connects to Seahorse Agent and provides PTY-based bash tools.
6
+ */
7
+
8
+ import { Command } from 'commander';
9
+ import chalk from 'chalk';
10
+ import ora from 'ora';
11
+ import os from 'os';
12
+ import { WebSocketClient } from './websocket-client';
13
+ import { ClientConfig } from './types';
14
+
15
+ const VERSION = '1.0.0';
16
+
17
+ const program = new Command();
18
+
19
+ program
20
+ .name('seahorse-bash')
21
+ .description('Seahorse Bash Client - PTY-based bash tools for Seahorse Agent')
22
+ .version(VERSION);
23
+
24
+ program
25
+ .command('connect')
26
+ .description('Connect to Seahorse Agent and provide bash tools')
27
+ .requiredOption('-u, --url <url>', 'Seahorse Agent WebSocket URL (e.g., wss://agent.example.com/ws/remote-mcp)')
28
+ .option('-n, --name <name>', 'Server name for identification', `bash-${os.hostname()}`)
29
+ .option('-s, --shell <shell>', 'Default shell to use', process.env.SHELL || '/bin/bash')
30
+ .option('--reconnect-interval <ms>', 'Reconnect interval in milliseconds', '5000')
31
+ .option('--heartbeat-interval <ms>', 'Heartbeat interval in milliseconds', '30000')
32
+ .option('--max-output-lines <lines>', 'Maximum output lines to buffer per session', '50000')
33
+ .option('--allowed-commands <cmds>', 'Comma-separated list of allowed command prefixes')
34
+ .option('--blocked-commands <cmds>', 'Comma-separated list of blocked command patterns')
35
+ .option('--allowed-cwd <paths>', 'Comma-separated list of allowed working directory paths')
36
+ .option('-k, --insecure', 'Skip SSL certificate verification (for self-signed certs)')
37
+ .action(async (options) => {
38
+ const spinner = ora('Connecting to Seahorse Agent...').start();
39
+
40
+ const config: ClientConfig = {
41
+ serverUrl: options.url,
42
+ serverName: options.name,
43
+ defaultShell: options.shell,
44
+ reconnectInterval: parseInt(options.reconnectInterval, 10),
45
+ heartbeatInterval: parseInt(options.heartbeatInterval, 10),
46
+ maxOutputLines: parseInt(options.maxOutputLines, 10),
47
+ allowedCommands: options.allowedCommands?.split(',').map((s: string) => s.trim()),
48
+ blockedCommands: options.blockedCommands?.split(',').map((s: string) => s.trim()),
49
+ allowedCwdPaths: options.allowedCwd?.split(',').map((s: string) => s.trim()),
50
+ insecure: options.insecure || false,
51
+ };
52
+
53
+ const client = new WebSocketClient(config);
54
+
55
+ // Event handlers
56
+ client.on('connected', () => {
57
+ spinner.succeed(chalk.green('Connected to Seahorse Agent'));
58
+ console.log(chalk.gray(` Server name: ${config.serverName}`));
59
+ console.log(chalk.gray(` Shell: ${config.defaultShell}`));
60
+ console.log(chalk.gray(` Platform: ${process.platform}`));
61
+ console.log();
62
+ console.log(chalk.cyan('Listening for tool calls...'));
63
+ console.log(chalk.gray('Press Ctrl+C to disconnect'));
64
+ console.log();
65
+ });
66
+
67
+ client.on('registered', ({ tools }) => {
68
+ console.log(chalk.green(`Registered ${tools.length} tools:`));
69
+ tools.forEach((tool: { name: string }) => {
70
+ console.log(chalk.gray(` - ${tool.name}`));
71
+ });
72
+ console.log();
73
+ });
74
+
75
+ client.on('disconnected', ({ code, reason }) => {
76
+ console.log(chalk.yellow(`Disconnected (code: ${code}, reason: ${reason})`));
77
+ });
78
+
79
+ client.on('reconnecting', ({ interval }) => {
80
+ console.log(chalk.yellow(`Reconnecting in ${interval}ms...`));
81
+ });
82
+
83
+ client.on('error', (error) => {
84
+ console.error(chalk.red(`Error: ${error.message}`));
85
+ });
86
+
87
+ client.on('tool:call', ({ name, requestId }) => {
88
+ console.log(chalk.blue(`[${new Date().toISOString()}] Tool call: ${name} (${requestId})`));
89
+ });
90
+
91
+ client.on('tool:success', ({ name, requestId }) => {
92
+ console.log(chalk.green(`[${new Date().toISOString()}] Tool success: ${name} (${requestId})`));
93
+ });
94
+
95
+ client.on('tool:error', ({ name, requestId, error }) => {
96
+ console.log(chalk.red(`[${new Date().toISOString()}] Tool error: ${name} (${requestId}): ${error}`));
97
+ });
98
+
99
+ client.on('notification:sent', ({ method }) => {
100
+ console.log(chalk.magenta(`[${new Date().toISOString()}] Notification: ${method}`));
101
+ });
102
+
103
+ // Graceful shutdown
104
+ const shutdown = async () => {
105
+ console.log();
106
+ console.log(chalk.yellow('Shutting down...'));
107
+ await client.disconnect();
108
+ console.log(chalk.green('Disconnected. Goodbye!'));
109
+ process.exit(0);
110
+ };
111
+
112
+ process.on('SIGINT', shutdown);
113
+ process.on('SIGTERM', shutdown);
114
+
115
+ // Connect
116
+ try {
117
+ await client.connect();
118
+ } catch (error) {
119
+ spinner.fail(chalk.red(`Failed to connect: ${error instanceof Error ? error.message : error}`));
120
+ process.exit(1);
121
+ }
122
+ });
123
+
124
+ program
125
+ .command('test')
126
+ .description('Test PTY functionality locally (without connecting to agent)')
127
+ .option('-c, --command <cmd>', 'Command to execute', 'echo "Hello from PTY!"')
128
+ .option('-s, --shell <shell>', 'Shell to use', process.env.SHELL || '/bin/bash')
129
+ .action(async (options) => {
130
+ const { PTYManager } = await import('./pty-manager');
131
+ const ptyManager = new PTYManager({ defaultShell: options.shell });
132
+
133
+ console.log(chalk.cyan('Testing PTY Manager...'));
134
+ console.log();
135
+
136
+ // Test bash_exec
137
+ console.log(chalk.yellow('1. Testing bash_exec:'));
138
+ const execResult = await ptyManager.exec({ command: options.command });
139
+ console.log(chalk.gray(` Exit code: ${execResult.exitCode}`));
140
+ console.log(chalk.gray(` Duration: ${execResult.duration}ms`));
141
+ console.log(chalk.gray(` Output: ${execResult.stdout}`));
142
+ console.log();
143
+
144
+ // Test bash_spawn
145
+ console.log(chalk.yellow('2. Testing bash_spawn:'));
146
+ const spawnResult = await ptyManager.spawn({
147
+ command: 'for i in 1 2 3; do echo "Count: $i"; sleep 0.5; done',
148
+ name: 'test-session',
149
+ });
150
+ console.log(chalk.gray(` Session ID: ${spawnResult.sessionId}`));
151
+ console.log(chalk.gray(` PID: ${spawnResult.pid}`));
152
+
153
+ // Wait for output
154
+ await new Promise((resolve) => setTimeout(resolve, 2000));
155
+
156
+ // Test bash_read
157
+ console.log();
158
+ console.log(chalk.yellow('3. Testing bash_read:'));
159
+ const readResult = ptyManager.read({ sessionId: spawnResult.sessionId, tail: true, limit: 10 });
160
+ console.log(chalk.gray(` Total lines: ${readResult.totalLines}`));
161
+ readResult.lines.forEach((line) => {
162
+ console.log(chalk.gray(` [${line.line}] ${line.text}`));
163
+ });
164
+
165
+ // Test bash_list
166
+ console.log();
167
+ console.log(chalk.yellow('4. Testing bash_list:'));
168
+ const listResult = ptyManager.list({});
169
+ console.log(chalk.gray(` Total sessions: ${listResult.summary.total}`));
170
+ console.log(chalk.gray(` Running: ${listResult.summary.running}`));
171
+ console.log(chalk.gray(` Exited: ${listResult.summary.exited}`));
172
+
173
+ // Cleanup
174
+ await ptyManager.shutdown();
175
+ console.log();
176
+ console.log(chalk.green('All tests passed!'));
177
+ });
178
+
179
+ program.parse();
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Seahorse Bash Client
3
+ *
4
+ * Reverse MCP client that provides PTY-based bash tools to Seahorse Agent.
5
+ */
6
+
7
+ export { WebSocketClient } from './websocket-client';
8
+ export { PTYManager, ptyManager } from './pty-manager';
9
+ export { TOOL_DEFINITIONS } from './tools';
10
+ export * from './types';