@dolusoft/claude-collab 1.3.1 → 1.4.2

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/mcp-main.js CHANGED
@@ -1,169 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { WebSocketServer, WebSocket } from 'ws';
3
- import { createServer } from 'net';
4
3
  import { v4 } from 'uuid';
5
- import multicastDns from 'multicast-dns';
6
- import { tmpdir, networkInterfaces } from 'os';
7
4
  import { EventEmitter } from 'events';
8
5
  import { execFile } from 'child_process';
9
6
  import { unlinkSync } from 'fs';
7
+ import { tmpdir } from 'os';
10
8
  import { join } from 'path';
11
9
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
10
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
13
11
  import { z } from 'zod';
14
12
 
15
- function getLocalIp() {
16
- const nets = networkInterfaces();
17
- for (const name of Object.keys(nets)) {
18
- for (const net of nets[name] ?? []) {
19
- if (net.family === "IPv4" && !net.internal) {
20
- return net.address;
21
- }
22
- }
23
- }
24
- return "127.0.0.1";
25
- }
26
- var SERVICE_TYPE = "_claude-collab._tcp.local";
27
- var MdnsDiscovery = class {
28
- mdns;
29
- announced = false;
30
- port = 0;
31
- teamName = "";
32
- memberId = "";
33
- peersByTeam = /* @__PURE__ */ new Map();
34
- peersByMemberId = /* @__PURE__ */ new Map();
35
- onPeerFoundCb;
36
- onPeerLostCb;
37
- constructor() {
38
- this.mdns = multicastDns();
39
- this.setupHandlers();
40
- }
41
- get serviceName() {
42
- return `${this.memberId}.${SERVICE_TYPE}`;
43
- }
44
- buildAnswers() {
45
- return [
46
- {
47
- name: SERVICE_TYPE,
48
- type: "PTR",
49
- ttl: 300,
50
- data: this.serviceName
51
- },
52
- {
53
- name: this.serviceName,
54
- type: "SRV",
55
- ttl: 300,
56
- data: {
57
- priority: 0,
58
- weight: 0,
59
- port: this.port,
60
- target: getLocalIp()
61
- }
62
- },
63
- {
64
- name: this.serviceName,
65
- type: "TXT",
66
- ttl: 300,
67
- data: [
68
- Buffer.from(`team=${this.teamName}`),
69
- Buffer.from(`memberId=${this.memberId}`),
70
- Buffer.from("ver=1")
71
- ]
72
- }
73
- ];
74
- }
75
- setupHandlers() {
76
- this.mdns.on("query", (query) => {
77
- if (!this.announced) return;
78
- const questions = query.questions ?? [];
79
- const ptrQuery = questions.find(
80
- (q) => q.type === "PTR" && q.name === SERVICE_TYPE
81
- );
82
- if (!ptrQuery) return;
83
- this.mdns.respond({ answers: this.buildAnswers() });
84
- });
85
- this.mdns.on("response", (response) => {
86
- this.parseResponse(response);
87
- });
88
- }
89
- parseResponse(response) {
90
- const allRecords = [
91
- ...response.answers ?? [],
92
- ...response.additionals ?? []
93
- ];
94
- const ptrRecords = allRecords.filter(
95
- (r) => r.type === "PTR" && r.name === SERVICE_TYPE
96
- );
97
- for (const ptr of ptrRecords) {
98
- const instanceName = ptr.data;
99
- const srv = allRecords.find(
100
- (r) => r.type === "SRV" && r.name === instanceName
101
- );
102
- const txt = allRecords.find(
103
- (r) => r.type === "TXT" && r.name === instanceName
104
- );
105
- if (!srv) continue;
106
- const port = srv.data.port;
107
- const host = srv.data.target || "127.0.0.1";
108
- let teamName = "";
109
- let memberId = "";
110
- if (txt) {
111
- const txtData = txt.data ?? [];
112
- for (const entry of txtData) {
113
- const str = Buffer.isBuffer(entry) ? entry.toString() : String(entry);
114
- if (str.startsWith("team=")) teamName = str.slice(5);
115
- if (str.startsWith("memberId=")) memberId = str.slice(9);
116
- }
117
- }
118
- if (!teamName || !memberId) continue;
119
- if (memberId === this.memberId) continue;
120
- const ptrTtl = ptr.ttl ?? 300;
121
- const srvTtl = srv.ttl ?? 300;
122
- if (ptrTtl === 0 || srvTtl === 0) {
123
- this.peersByTeam.delete(teamName);
124
- this.peersByMemberId.delete(memberId);
125
- this.onPeerLostCb?.(memberId);
126
- continue;
127
- }
128
- const peer = { host, port, teamName, memberId };
129
- this.peersByTeam.set(teamName, peer);
130
- this.peersByMemberId.set(memberId, peer);
131
- this.onPeerFoundCb?.(peer);
132
- }
133
- }
134
- /**
135
- * Announce this node's service via mDNS.
136
- * Sends an unsolicited response so existing peers notice immediately.
137
- */
138
- announce(port, teamName, memberId) {
139
- this.port = port;
140
- this.teamName = teamName;
141
- this.memberId = memberId;
142
- this.announced = true;
143
- this.mdns.respond({ answers: this.buildAnswers() });
144
- }
145
- /**
146
- * Send a PTR query to discover existing peers.
147
- */
148
- discover() {
149
- this.mdns.query({
150
- questions: [{ name: SERVICE_TYPE, type: "PTR" }]
151
- });
152
- }
153
- getPeerByTeam(teamName) {
154
- return this.peersByTeam.get(teamName);
155
- }
156
- onPeerFound(cb) {
157
- this.onPeerFoundCb = cb;
158
- }
159
- onPeerLost(cb) {
160
- this.onPeerLostCb = cb;
161
- }
162
- destroy() {
163
- this.mdns.destroy();
164
- }
165
- };
166
-
167
13
  // src/infrastructure/p2p/p2p-message-protocol.ts
