mcp-coordinator 0.2.1 → 0.3.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.
- package/README.md +846 -846
- package/dashboard/Dockerfile +19 -19
- package/dashboard/public/index.html +1178 -1178
- package/dist/cli/dashboard.js +9 -5
- package/dist/cli/server/start.js +24 -1
- package/dist/cli/server/status.js +16 -23
- package/dist/src/agent-activity.js +6 -6
- package/dist/src/agent-registry.js +6 -6
- package/dist/src/announce-workflow.d.ts +52 -0
- package/dist/src/announce-workflow.js +91 -0
- package/dist/src/consultation.d.ts +14 -0
- package/dist/src/consultation.js +110 -45
- package/dist/src/database.js +126 -126
- package/dist/src/dependency-map.js +3 -3
- package/dist/src/file-tracker.js +8 -8
- package/dist/src/http/handle-rest.d.ts +23 -0
- package/dist/src/http/handle-rest.js +374 -0
- package/dist/src/http/utils.d.ts +15 -0
- package/dist/src/http/utils.js +39 -0
- package/dist/src/introspection.js +1 -1
- package/dist/src/mqtt-bridge.d.ts +2 -0
- package/dist/src/mqtt-bridge.js +2 -0
- package/dist/src/mqtt-broker.d.ts +16 -0
- package/dist/src/mqtt-broker.js +16 -1
- package/dist/src/path-guard.d.ts +14 -0
- package/dist/src/path-guard.js +44 -0
- package/dist/src/reset-guard.d.ts +16 -0
- package/dist/src/reset-guard.js +24 -0
- package/dist/src/serve-http.d.ts +31 -1
- package/dist/src/serve-http.js +154 -445
- package/dist/src/server-setup.js +15 -364
- package/dist/src/tools/agents-tools.d.ts +8 -0
- package/dist/src/tools/agents-tools.js +46 -0
- package/dist/src/tools/consultation-tools.d.ts +21 -0
- package/dist/src/tools/consultation-tools.js +170 -0
- package/dist/src/tools/dependencies-tools.d.ts +8 -0
- package/dist/src/tools/dependencies-tools.js +27 -0
- package/dist/src/tools/files-tools.d.ts +8 -0
- package/dist/src/tools/files-tools.js +28 -0
- package/dist/src/tools/mqtt-tools.d.ts +9 -0
- package/dist/src/tools/mqtt-tools.js +33 -0
- package/dist/src/tools/status-tools.d.ts +8 -0
- package/dist/src/tools/status-tools.js +63 -0
- package/package.json +81 -80
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* S1: dependency map MCP tools (3 tools).
|
|
4
|
+
* set_dependency_map, get_blast_radius, get_module_info.
|
|
5
|
+
*/
|
|
6
|
+
export function registerDependenciesTools(server, services, _mcpLog) {
|
|
7
|
+
const { depMap } = services;
|
|
8
|
+
server.tool("set_dependency_map", "Load module dependency graph", {
|
|
9
|
+
modules: z.string(), // JSON DependencyMap
|
|
10
|
+
}, async ({ modules }) => {
|
|
11
|
+
const map = JSON.parse(modules);
|
|
12
|
+
depMap.setMap(map);
|
|
13
|
+
return { content: [{ type: "text", text: "ok" }] };
|
|
14
|
+
});
|
|
15
|
+
server.tool("get_blast_radius", "Calculate impact of changes to a module", {
|
|
16
|
+
module_id: z.string(),
|
|
17
|
+
}, async ({ module_id }) => {
|
|
18
|
+
const radius = depMap.getBlastRadius(module_id);
|
|
19
|
+
return { content: [{ type: "text", text: JSON.stringify(radius) }] };
|
|
20
|
+
});
|
|
21
|
+
server.tool("get_module_info", "Get module dependency info", {
|
|
22
|
+
module_id: z.string(),
|
|
23
|
+
}, async ({ module_id }) => {
|
|
24
|
+
const info = depMap.getModuleInfo(module_id);
|
|
25
|
+
return { content: [{ type: "text", text: JSON.stringify(info) }] };
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { CoordinatorServices } from "../server-setup.js";
|
|
3
|
+
import type { Logger } from "../logger.js";
|
|
4
|
+
/**
|
|
5
|
+
* S1: file tracking MCP tools (3 tools).
|
|
6
|
+
* hot_files, get_session_files, check_file_conflict.
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerFilesTools(server: McpServer, services: CoordinatorServices, _mcpLog: Logger): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* S1: file tracking MCP tools (3 tools).
|
|
4
|
+
* hot_files, get_session_files, check_file_conflict.
|
|
5
|
+
*/
|
|
6
|
+
export function registerFilesTools(server, services, _mcpLog) {
|
|
7
|
+
const { fileTracker } = services;
|
|
8
|
+
server.tool("hot_files", "List files modified by multiple agents", {
|
|
9
|
+
since_minutes: z.number().optional(),
|
|
10
|
+
}, async ({ since_minutes }) => {
|
|
11
|
+
const files = fileTracker.getHotFiles(since_minutes || 30);
|
|
12
|
+
return { content: [{ type: "text", text: JSON.stringify(files) }] };
|
|
13
|
+
});
|
|
14
|
+
server.tool("get_session_files", "Get files modified in a session", {
|
|
15
|
+
session_id: z.string(),
|
|
16
|
+
}, async ({ session_id }) => {
|
|
17
|
+
const files = fileTracker.getBySession(session_id);
|
|
18
|
+
return { content: [{ type: "text", text: JSON.stringify(files) }] };
|
|
19
|
+
});
|
|
20
|
+
server.tool("check_file_conflict", "Check if another agent is editing a file", {
|
|
21
|
+
file_path: z.string(),
|
|
22
|
+
agent_id: z.string(),
|
|
23
|
+
within_minutes: z.number().optional(),
|
|
24
|
+
}, async ({ file_path, agent_id, within_minutes }) => {
|
|
25
|
+
const result = fileTracker.checkFileConflict(file_path, agent_id, within_minutes || 30);
|
|
26
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { CoordinatorServices } from "../server-setup.js";
|
|
3
|
+
import type { Logger } from "../logger.js";
|
|
4
|
+
/**
|
|
5
|
+
* S1: MQTT listener MCP tools (3 tools).
|
|
6
|
+
* wait_for_message, get_queued_messages, mqtt_publish.
|
|
7
|
+
* Replaces what used to be a standalone mqtt-mcp-bridge sidecar.
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerMqttTools(server: McpServer, services: CoordinatorServices, _mcpLog: Logger): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* S1: MQTT listener MCP tools (3 tools).
|
|
4
|
+
* wait_for_message, get_queued_messages, mqtt_publish.
|
|
5
|
+
* Replaces what used to be a standalone mqtt-mcp-bridge sidecar.
|
|
6
|
+
*/
|
|
7
|
+
export function registerMqttTools(server, services, _mcpLog) {
|
|
8
|
+
const { mqttBridge } = services;
|
|
9
|
+
server.tool("wait_for_message", "Block until an MQTT consultation message arrives or timeout", {
|
|
10
|
+
agent_id: z.string(),
|
|
11
|
+
timeout_seconds: z.number().optional(),
|
|
12
|
+
}, async ({ agent_id, timeout_seconds }) => {
|
|
13
|
+
const timeoutMs = (timeout_seconds || 15) * 1000;
|
|
14
|
+
const msg = await mqttBridge.waitForMessage(agent_id, timeoutMs);
|
|
15
|
+
if (msg) {
|
|
16
|
+
return { content: [{ type: "text", text: JSON.stringify(msg) }] };
|
|
17
|
+
}
|
|
18
|
+
return { content: [{ type: "text", text: JSON.stringify({ timeout: true }) }] };
|
|
19
|
+
});
|
|
20
|
+
server.tool("get_queued_messages", "Get all queued MQTT messages without blocking", {
|
|
21
|
+
agent_id: z.string(),
|
|
22
|
+
}, async ({ agent_id }) => {
|
|
23
|
+
const messages = mqttBridge.getQueuedMessages(agent_id);
|
|
24
|
+
return { content: [{ type: "text", text: JSON.stringify(messages) }] };
|
|
25
|
+
});
|
|
26
|
+
server.tool("mqtt_publish", "Publish a message to an MQTT topic", {
|
|
27
|
+
topic: z.string(),
|
|
28
|
+
payload: z.string(),
|
|
29
|
+
}, async ({ topic, payload }) => {
|
|
30
|
+
mqttBridge.mqttPublish(topic, payload);
|
|
31
|
+
return { content: [{ type: "text", text: "published" }] };
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { CoordinatorServices } from "../server-setup.js";
|
|
3
|
+
import type { Logger } from "../logger.js";
|
|
4
|
+
/**
|
|
5
|
+
* S1: status + coordination helper MCP tools (2 tools).
|
|
6
|
+
* coordinator_status, wait_for_peers.
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerStatusTools(server: McpServer, services: CoordinatorServices, mcpLog: Logger): void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* S1: status + coordination helper MCP tools (2 tools).
|
|
4
|
+
* coordinator_status, wait_for_peers.
|
|
5
|
+
*/
|
|
6
|
+
export function registerStatusTools(server, services, mcpLog) {
|
|
7
|
+
const { registry, consultation, fileTracker, mqttBridge } = services;
|
|
8
|
+
server.tool("coordinator_status", "Full system status", {}, async () => {
|
|
9
|
+
const online = registry.listOnline();
|
|
10
|
+
const openThreads = consultation.listThreads({ status: "open" });
|
|
11
|
+
const resolvingThreads = consultation.listThreads({ status: "resolving" });
|
|
12
|
+
const hotFiles = fileTracker.getHotFiles(30);
|
|
13
|
+
const status = {
|
|
14
|
+
agents_online: online.length,
|
|
15
|
+
agents: online.map((a) => ({ id: a.id, name: a.name, modules: JSON.parse(a.modules) })),
|
|
16
|
+
open_threads: openThreads.length,
|
|
17
|
+
resolving_threads: resolvingThreads.length,
|
|
18
|
+
hot_files: hotFiles.length,
|
|
19
|
+
mqtt_connected: mqttBridge.isConnected(),
|
|
20
|
+
};
|
|
21
|
+
mcpLog.debug({ tool: "coordinator_status", agents_online: online.length, open_threads: openThreads.length }, "Tool called");
|
|
22
|
+
return { content: [{ type: "text", text: JSON.stringify(status) }] };
|
|
23
|
+
});
|
|
24
|
+
server.tool("wait_for_peers", "Block until at least N other online agents are registered, or timeout. Use before the first announce_work to avoid the race where one agent announces before peers have booted.", {
|
|
25
|
+
agent_id: z.string(),
|
|
26
|
+
min_peers: z.number().optional(),
|
|
27
|
+
timeout_seconds: z.number().optional(),
|
|
28
|
+
}, async ({ agent_id, min_peers, timeout_seconds }) => {
|
|
29
|
+
const targetPeers = min_peers ?? 1;
|
|
30
|
+
const timeoutMs = (timeout_seconds ?? 30) * 1000;
|
|
31
|
+
const pollIntervalMs = 1000;
|
|
32
|
+
const startedAt = Date.now();
|
|
33
|
+
mcpLog.info({ tool: "wait_for_peers", agent_id, min_peers: targetPeers, timeout_seconds: timeoutMs / 1000 }, "Tool called");
|
|
34
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
35
|
+
const peers = registry.listOnline().filter((a) => a.id !== agent_id);
|
|
36
|
+
if (peers.length >= targetPeers) {
|
|
37
|
+
return {
|
|
38
|
+
content: [{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: JSON.stringify({
|
|
41
|
+
ready: true,
|
|
42
|
+
online_peers: peers.map((p) => ({ id: p.id, name: p.name })),
|
|
43
|
+
waited_ms: Date.now() - startedAt,
|
|
44
|
+
}),
|
|
45
|
+
}],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
49
|
+
}
|
|
50
|
+
const finalPeers = registry.listOnline().filter((a) => a.id !== agent_id);
|
|
51
|
+
return {
|
|
52
|
+
content: [{
|
|
53
|
+
type: "text",
|
|
54
|
+
text: JSON.stringify({
|
|
55
|
+
ready: false,
|
|
56
|
+
timeout: true,
|
|
57
|
+
online_peers: finalPeers.map((p) => ({ id: p.id, name: p.name })),
|
|
58
|
+
waited_ms: Date.now() - startedAt,
|
|
59
|
+
}),
|
|
60
|
+
}],
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
}
|
package/package.json
CHANGED
|
@@ -1,80 +1,81 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "mcp-coordinator",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"dist/
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"
|
|
50
|
-
"test
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"dev
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
"@types/
|
|
71
|
-
"@types/
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-coordinator",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"mcpName": "io.github.swoofer/mcp-coordinator",
|
|
5
|
+
"description": "Embedded MQTT broker + MCP server for multi-agent coordination",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "Maxime Gagnon",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/swoofer/mcp-coordinator.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://swoofer.github.io/mcp-coordinator",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/swoofer/mcp-coordinator/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"mqtt",
|
|
20
|
+
"broker",
|
|
21
|
+
"multi-agent",
|
|
22
|
+
"coordination",
|
|
23
|
+
"claude",
|
|
24
|
+
"anthropic"
|
|
25
|
+
],
|
|
26
|
+
"main": "./dist/src/index.js",
|
|
27
|
+
"types": "./dist/src/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/src/index.d.ts",
|
|
31
|
+
"default": "./dist/src/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./types": {
|
|
34
|
+
"types": "./dist/src/types.d.ts",
|
|
35
|
+
"default": "./dist/src/types.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"bin": {
|
|
39
|
+
"mcp-coordinator": "./dist/cli/index.js"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist/src/",
|
|
43
|
+
"dist/cli/",
|
|
44
|
+
"dashboard/",
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"README.md"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:watch": "vitest",
|
|
52
|
+
"cli": "tsx cli/index.ts",
|
|
53
|
+
"start": "node dist/src/serve-http.js",
|
|
54
|
+
"dev": "tsx src/serve-http.ts",
|
|
55
|
+
"dev:stdio": "tsx src/index.ts",
|
|
56
|
+
"prepublishOnly": "npm run build && npm test"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
60
|
+
"aedes": "^1.0.2",
|
|
61
|
+
"better-sqlite3": "^12.8.0",
|
|
62
|
+
"commander": "^14.0.3",
|
|
63
|
+
"jose": "^6.2.2",
|
|
64
|
+
"mqtt": "^5.15.0",
|
|
65
|
+
"pino": "^10.3.1",
|
|
66
|
+
"ws": "^8.20.0",
|
|
67
|
+
"zod": "^3.23.0"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
71
|
+
"@types/node": "^22.0.0",
|
|
72
|
+
"@types/ws": "^8.18.1",
|
|
73
|
+
"pino-pretty": "^13.1.3",
|
|
74
|
+
"tsx": "^4.19.0",
|
|
75
|
+
"typescript": "^5.7.0",
|
|
76
|
+
"vitest": "^4.1.0"
|
|
77
|
+
},
|
|
78
|
+
"engines": {
|
|
79
|
+
"node": ">=20"
|
|
80
|
+
}
|
|
81
|
+
}
|