mcp-coordinator 0.6.0 → 0.7.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 +24 -0
- package/dist/src/agent-activity.d.ts +13 -9
- package/dist/src/agent-activity.js +45 -24
- package/dist/src/agent-registry.d.ts +7 -7
- package/dist/src/agent-registry.js +19 -18
- package/dist/src/announce-workflow.d.ts +1 -0
- package/dist/src/announce-workflow.js +13 -12
- package/dist/src/auth/providers/registry.d.ts +4 -0
- package/dist/src/auth/providers/registry.js +7 -0
- package/dist/src/auth/providers/types.d.ts +11 -0
- package/dist/src/auth/providers/types.js +1 -0
- package/dist/src/auth.d.ts +24 -5
- package/dist/src/auth.js +172 -23
- package/dist/src/conflict-detector.d.ts +1 -0
- package/dist/src/conflict-detector.js +4 -4
- package/dist/src/consultation.d.ts +28 -14
- package/dist/src/consultation.js +101 -68
- package/dist/src/context-provider.d.ts +2 -2
- package/dist/src/context-provider.js +3 -4
- package/dist/src/database.js +203 -4
- package/dist/src/dependency-map.d.ts +25 -4
- package/dist/src/dependency-map.js +49 -11
- package/dist/src/file-tracker.d.ts +5 -4
- package/dist/src/file-tracker.js +16 -14
- package/dist/src/git-cochange-builder.d.ts +11 -2
- package/dist/src/git-cochange-builder.js +15 -7
- package/dist/src/http/handle-health.d.ts +9 -5
- package/dist/src/http/handle-health.js +22 -8
- package/dist/src/http/handle-rest.d.ts +3 -0
- package/dist/src/http/handle-rest.js +86 -57
- package/dist/src/http/utils.d.ts +4 -0
- package/dist/src/http/utils.js +7 -1
- package/dist/src/impact-scorer.d.ts +3 -0
- package/dist/src/impact-scorer.js +65 -51
- package/dist/src/introspection.d.ts +13 -7
- package/dist/src/introspection.js +34 -11
- package/dist/src/metrics.js +2 -1
- package/dist/src/mqtt-bridge.d.ts +3 -2
- package/dist/src/mqtt-bridge.js +33 -23
- package/dist/src/mqtt-broker.d.ts +16 -7
- package/dist/src/mqtt-broker.js +57 -15
- package/dist/src/security/audit.d.ts +11 -0
- package/dist/src/security/audit.js +7 -0
- package/dist/src/security/encryption.d.ts +17 -0
- package/dist/src/security/encryption.js +5 -0
- package/dist/src/serve-http.js +136 -57
- package/dist/src/server-setup.d.ts +12 -2
- package/dist/src/server-setup.js +33 -15
- package/dist/src/sse-emitter.d.ts +7 -4
- package/dist/src/sse-emitter.js +27 -21
- package/dist/src/tools/agents-tools.d.ts +2 -1
- package/dist/src/tools/agents-tools.js +36 -12
- package/dist/src/tools/consultation-tools.d.ts +2 -1
- package/dist/src/tools/consultation-tools.js +106 -40
- package/dist/src/tools/dependencies-tools.d.ts +2 -1
- package/dist/src/tools/dependencies-tools.js +25 -7
- package/dist/src/tools/files-tools.d.ts +2 -1
- package/dist/src/tools/files-tools.js +26 -8
- package/dist/src/tools/mqtt-tools.d.ts +7 -1
- package/dist/src/tools/mqtt-tools.js +27 -4
- package/dist/src/tools/status-tools.d.ts +7 -1
- package/dist/src/tools/status-tools.js +26 -9
- package/dist/src/types.d.ts +2 -0
- package/dist/src/working-files-tracker.d.ts +21 -11
- package/dist/src/working-files-tracker.js +32 -21
- package/package.json +4 -1
|
@@ -3,44 +3,68 @@ import { z } from "zod";
|
|
|
3
3
|
* S1: agent registry MCP tools (4 tools).
|
|
4
4
|
* register_agent, list_agents, heartbeat, agent_activity.
|
|
5
5
|
*/
|
|
6
|
-
export function registerAgentTools(server, services, mcpLog) {
|
|
6
|
+
export function registerAgentTools(server, services, mcpLog, getSessionClaims) {
|
|
7
7
|
const { registry, activityTracker, sseEmitter, mqttBridge } = services;
|
|
8
8
|
server.tool("register_agent", "Register agent as online with module list", {
|
|
9
9
|
agent_id: z.string(),
|
|
10
10
|
name: z.string(),
|
|
11
11
|
modules: z.array(z.string()),
|
|
12
|
-
}, async ({ agent_id, name, modules }) => {
|
|
12
|
+
}, async ({ agent_id, name, modules }, extra) => {
|
|
13
|
+
const sessionId = extra.sessionId;
|
|
14
|
+
if (!sessionId)
|
|
15
|
+
throw new Error("MCP tool requires a session");
|
|
16
|
+
const claims = getSessionClaims(sessionId);
|
|
17
|
+
if (!claims)
|
|
18
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
13
19
|
mcpLog.info({ tool: "register_agent", agent_id, name, module_count: modules.length }, "Tool called");
|
|
14
|
-
const agent = registry.register(agent_id, name, modules);
|
|
15
|
-
sseEmitter.emit("agent_online", { agent_id, name, modules });
|
|
20
|
+
const agent = registry.register(claims.org, agent_id, name, modules);
|
|
21
|
+
sseEmitter.emit("agent_online", { agent_id, name, modules }, { org_id: claims.org });
|
|
16
22
|
mqttBridge.registerAgent(agent_id, name);
|
|
17
23
|
return { content: [{ type: "text", text: JSON.stringify(agent) }] };
|
|
18
24
|
});
|
|
19
25
|
server.tool("list_agents", "List registered agents", {
|
|
20
26
|
online_only: z.boolean().optional(),
|
|
21
|
-
}, async ({ online_only }) => {
|
|
22
|
-
const
|
|
27
|
+
}, async ({ online_only }, extra) => {
|
|
28
|
+
const sessionId = extra.sessionId;
|
|
29
|
+
if (!sessionId)
|
|
30
|
+
throw new Error("MCP tool requires a session");
|
|
31
|
+
const claims = getSessionClaims(sessionId);
|
|
32
|
+
if (!claims)
|
|
33
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
34
|
+
const agents = online_only ? registry.listOnline(claims.org) : registry.listAll(claims.org);
|
|
23
35
|
return { content: [{ type: "text", text: JSON.stringify(agents) }] };
|
|
24
36
|
});
|
|
25
37
|
server.tool("heartbeat", "Update agent activity status and last seen timestamp", {
|
|
26
38
|
agent_id: z.string(),
|
|
27
39
|
current_file: z.string().optional(),
|
|
28
40
|
current_thread: z.string().optional(),
|
|
29
|
-
}, async ({ agent_id, current_file, current_thread }) => {
|
|
30
|
-
|
|
41
|
+
}, async ({ agent_id, current_file, current_thread }, extra) => {
|
|
42
|
+
const sessionId = extra.sessionId;
|
|
43
|
+
if (!sessionId)
|
|
44
|
+
throw new Error("MCP tool requires a session");
|
|
45
|
+
const claims = getSessionClaims(sessionId);
|
|
46
|
+
if (!claims)
|
|
47
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
48
|
+
registry.heartbeat(claims.org, agent_id);
|
|
31
49
|
activityTracker.heartbeat(agent_id, {
|
|
32
50
|
currentFile: current_file || null,
|
|
33
51
|
currentThread: current_thread || null,
|
|
34
52
|
});
|
|
35
|
-
const activity = activityTracker.getActivity(agent_id);
|
|
53
|
+
const activity = activityTracker.getActivity(claims.org, agent_id);
|
|
36
54
|
sseEmitter.emit("agent_activity", {
|
|
37
55
|
agent_id, activity_status: activity.activity_status,
|
|
38
56
|
current_file: activity.current_file, current_thread: activity.current_thread,
|
|
39
|
-
});
|
|
57
|
+
}, { org_id: claims.org });
|
|
40
58
|
return { content: [{ type: "text", text: JSON.stringify(activity) }] };
|
|
41
59
|
});
|
|
42
|
-
server.tool("agent_activity", "Get activity status for all online agents", {}, async () => {
|
|
43
|
-
const
|
|
60
|
+
server.tool("agent_activity", "Get activity status for all online agents", {}, async (_args, extra) => {
|
|
61
|
+
const sessionId = extra.sessionId;
|
|
62
|
+
if (!sessionId)
|
|
63
|
+
throw new Error("MCP tool requires a session");
|
|
64
|
+
const claims = getSessionClaims(sessionId);
|
|
65
|
+
if (!claims)
|
|
66
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
67
|
+
const activities = activityTracker.listAll(claims.org, { idleAfterMinutes: 5 });
|
|
44
68
|
return { content: [{ type: "text", text: JSON.stringify(activities) }] };
|
|
45
69
|
});
|
|
46
70
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { CoordinatorServices } from "../server-setup.js";
|
|
3
3
|
import type { Logger } from "../logger.js";
|
|
4
|
+
import type { AuthClaims } from "../auth.js";
|
|
4
5
|
/**
|
|
5
6
|
* S1 fix (partial): consultation domain tools extracted from server-setup.ts.
|
|
6
7
|
*
|
|
@@ -18,4 +19,4 @@ import type { Logger } from "../logger.js";
|
|
|
18
19
|
* straightforward following this pattern but kept out of this PR to minimize
|
|
19
20
|
* the diff for reviewers.
|
|
20
21
|
*/
|
|
21
|
-
export declare function registerConsultationTools(server: McpServer, services: CoordinatorServices, mcpLog: Logger): void;
|
|
22
|
+
export declare function registerConsultationTools(server: McpServer, services: CoordinatorServices, mcpLog: Logger, getSessionClaims: (sessionId: string) => AuthClaims | null): void;
|
|
@@ -18,24 +18,30 @@ import { runCommonAnnounceFlow } from "../announce-workflow.js";
|
|
|
18
18
|
* straightforward following this pattern but kept out of this PR to minimize
|
|
19
19
|
* the diff for reviewers.
|
|
20
20
|
*/
|
|
21
|
-
export function registerConsultationTools(server, services, mcpLog) {
|
|
21
|
+
export function registerConsultationTools(server, services, mcpLog, getSessionClaims) {
|
|
22
22
|
const { registry, consultation, conflictDetector, contextProvider, sseEmitter, mqttBridge } = services;
|
|
23
23
|
server.tool("announce_work", "Open a consultation thread before starting work", {
|
|
24
24
|
agent_id: z.string(),
|
|
25
25
|
subject: z.string(),
|
|
26
26
|
plan: z.string().optional(),
|
|
27
27
|
target_modules: z.array(z.string()),
|
|
28
|
-
target_files: z.array(z.string()),
|
|
29
|
-
depends_on_files: z.array(z.string()).optional(),
|
|
30
|
-
exports_affected: z.array(z.string()).optional(),
|
|
28
|
+
target_files: z.array(z.string()).describe("Repo-relative file paths (forward-slash, e.g. 'src/foo.ts'). Absolute paths are not accepted in team-mode."),
|
|
29
|
+
depends_on_files: z.array(z.string()).optional().describe("Repo-relative file paths your work depends on."),
|
|
30
|
+
exports_affected: z.array(z.string()).optional().describe("Repo-relative file paths whose exports your work modifies."),
|
|
31
31
|
keep_open: z.boolean().optional().describe("Keep thread open even if no agents are concerned (for manual coordination like games or debates)"),
|
|
32
32
|
assigned_to: z.string().optional().describe("Directed-dispatch: only this agent_id will be allowed to claim the thread. Use for lead→worker handoffs in maitre/chaine/relais presets. Implies keep_open=true."),
|
|
33
33
|
target_symbols: z.array(z.string().max(256)).max(200).optional()
|
|
34
34
|
.describe("Qualified symbol names you intend to touch (e.g. 'UserService.getById'). Used by Layer 0.5 to annotate same-file overlaps."),
|
|
35
|
-
}, async ({ agent_id, subject, plan, target_modules, target_files, depends_on_files, exports_affected, keep_open, assigned_to, target_symbols }) => {
|
|
35
|
+
}, async ({ agent_id, subject, plan, target_modules, target_files, depends_on_files, exports_affected, keep_open, assigned_to, target_symbols }, extra) => {
|
|
36
|
+
const sessionId = extra.sessionId;
|
|
37
|
+
if (!sessionId)
|
|
38
|
+
throw new Error("MCP tool requires a session");
|
|
39
|
+
const claims = getSessionClaims(sessionId);
|
|
40
|
+
if (!claims)
|
|
41
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
36
42
|
mcpLog.info({ tool: "announce_work", agent_id, subject, target_modules, target_files, assigned_to }, "Tool called");
|
|
37
|
-
const conflicts = conflictDetector.detect({ agent_id, target_modules, target_files });
|
|
38
|
-
const thread = consultation.announceWork({
|
|
43
|
+
const conflicts = conflictDetector.detect({ org_id: claims.org, agent_id, target_modules, target_files });
|
|
44
|
+
const thread = consultation.announceWork(claims.org, {
|
|
39
45
|
agent_id, subject, plan, target_modules, target_files, depends_on_files, exports_affected, keep_open, assigned_to,
|
|
40
46
|
});
|
|
41
47
|
if (conflicts.length > 0) {
|
|
@@ -43,7 +49,7 @@ export function registerConsultationTools(server, services, mcpLog) {
|
|
|
43
49
|
.run(JSON.stringify(conflicts), thread.id);
|
|
44
50
|
}
|
|
45
51
|
const { updated, categorized, respondents, planQuality } = runCommonAnnounceFlow(services, thread.id, {
|
|
46
|
-
agent_id, subject, plan, target_modules, target_files, depends_on_files, exports_affected, keep_open, target_symbols,
|
|
52
|
+
org_id: claims.org, agent_id, subject, plan, target_modules, target_files, depends_on_files, exports_affected, keep_open, target_symbols,
|
|
47
53
|
});
|
|
48
54
|
sseEmitter.emit("thread_opened", {
|
|
49
55
|
thread_id: thread.id, initiator: agent_id, subject, target_modules, conflicts,
|
|
@@ -51,9 +57,9 @@ export function registerConsultationTools(server, services, mcpLog) {
|
|
|
51
57
|
mode: planQuality.mode,
|
|
52
58
|
plan: plan || null,
|
|
53
59
|
plan_quality: planQuality,
|
|
54
|
-
});
|
|
60
|
+
}, { org_id: claims.org });
|
|
55
61
|
mqttBridge.publishConsultation(thread.id, agent_id, subject, target_modules);
|
|
56
|
-
const contextForInitiator = respondents.map((rid) => contextProvider.getRelevantContext(rid, { thread_id: updated.id, subject, target_modules, target_files })).filter((ctx) => ctx.modules.length > 0);
|
|
62
|
+
const contextForInitiator = respondents.map((rid) => contextProvider.getRelevantContext(claims.org, rid, { thread_id: updated.id, subject, target_modules, target_files })).filter((ctx) => ctx.modules.length > 0);
|
|
57
63
|
return {
|
|
58
64
|
content: [{
|
|
59
65
|
type: "text",
|
|
@@ -69,17 +75,23 @@ export function registerConsultationTools(server, services, mcpLog) {
|
|
|
69
75
|
content: z.string(),
|
|
70
76
|
context_snapshot: z.string().optional(),
|
|
71
77
|
in_reply_to: z.string().optional(),
|
|
72
|
-
}, async ({ thread_id, agent_id, agent_name, type, content, context_snapshot, in_reply_to }) => {
|
|
78
|
+
}, async ({ thread_id, agent_id, agent_name, type, content, context_snapshot, in_reply_to }, extra) => {
|
|
79
|
+
const sessionId = extra.sessionId;
|
|
80
|
+
if (!sessionId)
|
|
81
|
+
throw new Error("MCP tool requires a session");
|
|
82
|
+
const claims = getSessionClaims(sessionId);
|
|
83
|
+
if (!claims)
|
|
84
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
73
85
|
mcpLog.info({ tool: "post_to_thread", thread_id, agent_id, type }, "Tool called");
|
|
74
|
-
const msg = consultation.postToThread({
|
|
86
|
+
const msg = consultation.postToThread(claims.org, {
|
|
75
87
|
thread_id, agent_id, agent_name, type, content, context_snapshot, in_reply_to,
|
|
76
88
|
});
|
|
77
|
-
const thread = consultation.getThread(thread_id);
|
|
89
|
+
const thread = consultation.getThread(claims.org, thread_id);
|
|
78
90
|
sseEmitter.emit("message_posted", {
|
|
79
91
|
thread_id, agent_id, agent_name: agent_name || agent_id,
|
|
80
92
|
type, content, round: thread?.round || 1,
|
|
81
93
|
token_estimate: msg.token_estimate || 0,
|
|
82
|
-
});
|
|
94
|
+
}, { org_id: claims.org });
|
|
83
95
|
mqttBridge.publishMessage(thread_id, agent_id, type, content);
|
|
84
96
|
return { content: [{ type: "text", text: JSON.stringify(msg) }] };
|
|
85
97
|
});
|
|
@@ -88,65 +100,107 @@ export function registerConsultationTools(server, services, mcpLog) {
|
|
|
88
100
|
agent_id: z.string(),
|
|
89
101
|
summary: z.string(),
|
|
90
102
|
plan: z.string().optional(),
|
|
91
|
-
}, async ({ thread_id, agent_id, summary }) => {
|
|
103
|
+
}, async ({ thread_id, agent_id, summary }, extra) => {
|
|
104
|
+
const sessionId = extra.sessionId;
|
|
105
|
+
if (!sessionId)
|
|
106
|
+
throw new Error("MCP tool requires a session");
|
|
107
|
+
const claims = getSessionClaims(sessionId);
|
|
108
|
+
if (!claims)
|
|
109
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
92
110
|
mcpLog.info({ tool: "propose_resolution", thread_id, agent_id }, "Tool called");
|
|
93
|
-
consultation.proposeResolution(thread_id, agent_id, summary);
|
|
94
|
-
sseEmitter.emit("resolution_proposed", { thread_id, agent_id, summary });
|
|
111
|
+
consultation.proposeResolution(claims.org, thread_id, agent_id, summary);
|
|
112
|
+
sseEmitter.emit("resolution_proposed", { thread_id, agent_id, summary }, { org_id: claims.org });
|
|
95
113
|
mqttBridge.publishResolution(thread_id, "resolving", summary);
|
|
96
|
-
const thread = consultation.getThread(thread_id);
|
|
114
|
+
const thread = consultation.getThread(claims.org, thread_id);
|
|
97
115
|
return { content: [{ type: "text", text: JSON.stringify(thread) }] };
|
|
98
116
|
});
|
|
99
117
|
server.tool("approve_resolution", "Approve the proposed resolution", {
|
|
100
118
|
thread_id: z.string(),
|
|
101
119
|
agent_id: z.string(),
|
|
102
|
-
}, async ({ thread_id, agent_id }) => {
|
|
120
|
+
}, async ({ thread_id, agent_id }, extra) => {
|
|
121
|
+
const sessionId = extra.sessionId;
|
|
122
|
+
if (!sessionId)
|
|
123
|
+
throw new Error("MCP tool requires a session");
|
|
124
|
+
const claims = getSessionClaims(sessionId);
|
|
125
|
+
if (!claims)
|
|
126
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
103
127
|
mcpLog.info({ tool: "approve_resolution", thread_id, agent_id }, "Tool called");
|
|
104
|
-
const agentInfo = registry.get(agent_id);
|
|
105
|
-
consultation.approveResolution(thread_id, agent_id, agentInfo?.name);
|
|
106
|
-
const thread = consultation.getThread(thread_id);
|
|
128
|
+
const agentInfo = registry.get(claims.org, agent_id);
|
|
129
|
+
consultation.approveResolution(claims.org, thread_id, agent_id, agentInfo?.name ?? undefined);
|
|
130
|
+
const thread = consultation.getThread(claims.org, thread_id);
|
|
107
131
|
return { content: [{ type: "text", text: JSON.stringify(thread) }] };
|
|
108
132
|
});
|
|
109
133
|
server.tool("contest_resolution", "Contest the proposed resolution", {
|
|
110
134
|
thread_id: z.string(),
|
|
111
135
|
agent_id: z.string(),
|
|
112
136
|
reason: z.string(),
|
|
113
|
-
}, async ({ thread_id, agent_id, reason }) => {
|
|
137
|
+
}, async ({ thread_id, agent_id, reason }, extra) => {
|
|
138
|
+
const sessionId = extra.sessionId;
|
|
139
|
+
if (!sessionId)
|
|
140
|
+
throw new Error("MCP tool requires a session");
|
|
141
|
+
const claims = getSessionClaims(sessionId);
|
|
142
|
+
if (!claims)
|
|
143
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
114
144
|
mcpLog.info({ tool: "contest_resolution", thread_id, agent_id }, "Tool called");
|
|
115
|
-
consultation.contestResolution(thread_id, agent_id, reason);
|
|
116
|
-
const thread = consultation.getThread(thread_id);
|
|
145
|
+
consultation.contestResolution(claims.org, thread_id, agent_id, reason);
|
|
146
|
+
const thread = consultation.getThread(claims.org, thread_id);
|
|
117
147
|
return { content: [{ type: "text", text: JSON.stringify(thread) }] };
|
|
118
148
|
});
|
|
119
149
|
server.tool("close_thread", "Close a consultation thread", {
|
|
120
150
|
thread_id: z.string(),
|
|
121
151
|
agent_id: z.string(),
|
|
122
152
|
summary: z.string(),
|
|
123
|
-
}, async ({ thread_id, agent_id, summary }) => {
|
|
153
|
+
}, async ({ thread_id, agent_id, summary }, extra) => {
|
|
154
|
+
const sessionId = extra.sessionId;
|
|
155
|
+
if (!sessionId)
|
|
156
|
+
throw new Error("MCP tool requires a session");
|
|
157
|
+
const claims = getSessionClaims(sessionId);
|
|
158
|
+
if (!claims)
|
|
159
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
124
160
|
mcpLog.info({ tool: "close_thread", thread_id, agent_id }, "Tool called");
|
|
125
|
-
consultation.closeThread(thread_id, agent_id, summary);
|
|
161
|
+
consultation.closeThread(claims.org, thread_id, agent_id, summary);
|
|
126
162
|
return { content: [{ type: "text", text: "closed" }] };
|
|
127
163
|
});
|
|
128
164
|
server.tool("cancel_thread", "Cancel a consultation thread", {
|
|
129
165
|
thread_id: z.string(),
|
|
130
166
|
agent_id: z.string(),
|
|
131
167
|
reason: z.string().optional(),
|
|
132
|
-
}, async ({ thread_id, agent_id, reason }) => {
|
|
168
|
+
}, async ({ thread_id, agent_id, reason }, extra) => {
|
|
169
|
+
const sessionId = extra.sessionId;
|
|
170
|
+
if (!sessionId)
|
|
171
|
+
throw new Error("MCP tool requires a session");
|
|
172
|
+
const claims = getSessionClaims(sessionId);
|
|
173
|
+
if (!claims)
|
|
174
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
133
175
|
mcpLog.info({ tool: "cancel_thread", thread_id, agent_id }, "Tool called");
|
|
134
|
-
consultation.cancelThread(thread_id, agent_id, reason);
|
|
135
|
-
sseEmitter.emit("thread_cancelled", { thread_id, reason });
|
|
176
|
+
consultation.cancelThread(claims.org, thread_id, agent_id, reason ?? undefined);
|
|
177
|
+
sseEmitter.emit("thread_cancelled", { thread_id, reason }, { org_id: claims.org });
|
|
136
178
|
return { content: [{ type: "text", text: "cancelled" }] };
|
|
137
179
|
});
|
|
138
180
|
server.tool("get_thread", "Get a thread with all messages", {
|
|
139
181
|
thread_id: z.string(),
|
|
140
|
-
}, async ({ thread_id }) => {
|
|
141
|
-
const
|
|
182
|
+
}, async ({ thread_id }, extra) => {
|
|
183
|
+
const sessionId = extra.sessionId;
|
|
184
|
+
if (!sessionId)
|
|
185
|
+
throw new Error("MCP tool requires a session");
|
|
186
|
+
const claims = getSessionClaims(sessionId);
|
|
187
|
+
if (!claims)
|
|
188
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
189
|
+
const result = consultation.getThreadWithMessages(claims.org, thread_id);
|
|
142
190
|
mcpLog.debug({ tool: "get_thread", thread_id, message_count: result?.messages.length }, "Tool called");
|
|
143
191
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
144
192
|
});
|
|
145
193
|
server.tool("get_thread_updates", "Get new messages since timestamp", {
|
|
146
194
|
agent_id: z.string(),
|
|
147
195
|
since: z.string().optional(),
|
|
148
|
-
}, async ({ agent_id, since }) => {
|
|
149
|
-
const
|
|
196
|
+
}, async ({ agent_id, since }, extra) => {
|
|
197
|
+
const sessionId = extra.sessionId;
|
|
198
|
+
if (!sessionId)
|
|
199
|
+
throw new Error("MCP tool requires a session");
|
|
200
|
+
const claims = getSessionClaims(sessionId);
|
|
201
|
+
if (!claims)
|
|
202
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
203
|
+
const updates = consultation.getThreadUpdates(claims.org, agent_id, since ?? undefined);
|
|
150
204
|
return { content: [{ type: "text", text: JSON.stringify(updates) }] };
|
|
151
205
|
});
|
|
152
206
|
server.tool("list_threads", "List consultation threads", {
|
|
@@ -154,19 +208,31 @@ export function registerConsultationTools(server, services, mcpLog) {
|
|
|
154
208
|
agent_id: z.string().optional(),
|
|
155
209
|
module: z.string().optional(),
|
|
156
210
|
assigned_to_me: z.string().optional().describe("Filter to threads claimable by this agent_id: open pool (assigned_to NULL) OR directed to me. Use for worker agents receiving directed dispatches."),
|
|
157
|
-
}, async ({ status, agent_id, module, assigned_to_me }) => {
|
|
158
|
-
const
|
|
211
|
+
}, async ({ status, agent_id, module, assigned_to_me }, extra) => {
|
|
212
|
+
const sessionId = extra.sessionId;
|
|
213
|
+
if (!sessionId)
|
|
214
|
+
throw new Error("MCP tool requires a session");
|
|
215
|
+
const claims = getSessionClaims(sessionId);
|
|
216
|
+
if (!claims)
|
|
217
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
218
|
+
const threads = consultation.listThreads(claims.org, { status, agent_id, module, assigned_to_me });
|
|
159
219
|
mcpLog.debug({ tool: "list_threads", status, agent_id, module, assigned_to_me, result_count: threads.length }, "Tool called");
|
|
160
220
|
return { content: [{ type: "text", text: JSON.stringify(threads) }] };
|
|
161
221
|
});
|
|
162
222
|
server.tool("log_action_summary", "Log a one-liner summary of an action", {
|
|
163
223
|
session_id: z.string(),
|
|
164
224
|
agent_id: z.string(),
|
|
165
|
-
file_path: z.string().optional(),
|
|
225
|
+
file_path: z.string().optional().describe("Repo-relative file path."),
|
|
166
226
|
summary: z.string(),
|
|
167
|
-
}, async ({ session_id, agent_id, file_path, summary }) => {
|
|
168
|
-
const
|
|
169
|
-
|
|
227
|
+
}, async ({ session_id, agent_id, file_path, summary }, extra) => {
|
|
228
|
+
const sessionId = extra.sessionId;
|
|
229
|
+
if (!sessionId)
|
|
230
|
+
throw new Error("MCP tool requires a session");
|
|
231
|
+
const claims = getSessionClaims(sessionId);
|
|
232
|
+
if (!claims)
|
|
233
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
234
|
+
const result = consultation.logActionSummary(claims.org, { session_id, agent_id, file_path, summary });
|
|
235
|
+
sseEmitter.emit("action_summary", { agent_id, file_path, summary }, { org_id: claims.org });
|
|
170
236
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
171
237
|
});
|
|
172
238
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { CoordinatorServices } from "../server-setup.js";
|
|
3
3
|
import type { Logger } from "../logger.js";
|
|
4
|
+
import type { AuthClaims } from "../auth.js";
|
|
4
5
|
/**
|
|
5
6
|
* S1: dependency map MCP tools (3 tools).
|
|
6
7
|
* set_dependency_map, get_blast_radius, get_module_info.
|
|
7
8
|
*/
|
|
8
|
-
export declare function registerDependenciesTools(server: McpServer, services: CoordinatorServices, _mcpLog: Logger): void;
|
|
9
|
+
export declare function registerDependenciesTools(server: McpServer, services: CoordinatorServices, _mcpLog: Logger, getSessionClaims: (sessionId: string) => AuthClaims | null): void;
|
|
@@ -3,25 +3,43 @@ import { z } from "zod";
|
|
|
3
3
|
* S1: dependency map MCP tools (3 tools).
|
|
4
4
|
* set_dependency_map, get_blast_radius, get_module_info.
|
|
5
5
|
*/
|
|
6
|
-
export function registerDependenciesTools(server, services, _mcpLog) {
|
|
6
|
+
export function registerDependenciesTools(server, services, _mcpLog, getSessionClaims) {
|
|
7
7
|
const { depMap } = services;
|
|
8
8
|
server.tool("set_dependency_map", "Load module dependency graph", {
|
|
9
9
|
modules: z.string(), // JSON DependencyMap
|
|
10
|
-
}, async ({ modules }) => {
|
|
10
|
+
}, async ({ modules }, extra) => {
|
|
11
|
+
const sessionId = extra.sessionId;
|
|
12
|
+
if (!sessionId)
|
|
13
|
+
throw new Error("MCP tool requires a session");
|
|
14
|
+
const claims = getSessionClaims(sessionId);
|
|
15
|
+
if (!claims)
|
|
16
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
11
17
|
const map = JSON.parse(modules);
|
|
12
|
-
depMap.setMap(map);
|
|
18
|
+
depMap.setMap(claims.org, map);
|
|
13
19
|
return { content: [{ type: "text", text: "ok" }] };
|
|
14
20
|
});
|
|
15
21
|
server.tool("get_blast_radius", "Calculate impact of changes to a module", {
|
|
16
22
|
module_id: z.string(),
|
|
17
|
-
}, async ({ module_id }) => {
|
|
18
|
-
const
|
|
23
|
+
}, async ({ module_id }, extra) => {
|
|
24
|
+
const sessionId = extra.sessionId;
|
|
25
|
+
if (!sessionId)
|
|
26
|
+
throw new Error("MCP tool requires a session");
|
|
27
|
+
const claims = getSessionClaims(sessionId);
|
|
28
|
+
if (!claims)
|
|
29
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
30
|
+
const radius = depMap.getBlastRadius(claims.org, module_id);
|
|
19
31
|
return { content: [{ type: "text", text: JSON.stringify(radius) }] };
|
|
20
32
|
});
|
|
21
33
|
server.tool("get_module_info", "Get module dependency info", {
|
|
22
34
|
module_id: z.string(),
|
|
23
|
-
}, async ({ module_id }) => {
|
|
24
|
-
const
|
|
35
|
+
}, async ({ module_id }, extra) => {
|
|
36
|
+
const sessionId = extra.sessionId;
|
|
37
|
+
if (!sessionId)
|
|
38
|
+
throw new Error("MCP tool requires a session");
|
|
39
|
+
const claims = getSessionClaims(sessionId);
|
|
40
|
+
if (!claims)
|
|
41
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
42
|
+
const info = depMap.getModuleInfo(claims.org, module_id);
|
|
25
43
|
return { content: [{ type: "text", text: JSON.stringify(info) }] };
|
|
26
44
|
});
|
|
27
45
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { CoordinatorServices } from "../server-setup.js";
|
|
3
3
|
import type { Logger } from "../logger.js";
|
|
4
|
+
import type { AuthClaims } from "../auth.js";
|
|
4
5
|
/**
|
|
5
6
|
* S1: file tracking MCP tools (3 tools).
|
|
6
7
|
* hot_files, get_session_files, check_file_conflict.
|
|
7
8
|
*/
|
|
8
|
-
export declare function registerFilesTools(server: McpServer, services: CoordinatorServices, _mcpLog: Logger): void;
|
|
9
|
+
export declare function registerFilesTools(server: McpServer, services: CoordinatorServices, _mcpLog: Logger, getSessionClaims: (sessionId: string) => AuthClaims | null): void;
|
|
@@ -3,26 +3,44 @@ import { z } from "zod";
|
|
|
3
3
|
* S1: file tracking MCP tools (3 tools).
|
|
4
4
|
* hot_files, get_session_files, check_file_conflict.
|
|
5
5
|
*/
|
|
6
|
-
export function registerFilesTools(server, services, _mcpLog) {
|
|
6
|
+
export function registerFilesTools(server, services, _mcpLog, getSessionClaims) {
|
|
7
7
|
const { fileTracker } = services;
|
|
8
8
|
server.tool("hot_files", "List files modified by multiple agents", {
|
|
9
9
|
since_minutes: z.number().optional(),
|
|
10
|
-
}, async ({ since_minutes }) => {
|
|
11
|
-
const
|
|
10
|
+
}, async ({ since_minutes }, extra) => {
|
|
11
|
+
const sessionId = extra.sessionId;
|
|
12
|
+
if (!sessionId)
|
|
13
|
+
throw new Error("MCP tool requires a session");
|
|
14
|
+
const claims = getSessionClaims(sessionId);
|
|
15
|
+
if (!claims)
|
|
16
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
17
|
+
const files = fileTracker.getHotFiles(claims.org, since_minutes || 30);
|
|
12
18
|
return { content: [{ type: "text", text: JSON.stringify(files) }] };
|
|
13
19
|
});
|
|
14
20
|
server.tool("get_session_files", "Get files modified in a session", {
|
|
15
21
|
session_id: z.string(),
|
|
16
|
-
}, async ({ session_id }) => {
|
|
17
|
-
const
|
|
22
|
+
}, async ({ session_id }, extra) => {
|
|
23
|
+
const sessionId = extra.sessionId;
|
|
24
|
+
if (!sessionId)
|
|
25
|
+
throw new Error("MCP tool requires a session");
|
|
26
|
+
const claims = getSessionClaims(sessionId);
|
|
27
|
+
if (!claims)
|
|
28
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
29
|
+
const files = fileTracker.getBySession(claims.org, session_id);
|
|
18
30
|
return { content: [{ type: "text", text: JSON.stringify(files) }] };
|
|
19
31
|
});
|
|
20
32
|
server.tool("check_file_conflict", "Check if another agent is editing a file", {
|
|
21
|
-
file_path: z.string(),
|
|
33
|
+
file_path: z.string().describe("Repo-relative file path."),
|
|
22
34
|
agent_id: z.string(),
|
|
23
35
|
within_minutes: z.number().optional(),
|
|
24
|
-
}, async ({ file_path, agent_id, within_minutes }) => {
|
|
25
|
-
const
|
|
36
|
+
}, async ({ file_path, agent_id, within_minutes }, extra) => {
|
|
37
|
+
const sessionId = extra.sessionId;
|
|
38
|
+
if (!sessionId)
|
|
39
|
+
throw new Error("MCP tool requires a session");
|
|
40
|
+
const claims = getSessionClaims(sessionId);
|
|
41
|
+
if (!claims)
|
|
42
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
43
|
+
const result = fileTracker.checkFileConflict(claims.org, file_path, agent_id, within_minutes || 30);
|
|
26
44
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
27
45
|
});
|
|
28
46
|
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { CoordinatorServices } from "../server-setup.js";
|
|
3
3
|
import type { Logger } from "../logger.js";
|
|
4
|
+
import type { AuthClaims } from "../auth.js";
|
|
4
5
|
/**
|
|
5
6
|
* S1: MQTT listener MCP tools (3 tools).
|
|
6
7
|
* wait_for_message, get_queued_messages, mqtt_publish.
|
|
7
8
|
* Replaces what used to be a standalone mqtt-mcp-bridge sidecar.
|
|
9
|
+
*
|
|
10
|
+
* Note (Task 23.5): the MqttBridge message-queue API is keyed by agent_id,
|
|
11
|
+
* not by org. MQTT topic scoping and per-org ACLs are deferred to Task 22.
|
|
12
|
+
* We still require valid session claims here so callers are authenticated,
|
|
13
|
+
* even though the underlying bridge calls don't yet filter by claims.org.
|
|
8
14
|
*/
|
|
9
|
-
export declare function registerMqttTools(server: McpServer, services: CoordinatorServices, _mcpLog: Logger): void;
|
|
15
|
+
export declare function registerMqttTools(server: McpServer, services: CoordinatorServices, _mcpLog: Logger, getSessionClaims: (sessionId: string) => AuthClaims | null): void;
|
|
@@ -3,13 +3,24 @@ import { z } from "zod";
|
|
|
3
3
|
* S1: MQTT listener MCP tools (3 tools).
|
|
4
4
|
* wait_for_message, get_queued_messages, mqtt_publish.
|
|
5
5
|
* Replaces what used to be a standalone mqtt-mcp-bridge sidecar.
|
|
6
|
+
*
|
|
7
|
+
* Note (Task 23.5): the MqttBridge message-queue API is keyed by agent_id,
|
|
8
|
+
* not by org. MQTT topic scoping and per-org ACLs are deferred to Task 22.
|
|
9
|
+
* We still require valid session claims here so callers are authenticated,
|
|
10
|
+
* even though the underlying bridge calls don't yet filter by claims.org.
|
|
6
11
|
*/
|
|
7
|
-
export function registerMqttTools(server, services, _mcpLog) {
|
|
12
|
+
export function registerMqttTools(server, services, _mcpLog, getSessionClaims) {
|
|
8
13
|
const { mqttBridge } = services;
|
|
9
14
|
server.tool("wait_for_message", "Block until an MQTT consultation message arrives or timeout", {
|
|
10
15
|
agent_id: z.string(),
|
|
11
16
|
timeout_seconds: z.number().optional(),
|
|
12
|
-
}, async ({ agent_id, timeout_seconds }) => {
|
|
17
|
+
}, async ({ agent_id, timeout_seconds }, extra) => {
|
|
18
|
+
const sessionId = extra.sessionId;
|
|
19
|
+
if (!sessionId)
|
|
20
|
+
throw new Error("MCP tool requires a session");
|
|
21
|
+
const claims = getSessionClaims(sessionId);
|
|
22
|
+
if (!claims)
|
|
23
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
13
24
|
const timeoutMs = (timeout_seconds || 15) * 1000;
|
|
14
25
|
const msg = await mqttBridge.waitForMessage(agent_id, timeoutMs);
|
|
15
26
|
if (msg) {
|
|
@@ -19,14 +30,26 @@ export function registerMqttTools(server, services, _mcpLog) {
|
|
|
19
30
|
});
|
|
20
31
|
server.tool("get_queued_messages", "Get all queued MQTT messages without blocking", {
|
|
21
32
|
agent_id: z.string(),
|
|
22
|
-
}, async ({ agent_id }) => {
|
|
33
|
+
}, async ({ agent_id }, extra) => {
|
|
34
|
+
const sessionId = extra.sessionId;
|
|
35
|
+
if (!sessionId)
|
|
36
|
+
throw new Error("MCP tool requires a session");
|
|
37
|
+
const claims = getSessionClaims(sessionId);
|
|
38
|
+
if (!claims)
|
|
39
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
23
40
|
const messages = mqttBridge.getQueuedMessages(agent_id);
|
|
24
41
|
return { content: [{ type: "text", text: JSON.stringify(messages) }] };
|
|
25
42
|
});
|
|
26
43
|
server.tool("mqtt_publish", "Publish a message to an MQTT topic", {
|
|
27
44
|
topic: z.string(),
|
|
28
45
|
payload: z.string(),
|
|
29
|
-
}, async ({ topic, payload }) => {
|
|
46
|
+
}, async ({ topic, payload }, extra) => {
|
|
47
|
+
const sessionId = extra.sessionId;
|
|
48
|
+
if (!sessionId)
|
|
49
|
+
throw new Error("MCP tool requires a session");
|
|
50
|
+
const claims = getSessionClaims(sessionId);
|
|
51
|
+
if (!claims)
|
|
52
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
30
53
|
mqttBridge.mqttPublish(topic, payload);
|
|
31
54
|
return { content: [{ type: "text", text: "published" }] };
|
|
32
55
|
});
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { CoordinatorServices } from "../server-setup.js";
|
|
3
3
|
import type { Logger } from "../logger.js";
|
|
4
|
+
import type { AuthClaims } from "../auth.js";
|
|
4
5
|
/**
|
|
5
6
|
* S1: status + coordination helper MCP tools (2 tools).
|
|
6
7
|
* coordinator_status, wait_for_peers.
|
|
8
|
+
*
|
|
9
|
+
* Note (Task 23.5): both tools are org-scoped — coordinator_status reports
|
|
10
|
+
* agents/threads/files for the caller's org, and wait_for_peers polls the
|
|
11
|
+
* same org's registry. Neither surfaces system-global (cross-org) data, so
|
|
12
|
+
* both require valid claims and use claims.org throughout.
|
|
7
13
|
*/
|
|
8
|
-
export declare function registerStatusTools(server: McpServer, services: CoordinatorServices, mcpLog: Logger): void;
|
|
14
|
+
export declare function registerStatusTools(server: McpServer, services: CoordinatorServices, mcpLog: Logger, getSessionClaims: (sessionId: string) => AuthClaims | null): void;
|
|
@@ -2,14 +2,25 @@ import { z } from "zod";
|
|
|
2
2
|
/**
|
|
3
3
|
* S1: status + coordination helper MCP tools (2 tools).
|
|
4
4
|
* coordinator_status, wait_for_peers.
|
|
5
|
+
*
|
|
6
|
+
* Note (Task 23.5): both tools are org-scoped — coordinator_status reports
|
|
7
|
+
* agents/threads/files for the caller's org, and wait_for_peers polls the
|
|
8
|
+
* same org's registry. Neither surfaces system-global (cross-org) data, so
|
|
9
|
+
* both require valid claims and use claims.org throughout.
|
|
5
10
|
*/
|
|
6
|
-
export function registerStatusTools(server, services, mcpLog) {
|
|
11
|
+
export function registerStatusTools(server, services, mcpLog, getSessionClaims) {
|
|
7
12
|
const { registry, consultation, fileTracker, mqttBridge } = services;
|
|
8
|
-
server.tool("coordinator_status", "Full system status", {}, async () => {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
+
server.tool("coordinator_status", "Full system status", {}, async (_args, extra) => {
|
|
14
|
+
const sessionId = extra.sessionId;
|
|
15
|
+
if (!sessionId)
|
|
16
|
+
throw new Error("MCP tool requires a session");
|
|
17
|
+
const claims = getSessionClaims(sessionId);
|
|
18
|
+
if (!claims)
|
|
19
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
20
|
+
const online = registry.listOnline(claims.org);
|
|
21
|
+
const openThreads = consultation.listThreads(claims.org, { status: "open" });
|
|
22
|
+
const resolvingThreads = consultation.listThreads(claims.org, { status: "resolving" });
|
|
23
|
+
const hotFiles = fileTracker.getHotFiles(claims.org, 30);
|
|
13
24
|
const status = {
|
|
14
25
|
agents_online: online.length,
|
|
15
26
|
agents: online.map((a) => ({ id: a.id, name: a.name, modules: JSON.parse(a.modules) })),
|
|
@@ -25,14 +36,20 @@ export function registerStatusTools(server, services, mcpLog) {
|
|
|
25
36
|
agent_id: z.string(),
|
|
26
37
|
min_peers: z.number().optional(),
|
|
27
38
|
timeout_seconds: z.number().optional(),
|
|
28
|
-
}, async ({ agent_id, min_peers, timeout_seconds }) => {
|
|
39
|
+
}, async ({ agent_id, min_peers, timeout_seconds }, extra) => {
|
|
40
|
+
const sessionId = extra.sessionId;
|
|
41
|
+
if (!sessionId)
|
|
42
|
+
throw new Error("MCP tool requires a session");
|
|
43
|
+
const claims = getSessionClaims(sessionId);
|
|
44
|
+
if (!claims)
|
|
45
|
+
throw new Error("Session has no captured claims (auth bug)");
|
|
29
46
|
const targetPeers = min_peers ?? 1;
|
|
30
47
|
const timeoutMs = (timeout_seconds ?? 30) * 1000;
|
|
31
48
|
const pollIntervalMs = 1000;
|
|
32
49
|
const startedAt = Date.now();
|
|
33
50
|
mcpLog.info({ tool: "wait_for_peers", agent_id, min_peers: targetPeers, timeout_seconds: timeoutMs / 1000 }, "Tool called");
|
|
34
51
|
while (Date.now() - startedAt < timeoutMs) {
|
|
35
|
-
const peers = registry.listOnline().filter((a) => a.id !== agent_id);
|
|
52
|
+
const peers = registry.listOnline(claims.org).filter((a) => a.id !== agent_id);
|
|
36
53
|
if (peers.length >= targetPeers) {
|
|
37
54
|
return {
|
|
38
55
|
content: [{
|
|
@@ -47,7 +64,7 @@ export function registerStatusTools(server, services, mcpLog) {
|
|
|
47
64
|
}
|
|
48
65
|
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
49
66
|
}
|
|
50
|
-
const finalPeers = registry.listOnline().filter((a) => a.id !== agent_id);
|
|
67
|
+
const finalPeers = registry.listOnline(claims.org).filter((a) => a.id !== agent_id);
|
|
51
68
|
return {
|
|
52
69
|
content: [{
|
|
53
70
|
type: "text",
|