168
14
  function serializeP2PMsg(msg) {
169
15
  return JSON.stringify(msg);
@@ -179,8 +25,6 @@ using System.Runtime.InteropServices;
179
25
  public class ConInject {
180
26
  [DllImport("kernel32.dll")] public static extern bool FreeConsole();
181
27
  [DllImport("kernel32.dll")] public static extern bool AttachConsole(uint pid);
182
- [DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow();
183
- [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hwnd);
184
28
  [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
185
29
  public static extern IntPtr CreateFile(
186
30
  string lpFileName, uint dwDesiredAccess, uint dwShareMode,
@@ -209,7 +53,7 @@ public class ConInject {
209
53
  return CreateFile("CONIN$", 0xC0000000, 3, IntPtr.Zero, 3, 0, IntPtr.Zero);
210
54
  }
211
55
 
212
- // Inject only text characters into console input buffer (no Ctrl keys, no Enter)
56
+ // Inject plain text characters into console input buffer
213
57
  public static int InjectText(uint pid, string text) {
214
58
  IntPtr hIn = OpenConin(pid);
215
59
  if (hIn == new IntPtr(-1)) return -1;
@@ -227,13 +71,56 @@ public class ConInject {
227
71
  return ok ? (int)written : -2;
228
72
  }
229
73
 
230
- // Focus the console window for WScript.Shell.SendKeys
231
- public static IntPtr FocusConsole(uint pid) {
232
- FreeConsole();
233
- if (!AttachConsole(pid)) return IntPtr.Zero;
234
- IntPtr hwnd = GetConsoleWindow();
235
- if (hwnd != IntPtr.Zero) SetForegroundWindow(hwnd);
236
- return hwnd;
74
+ // Inject Enter (VK_RETURN = 0x0D)
75
+ public static int InjectEnter(uint pid) {
76
+ IntPtr hIn = OpenConin(pid);
77
+ if (hIn == new IntPtr(-1)) return -1;
78
+
79
+ var records = new INPUT_RECORD[] {
80
+ new INPUT_RECORD { EventType=1, bKeyDown=1, wRepeatCount=1, wVirtualKeyCode=0x0D, UnicodeChar=0x0D },
81
+ new INPUT_RECORD { EventType=1, bKeyDown=0, wRepeatCount=1, wVirtualKeyCode=0x0D, UnicodeChar=0x0D }
82
+ };
83
+
84
+ uint written;
85
+ bool ok = WriteConsoleInput(hIn, records, (uint)records.Length, out written);
86
+ CloseHandle(hIn);
87
+ return ok ? (int)written : -2;
88
+ }
89
+
90
+ // Inject Ctrl+U (VK_U = 0x55, char = 0x15)
91
+ public static int InjectCtrlU(uint pid) {
92
+ IntPtr hIn = OpenConin(pid);
93
+ if (hIn == new IntPtr(-1)) return -1;
94
+
95
+ var records = new INPUT_RECORD[] {
96
+ new INPUT_RECORD { EventType=1, bKeyDown=1, wRepeatCount=1, wVirtualKeyCode=0xA2, dwControlKeyState=LEFT_CTRL },
97
+ new INPUT_RECORD { EventType=1, bKeyDown=1, wRepeatCount=1, wVirtualKeyCode=0x55, UnicodeChar=0x15, dwControlKeyState=LEFT_CTRL },
98
+ new INPUT_RECORD { EventType=1, bKeyDown=0, wRepeatCount=1, wVirtualKeyCode=0x55, UnicodeChar=0x15, dwControlKeyState=LEFT_CTRL },
99
+ new INPUT_RECORD { EventType=1, bKeyDown=0, wRepeatCount=1, wVirtualKeyCode=0xA2, dwControlKeyState=0 }
100
+ };
101
+
102
+ uint written;
103
+ bool ok = WriteConsoleInput(hIn, records, (uint)records.Length, out written);
104
+ CloseHandle(hIn);
105
+ return ok ? (int)written : -2;
106
+ }
107
+
108
+ // Inject Ctrl+Y (VK_Y = 0x59, char = 0x19)
109
+ public static int InjectCtrlY(uint pid) {
110
+ IntPtr hIn = OpenConin(pid);
111
+ if (hIn == new IntPtr(-1)) return -1;
112
+
113
+ var records = new INPUT_RECORD[] {
114
+ new INPUT_RECORD { EventType=1, bKeyDown=1, wRepeatCount=1, wVirtualKeyCode=0xA2, dwControlKeyState=LEFT_CTRL },
115
+ new INPUT_RECORD { EventType=1, bKeyDown=1, wRepeatCount=1, wVirtualKeyCode=0x59, UnicodeChar=0x19, dwControlKeyState=LEFT_CTRL },
116
+ new INPUT_RECORD { EventType=1, bKeyDown=0, wRepeatCount=1, wVirtualKeyCode=0x59, UnicodeChar=0x19, dwControlKeyState=LEFT_CTRL },
117
+ new INPUT_RECORD { EventType=1, bKeyDown=0, wRepeatCount=1, wVirtualKeyCode=0xA2, dwControlKeyState=0 }
118
+ };
119
+
120
+ uint written;
121
+ bool ok = WriteConsoleInput(hIn, records, (uint)records.Length, out written);
122
+ CloseHandle(hIn);
123
+ return ok ? (int)written : -2;
237
124
  }
238
125
  }
239
126
  `;
@@ -271,33 +158,24 @@ async function windowsInject(text) {
271
158
  const script = buildScript(claudePid, `
272
159
  $textBytes = [System.Convert]::FromBase64String('${textB64}')
273
160
  $text = [System.Text.Encoding]::Unicode.GetString($textBytes)
274
- $wsh = New-Object -ComObject WScript.Shell
275
161
 
276
- # 1. Focus console and send Ctrl+U to save user's current text to kill ring
277
- $hwnd = [ConInject]::FocusConsole([uint32]$claudePid)
278
- Start-Sleep -Milliseconds 150
279
- $wsh.SendKeys('^u')
280
- Start-Sleep -Milliseconds 150
162
+ # 1. Ctrl+U to save user's current text to kill ring
163
+ [ConInject]::InjectCtrlU([uint32]$claudePid) | Out-Null
164
+ Start-Sleep -Milliseconds 100
281
165
 
282
166
  # 2. Write question text into console input buffer
283
167
  [ConInject]::InjectText([uint32]$claudePid, $text) | Out-Null
168
+ Start-Sleep -Milliseconds 50
284
169
 
285
- # 3. Re-focus (InjectText calls FreeConsole internally, focus may be lost)
286
- [ConInject]::FocusConsole([uint32]$claudePid) | Out-Null
287
- Start-Sleep -Milliseconds 150
288
-
289
- # 4. Send Enter
290
- $wsh.SendKeys('~')
170
+ # 3. Send Enter
171
+ [ConInject]::InjectEnter([uint32]$claudePid) | Out-Null
291
172
  `);
292
173
  await run(script);
293
174
  }
294
175
  async function windowsInjectCtrlY() {
295
176
  const claudePid = process.ppid;
296
177
  const script = buildScript(claudePid, `
297
- $wsh = New-Object -ComObject WScript.Shell
298
- $hwnd = [ConInject]::FocusConsole([uint32]$claudePid)
299
- Start-Sleep -Milliseconds 150
300
- $wsh.SendKeys('^y')
178
+ [ConInject]::InjectCtrlY([uint32]$claudePid) | Out-Null
301
179
  `);
302
180
  await run(script);
303
181
  }
@@ -364,35 +242,18 @@ var config = {
364
242
  */
365
243
  p2p: {
366
244
  /**
367
- * Minimum port for the random WS server port range
245
+ * Fixed port for the WS server. Override with CLAUDE_COLLAB_PORT env var.
368
246
  */
369
- portRangeMin: 1e4,
370
- /**
371
- * Maximum port for the random WS server port range
372
- */
373
- portRangeMax: 19999
247
+ port: Number(process.env["CLAUDE_COLLAB_PORT"] ?? 11777)
374
248
  }};
375
249
 
376
250
  // src/infrastructure/p2p/p2p-node.ts
377
- function getRandomPort(min, max) {
378
- return new Promise((resolve) => {
379
- const port = Math.floor(Math.random() * (max - min + 1)) + min;
380
- const server = createServer();
381
- server.listen(port, () => {
382
- server.close(() => resolve(port));
383
- });
384
- server.on("error", () => {
385
- resolve(getRandomPort(min, max));
386
- });
387
- });
388
- }
389
251
  var P2PNode = class {
390
252
  wss = null;
391
- mdnsDiscovery = null;
392
253
  port = 0;
393
254
  // Connections indexed by remote team name
394
255
  peerConns = /* @__PURE__ */ new Map();
395
- // Reverse lookup: ws → teamName (for cleanup on incoming connections)
256
+ // Reverse lookup: ws → teamName (for cleanup)
396
257
  wsToTeam = /* @__PURE__ */ new Map();
397
258
  // Questions we received from remote peers (our inbox)
398
259
  incomingQuestions = /* @__PURE__ */ new Map();
@@ -411,14 +272,13 @@ var P2PNode = class {
411
272
  return this.localMember?.teamName;
412
273
  }
413
274
  /**
414
- * Starts the WS server on a random port and initialises mDNS.
275
+ * Starts the WS server on the configured fixed port.
415
276
  * Called automatically from join() if not yet started.
416
277
  */
417
278
  async start() {
418
- this.port = await getRandomPort(config.p2p.portRangeMin, config.p2p.portRangeMax);
279
+ this.port = config.p2p.port;
419
280
  this.wss = new WebSocketServer({ port: this.port });
420
281
  this.setupWssHandlers();
421
- this.mdnsDiscovery = new MdnsDiscovery();
422
282
  this._isStarted = true;
423
283
  console.error(`P2P node started on port ${this.port}`);
424
284
  }
@@ -428,17 +288,60 @@ var P2PNode = class {
428
288
  }
429
289
  const memberId = v4();
430
290
  this.localMember = { memberId, teamName, displayName };
431
- this.mdnsDiscovery.onPeerFound((peer) => {
432
- if (peer.teamName !== teamName) {
433
- console.error(`Discovered peer '${peer.teamName}' at ${peer.host}:${peer.port}`);
434
- this.connectToPeer(peer.teamName, peer.host, peer.port).catch((err) => {
435
- console.error(`Could not eagerly connect to ${peer.teamName}:`, err);
436
- });
291
+ return { memberId, teamId: teamName, teamName, displayName, status: "ONLINE" };
292
+ }
293
+ /**
294
+ * Connects to a peer at the given IP and port.
295
+ * Performs a bidirectional HELLO handshake and returns the peer's team name.
296
+ */
297
+ async connectPeer(ip, port) {
298
+ if (!this.localMember) {
299
+ throw new Error("Must call join() before connectPeer()");
300
+ }
301
+ const targetPort = port ?? config.p2p.port;
302
+ const ws = new WebSocket(`ws://${ip}:${targetPort}`);
303
+ ws.on("message", (data) => {
304
+ try {
305
+ const msg = parseP2PMsg(data.toString());
306
+ this.handleMessage(ws, msg);
307
+ } catch (err) {
308
+ console.error("Failed to parse P2P message:", err);
437
309
  }
438
310
  });
439
- this.mdnsDiscovery.announce(this.port, teamName, memberId);
440
- this.mdnsDiscovery.discover();
441
- return { memberId, teamId: teamName, teamName, displayName, status: "ONLINE" };
311
+ ws.on("close", () => {
312
+ const team = this.wsToTeam.get(ws);
313
+ if (team) {
314
+ if (this.peerConns.get(team) === ws) {
315
+ this.peerConns.delete(team);
316
+ }
317
+ this.wsToTeam.delete(ws);
318
+ }
319
+ });
320
+ await new Promise((resolve, reject) => {
321
+ const timeout = setTimeout(
322
+ () => reject(new Error(`Connection timeout to ${ip}:${targetPort}`)),
323
+ 5e3
324
+ );
325
+ ws.on("open", () => {
326
+ clearTimeout(timeout);
327
+ const hello = {
328
+ type: "P2P_HELLO",
329
+ fromTeam: this.localMember.teamName,
330
+ fromMemberId: this.localMember.memberId
331
+ };
332
+ ws.send(serializeP2PMsg(hello));
333
+ resolve();
334
+ });
335
+ ws.on("error", (err) => {
336
+ clearTimeout(timeout);
337
+ reject(err);
338
+ });
339
+ });
340
+ const helloMsg = await this.waitForResponse(
341
+ (m) => m.type === "P2P_HELLO",
342
+ 1e4
343
+ );
344
+ return helloMsg.fromTeam;
442
345
  }
443
346
  async ask(toTeam, content, format) {
444
347
  const ws = await this.getPeerConnection(toTeam);
@@ -544,7 +447,6 @@ var P2PNode = class {
544
447
  };
545
448
  }
546
449
  async disconnect() {
547
- this.mdnsDiscovery?.destroy();
548
450
  for (const ws of this.peerConns.values()) {
549
451
  ws.close();
550
452
  }
@@ -566,6 +468,14 @@ var P2PNode = class {
566
468
  ws.on("message", (data) => {
567
469
  try {
568
470
  const msg = parseP2PMsg(data.toString());
471
+ if (msg.type === "P2P_HELLO" && this.localMember) {
472
+ const hello = {
473
+ type: "P2P_HELLO",
474
+ fromTeam: this.localMember.teamName,
475
+ fromMemberId: this.localMember.memberId
476
+ };
477
+ ws.send(serializeP2PMsg(hello));
478
+ }
569
479
  this.handleMessage(ws, msg);
570
480
  } catch (err) {
571
481
  console.error("Failed to parse incoming P2P message:", err);
@@ -583,7 +493,7 @@ var P2PNode = class {
583
493
  });
584
494
  }
585
495
  // ---------------------------------------------------------------------------
586
- // Private: unified message handler (used for both incoming & outgoing sockets)
496
+ // Private: unified message handler
587
497
  // ---------------------------------------------------------------------------
588
498
  handleMessage(ws, msg) {
589
499
  for (const handler of this.pendingHandlers) {
@@ -678,78 +588,9 @@ var P2PNode = class {
678
588
  if (existing && existing.readyState === WebSocket.OPEN) {
679
589
  return existing;
680
590
  }
681
- let peer = this.mdnsDiscovery?.getPeerByTeam(teamName);
682
- if (!peer) {
683
- this.mdnsDiscovery?.discover();
684
- await this.waitForMdnsPeer(teamName, 1e4);
685
- peer = this.mdnsDiscovery?.getPeerByTeam(teamName);
686
- }
687
- if (!peer) {
688
- throw new Error(
689
- `Peer for team '${teamName}' not found via mDNS. Make sure the other terminal has joined with that team name.`
690
- );
691
- }
692
- return this.connectToPeer(teamName, peer.host, peer.port);
693
- }
694
- async connectToPeer(teamName, host, port) {
695
- const existing = this.peerConns.get(teamName);
696
- if (existing && existing.readyState === WebSocket.OPEN) {
697
- return existing;
698
- }
699
- const ws = new WebSocket(`ws://${host}:${port}`);
700
- await new Promise((resolve, reject) => {
701
- const timeout = setTimeout(
702
- () => reject(new Error(`Connection timeout to team '${teamName}'`)),
703
- 5e3
704
- );
705
- ws.on("open", () => {
706
- clearTimeout(timeout);
707
- const hello = {
708
- type: "P2P_HELLO",
709
- fromTeam: this.localMember?.teamName ?? "unknown",
710
- fromMemberId: this.localMember?.memberId ?? "unknown"
711
- };
712
- ws.send(serializeP2PMsg(hello));
713
- resolve();
714
- });
715
- ws.on("error", (err) => {
716
- clearTimeout(timeout);
717
- reject(err);
718
- });
719
- });
720
- ws.on("message", (data) => {
721
- try {
722
- const msg = parseP2PMsg(data.toString());
723
- this.handleMessage(ws, msg);
724
- } catch (err) {
725
- console.error("Failed to parse P2P message:", err);
726
- }
727
- });
728
- ws.on("close", () => {
729
- if (this.peerConns.get(teamName) === ws) {
730
- this.peerConns.delete(teamName);
731
- }
732
- });
733
- this.peerConns.set(teamName, ws);
734
- return ws;
735
- }
736
- waitForMdnsPeer(teamName, timeoutMs) {
737
- return new Promise((resolve, reject) => {
738
- const deadline = Date.now() + timeoutMs;
739
- const check = () => {
740
- if (this.mdnsDiscovery?.getPeerByTeam(teamName)) {
741
- resolve();
742
- return;
743
- }
744
- if (Date.now() >= deadline) {
745
- reject(new Error(`mDNS timeout: team '${teamName}' not found`));
746
- return;
747
- }
748
- this.mdnsDiscovery?.discover();
749
- setTimeout(check, 500);
750
- };
751
- check();
752
- });
591
+ throw new Error(
592
+ `No connection to team '${teamName}'. Use the connect_peer tool to connect first.`
593
+ );
753
594
  }
754
595
  waitForResponse(filter, timeoutMs) {
755
596
  return new Promise((resolve, reject) => {
@@ -773,9 +614,9 @@ var joinSchema = {
773
614
  displayName: z.string().optional().describe('Display name for this terminal (default: team + " Claude")')
774
615
  };
775
616
  function registerJoinTool(server, client) {
776
- server.tool("join", joinSchema, async (args2) => {
777
- const teamName = args2.team;
778
- const displayName = args2.displayName ?? `${teamName} Claude`;
617
+ server.tool("join", joinSchema, async (args) => {
618
+ const teamName = args.team;
619
+ const displayName = args.displayName ?? `${teamName} Claude`;
779
620
  try {
780
621
  const member = await client.join(teamName, displayName);
781
622
  return {
@@ -804,14 +645,45 @@ Status: ${member.status}`
804
645
  }
805
646
  });
806
647
  }
648
+ var connectPeerSchema = {
649
+ ip: z.string().describe('IP address of the peer to connect to (e.g., "172.16.40.137")'),
650
+ port: z.number().optional().describe(`Port the peer is listening on (default: ${config.p2p.port})`)
651
+ };
652
+ function registerConnectPeerTool(server, client) {
653
+ server.tool("connect_peer", connectPeerSchema, async (args) => {
654
+ const targetPort = args.port ?? config.p2p.port;
655
+ try {
656
+ const peerTeam = await client.connectPeer(args.ip, args.port);
657
+ return {
658
+ content: [
659
+ {
660
+ type: "text",
661
+ text: `Connected to peer "${peerTeam}" at ${args.ip}:${targetPort}.`
662
+ }
663
+ ]
664
+ };
665
+ } catch (error) {
666
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
667
+ return {
668
+ content: [
669
+ {
670
+ type: "text",
671
+ text: `Failed to connect to ${args.ip}:${targetPort}: ${errorMessage}`
672
+ }
673
+ ],
674
+ isError: true
675
+ };
676
+ }
677
+ });
678
+ }
807
679
  var askSchema = {
808
680
  team: z.string().describe('Target team name to ask (e.g., "backend", "frontend")'),
809
681
  question: z.string().describe("The question to ask (supports markdown)")
810
682
  };
811
683
  function registerAskTool(server, client) {
812
- server.tool("ask", askSchema, async (args2) => {
813
- const targetTeam = args2.team;
814
- const question = args2.question;
684
+ server.tool("ask", askSchema, async (args) => {
685
+ const targetTeam = args.team;
686
+ const question = args.question;
815
687
  try {
816
688
  if (!client.currentTeamId) {
817
689
  return {
@@ -873,8 +745,8 @@ var checkAnswerSchema = {
873
745
  question_id: z.string().describe('The question ID returned by the "ask" tool')
874
746
  };
875
747
  function registerCheckAnswerTool(server, client) {
876
- server.tool("check_answer", checkAnswerSchema, async (args2) => {
877
- const questionId = args2.question_id;
748
+ server.tool("check_answer", checkAnswerSchema, async (args) => {
749
+ const questionId = args.question_id;
878
750
  try {
879
751
  if (!client.currentTeamId) {
880
752
  return {
@@ -992,9 +864,9 @@ var replySchema = {
992
864
  answer: z.string().describe("Your answer to the question (supports markdown)")
993
865
  };
994
866
  function registerReplyTool(server, client) {
995
- server.tool("reply", replySchema, async (args2) => {
996
- const questionId = args2.questionId;
997
- const answer = args2.answer;
867
+ server.tool("reply", replySchema, async (args) => {
868
+ const questionId = args.questionId;
869
+ const answer = args.answer;
998
870
  try {
999
871
  if (!client.currentTeamId) {
1000
872
  return {
@@ -1050,6 +922,7 @@ function createMcpServer(options) {
1050
922
  }
1051
923
  );
1052
924
  registerJoinTool(server, client);
925
+ registerConnectPeerTool(server, client);
1053
926
  registerAskTool(server, client);
1054
927
  registerCheckAnswerTool(server, client);
1055
928
  registerInboxTool(server, client);
@@ -1085,40 +958,10 @@ async function startMcpServer(options) {
1085
958
  }
1086
959
 
1087
960
  // src/mcp-main.ts
1088
- var args = process.argv.slice(2);
1089
- function parseArgs() {
1090
- const options = {};
1091
- for (let i = 0; i < args.length; i++) {
1092
- const arg = args[i];
1093
- const nextArg = args[i + 1];
1094
- if (arg === "--team" && nextArg) {
1095
- options.team = nextArg;
1096
- i++;
1097
- } else if (arg === "-h" || arg === "--help") {
1098
- console.error(`
1099
- Claude Collab MCP Client (P2P mode)
1100
-
1101
- Usage:
1102
- mcp-main [options]
1103
-
1104
- Options:
1105
- --team <team> Team to auto-join (optional)
1106
- -h, --help Show this help message
1107
- `);
1108
- process.exit(0);
1109
- }
1110
- }
1111
- return options;
1112
- }
1113
961
  async function main() {
1114
- const options = parseArgs();
1115
962
  const p2pNode = new P2PNode();
1116
963
  try {
1117
964
  await p2pNode.start();
1118
- if (options.team) {
1119
- await p2pNode.join(options.team, `${options.team} Claude`);
1120
- console.error(`Auto-joined team: ${options.team}`);
1121
- }
1122
965
  } catch (error) {
1123
966
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
1124
967
  console.error(`Failed to start P2P node: ${errorMessage}`);