Skip to content

Commit 8cc80dc

Browse files
hyperpolymathclaude
andcommitted
feat(local-coord-mcp): wire into BoJ MCP bridge — end-to-end working
Integration of local-coord-mcp cartridge with the MCP bridge: - mcp-bridge/lib/tools.js: add 6 coord_* tool definitions (register, list_peers, send, receive, claim_task, status) with JSON schemas - mcp-bridge/main.js: dispatch coord_* directly to 127.0.0.1:7745 with graceful unavailable fallback + hint - mcp-bridge/lib/offline-menu.js: add local-coord-mcp to tier_ayo, total 23→24 - TOPOLOGY.md: port 7745 + cartridge listing, 96→97 total Also fixes two pre-existing bugs blocking the integration: 1. mcp-bridge/main.js: restore import statements stripped by f1fd38e. The modularization commit split main.js into lib/ modules but a follow-up removed the imports without inlining the functions, leaving buildToolList/fetchHealth/etc. as undefined references. The bridge has been broken since then; this commit restores the imports matching the pre-f1fd38e state. 2. cartridges/local-coord-mcp/adapter/build.zig: update to Zig 0.15 ExecutableOptions API (use root_module, not root_source_file). 3. cartridges/local-coord-mcp/adapter/local_coord_adapter.zig: discard the c_int return from boj_cartridge_init() — Zig 0.15 rejects ignored return values. End-to-end verified: - Adapter binds to 127.0.0.1:7745, direct curl returns success - MCP bridge tools/list includes all 6 coord_* tools - MCP bridge tools/call for coord_register dispatches to adapter and returns success through the full JSON-RPC stack Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent fc41c74 commit 8cc80dc

6 files changed

Lines changed: 151 additions & 9 deletions

File tree

TOPOLOGY.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# SPDX-License-Identifier: PMPL-1.0-or-later
22
# TOPOLOGY.md — BoJ Server Component Matrix
33
#
4-
# Auto-generated 2026-04-09. 96 cartridges across 6 tiers.
4+
# Auto-generated 2026-04-09. 97 cartridges across 6 tiers.
55

66
## Ports
77

@@ -11,8 +11,9 @@
1111
| 7701 | gRPC | Running |
1212
| 7702 | GraphQL | Running |
1313
| 7703 | SSE (Server-Sent Events) | Running |
14+
| 7745 | local-coord-mcp (loopback only) | Running |
1415

15-
## Cartridge Matrix (96 total)
16+
## Cartridge Matrix (97 total)
1617

1718
### Tier 1 — High-Value APIs (11)
1819
| Cartridge | Domain |
@@ -94,10 +95,11 @@
9495
| laminar-mcp | Flow monitoring |
9596
| ums-mcp | Unified monitoring |
9697

97-
### Hyperpolymath Ecosystem (26)
98+
### Hyperpolymath Ecosystem (27)
9899
| Cartridge | Domain |
99100
|-----------|--------|
100101
| agent-mcp | Agent orchestration |
102+
| local-coord-mcp | Localhost multi-instance coordination (loopback only, port 7745) |
101103
| affinescript-mcp | Language tooling |
102104
| aerie-mcp | Deployment |
103105
| bsp-mcp | Build Server Protocol |

cartridges/local-coord-mcp/adapter/build.zig

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@ pub fn build(b: *std.Build) void {
1515
.optimize = optimize,
1616
});
1717

18-
const adapter = b.addExecutable(.{
19-
.name = "local_coord_adapter",
18+
const adapter_mod = b.createModule(.{
2019
.root_source_file = b.path("local_coord_adapter.zig"),
2120
.target = target,
2221
.optimize = optimize,
2322
});
24-
adapter.root_module.addImport("local_coord_ffi", ffi_mod);
23+
adapter_mod.addImport("local_coord_ffi", ffi_mod);
24+
25+
const adapter = b.addExecutable(.{
26+
.name = "local_coord_adapter",
27+
.root_module = adapter_mod,
28+
});
2529
b.installArtifact(adapter);
2630
}

cartridges/local-coord-mcp/adapter/local_coord_adapter.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ fn handleConnection(stream: std.net.Stream) void {
8787
}
8888

8989
pub fn main() !void {
90-
ffi.boj_cartridge_init();
90+
_ = ffi.boj_cartridge_init();
9191

9292
// CRITICAL: Bind to loopback ONLY.
9393
const addr = std.net.Address.initIp4(BIND_ADDR, REST_PORT);

mcp-bridge/lib/offline-menu.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export const OFFLINE_MENU = {
3535
{ name: "secrets-mcp", version: "0.1.0", domain: "Secrets", protocols: ["MCP","REST"], status: "Available", available: true },
3636
{ name: "proof-mcp", version: "0.1.0", domain: "Proof", protocols: ["MCP","REST"], status: "Available", available: true },
3737
],
38-
tier_ayo: [],
39-
summary: { total: 23, ready: 23, mounted: 0 },
38+
tier_ayo: [
39+
{ name: "local-coord-mcp", version: "0.1.0", domain: "Agent", protocols: ["MCP","Agentic"], status: "Available", available: true, notes: "Localhost-only (127.0.0.1:7745) multi-instance AI coordination — peer discovery, message passing, task claiming" },
40+
],
41+
summary: { total: 24, ready: 24, mounted: 0 },
4042
};

mcp-bridge/lib/tools.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,83 @@ function buildToolList() {
264264
},
265265
});
266266

