@spencerbeggs/claude-coordinator-mcp 0.1.0 → 0.1.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.
@@ -1,18 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import { DEFAULT_URL, createMcpServer } from "../433.js";
2
+ import { createMcpServer } from "../mcp-server.js";
3
+ import { DEFAULT_URL } from "@spencerbeggs/claude-coordinator-core";
4
+
5
+ //#region src/bin/cli.ts
3
6
  const args = process.argv.slice(2);
4
7
  let url = DEFAULT_URL;
5
- for(let i = 0; i < args.length; i++){
6
- const arg = args[i];
7
- if ("--url" === arg && args[i + 1]) {
8
- url = args[i + 1];
9
- i++;
10
- } else if (arg?.startsWith("--url=")) url = arg.slice(6);
8
+ for (let i = 0; i < args.length; i++) {
9
+ const arg = args[i];
10
+ if (arg === "--url" && args[i + 1]) {
11
+ url = args[i + 1];
12
+ i++;
13
+ } else if (arg?.startsWith("--url=")) url = arg.slice(6);
11
14
  }
12
15
  console.error(`[coordinator-mcp] Connecting to coordinator at ${url}`);
13
- createMcpServer({
14
- url: url
15
- }).catch((error)=>{
16
- console.error("[coordinator-mcp] Failed to start:", error);
17
- process.exit(1);
16
+ createMcpServer({ url }).catch((error) => {
17
+ console.error("[coordinator-mcp] Failed to start:", error);
18
+ process.exit(1);
18
19
  });
20
+
21
+ //#endregion
22
+ export { };
package/client.js ADDED
@@ -0,0 +1,27 @@
1
+ import { DEFAULT_URL } from "@spencerbeggs/claude-coordinator-core";
2
+ import { createTRPCClient, createWSClient, wsLink } from "@trpc/client";
3
+ import WebSocket from "ws";
4
+
5
+ //#region src/client.ts
6
+ /**
7
+ * Create a tRPC client that connects to the coordinator server
8
+ *
9
+ * @public
10
+ */
11
+ function createCoordinatorClient(options = {}) {
12
+ const wsClient = createWSClient({
13
+ url: options.url ?? DEFAULT_URL,
14
+ WebSocket
15
+ });
16
+ const trpc = createTRPCClient({ links: [wsLink({ client: wsClient })] });
17
+ const close = () => {
18
+ wsClient.close();
19
+ };
20
+ return {
21
+ trpc,
22
+ close
23
+ };
24
+ }
25
+
26
+ //#endregion
27
+ export { createCoordinatorClient };
package/index.d.ts CHANGED
@@ -1,126 +1,121 @@
1
- /**
2
- * \@spencerbeggs/claude-coordinator-mcp
3
- *
4
- * MCP stdio bridge for the Claude Coordinator system.
5
- * Provides MCP tools that Claude Code can use to communicate
6
- * with other Claude Code instances through the coordinator server.
7
- *
8
- * @packageDocumentation
9
- */
10
-
11
- import type { Agent } from '@spencerbeggs/claude-coordinator-core';
12
- import type { ContextEntry } from '@spencerbeggs/claude-coordinator-core';
13
- import type { Decision } from '@spencerbeggs/claude-coordinator-core';
14
- import type { JoinInput } from '@spencerbeggs/claude-coordinator-core';
15
- import type { JoinResult } from '@spencerbeggs/claude-coordinator-core';
16
- import type { Question } from '@spencerbeggs/claude-coordinator-core';
17
-
18
- /**
19
- * Options for creating the coordinator client
20
- */
21
- export declare interface ClientOptions {
22
- url?: string;
23
- }
24
-
25
- /**
26
- * Coordinator client instance
27
- */
28
- export declare interface CoordinatorClient {
29
- trpc: TypedTRPCClient;
30
- close: () => void;
31
- }
32
-
33
- /**
34
- * Create a tRPC client that connects to the coordinator server
35
- */
36
- export declare function createCoordinatorClient(options?: ClientOptions): CoordinatorClient;
37
-
38
- /**
39
- * Create and run the MCP server
40
- */
41
- export declare function createMcpServer(options?: McpServerOptions): Promise<void>;
42
-
43
- /**
44
- * MCP server options
45
- */
46
- export declare interface McpServerOptions {
47
- url?: string;
48
- }
49
-
50
- /**
51
- * Typed tRPC client interface matching the coordinator server's router
52
- * This provides type safety without requiring cross-package type inference
53
- */
54
- declare interface TypedTRPCClient {
55
- session: {
56
- join: {
57
- mutate: (input: JoinInput) => Promise<JoinResult>;
58
- };
59
- leave: {
60
- mutate: (input: {
61
- agentId: string;
62
- }) => Promise<{
63
- success: boolean;
64
- }>;
65
- };
66
- list: {
67
- query: () => Promise<Agent[]>;
68
- };
69
- };
70
- context: {
71
- share: {
72
- mutate: (input: {
73
- key: string;
74
- value: string;
75
- tags?: string[];
76
- agentId: string;
77
- }) => Promise<ContextEntry>;
78
- };
79
- get: {
80
- query: (input: {
81
- key: string;
82
- }) => Promise<ContextEntry | null>;
83
- };
84
- list: {
85
- query: (input?: {
86
- prefix?: string;
87
- tags?: string[];
88
- }) => Promise<ContextEntry[]>;
89
- };
90
- };
91
- questions: {
92
- ask: {
93
- mutate: (input: {
94
- question: string;
95
- to?: string;
96
- agentId: string;
97
- }) => Promise<Question>;
98
- };
99
- answer: {
100
- mutate: (input: {
101
- questionId: string;
102
- answer: string;
103
- agentId: string;
104
- }) => Promise<Question>;
105
- };
106
- listPending: {
107
- query: (input?: {
108
- agentId?: string;
109
- }) => Promise<Question[]>;
110
- };
111
- };
112
- decisions: {
113
- log: {
114
- mutate: (input: {
115
- decision: string;
116
- rationale?: string;
117
- agentId: string;
118
- }) => Promise<Decision>;
119
- };
120
- list: {
121
- query: () => Promise<Decision[]>;
122
- };
123
- };
124
- }
125
-
126
- export { }
1
+ import { Agent, ContextEntry, Decision, JoinInput, JoinResult, Question } from "@spencerbeggs/claude-coordinator-core";
2
+ //#region src/client.d.ts
3
+ /**
4
+ * Options for creating the coordinator client
5
+ *
6
+ * @public
7
+ */
8
+ interface ClientOptions {
9
+ url?: string;
10
+ }
11
+ /**
12
+ * Typed tRPC client interface matching the coordinator server's router
13
+ * This provides type safety without requiring cross-package type inference
14
+ *
15
+ * @public
16
+ */
17
+ interface TypedTRPCClient {
18
+ session: {
19
+ join: {
20
+ mutate: (input: JoinInput) => Promise<JoinResult>;
21
+ };
22
+ leave: {
23
+ mutate: (input: {
24
+ agentId: string;
25
+ }) => Promise<{
26
+ success: boolean;
27
+ }>;
28
+ };
29
+ list: {
30
+ query: () => Promise<Agent[]>;
31
+ };
32
+ };
33
+ context: {
34
+ share: {
35
+ mutate: (input: {
36
+ key: string;
37
+ value: string;
38
+ tags?: string[];
39
+ agentId: string;
40
+ }) => Promise<ContextEntry>;
41
+ };
42
+ get: {
43
+ query: (input: {
44
+ key: string;
45
+ }) => Promise<ContextEntry | null>;
46
+ };
47
+ list: {
48
+ query: (input?: {
49
+ prefix?: string;
50
+ tags?: string[];
51
+ }) => Promise<ContextEntry[]>;
52
+ };
53
+ };
54
+ questions: {
55
+ ask: {
56
+ mutate: (input: {
57
+ question: string;
58
+ to?: string;
59
+ agentId: string;
60
+ }) => Promise<Question>;
61
+ };
62
+ answer: {
63
+ mutate: (input: {
64
+ questionId: string;
65
+ answer: string;
66
+ agentId: string;
67
+ }) => Promise<Question>;
68
+ };
69
+ listPending: {
70
+ query: (input?: {
71
+ agentId?: string;
72
+ }) => Promise<Question[]>;
73
+ };
74
+ };
75
+ decisions: {
76
+ log: {
77
+ mutate: (input: {
78
+ decision: string;
79
+ rationale?: string;
80
+ agentId: string;
81
+ }) => Promise<Decision>;
82
+ };
83
+ list: {
84
+ query: () => Promise<Decision[]>;
85
+ };
86
+ };
87
+ }
88
+ /**
89
+ * Coordinator client instance
90
+ *
91
+ * @public
92
+ */
93
+ interface CoordinatorClient {
94
+ trpc: TypedTRPCClient;
95
+ close: () => void;
96
+ }
97
+ /**
98
+ * Create a tRPC client that connects to the coordinator server
99
+ *
100
+ * @public
101
+ */
102
+ declare function createCoordinatorClient(options?: ClientOptions): CoordinatorClient;
103
+ //#endregion
104
+ //#region src/mcp-server.d.ts
105
+ /**
106
+ * MCP server options
107
+ *
108
+ * @public
109
+ */
110
+ interface McpServerOptions {
111
+ url?: string;
112
+ }
113
+ /**
114
+ * Create and run the MCP server
115
+ *
116
+ * @public
117
+ */
118
+ declare function createMcpServer(options?: McpServerOptions): Promise<void>;
119
+ //#endregion
120
+ export { type ClientOptions, type CoordinatorClient, type McpServerOptions, type TypedTRPCClient, createCoordinatorClient, createMcpServer };
121
+ //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -1 +1,4 @@
1
- export { createCoordinatorClient, createMcpServer } from "./433.js";
1
+ import { createCoordinatorClient } from "./client.js";
2
+ import { createMcpServer } from "./mcp-server.js";
3
+
4
+ export { createCoordinatorClient, createMcpServer };
package/mcp-server.js ADDED
@@ -0,0 +1,327 @@
1
+ import { createCoordinatorClient } from "./client.js";
2
+ import { AnswerInputSchema, AskInputSchema, DEFAULT_URL, GetContextInputSchema, JoinInputSchema, ListContextInputSchema, LogDecisionInputSchema, ShareContextInputSchema } from "@spencerbeggs/claude-coordinator-core";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+
7
+ //#region src/mcp-server.ts
8
+ /**
9
+ * Format connection errors with helpful guidance
10
+ */
11
+ function formatConnectionError(error, url) {
12
+ const errorStr = String(error);
13
+ if (errorStr.includes("ECONNREFUSED") || errorStr.includes("ENOTFOUND") || errorStr.includes("WebSocket") || errorStr.includes("connect")) return `Could not connect to coordinator server at ${url}. Please start the server with: npx @spencerbeggs/claude-coordinator-server`;
14
+ return errorStr;
15
+ }
16
+ /**
17
+ * Bridge the MCP SDK's tool-argument typing (optional fields surface as
18
+ * required `prop: T | undefined`) to the tRPC input typing (`prop?: T`).
19
+ * Under `exactOptionalPropertyTypes` the two are incompatible, so we drop
20
+ * undefined-valued keys — an absent optional and an `undefined` optional are
21
+ * equivalent for these schemas — and re-type the result to match.
22
+ */
23
+ function exactOptional(value) {
24
+ return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
25
+ }
26
+ /**
27
+ * Create and run the MCP server
28
+ *
29
+ * @public
30
+ */
31
+ async function createMcpServer(options = {}) {
32
+ const serverUrl = options.url ?? DEFAULT_URL;
33
+ let client = null;
34
+ let agentId = null;
35
+ const server = new McpServer({
36
+ name: "claude-coordinator",
37
+ version: "0.1.0"
38
+ });
39
+ const getClient = () => {
40
+ if (!client) client = createCoordinatorClient({ url: serverUrl });
41
+ return client;
42
+ };
43
+ const formatError = (error) => formatConnectionError(error, serverUrl);
44
+ const requireAgentId = () => {
45
+ if (!agentId) throw new Error("Not joined to session. Call coordinator_join first.");
46
+ return agentId;
47
+ };
48
+ server.tool("coordinator_join", "Join the coordination session as an agent", JoinInputSchema.shape, async (params) => {
49
+ try {
50
+ const result = await getClient().trpc.session.join.mutate(params);
51
+ agentId = result.agent.id;
52
+ return { content: [{
53
+ type: "text",
54
+ text: JSON.stringify({
55
+ success: true,
56
+ agent: result.agent,
57
+ sessionId: result.sessionId
58
+ })
59
+ }] };
60
+ } catch (error) {
61
+ return {
62
+ content: [{
63
+ type: "text",
64
+ text: JSON.stringify({
65
+ success: false,
66
+ error: formatError(error)
67
+ })
68
+ }],
69
+ isError: true
70
+ };
71
+ }
72
+ });
73
+ server.tool("coordinator_leave", "Leave the coordination session", {}, async () => {
74
+ try {
75
+ const id = requireAgentId();
76
+ const result = await getClient().trpc.session.leave.mutate({ agentId: id });
77
+ if (result.success) agentId = null;
78
+ return { content: [{
79
+ type: "text",
80
+ text: JSON.stringify({ success: result.success })
81
+ }] };
82
+ } catch (error) {
83
+ return {
84
+ content: [{
85
+ type: "text",
86
+ text: JSON.stringify({
87
+ success: false,
88
+ error: formatError(error)
89
+ })
90
+ }],
91
+ isError: true
92
+ };
93
+ }
94
+ });
95
+ server.tool("coordinator_list_agents", "List all connected agents in the session", {}, async () => {
96
+ try {
97
+ const agents = await getClient().trpc.session.list.query();
98
+ return { content: [{
99
+ type: "text",
100
+ text: JSON.stringify({
101
+ success: true,
102
+ agents
103
+ })
104
+ }] };
105
+ } catch (error) {
106
+ return {
107
+ content: [{
108
+ type: "text",
109
+ text: JSON.stringify({
110
+ success: false,
111
+ error: formatError(error)
112
+ })
113
+ }],
114
+ isError: true
115
+ };
116
+ }
117
+ });
118
+ server.tool("coordinator_share_context", "Share a context entry with other agents", ShareContextInputSchema.shape, async (params) => {
119
+ try {
120
+ const id = requireAgentId();
121
+ const entry = await getClient().trpc.context.share.mutate(exactOptional({
122
+ ...params,
123
+ agentId: id
124
+ }));
125
+ return { content: [{
126
+ type: "text",
127
+ text: JSON.stringify({
128
+ success: true,
129
+ entry
130
+ })
131
+ }] };
132
+ } catch (error) {
133
+ return {
134
+ content: [{
135
+ type: "text",
136
+ text: JSON.stringify({
137
+ success: false,
138
+ error: formatError(error)
139
+ })
140
+ }],
141
+ isError: true
142
+ };
143
+ }
144
+ });
145
+ server.tool("coordinator_get_context", "Get a context entry by key", GetContextInputSchema.shape, async (params) => {
146
+ try {
147
+ const entry = await getClient().trpc.context.get.query(params);
148
+ return { content: [{
149
+ type: "text",
150
+ text: JSON.stringify({
151
+ success: true,
152
+ entry
153
+ })
154
+ }] };
155
+ } catch (error) {
156
+ return {
157
+ content: [{
158
+ type: "text",
159
+ text: JSON.stringify({
160
+ success: false,
161
+ error: formatError(error)
162
+ })
163
+ }],
164
+ isError: true
165
+ };
166
+ }
167
+ });
168
+ server.tool("coordinator_list_context", "List context entries with optional filters", { ...ListContextInputSchema.shape }, async (params) => {
169
+ try {
170
+ const entries = await getClient().trpc.context.list.query(exactOptional(params));
171
+ return { content: [{
172
+ type: "text",
173
+ text: JSON.stringify({
174
+ success: true,
175
+ entries
176
+ })
177
+ }] };
178
+ } catch (error) {
179
+ return {
180
+ content: [{
181
+ type: "text",
182
+ text: JSON.stringify({
183
+ success: false,
184
+ error: formatError(error)
185
+ })
186
+ }],
187
+ isError: true
188
+ };
189
+ }
190
+ });
191
+ server.tool("coordinator_ask", "Ask a question to other agents", AskInputSchema.shape, async (params) => {
192
+ try {
193
+ const id = requireAgentId();
194
+ const question = await getClient().trpc.questions.ask.mutate(exactOptional({
195
+ ...params,
196
+ agentId: id
197
+ }));
198
+ return { content: [{
199
+ type: "text",
200
+ text: JSON.stringify({
201
+ success: true,
202
+ question
203
+ })
204
+ }] };
205
+ } catch (error) {
206
+ return {
207
+ content: [{
208
+ type: "text",
209
+ text: JSON.stringify({
210
+ success: false,
211
+ error: formatError(error)
212
+ })
213
+ }],
214
+ isError: true
215
+ };
216
+ }
217
+ });
218
+ server.tool("coordinator_answer", "Answer a pending question", AnswerInputSchema.shape, async (params) => {
219
+ try {
220
+ const id = requireAgentId();
221
+ const question = await getClient().trpc.questions.answer.mutate({
222
+ ...params,
223
+ agentId: id
224
+ });
225
+ return { content: [{
226
+ type: "text",
227
+ text: JSON.stringify({
228
+ success: true,
229
+ question
230
+ })
231
+ }] };
232
+ } catch (error) {
233
+ return {
234
+ content: [{
235
+ type: "text",
236
+ text: JSON.stringify({
237
+ success: false,
238
+ error: formatError(error)
239
+ })
240
+ }],
241
+ isError: true
242
+ };
243
+ }
244
+ });
245
+ server.tool("coordinator_pending_questions", "List pending questions (optionally for a specific agent)", { agentId: z.string().uuid().optional().describe("Filter questions directed to this agent") }, async (params) => {
246
+ try {
247
+ const questions = await getClient().trpc.questions.listPending.query(exactOptional(params));
248
+ return { content: [{
249
+ type: "text",
250
+ text: JSON.stringify({
251
+ success: true,
252
+ questions
253
+ })
254
+ }] };
255
+ } catch (error) {
256
+ return {
257
+ content: [{
258
+ type: "text",
259
+ text: JSON.stringify({
260
+ success: false,
261
+ error: formatError(error)
262
+ })
263
+ }],
264
+ isError: true
265
+ };
266
+ }
267
+ });
268
+ server.tool("coordinator_log_decision", "Log a decision made during the session", LogDecisionInputSchema.shape, async (params) => {
269
+ try {
270
+ const id = requireAgentId();
271
+ const decision = await getClient().trpc.decisions.log.mutate(exactOptional({
272
+ ...params,
273
+ agentId: id
274
+ }));
275
+ return { content: [{
276
+ type: "text",
277
+ text: JSON.stringify({
278
+ success: true,
279
+ decision
280
+ })
281
+ }] };
282
+ } catch (error) {
283
+ return {
284
+ content: [{
285
+ type: "text",
286
+ text: JSON.stringify({
287
+ success: false,
288
+ error: formatError(error)
289
+ })
290
+ }],
291
+ isError: true
292
+ };
293
+ }
294
+ });
295
+ server.tool("coordinator_list_decisions", "List all decisions made during the session", {}, async () => {
296
+ try {
297
+ const decisions = await getClient().trpc.decisions.list.query();
298
+ return { content: [{
299
+ type: "text",
300
+ text: JSON.stringify({
301
+ success: true,
302
+ decisions
303
+ })
304
+ }] };
305
+ } catch (error) {
306
+ return {
307
+ content: [{
308
+ type: "text",
309
+ text: JSON.stringify({
310
+ success: false,
311
+ error: formatError(error)
312
+ })
313
+ }],
314
+ isError: true
315
+ };
316
+ }
317
+ });
318
+ const transport = new StdioServerTransport();
319
+ await server.connect(transport);
320
+ console.error("[coordinator-mcp] MCP server started");
321
+ process.on("exit", () => {
322
+ if (client) client.close();
323
+ });
324
+ }
325
+
326
+ //#endregion
327
+ export { createMcpServer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spencerbeggs/claude-coordinator-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "description": "MCP stdio bridge for Claude Coordinator",
6
6
  "keywords": [
@@ -20,35 +20,29 @@
20
20
  "email": "spencer@beggs.codes",
21
21
  "url": "https://spencerbeg.gs"
22
22
  },
23
+ "sideEffects": false,
23
24
  "type": "module",
24
25
  "exports": {
25
26
  ".": {
26
27
  "types": "./index.d.ts",
27
- "import": "./index.js"
28
- }
28
+ "import": "./index.js",
29
+ "default": "./index.js"
30
+ },
31
+ "./package.json": "./package.json"
29
32
  },
30
33
  "bin": {
31
- "claude-coordinator-mcp": "./bin/claude-coordinator-mcp.js"
34
+ "claude-coordinator-mcp": "bin/claude-coordinator-mcp.js"
32
35
  },
33
36
  "dependencies": {
34
- "@modelcontextprotocol/sdk": "^1.25.3",
37
+ "@modelcontextprotocol/sdk": "^1.29.0",
35
38
  "@spencerbeggs/claude-coordinator-core": "0.1.0",
36
- "@spencerbeggs/claude-coordinator-server": "0.1.0",
37
- "@trpc/client": "^11.8.1",
38
- "@trpc/server": "^11.8.1",
39
- "ws": "^8.19.0",
40
- "zod": "^4.3.5"
39
+ "@spencerbeggs/claude-coordinator-server": "0.1.2",
40
+ "@trpc/client": "^11.18.0",
41
+ "@trpc/server": "^11.18.0",
42
+ "ws": "^8.21.1",
43
+ "zod": "^4.4.3"
41
44
  },
42
45
  "engines": {
43
- "node": ">=20.0.0"
44
- },
45
- "files": [
46
- "433.js",
47
- "LICENSE",
48
- "README.md",
49
- "bin/claude-coordinator-mcp.js",
50
- "index.d.ts",
51
- "index.js",
52
- "package.json"
53
- ]
54
- }
46
+ "node": ">=24.11.0"
47
+ }
48
+ }
@@ -0,0 +1,11 @@
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.58.12"
9
+ }
10
+ ]
11
+ }
package/433.js DELETED
@@ -1,403 +0,0 @@
1
- import { AnswerInputSchema, AskInputSchema, DEFAULT_URL, GetContextInputSchema, JoinInputSchema, ListContextInputSchema, LogDecisionInputSchema, ShareContextInputSchema } from "@spencerbeggs/claude-coordinator-core";
2
- import { createTRPCClient, createWSClient, wsLink } from "@trpc/client";
3
- import ws from "ws";
4
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { z } from "zod";
7
- function createCoordinatorClient(options = {}) {
8
- const url = options.url ?? DEFAULT_URL;
9
- const wsClient = createWSClient({
10
- url,
11
- WebSocket: ws
12
- });
13
- const trpc = createTRPCClient({
14
- links: [
15
- wsLink({
16
- client: wsClient
17
- })
18
- ]
19
- });
20
- const close = ()=>{
21
- wsClient.close();
22
- };
23
- return {
24
- trpc,
25
- close
26
- };
27
- }
28
- function formatConnectionError(error, url) {
29
- const errorStr = String(error);
30
- if (errorStr.includes("ECONNREFUSED") || errorStr.includes("ENOTFOUND") || errorStr.includes("WebSocket") || errorStr.includes("connect")) return `Could not connect to coordinator server at ${url}. Please start the server with: npx @spencerbeggs/claude-coordinator-server`;
31
- return errorStr;
32
- }
33
- async function createMcpServer(options = {}) {
34
- const serverUrl = options.url ?? DEFAULT_URL;
35
- let client = null;
36
- let agentId = null;
37
- const server = new McpServer({
38
- name: "claude-coordinator",
39
- version: "0.1.0"
40
- });
41
- const getClient = ()=>{
42
- if (!client) client = createCoordinatorClient({
43
- url: serverUrl
44
- });
45
- return client;
46
- };
47
- const formatError = (error)=>formatConnectionError(error, serverUrl);
48
- const requireAgentId = ()=>{
49
- if (!agentId) throw new Error("Not joined to session. Call coordinator_join first.");
50
- return agentId;
51
- };
52
- server.tool("coordinator_join", "Join the coordination session as an agent", JoinInputSchema.shape, async (params)=>{
53
- try {
54
- const result = await getClient().trpc.session.join.mutate(params);
55
- agentId = result.agent.id;
56
- return {
57
- content: [
58
- {
59
- type: "text",
60
- text: JSON.stringify({
61
- success: true,
62
- agent: result.agent,
63
- sessionId: result.sessionId
64
- })
65
- }
66
- ]
67
- };
68
- } catch (error) {
69
- return {
70
- content: [
71
- {
72
- type: "text",
73
- text: JSON.stringify({
74
- success: false,
75
- error: formatError(error)
76
- })
77
- }
78
- ],
79
- isError: true
80
- };
81
- }
82
- });
83
- server.tool("coordinator_leave", "Leave the coordination session", {}, async ()=>{
84
- try {
85
- const id = requireAgentId();
86
- const result = await getClient().trpc.session.leave.mutate({
87
- agentId: id
88
- });
89
- if (result.success) agentId = null;
90
- return {
91
- content: [
92
- {
93
- type: "text",
94
- text: JSON.stringify({
95
- success: result.success
96
- })
97
- }
98
- ]
99
- };
100
- } catch (error) {
101
- return {
102
- content: [
103
- {
104
- type: "text",
105
- text: JSON.stringify({
106
- success: false,
107
- error: formatError(error)
108
- })
109
- }
110
- ],
111
- isError: true
112
- };
113
- }
114
- });
115
- server.tool("coordinator_list_agents", "List all connected agents in the session", {}, async ()=>{
116
- try {
117
- const agents = await getClient().trpc.session.list.query();
118
- return {
119
- content: [
120
- {
121
- type: "text",
122
- text: JSON.stringify({
123
- success: true,
124
- agents
125
- })
126
- }
127
- ]
128
- };
129
- } catch (error) {
130
- return {
131
- content: [
132
- {
133
- type: "text",
134
- text: JSON.stringify({
135
- success: false,
136
- error: formatError(error)
137
- })
138
- }
139
- ],
140
- isError: true
141
- };
142
- }
143
- });
144
- server.tool("coordinator_share_context", "Share a context entry with other agents", ShareContextInputSchema.shape, async (params)=>{
145
- try {
146
- const id = requireAgentId();
147
- const entry = await getClient().trpc.context.share.mutate({
148
- ...params,
149
- agentId: id
150
- });
151
- return {
152
- content: [
153
- {
154
- type: "text",
155
- text: JSON.stringify({
156
- success: true,
157
- entry
158
- })
159
- }
160
- ]
161
- };
162
- } catch (error) {
163
- return {
164
- content: [
165
- {
166
- type: "text",
167
- text: JSON.stringify({
168
- success: false,
169
- error: formatError(error)
170
- })
171
- }
172
- ],
173
- isError: true
174
- };
175
- }
176
- });
177
- server.tool("coordinator_get_context", "Get a context entry by key", GetContextInputSchema.shape, async (params)=>{
178
- try {
179
- const entry = await getClient().trpc.context.get.query(params);
180
- return {
181
- content: [
182
- {
183
- type: "text",
184
- text: JSON.stringify({
185
- success: true,
186
- entry
187
- })
188
- }
189
- ]
190
- };
191
- } catch (error) {
192
- return {
193
- content: [
194
- {
195
- type: "text",
196
- text: JSON.stringify({
197
- success: false,
198
- error: formatError(error)
199
- })
200
- }
201
- ],
202
- isError: true
203
- };
204
- }
205
- });
206
- server.tool("coordinator_list_context", "List context entries with optional filters", {
207
- ...ListContextInputSchema.shape
208
- }, async (params)=>{
209
- try {
210
- const entries = await getClient().trpc.context.list.query(params);
211
- return {
212
- content: [
213
- {
214
- type: "text",
215
- text: JSON.stringify({
216
- success: true,
217
- entries
218
- })
219
- }
220
- ]
221
- };
222
- } catch (error) {
223
- return {
224
- content: [
225
- {
226
- type: "text",
227
- text: JSON.stringify({
228
- success: false,
229
- error: formatError(error)
230
- })
231
- }
232
- ],
233
- isError: true
234
- };
235
- }
236
- });
237
- server.tool("coordinator_ask", "Ask a question to other agents", AskInputSchema.shape, async (params)=>{
238
- try {
239
- const id = requireAgentId();
240
- const question = await getClient().trpc.questions.ask.mutate({
241
- ...params,
242
- agentId: id
243
- });
244
- return {
245
- content: [
246
- {
247
- type: "text",
248
- text: JSON.stringify({
249
- success: true,
250
- question
251
- })
252
- }
253
- ]
254
- };
255
- } catch (error) {
256
- return {
257
- content: [
258
- {
259
- type: "text",
260
- text: JSON.stringify({
261
- success: false,
262
- error: formatError(error)
263
- })
264
- }
265
- ],
266
- isError: true
267
- };
268
- }
269
- });
270
- server.tool("coordinator_answer", "Answer a pending question", AnswerInputSchema.shape, async (params)=>{
271
- try {
272
- const id = requireAgentId();
273
- const question = await getClient().trpc.questions.answer.mutate({
274
- ...params,
275
- agentId: id
276
- });
277
- return {
278
- content: [
279
- {
280
- type: "text",
281
- text: JSON.stringify({
282
- success: true,
283
- question
284
- })
285
- }
286
- ]
287
- };
288
- } catch (error) {
289
- return {
290
- content: [
291
- {
292
- type: "text",
293
- text: JSON.stringify({
294
- success: false,
295
- error: formatError(error)
296
- })
297
- }
298
- ],
299
- isError: true
300
- };
301
- }
302
- });
303
- server.tool("coordinator_pending_questions", "List pending questions (optionally for a specific agent)", {
304
- agentId: z.string().uuid().optional().describe("Filter questions directed to this agent")
305
- }, async (params)=>{
306
- try {
307
- const questions = await getClient().trpc.questions.listPending.query(params);
308
- return {
309
- content: [
310
- {
311
- type: "text",
312
- text: JSON.stringify({
313
- success: true,
314
- questions
315
- })
316
- }
317
- ]
318
- };
319
- } catch (error) {
320
- return {
321
- content: [
322
- {
323
- type: "text",
324
- text: JSON.stringify({
325
- success: false,
326
- error: formatError(error)
327
- })
328
- }
329
- ],
330
- isError: true
331
- };
332
- }
333
- });
334
- server.tool("coordinator_log_decision", "Log a decision made during the session", LogDecisionInputSchema.shape, async (params)=>{
335
- try {
336
- const id = requireAgentId();
337
- const decision = await getClient().trpc.decisions.log.mutate({
338
- ...params,
339
- agentId: id
340
- });
341
- return {
342
- content: [
343
- {
344
- type: "text",
345
- text: JSON.stringify({
346
- success: true,
347
- decision
348
- })
349
- }
350
- ]
351
- };
352
- } catch (error) {
353
- return {
354
- content: [
355
- {
356
- type: "text",
357
- text: JSON.stringify({
358
- success: false,
359
- error: formatError(error)
360
- })
361
- }
362
- ],
363
- isError: true
364
- };
365
- }
366
- });
367
- server.tool("coordinator_list_decisions", "List all decisions made during the session", {}, async ()=>{
368
- try {
369
- const decisions = await getClient().trpc.decisions.list.query();
370
- return {
371
- content: [
372
- {
373
- type: "text",
374
- text: JSON.stringify({
375
- success: true,
376
- decisions
377
- })
378
- }
379
- ]
380
- };
381
- } catch (error) {
382
- return {
383
- content: [
384
- {
385
- type: "text",
386
- text: JSON.stringify({
387
- success: false,
388
- error: formatError(error)
389
- })
390
- }
391
- ],
392
- isError: true
393
- };
394
- }
395
- });
396
- const transport = new StdioServerTransport();
397
- await server.connect(transport);
398
- console.error("[coordinator-mcp] MCP server started");
399
- process.on("exit", ()=>{
400
- if (client) client.close();
401
- });
402
- }
403
- export { DEFAULT_URL, createCoordinatorClient, createMcpServer };