Skip to content
This repository was archived by the owner on Sep 23, 2025. It is now read-only.

Commit ed87911

Browse files
committed
Implement Marco-Polo discovery protocol in extension
- Add marco/polo/goodbye message types to IPCMessage interface - Implement sendMarco() method for discovery broadcasts - Add automatic Marco broadcast on daemon connection - Process Polo messages to discover active MCP servers - Handle Goodbye messages for server departure tracking - Add Marco message filtering to prevent error loops - Include comprehensive discovery logging with [DISCOVERY] prefix Test results: ✅ Extension discovers MCP server in terminal PID 35059 ✅ Clean message flow with no error loops ✅ Automatic discovery on connection working Next: Implement daemon terminal registry for Ask Socratic Shell integration.
1 parent d82d30a commit ed87911

File tree

1 file changed

+82
-27
lines changed

1 file changed

+82
-27
lines changed

extension/src/extension.ts

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { ReviewWebviewProvider } from './reviewWebview';
77

88
// 💡: Types for IPC communication with MCP server
99
interface IPCMessage {
10-
type: 'present_review' | 'log' | 'get_selection' | 'response';
10+
type: 'present_review' | 'log' | 'get_selection' | 'response' | 'marco' | 'polo' | 'goodbye';
1111
payload: {
1212
content: string;
1313
mode: 'replace' | 'update-section' | 'append';
@@ -52,6 +52,9 @@ class DaemonClient implements vscode.Disposable {
5252
this.socket.on('connect', () => {
5353
this.outputChannel.appendLine('✅ Connected to message bus daemon');
5454
this.clearReconnectTimer();
55+
56+
// Send Marco broadcast to discover existing MCP servers
57+
this.sendMarco();
5558
});
5659

5760
this.socket.on('error', (error) => {
@@ -98,17 +101,6 @@ class DaemonClient implements vscode.Disposable {
98101
}
99102

100103
private async handleIncomingMessage(message: IPCMessage): Promise<void> {
101-
// Extract shell PID from message payload for filtering
102-
const shellPid = this.extractShellPidFromMessage(message);
103-
104-
if (shellPid) {
105-
// Check if this message is intended for our VSCode window
106-
const isForOurWindow = await this.isMessageForOurWindow(shellPid);
107-
if (!isForOurWindow) {
108-
this.outputChannel.appendLine(`Debug: Ignoring message from shell PID ${shellPid} (not in our window)`);
109-
return;
110-
}
111-
}
112104
if (message.type === 'present_review') {
113105
try {
114106
const reviewPayload = message.payload as {
@@ -119,14 +111,16 @@ class DaemonClient implements vscode.Disposable {
119111
terminal_shell_pid: number;
120112
};
121113

122-
this.reviewProvider.updateReview(
123-
reviewPayload.content,
124-
reviewPayload.mode,
125-
reviewPayload.baseUri
126-
);
114+
if (await this.isMessageForOurWindow(reviewPayload.terminal_shell_pid)) {
115+
this.reviewProvider.updateReview(
116+
reviewPayload.content,
117+
reviewPayload.mode,
118+
reviewPayload.baseUri
119+
);
127120

128-
// Send success response back through daemon
129-
this.sendResponse(message.id, { success: true });
121+
// Send success response back through daemon
122+
this.sendResponse(message.id, { success: true });
123+
}
130124
} catch (error) {
131125
this.outputChannel.appendLine(`Error handling present_review: ${error}`);
132126
this.sendResponse(message.id, {
@@ -136,11 +130,17 @@ class DaemonClient implements vscode.Disposable {
136130
}
137131
} else if (message.type === 'get_selection') {
138132
try {
139-
const selectionData = this.getCurrentSelection();
140-
this.sendResponse(message.id, {
141-
success: true,
142-
data: selectionData
143-
});
133+
const selectionPayload = message.payload as {
134+
terminal_shell_pid: number;
135+
};
136+
137+
if (await this.isMessageForOurWindow(selectionPayload.terminal_shell_pid)) {
138+
const selectionData = this.getCurrentSelection();
139+
this.sendResponse(message.id, {
140+
success: true,
141+
data: selectionData
142+
});
143+
}
144144
} catch (error) {
145145
this.outputChannel.appendLine(`Error handling get_selection: ${error}`);
146146
this.sendResponse(message.id, {
@@ -156,12 +156,45 @@ class DaemonClient implements vscode.Disposable {
156156
message: string;
157157
terminal_shell_pid: number;
158158
};
159-
160-
const levelPrefix = logPayload.level.toUpperCase();
161-
this.outputChannel.appendLine(`[${levelPrefix}] ${logPayload.message}`);
159+
160+
if (await this.isMessageForOurWindow(logPayload.terminal_shell_pid)) {
161+
const levelPrefix = logPayload.level.toUpperCase();
162+
this.outputChannel.appendLine(`[${levelPrefix}] ${logPayload.message}`);
163+
}
162164
} catch (error) {
163165
this.outputChannel.appendLine(`Error handling log message: ${error}`);
164166
}
167+
} else if (message.type === 'polo') {
168+
// Handle Polo messages - MCP server announcing presence
169+
try {
170+
const poloPayload = message.payload as {
171+
terminal_shell_pid: number;
172+
};
173+
174+
if (await this.isMessageForOurWindow(poloPayload.terminal_shell_pid)) {
175+
this.outputChannel.appendLine(`[DISCOVERY] MCP server connected in terminal PID ${poloPayload.terminal_shell_pid}`);
176+
// TODO: Add to terminal registry for Ask Socratic Shell integration
177+
}
178+
} catch (error) {
179+
this.outputChannel.appendLine(`Error handling polo message: ${error}`);
180+
}
181+
} else if (message.type === 'goodbye') {
182+
// Handle Goodbye messages - MCP server announcing departure
183+
try {
184+
const goodbyePayload = message.payload as {
185+
terminal_shell_pid: number;
186+
};
187+
188+
if (await this.isMessageForOurWindow(goodbyePayload.terminal_shell_pid)) {
189+
this.outputChannel.appendLine(`[DISCOVERY] MCP server disconnected from terminal PID ${goodbyePayload.terminal_shell_pid}`);
190+
// TODO: Remove from terminal registry for Ask Socratic Shell integration
191+
}
192+
} catch (error) {
193+
this.outputChannel.appendLine(`Error handling goodbye message: ${error}`);
194+
}
195+
} else if (message.type === 'marco') {
196+
// Ignore Marco messages - these are broadcasts we send, MCP servers respond to them
197+
// Extensions don't need to respond to Marco broadcasts
165198
} else if (message.type == 'response') {
166199
// Ignore this, response messages are messages that WE send to clients.
167200
} else {
@@ -201,6 +234,7 @@ class DaemonClient implements vscode.Disposable {
201234
try {
202235
const terminalPid = await terminal.processId;
203236
if (terminalPid === shellPid) {
237+
this.outputChannel.appendLine(`Debug: shell PID ${shellPid} is in our window`);
204238
return true;
205239
}
206240
} catch (error) {
@@ -209,6 +243,7 @@ class DaemonClient implements vscode.Disposable {
209243
}
210244
}
211245

246+
this.outputChannel.appendLine(`Debug: shell PID ${shellPid} is not in our window`);
212247
return false;
213248
} catch (error) {
214249
this.outputChannel.appendLine(`Error checking if message is for our window: ${error}`);
@@ -278,6 +313,26 @@ class DaemonClient implements vscode.Disposable {
278313
}
279314
}
280315

316+
private sendMarco(): void {
317+
if (!this.socket || this.socket.destroyed) {
318+
this.outputChannel.appendLine(`Cannot send Marco - socket not connected`);
319+
return;
320+
}
321+
322+
const marcoMessage = {
323+
type: 'marco',
324+
payload: {},
325+
id: crypto.randomUUID()
326+
};
327+
328+
try {
329+
this.socket.write(JSON.stringify(marcoMessage) + '\n');
330+
this.outputChannel.appendLine('[DISCOVERY] Sent Marco broadcast to discover MCP servers');
331+
} catch (error) {
332+
this.outputChannel.appendLine(`Failed to send Marco: ${error}`);
333+
}
334+
}
335+
281336
private getDaemonSocketPath(): string {
282337
const discoveredPid = findVSCodePID(this.outputChannel);
283338
const vscodePid = discoveredPid || (() => {

0 commit comments

Comments
 (0)