267+
// Local coordination (localhost multi-instance AI coordination — local-coord-mcp cartridge)
268+
tools.push({
269+
name: "coord_register",
270+
description: "Register this AI instance as a coordination peer on localhost. Returns a hybrid peer ID (e.g. claude-7f3a) and a session token for all subsequent calls. Loopback-only, never exposed beyond 127.0.0.1.",
271+
inputSchema: {
272+
type: "object",
273+
properties: {
274+
client_kind: { type: "string", enum: ["claude", "gemini", "copilot", "custom"], description: "Client type prefix for the peer ID" },
275+
},
276+
required: ["client_kind"],
277+
},
278+
});
279+
280+
tools.push({
281+
name: "coord_list_peers",
282+
description: "List all active AI instances registered on this machine — their peer IDs, client kinds, states, and current status.",
283+
inputSchema: {
284+
type: "object",
285+
properties: {
286+
token: { type: "string", description: "Session token from coord_register" },
287+
},
288+
required: ["token"],
289+
},
290+
});
291+
292+
tools.push({
293+
name: "coord_send",
294+
description: "Send a message to a specific peer (by peer ID) or broadcast to all peers (target '*'). Messages are queued in recipient inboxes.",
295+
inputSchema: {
296+
type: "object",
297+
properties: {
298+
token: { type: "string", description: "Session token from coord_register" },
299+
target: { type: "string", description: "Peer ID to send to, or '*' for broadcast" },
300+
message: { type: "string", description: "Message content" },
301+
},
302+
required: ["token", "target", "message"],
303+
},
304+
});
305+
306+
tools.push({
307+
name: "coord_receive",
308+
description: "Receive the next message from this peer's inbox. Returns the message content and sender, or indicates empty inbox.",
309+
inputSchema: {
310+
type: "object",
311+
properties: {
312+
token: { type: "string", description: "Session token from coord_register" },
313+
},
314+
required: ["token"],
315+
},
316+
});
317+
318+
tools.push({
319+
name: "coord_claim_task",
320+
description: "Attempt to claim a task (mutex-style). If the task is unclaimed, this peer becomes the holder. If another peer holds it, returns the holder's peer ID. Idempotent if already held by caller. Use this to prevent two AI instances from duplicating work.",
321+
inputSchema: {
322+
type: "object",
323+
properties: {
324+
token: { type: "string", description: "Session token from coord_register" },
325+
task: { type: "string", description: "Task identifier to claim (e.g. 'audit-boj-server')" },
326+
},
327+
required: ["token", "task"],
328+
},
329+
});
330+
331+
tools.push({
332+
name: "coord_status",
333+
description: "Set this peer's current work status, visible to other peers via coord_list_peers.",
334+
inputSchema: {
335+
type: "object",
336+
properties: {
337+
token: { type: "string", description: "Session token from coord_register" },
338+
status: { type: "string", description: "Current work status description" },
339+
},
340+
required: ["token", "status"],
341+
},
342+
});
343+
267344
return tools;
268345
}
269346

mcp-bridge/main.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,27 @@
1111
// Usage: deno run --allow-net main.js
1212
// or: node main.js
1313

14+
import {
15+
RATE_LIMIT,
16+
isInputSizeOk,
17+
isValidToolName,
18+
rateLimitAllow,
19+
sanitizeErrorMessage,
20+
scanObjectForInjection,
21+
validateRequiredStrings,
22+
} from "./lib/security.js";
23+
import {
24+
fetchCartridgeInfo,
25+
fetchCartridges,
26+
fetchHealth,
27+
fetchMenu,
28+
handleGitHubTool,
29+
handleGitLabTool,
30+
invokeCartridge,
31+
} from "./lib/api-clients.js";
32+
import { buildToolList } from "./lib/tools.js";
33+
import { info, warn, error as logError } from "./lib/logger.js";
34+
1435
const BOJ_BASE = process.env.BOJ_URL || "http://localhost:7700";
1536
const SERVER_NAME = "boj-server";
1637
const SERVER_VERSION = "0.3.1";
@@ -206,11 +227,47 @@ async function dispatchTool(toolName, args) {
206227
case "boj_research":
207228
return invokeCartridge("research-mcp", args);
208229

230+
// Local coordination — direct to loopback backend on port 7745
231+
case "coord_register":
232+
case "coord_list_peers":
233+
case "coord_send":
234+
case "coord_receive":
235+
case "coord_claim_task":
236+
case "coord_status":
237+
return dispatchLocalCoord(toolName, args);
238+
209239
default:
210240
return null; // unknown tool
211241
}
212242
}
213243

244+
// ===================================================================
245+
// local-coord-mcp direct dispatch (loopback only, port 7745)
246+
// ===================================================================
247+
248+
const LOCAL_COORD_URL = process.env.COORD_BACKEND_URL || "http://127.0.0.1:7745";
249+
250+
async function dispatchLocalCoord(toolName, args) {
251+
try {
252+
const res = await fetch(`${LOCAL_COORD_URL}/tools/${toolName}`, {
253+
method: "POST",
254+
headers: { "Content-Type": "application/json" },
255+
body: JSON.stringify(args || {}),
256+
});
257+
try {
258+
return await res.json();
259+
} catch {
260+
return { success: false, error: "local-coord-mcp backend returned non-JSON" };
261+
}
262+
} catch (e) {
263+
return {
264+
success: false,
265+
error: `local-coord-mcp backend unavailable at ${LOCAL_COORD_URL}: ${e.message}`,
266+
hint: "Start the adapter: cd cartridges/local-coord-mcp/adapter && zig build run",
267+
};
268+
}
269+
}
270+
214271
// ===================================================================
215272
// MCP message handler
216273
// ===================================================================

0 commit comments

Comments
 (0)