@openagents-org/agent-launcher 0.2.64 → 0.2.65
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/package.json +1 -1
- package/src/cli.js +18 -0
- package/src/mcp-server.js +463 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -478,6 +478,7 @@ Commands:
|
|
|
478
478
|
workspace create [name] Create a new workspace
|
|
479
479
|
workspace join <token> Join workspace with token
|
|
480
480
|
workspace list List configured workspaces
|
|
481
|
+
mcp-server Start MCP server (stdio) for workspace tools
|
|
481
482
|
version Show version
|
|
482
483
|
help Show this help
|
|
483
484
|
|
|
@@ -535,6 +536,23 @@ async function main() {
|
|
|
535
536
|
workspace: () => cmdWorkspace(connector, flags, positional),
|
|
536
537
|
env: () => cmdEnv(connector, flags, positional),
|
|
537
538
|
'test-llm': () => cmdTestLLM(connector, flags, positional),
|
|
539
|
+
'mcp-server': () => {
|
|
540
|
+
const { runMcpServer } = require('./mcp-server');
|
|
541
|
+
const workspaceId = flags['workspace-id'] || process.env.OPENAGENTS_WORKSPACE_ID;
|
|
542
|
+
const channelName = flags['channel-name'] || process.env.OPENAGENTS_CHANNEL_NAME || 'general';
|
|
543
|
+
const agentName = flags['agent-name'] || process.env.OPENAGENTS_AGENT_NAME || 'agent';
|
|
544
|
+
const endpoint = flags.endpoint || process.env.OPENAGENTS_ENDPOINT || 'https://workspace-endpoint.openagents.org';
|
|
545
|
+
const token = process.env.OA_WORKSPACE_TOKEN || '';
|
|
546
|
+
if (!workspaceId || !token) {
|
|
547
|
+
print('Error: --workspace-id required and OA_WORKSPACE_TOKEN env var must be set');
|
|
548
|
+
process.exitCode = 1;
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const disabledModules = new Set();
|
|
552
|
+
if (flags['disable-files']) disabledModules.add('files');
|
|
553
|
+
if (flags['disable-browser']) disabledModules.add('browser');
|
|
554
|
+
runMcpServer({ workspaceId, channelName, agentName, endpoint, token, disabledModules });
|
|
555
|
+
},
|
|
538
556
|
};
|
|
539
557
|
|
|
540
558
|
const handler = commands[cmd];
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP (Model Context Protocol) server for OpenAgents workspace tools.
|
|
5
|
+
*
|
|
6
|
+
* Implements JSON-RPC 2.0 over stdio, exposing workspace operations
|
|
7
|
+
* (history, files, browser, agents) as MCP tools that Claude Code can use.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* openagents mcp-server --workspace-id <id> --channel-name <ch> --agent-name <name>
|
|
11
|
+
*
|
|
12
|
+
* The workspace token is read from the OA_WORKSPACE_TOKEN env var.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const readline = require('readline');
|
|
16
|
+
const { WorkspaceClient } = require('./workspace-client');
|
|
17
|
+
|
|
18
|
+
// ── Tool definitions ────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
function buildToolDefs(disabledModules) {
|
|
21
|
+
const tools = [
|
|
22
|
+
// -- Workspace core (always enabled) --
|
|
23
|
+
{
|
|
24
|
+
name: 'workspace_get_history',
|
|
25
|
+
description: 'Read recent messages in the current workspace channel.',
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
limit: { type: 'integer', description: 'Number of messages to return (default 20)', default: 20 },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'workspace_get_agents',
|
|
35
|
+
description: 'List all agents connected to the workspace with their status.',
|
|
36
|
+
inputSchema: { type: 'object', properties: {} },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'workspace_status',
|
|
40
|
+
description: 'Post a short status update visible to workspace viewers (e.g. "analyzing code...").',
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
status: { type: 'string', description: 'Short status description' },
|
|
45
|
+
},
|
|
46
|
+
required: ['status'],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// -- Files module --
|
|
52
|
+
if (!disabledModules.has('files')) {
|
|
53
|
+
tools.push(
|
|
54
|
+
{
|
|
55
|
+
name: 'workspace_list_files',
|
|
56
|
+
description: 'List files shared in the workspace.',
|
|
57
|
+
inputSchema: { type: 'object', properties: {} },
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'workspace_read_file',
|
|
61
|
+
description: 'Read a shared file by its ID. Returns text content or base64 for binary.',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
file_id: { type: 'string', description: 'File ID to read' },
|
|
66
|
+
},
|
|
67
|
+
required: ['file_id'],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'workspace_write_file',
|
|
72
|
+
description: 'Write/upload a file to shared workspace storage.',
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: {
|
|
76
|
+
filename: { type: 'string', description: 'Filename (e.g. "report.md")' },
|
|
77
|
+
content: { type: 'string', description: 'File content (text or base64 for binary)' },
|
|
78
|
+
content_type: { type: 'string', description: 'MIME type (auto-detected from filename if omitted)' },
|
|
79
|
+
},
|
|
80
|
+
required: ['filename', 'content'],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'workspace_delete_file',
|
|
85
|
+
description: 'Delete a shared file by its ID.',
|
|
86
|
+
inputSchema: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: {
|
|
89
|
+
file_id: { type: 'string', description: 'File ID to delete' },
|
|
90
|
+
},
|
|
91
|
+
required: ['file_id'],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// -- Browser module --
|
|
98
|
+
if (!disabledModules.has('browser')) {
|
|
99
|
+
tools.push(
|
|
100
|
+
{
|
|
101
|
+
name: 'workspace_browser_open',
|
|
102
|
+
description: 'Open a new shared browser tab.',
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {
|
|
106
|
+
url: { type: 'string', description: 'URL to open (default: about:blank)' },
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'workspace_browser_navigate',
|
|
112
|
+
description: 'Navigate a browser tab to a URL.',
|
|
113
|
+
inputSchema: {
|
|
114
|
+
type: 'object',
|
|
115
|
+
properties: {
|
|
116
|
+
tab_id: { type: 'string', description: 'Tab ID' },
|
|
117
|
+
url: { type: 'string', description: 'URL to navigate to' },
|
|
118
|
+
},
|
|
119
|
+
required: ['tab_id', 'url'],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'workspace_browser_click',
|
|
124
|
+
description: 'Click an element in a browser tab by CSS selector.',
|
|
125
|
+
inputSchema: {
|
|
126
|
+
type: 'object',
|
|
127
|
+
properties: {
|
|
128
|
+
tab_id: { type: 'string', description: 'Tab ID' },
|
|
129
|
+
selector: { type: 'string', description: 'CSS selector of element to click' },
|
|
130
|
+
},
|
|
131
|
+
required: ['tab_id', 'selector'],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'workspace_browser_type',
|
|
136
|
+
description: 'Type text into an element in a browser tab.',
|
|
137
|
+
inputSchema: {
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties: {
|
|
140
|
+
tab_id: { type: 'string', description: 'Tab ID' },
|
|
141
|
+
selector: { type: 'string', description: 'CSS selector of input element' },
|
|
142
|
+
text: { type: 'string', description: 'Text to type' },
|
|
143
|
+
},
|
|
144
|
+
required: ['tab_id', 'selector', 'text'],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'workspace_browser_screenshot',
|
|
149
|
+
description: 'Take a screenshot of a browser tab. Returns a base64 PNG image.',
|
|
150
|
+
inputSchema: {
|
|
151
|
+
type: 'object',
|
|
152
|
+
properties: {
|
|
153
|
+
tab_id: { type: 'string', description: 'Tab ID' },
|
|
154
|
+
},
|
|
155
|
+
required: ['tab_id'],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'workspace_browser_snapshot',
|
|
160
|
+
description: 'Get the accessibility tree (DOM structure) of a browser tab as text.',
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: 'object',
|
|
163
|
+
properties: {
|
|
164
|
+
tab_id: { type: 'string', description: 'Tab ID' },
|
|
165
|
+
},
|
|
166
|
+
required: ['tab_id'],
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'workspace_browser_list_tabs',
|
|
171
|
+
description: 'List all open shared browser tabs.',
|
|
172
|
+
inputSchema: { type: 'object', properties: {} },
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'workspace_browser_close',
|
|
176
|
+
description: 'Close a shared browser tab.',
|
|
177
|
+
inputSchema: {
|
|
178
|
+
type: 'object',
|
|
179
|
+
properties: {
|
|
180
|
+
tab_id: { type: 'string', description: 'Tab ID to close' },
|
|
181
|
+
},
|
|
182
|
+
required: ['tab_id'],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return tools;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── MIME type detection ─────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
const MIME_MAP = {
|
|
194
|
+
'.txt': 'text/plain', '.md': 'text/markdown', '.json': 'application/json',
|
|
195
|
+
'.js': 'text/javascript', '.ts': 'text/typescript', '.py': 'text/x-python',
|
|
196
|
+
'.html': 'text/html', '.css': 'text/css', '.xml': 'application/xml',
|
|
197
|
+
'.yaml': 'application/yaml', '.yml': 'application/yaml',
|
|
198
|
+
'.csv': 'text/csv', '.log': 'text/plain', '.sh': 'text/x-shellscript',
|
|
199
|
+
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
|
|
200
|
+
'.gif': 'image/gif', '.svg': 'image/svg+xml', '.pdf': 'application/pdf',
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
function detectMime(filename) {
|
|
204
|
+
const ext = (filename.match(/\.[^.]+$/) || [''])[0].toLowerCase();
|
|
205
|
+
return MIME_MAP[ext] || 'application/octet-stream';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function isTextMime(mime) {
|
|
209
|
+
return mime.startsWith('text/') || ['application/json', 'application/xml',
|
|
210
|
+
'application/javascript', 'application/yaml'].includes(mime);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── MCP Server ──────────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
class McpServer {
|
|
216
|
+
constructor({ wsClient, workspaceId, channelName, agentName, token, disabledModules }) {
|
|
217
|
+
this.ws = wsClient;
|
|
218
|
+
this.workspaceId = workspaceId;
|
|
219
|
+
this.channelName = channelName;
|
|
220
|
+
this.agentName = agentName;
|
|
221
|
+
this.token = token;
|
|
222
|
+
this.disabledModules = disabledModules || new Set();
|
|
223
|
+
this.tools = buildToolDefs(this.disabledModules);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
start() {
|
|
227
|
+
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
228
|
+
rl.on('line', (line) => {
|
|
229
|
+
line = line.trim();
|
|
230
|
+
if (!line) return;
|
|
231
|
+
let msg;
|
|
232
|
+
try {
|
|
233
|
+
msg = JSON.parse(line);
|
|
234
|
+
} catch {
|
|
235
|
+
this._write(jsonRpcError(null, -32700, 'Parse error'));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
// Notifications (no id) — acknowledge but don't respond
|
|
239
|
+
if (msg.id === undefined || msg.id === null) return;
|
|
240
|
+
this._handleRequest(msg).catch((e) => {
|
|
241
|
+
this._write(jsonRpcError(msg.id, -32603, e.message));
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
rl.on('close', () => process.exit(0));
|
|
245
|
+
this._log('MCP server started');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async _handleRequest(msg) {
|
|
249
|
+
const { id, method, params } = msg;
|
|
250
|
+
switch (method) {
|
|
251
|
+
case 'initialize':
|
|
252
|
+
this._write(jsonRpcResponse(id, {
|
|
253
|
+
protocolVersion: '2024-11-05',
|
|
254
|
+
capabilities: { tools: {} },
|
|
255
|
+
serverInfo: { name: 'openagents-workspace', version: '1.0.0' },
|
|
256
|
+
}));
|
|
257
|
+
break;
|
|
258
|
+
case 'tools/list':
|
|
259
|
+
this._write(jsonRpcResponse(id, { tools: this.tools }));
|
|
260
|
+
break;
|
|
261
|
+
case 'tools/call':
|
|
262
|
+
await this._handleToolCall(id, params || {});
|
|
263
|
+
break;
|
|
264
|
+
default:
|
|
265
|
+
this._write(jsonRpcError(id, -32601, `Unknown method: ${method}`));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async _handleToolCall(id, { name, arguments: args = {} }) {
|
|
270
|
+
try {
|
|
271
|
+
const result = await this._dispatch(name, args);
|
|
272
|
+
this._write(jsonRpcResponse(id, result));
|
|
273
|
+
} catch (e) {
|
|
274
|
+
this._write(jsonRpcResponse(id, {
|
|
275
|
+
content: [{ type: 'text', text: `Error: ${e.message}` }],
|
|
276
|
+
isError: true,
|
|
277
|
+
}));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async _dispatch(name, args) {
|
|
282
|
+
const text = (t) => ({ content: [{ type: 'text', text: t }] });
|
|
283
|
+
const image = (data, mime) => ({ content: [{ type: 'image', data, mimeType: mime }] });
|
|
284
|
+
|
|
285
|
+
switch (name) {
|
|
286
|
+
|
|
287
|
+
// ── Workspace core ──
|
|
288
|
+
|
|
289
|
+
case 'workspace_get_history': {
|
|
290
|
+
const limit = args.limit || 20;
|
|
291
|
+
const data = await this.ws.pollMessages(this.workspaceId, this.channelName, this.token, { limit });
|
|
292
|
+
const events = data.events || data || [];
|
|
293
|
+
if (!events.length) return text('No messages yet.');
|
|
294
|
+
const lines = events.map((e) => {
|
|
295
|
+
const sender = (e.source || '').replace(/^(human|openagents):/, '');
|
|
296
|
+
const content = e.payload?.content || '';
|
|
297
|
+
const type = e.payload?.message_type || 'chat';
|
|
298
|
+
if (type === 'status') return null; // skip status updates
|
|
299
|
+
return `[${sender}] ${content}`;
|
|
300
|
+
}).filter(Boolean);
|
|
301
|
+
return text(lines.join('\n'));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
case 'workspace_get_agents': {
|
|
305
|
+
const data = await this.ws.getAgents(this.workspaceId, this.token);
|
|
306
|
+
const agents = data.agents || data || [];
|
|
307
|
+
if (!agents.length) return text('No agents connected.');
|
|
308
|
+
const lines = agents.map((a) =>
|
|
309
|
+
`- ${a.name} (${a.type || 'unknown'}) — ${a.status || 'unknown'}${a.role ? ` [${a.role}]` : ''}`
|
|
310
|
+
);
|
|
311
|
+
return text(lines.join('\n'));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
case 'workspace_status': {
|
|
315
|
+
await this.ws.sendMessage(this.workspaceId, this.channelName, this.token, args.status, {
|
|
316
|
+
senderType: 'agent',
|
|
317
|
+
senderName: this.agentName,
|
|
318
|
+
messageType: 'status',
|
|
319
|
+
});
|
|
320
|
+
return text(`Status updated: ${args.status}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ── Files ──
|
|
324
|
+
|
|
325
|
+
case 'workspace_list_files': {
|
|
326
|
+
const data = await this.ws.listFiles(this.workspaceId, this.token, { limit: 50, offset: 0 });
|
|
327
|
+
const files = data.files || data || [];
|
|
328
|
+
if (!files.length) return text('No files shared yet.');
|
|
329
|
+
const lines = files.map((f) => {
|
|
330
|
+
const size = f.size ? `${(f.size / 1024).toFixed(1)}KB` : '?';
|
|
331
|
+
return `- ${f.filename || f.name} (id: ${f.id}, ${size}, by ${f.uploaded_by || f.source || 'unknown'})`;
|
|
332
|
+
});
|
|
333
|
+
return text(lines.join('\n'));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
case 'workspace_read_file': {
|
|
337
|
+
const info = await this.ws.getFileInfo(this.token, args.file_id);
|
|
338
|
+
const buf = await this.ws.readFile(this.workspaceId, this.token, args.file_id);
|
|
339
|
+
const mime = info.content_type || 'application/octet-stream';
|
|
340
|
+
if (mime.startsWith('image/')) {
|
|
341
|
+
const b64 = Buffer.isBuffer(buf) ? buf.toString('base64') : buf;
|
|
342
|
+
return image(b64, mime);
|
|
343
|
+
}
|
|
344
|
+
// Try to decode as text
|
|
345
|
+
const str = Buffer.isBuffer(buf) ? buf.toString('utf-8') : String(buf);
|
|
346
|
+
return text(str);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
case 'workspace_write_file': {
|
|
350
|
+
const mime = args.content_type || detectMime(args.filename);
|
|
351
|
+
let b64;
|
|
352
|
+
if (isTextMime(mime)) {
|
|
353
|
+
b64 = Buffer.from(args.content, 'utf-8').toString('base64');
|
|
354
|
+
} else {
|
|
355
|
+
b64 = args.content; // assume already base64
|
|
356
|
+
}
|
|
357
|
+
const result = await this.ws.uploadFile(this.workspaceId, this.token, args.filename, b64, {
|
|
358
|
+
contentType: mime,
|
|
359
|
+
source: `openagents:${this.agentName}`,
|
|
360
|
+
channelName: this.channelName,
|
|
361
|
+
});
|
|
362
|
+
const fileId = result.id || result.file_id || 'unknown';
|
|
363
|
+
return text(`File written: ${args.filename} (id: ${fileId})`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
case 'workspace_delete_file': {
|
|
367
|
+
await this.ws.deleteFile(this.workspaceId, this.token, args.file_id);
|
|
368
|
+
return text(`File deleted: ${args.file_id}`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ── Browser ──
|
|
372
|
+
|
|
373
|
+
case 'workspace_browser_open': {
|
|
374
|
+
const result = await this.ws.browserOpenTab(this.workspaceId, this.token, {
|
|
375
|
+
url: args.url || 'about:blank',
|
|
376
|
+
source: `openagents:${this.agentName}`,
|
|
377
|
+
});
|
|
378
|
+
const tabId = result.tab_id || result.id || 'unknown';
|
|
379
|
+
return text(`Browser tab opened: ${tabId} → ${args.url || 'about:blank'}`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
case 'workspace_browser_navigate': {
|
|
383
|
+
const result = await this.ws.browserNavigate(this.workspaceId, this.token, args.tab_id, args.url);
|
|
384
|
+
const title = result.title || '';
|
|
385
|
+
return text(`Navigated to: ${args.url}${title ? ` (title: ${title})` : ''}`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
case 'workspace_browser_click': {
|
|
389
|
+
const result = await this.ws.browserClick(this.workspaceId, this.token, args.tab_id, args.selector);
|
|
390
|
+
return text(`Clicked: ${args.selector}${result.url ? ` (url now: ${result.url})` : ''}`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
case 'workspace_browser_type': {
|
|
394
|
+
await this.ws.browserType(this.workspaceId, this.token, args.tab_id, args.selector, args.text);
|
|
395
|
+
return text(`Typed into ${args.selector}: ${args.text.slice(0, 50)}${args.text.length > 50 ? '...' : ''}`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
case 'workspace_browser_screenshot': {
|
|
399
|
+
const buf = await this.ws.browserScreenshot(this.workspaceId, this.token, args.tab_id);
|
|
400
|
+
const b64 = Buffer.isBuffer(buf) ? buf.toString('base64') : buf;
|
|
401
|
+
return image(b64, 'image/png');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
case 'workspace_browser_snapshot': {
|
|
405
|
+
const snapshot = await this.ws.browserSnapshot(this.workspaceId, this.token, args.tab_id);
|
|
406
|
+
return text(typeof snapshot === 'string' ? snapshot : JSON.stringify(snapshot));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
case 'workspace_browser_list_tabs': {
|
|
410
|
+
const data = await this.ws.browserListTabs(this.workspaceId, this.token);
|
|
411
|
+
const tabs = data.tabs || data || [];
|
|
412
|
+
if (!tabs.length) return text('No browser tabs open.');
|
|
413
|
+
const lines = tabs.map((t) =>
|
|
414
|
+
`- ${t.id || t.tab_id}: ${t.label || t.title || 'untitled'}\n URL: ${t.url || 'N/A'} | by ${t.created_by || 'unknown'}`
|
|
415
|
+
);
|
|
416
|
+
return text(lines.join('\n'));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
case 'workspace_browser_close': {
|
|
420
|
+
await this.ws.browserCloseTab(this.workspaceId, this.token, args.tab_id);
|
|
421
|
+
return text(`Browser tab closed: ${args.tab_id}`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
default:
|
|
425
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
_write(json) {
|
|
430
|
+
process.stdout.write(json + '\n');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
_log(msg) {
|
|
434
|
+
process.stderr.write(`[mcp] ${msg}\n`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ── JSON-RPC helpers ────────────────────────────────────────────────────────
|
|
439
|
+
|
|
440
|
+
function jsonRpcResponse(id, result) {
|
|
441
|
+
return JSON.stringify({ jsonrpc: '2.0', id, result });
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function jsonRpcError(id, code, message) {
|
|
445
|
+
return JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } });
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ── Entry point (called from cli.js) ────────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
function runMcpServer(opts) {
|
|
451
|
+
const wsClient = new WorkspaceClient(opts.endpoint);
|
|
452
|
+
const server = new McpServer({
|
|
453
|
+
wsClient,
|
|
454
|
+
workspaceId: opts.workspaceId,
|
|
455
|
+
channelName: opts.channelName,
|
|
456
|
+
agentName: opts.agentName,
|
|
457
|
+
token: opts.token,
|
|
458
|
+
disabledModules: opts.disabledModules,
|
|
459
|
+
});
|
|
460
|
+
server.start();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
module.exports = { McpServer, runMcpServer, buildToolDefs };
|