cc-claw 0.13.1 → 0.14.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.
@@ -46,347 +46,623 @@ async function callApi(path, body) {
46
46
  function sleep(ms) {
47
47
  return new Promise((resolve) => setTimeout(resolve, ms));
48
48
  }
49
+ function ok(text) {
50
+ return { content: [{ type: "text", text }] };
51
+ }
52
+ function fail(msg) {
53
+ return { content: [{ type: "text", text: msg }], isError: true };
54
+ }
49
55
  var server = new McpServer({
50
56
  name: "cc-claw-orchestrator",
51
- version: "1.0.0"
57
+ version: "2.0.0"
52
58
  });
53
- if (!IS_SUB_AGENT) {
54
- server.tool(
55
- "spawn_agent",
56
- `Spawn a sub-agent using a specific CLI runner to execute a task.
57
-
58
- CRITICAL \u2014 Path verification:
59
- Before calling this tool with a cwd, you MUST verify the path exists using your file system tools (Glob, Read, ls, etc.).
60
- User-provided paths are often approximate (wrong name, missing suffix, typo). Always confirm the exact path yourself.
61
- A non-existent cwd will cause the agent to fail immediately. Never pass an unverified path.
62
-
63
- Typical workflow:
64
- 1. User says "scan the foo-bar folder under dev_projects"
65
- 2. You run: ls /Users/.../dev_projects/ (or Glob) to find the actual folder name
66
- 3. You confirm the exact path exists (e.g., it might be foo-bars, foobar, foo_bar, etc.)
67
- 4. Only then call spawn_agent with the verified cwd`,
68
- {
69
- runner: z.string().describe("CLI runner ID (e.g., 'claude', 'gemini', 'codex', 'cursor', 'opencode')"),
70
- task: z.string().describe("Task description for the sub-agent"),
71
- name: z.string().optional().describe("Human-readable agent name (e.g., 'security-reviewer', 'linkedin-writer')"),
72
- description: z.string().optional().describe("Short description of what this agent does"),
73
- model: z.string().optional().describe("Model override for the sub-agent"),
74
- skills: z.array(z.string()).optional().describe("Skills to inject into the sub-agent"),
75
- permMode: z.string().optional().describe("Permission mode: yolo, safe, plan (defaults to chat's mode)"),
76
- role: z.string().optional().describe("Agent role. Presets: worker, reviewer, planner, researcher, writer, analyst, debugger, critic, synthesizer. Custom roles accepted as labels."),
77
- persona: z.string().optional().describe("Custom system prompt for the agent. Replaces the role's default prompt while preserving orchestrator tools and task."),
78
- allowedTools: z.array(z.string()).optional().describe("Restrict agent to specific tools (Claude-only). E.g., ['Read', 'Grep', 'Glob']"),
79
- maxRuntimeMs: z.number().optional().describe("Max runtime in ms before timeout (default: 600000 = 10 min)"),
80
- summarizeResult: z.boolean().optional().describe("Summarize agent output before posting to inbox"),
81
- mcps: z.array(z.string()).optional().describe("MCP servers to inject into the sub-agent"),
82
- cwd: z.string().optional().describe("Working directory for the sub-agent. MUST be a verified, existing absolute path \u2014 check it before calling."),
83
- template: z.string().optional().describe("Name of an agent template from ~/.cc-claw/agents/ to use as defaults. Call list_templates to discover available templates. Call-time params override template defaults.")
84
- },
85
- async (params) => {
86
- try {
87
- const result = await callApi("/api/orchestrator/spawn", { chatId: CHAT_ID, ...params });
88
- const status = result.queued ? "queued (at capacity, will start when a slot opens)" : "spawning";
89
- return {
90
- content: [{ type: "text", text: `Agent ${result.agentId} \u2014 ${status}` }]
91
- };
92
- } catch (err) {
93
- const msg = err instanceof Error ? err.message : String(err);
94
- return {
95
- content: [{ type: "text", text: `spawn_agent failed: ${msg}` }],
96
- isError: true
97
- };
98
- }
99
- }
100
- );
101
- server.tool(
102
- "list_templates",
103
- "List available agent templates from ~/.cc-claw/agents/. Use a template name with spawn_agent's template parameter to spawn a pre-configured agent.",
104
- {},
105
- async () => {
106
- const templates = await callApi("/api/orchestrator/list-templates");
107
- if (!templates.length) {
108
- return { content: [{ type: "text", text: "No agent templates found. Create .md files in ~/.cc-claw/agents/ with YAML frontmatter." }] };
109
- }
110
- const lines = templates.map(
111
- (t) => `\u2022 **${t.name}**${t.description ? ` \u2014 ${t.description}` : ""}${t.runner ? ` (runner: ${t.runner})` : ""}${t.model ? ` [${t.model}]` : ""}`
112
- );
113
- return { content: [{ type: "text", text: lines.join("\n") }] };
114
- }
115
- );
116
- server.tool(
117
- "cancel_agent",
118
- "Cancel a specific sub-agent",
119
- {
120
- agentId: z.string().describe("The agent ID to cancel"),
121
- reason: z.string().optional().describe("Reason for cancellation")
122
- },
123
- async ({ agentId, reason }) => {
124
- const result = await callApi("/api/orchestrator/cancel", { agentId, reason });
125
- return {
126
- content: [{ type: "text", text: result.success ? `Agent ${agentId} cancelled.` : `Agent ${agentId} not found or already completed.` }]
127
- };
128
- }
129
- );
130
- server.tool(
131
- "create_task",
132
- "Add a task to the shared task list",
133
- {
134
- subject: z.string().describe("Short imperative title for the task"),
135
- description: z.string().describe("Detailed task requirements"),
136
- assignee: z.string().optional().describe("Agent ID to assign the task to"),
137
- blockedBy: z.array(z.number()).optional().describe("Task IDs that must complete before this task can start")
138
- },
139
- async (params) => {
140
- const result = await callApi("/api/orchestrator/create-task", {
141
- chatId: CHAT_ID,
142
- task: { ...params, createdBy: AGENT_ID }
143
- });
144
- return {
145
- content: [{ type: "text", text: `Task #${result.taskId} created.` }]
146
- };
147
- }
148
- );
149
- }
150
59
  server.tool(
151
- "list_agents",
152
- "List all active and queued sub-agents",
153
- {},
154
- async () => {
155
- const agents = await callApi("/api/agents");
156
- if (!agents.length) {
157
- return { content: [{ type: "text", text: "No active agents." }] };
158
- }
159
- const lines = agents.map((a) => {
160
- const label = a.name ?? a.id.slice(0, 8);
161
- return `\u2022 ${label} (${a.runnerId}) \u2014 ${a.status}${a.task ? ` \u2192 ${a.task.slice(0, 80)}` : ""}`;
162
- });
163
- return { content: [{ type: "text", text: lines.join("\n") }] };
164
- }
165
- );
166
- server.tool(
167
- "check_agent",
168
- "Get detailed status of a specific agent",
60
+ "cc_claw_agents",
61
+ `Agent lifecycle management. Actions:
62
+ \u2022 spawn \u2014 Spawn a sub-agent (main agent only). CRITICAL: verify cwd paths exist before calling.
63
+ \u2022 cancel \u2014 Cancel a running agent (main agent only)
64
+ \u2022 list \u2014 List all active/queued agents
65
+ \u2022 status \u2014 Get detailed agent status (requires agentId)
66
+ \u2022 templates \u2014 List available agent templates from ~/.cc-claw/agents/
67
+ \u2022 runners \u2014 List registered CLI runners
68
+ \u2022 mcps \u2014 List MCP servers in registry`,
169
69
  {
170
- agentId: z.string().describe("The agent ID to check")
70
+ action: z.enum(["spawn", "cancel", "list", "status", "templates", "runners", "mcps"]).describe("Action to perform"),
71
+ // spawn params
72
+ runner: z.string().optional().describe("CLI runner ID (e.g., 'claude', 'gemini', 'codex')"),
73
+ task: z.string().optional().describe("Task description for the sub-agent"),
74
+ name: z.string().optional().describe("Human-readable agent name"),
75
+ description: z.string().optional().describe("Short description of what this agent does"),
76
+ model: z.string().optional().describe("Model override"),
77
+ skills: z.array(z.string()).optional().describe("Skills to inject"),
78
+ permMode: z.string().optional().describe("Permission mode: yolo, safe, plan"),
79
+ role: z.string().optional().describe("Agent role (worker, reviewer, planner, etc.)"),
80
+ persona: z.string().optional().describe("Custom system prompt"),
81
+ allowedTools: z.array(z.string()).optional().describe("Restrict agent to specific tools"),
82
+ maxRuntimeMs: z.number().optional().describe("Max runtime in ms before timeout"),
83
+ summarizeResult: z.boolean().optional().describe("Summarize agent output"),
84
+ mcps: z.array(z.string()).optional().describe("MCP servers to inject"),
85
+ cwd: z.string().optional().describe("Working directory (must be verified, existing absolute path)"),
86
+ template: z.string().optional().describe("Agent template name from ~/.cc-claw/agents/"),
87
+ // cancel params
88
+ agentId: z.string().optional().describe("Agent ID (for cancel, status)"),
89
+ reason: z.string().optional().describe("Reason for cancellation")
171
90
  },
172
- async ({ agentId }) => {
173
- const agent = await callApi("/api/orchestrator/check-agent", { agentId });
174
- if (!agent) {
175
- return { content: [{ type: "text", text: `Agent ${agentId} not found.` }] };
176
- }
177
- return {
178
- content: [{
179
- type: "text",
180
- text: [
181
- `Agent: ${agent.name ?? agent.id.slice(0, 8)} (${agent.runnerId})`,
182
- agent.name ? `ID: ${agent.id.slice(0, 8)}` : null,
183
- agent.description ? `Description: ${agent.description}` : null,
184
- `Status: ${agent.status}`,
185
- `Task: ${agent.task ?? "none"}`,
186
- `Role: ${agent.role}`,
187
- `Tokens: ${agent.tokenInput} in / ${agent.tokenOutput} out`,
188
- agent.resultSummary ? `Result: ${agent.resultSummary.slice(0, 5e3)}` : null
189
- ].filter(Boolean).join("\n")
190
- }]
191
- };
192
- }
193
- );
194
- server.tool(
195
- "list_tasks",
196
- "Get the full task board",
197
- {},
198
- async () => {
199
- const tasks = await callApi("/api/tasks");
200
- if (!tasks.length) {
201
- return { content: [{ type: "text", text: "No tasks." }] };
91
+ async (params) => {
92
+ try {
93
+ switch (params.action) {
94
+ case "spawn": {
95
+ if (IS_SUB_AGENT) return fail("spawn is only available to the main agent");
96
+ if (!params.runner || !params.task) return fail("spawn requires 'runner' and 'task'");
97
+ const { action, agentId, reason, ...spawnParams } = params;
98
+ const result = await callApi("/api/orchestrator/spawn", { chatId: CHAT_ID, ...spawnParams });
99
+ const status = result.queued ? "queued (at capacity, will start when a slot opens)" : "spawning";
100
+ return ok(`Agent ${result.agentId} \u2014 ${status}`);
101
+ }
102
+ case "cancel": {
103
+ if (IS_SUB_AGENT) return fail("cancel is only available to the main agent");
104
+ if (!params.agentId) return fail("cancel requires 'agentId'");
105
+ const result = await callApi("/api/orchestrator/cancel", { agentId: params.agentId, reason: params.reason });
106
+ return ok(result.success ? `Agent ${params.agentId} cancelled.` : `Agent ${params.agentId} not found or already completed.`);
107
+ }
108
+ case "list": {
109
+ const agents = await callApi("/api/agents");
110
+ if (!agents.length) return ok("No active agents.");
111
+ const lines = agents.map((a) => {
112
+ const label = a.name ?? a.id.slice(0, 8);
113
+ return `\u2022 ${label} (${a.runnerId}) \u2014 ${a.status}${a.task ? ` \u2192 ${a.task.slice(0, 80)}` : ""}`;
114
+ });
115
+ return ok(lines.join("\n"));
116
+ }
117
+ case "status": {
118
+ if (!params.agentId) return fail("status requires 'agentId'");
119
+ const agent = await callApi("/api/orchestrator/check-agent", { agentId: params.agentId });
120
+ if (!agent) return ok(`Agent ${params.agentId} not found.`);
121
+ return ok([
122
+ `Agent: ${agent.name ?? agent.id.slice(0, 8)} (${agent.runnerId})`,
123
+ agent.name ? `ID: ${agent.id.slice(0, 8)}` : null,
124
+ agent.description ? `Description: ${agent.description}` : null,
125
+ `Status: ${agent.status}`,
126
+ `Task: ${agent.task ?? "none"}`,
127
+ `Role: ${agent.role}`,
128
+ `Tokens: ${agent.tokenInput} in / ${agent.tokenOutput} out`,
129
+ agent.resultSummary ? `Result: ${agent.resultSummary.slice(0, 5e3)}` : null
130
+ ].filter(Boolean).join("\n"));
131
+ }
132
+ case "templates": {
133
+ if (IS_SUB_AGENT) return fail("templates is only available to the main agent");
134
+ const templates = await callApi("/api/orchestrator/list-templates");
135
+ if (!templates.length) return ok("No agent templates found. Create .md files in ~/.cc-claw/agents/ with YAML frontmatter.");
136
+ const lines = templates.map(
137
+ (t) => `\u2022 **${t.name}**${t.description ? ` \u2014 ${t.description}` : ""}${t.runner ? ` (runner: ${t.runner})` : ""}${t.model ? ` [${t.model}]` : ""}`
138
+ );
139
+ return ok(lines.join("\n"));
140
+ }
141
+ case "runners": {
142
+ const runners = await callApi("/api/orchestrator/list-runners");
143
+ const lines = runners.map(
144
+ (r) => `\u2022 ${r.id} (${r.displayName}) \u2014 ${r.capabilities.specialties.join(", ")}`
145
+ );
146
+ return ok(lines.join("\n"));
147
+ }
148
+ case "mcps": {
149
+ const mcps = await callApi("/api/orchestrator/list-mcps");
150
+ if (!mcps.length) return ok("No MCP servers registered.");
151
+ const lines = mcps.map(
152
+ (m) => `\u2022 ${m.name} (${m.transport})${m.description ? ` \u2014 ${m.description}` : ""}${m.enabledByDefault ? " [auto]" : ""}`
153
+ );
154
+ return ok(lines.join("\n"));
155
+ }
156
+ }
157
+ } catch (err) {
158
+ return fail(`cc_claw_agents.${params.action} failed: ${err instanceof Error ? err.message : String(err)}`);
202
159
  }
203
- const lines = tasks.map(
204
- (t) => `#${t.id} [${t.status}] ${t.subject}${t.assignee ? ` (\u2192 ${t.assignee.slice(0, 8)})` : ""}`
205
- );
206
- return { content: [{ type: "text", text: lines.join("\n") }] };
207
160
  }
208
161
  );
209
162
  server.tool(
210
- "update_task",
211
- "Update a task's status",
163
+ "cc_claw_tasks",
164
+ `Shared task board management. Actions:
165
+ \u2022 create \u2014 Add a task (main agent only)
166
+ \u2022 list \u2014 View the full task board
167
+ \u2022 update \u2014 Update a task's status`,
212
168
  {
213
- taskId: z.number().describe("The task ID to update"),
214
- status: z.enum(["pending", "in_progress", "completed", "failed", "abandoned"]).describe("New status"),
169
+ action: z.enum(["create", "list", "update"]).describe("Action to perform"),
170
+ // create params
171
+ subject: z.string().optional().describe("Short imperative title for the task"),
172
+ description: z.string().optional().describe("Detailed task requirements"),
173
+ assignee: z.string().optional().describe("Agent ID to assign the task to"),
174
+ blockedBy: z.array(z.number()).optional().describe("Task IDs that must complete first"),
175
+ // update params
176
+ taskId: z.number().optional().describe("Task ID to update"),
177
+ status: z.enum(["pending", "in_progress", "completed", "failed", "abandoned"]).optional().describe("New status"),
215
178
  result: z.string().optional().describe("Result summary (for completed/failed)")
216
179
  },
217
- async ({ taskId, status, result }) => {
218
- await callApi("/api/orchestrator/update-task", { taskId, status, result });
219
- return { content: [{ type: "text", text: `Task #${taskId} \u2192 ${status}` }] };
220
- }
221
- );
222
- server.tool(
223
- "send_message",
224
- "Send a message to another agent's inbox",
225
- {
226
- toAgentId: z.string().describe("Recipient agent ID (or 'main' for the main agent)"),
227
- content: z.string().describe("Message content"),
228
- messageType: z.enum(["task_result", "question", "status_update", "direct_message"]).optional().describe("Message type (default: direct_message)")
229
- },
230
- async ({ toAgentId, content, messageType }) => {
231
- await callApi("/api/orchestrator/send-message", {
232
- chatId: CHAT_ID,
233
- message: {
234
- toAgentId,
235
- fromAgentId: AGENT_ID,
236
- messageType: messageType ?? "direct_message",
237
- content
180
+ async (params) => {
181
+ try {
182
+ switch (params.action) {
183
+ case "create": {
184
+ if (IS_SUB_AGENT) return fail("create is only available to the main agent");
185
+ if (!params.subject || !params.description) return fail("create requires 'subject' and 'description'");
186
+ const result = await callApi("/api/orchestrator/create-task", {
187
+ chatId: CHAT_ID,
188
+ task: { subject: params.subject, description: params.description, assignee: params.assignee, blockedBy: params.blockedBy, createdBy: AGENT_ID }
189
+ });
190
+ return ok(`Task #${result.taskId} created.`);
191
+ }
192
+ case "list": {
193
+ const tasks = await callApi("/api/tasks");
194
+ if (!tasks.length) return ok("No tasks.");
195
+ const lines = tasks.map(
196
+ (t) => `#${t.id} [${t.status}] ${t.subject}${t.assignee ? ` (\u2192 ${t.assignee.slice(0, 8)})` : ""}`
197
+ );
198
+ return ok(lines.join("\n"));
199
+ }
200
+ case "update": {
201
+ if (!params.taskId || !params.status) return fail("update requires 'taskId' and 'status'");
202
+ await callApi("/api/orchestrator/update-task", { taskId: params.taskId, status: params.status, result: params.result });
203
+ return ok(`Task #${params.taskId} \u2192 ${params.status}`);
204
+ }
238
205
  }
239
- });
240
- return { content: [{ type: "text", text: `Message sent to ${toAgentId}.` }] };
241
- }
242
- );
243
- server.tool(
244
- "read_inbox",
245
- "Read pending messages from your inbox. Messages remain unread unless markRead is true.",
246
- {
247
- markRead: z.boolean().optional().describe("Mark messages as read after returning them (default: false)")
248
- },
249
- async ({ markRead }) => {
250
- const messages = await callApi("/api/orchestrator/read-inbox", {
251
- chatId: CHAT_ID,
252
- agentId: AGENT_ID,
253
- markRead: markRead ?? false
254
- });
255
- if (!messages.length) {
256
- return { content: [{ type: "text", text: "No new messages." }] };
206
+ } catch (err) {
207
+ return fail(`cc_claw_tasks.${params.action} failed: ${err instanceof Error ? err.message : String(err)}`);
257
208
  }
258
- const lines = messages.map(
259
- (m) => `[${m.messageType}] from ${m.fromAgentId.slice(0, 8)}: ${m.content.slice(0, 500)}`
260
- );
261
- return { content: [{ type: "text", text: lines.join("\n\n") }] };
262
- }
263
- );
264
- server.tool(
265
- "set_state",
266
- "Write a key-value pair to the shared state whiteboard",
267
- {
268
- key: z.string().describe("State key (1-256 chars)"),
269
- value: z.string().describe("State value (JSON-serialized data)")
270
- },
271
- async ({ key, value }) => {
272
- await callApi("/api/orchestrator/set-state", {
273
- chatId: CHAT_ID,
274
- key,
275
- value,
276
- setBy: AGENT_ID
277
- });
278
- return { content: [{ type: "text", text: `State '${key}' set.` }] };
279
209
  }
280
210
  );
281
211
  server.tool(
282
- "get_state",
283
- "Read a value from the shared state whiteboard",
212
+ "cc_claw_comms",
213
+ `Inter-agent communication and shared state. Actions:
214
+ \u2022 send \u2014 Send a message to another agent's inbox
215
+ \u2022 read_inbox \u2014 Read your pending messages
216
+ \u2022 broadcast \u2014 Send a message to ALL other agents
217
+ \u2022 set_state \u2014 Write to the shared whiteboard
218
+ \u2022 get_state \u2014 Read from the shared whiteboard
219
+ \u2022 list_state \u2014 List all whiteboard entries
220
+ \u2022 report_progress \u2014 Report progress on long-running tasks`,
284
221
  {
285
- key: z.string().describe("State key to read")
222
+ action: z.enum(["send", "read_inbox", "broadcast", "set_state", "get_state", "list_state", "report_progress"]).describe("Action to perform"),
223
+ // send params
224
+ toAgentId: z.string().optional().describe("Recipient agent ID (or 'main')"),
225
+ content: z.string().optional().describe("Message content / broadcast text / progress detail"),
226
+ messageType: z.enum(["task_result", "question", "status_update", "direct_message"]).optional().describe("Message type"),
227
+ // read_inbox params
228
+ markRead: z.boolean().optional().describe("Mark messages as read (default: false)"),
229
+ // state params
230
+ key: z.string().optional().describe("State key"),
231
+ value: z.string().optional().describe("State value (JSON)"),
232
+ // report_progress params
233
+ progressStatus: z.string().optional().describe("Short status message (e.g., '70% complete')")
286
234
  },
287
- async ({ key }) => {
288
- const entry = await callApi("/api/orchestrator/get-state", {
289
- chatId: CHAT_ID,
290
- key
291
- });
292
- if (!entry) {
293
- return { content: [{ type: "text", text: `State '${key}' not set.` }] };
294
- }
295
- return { content: [{ type: "text", text: `${key} = ${entry.value} (set by ${entry.setBy})` }] };
296
- }
297
- );
298
- server.tool(
299
- "list_state",
300
- "List all key-value entries on the shared whiteboard for this orchestration",
301
- {},
302
- async () => {
303
- const entries = await callApi("/api/orchestrator/list-state", {
304
- chatId: CHAT_ID
305
- });
306
- if (!entries.length) {
307
- return { content: [{ type: "text", text: "Whiteboard is empty." }] };
235
+ async (params) => {
236
+ try {
237
+ switch (params.action) {
238
+ case "send": {
239
+ if (!params.toAgentId || !params.content) return fail("send requires 'toAgentId' and 'content'");
240
+ await callApi("/api/orchestrator/send-message", {
241
+ chatId: CHAT_ID,
242
+ message: {
243
+ toAgentId: params.toAgentId,
244
+ fromAgentId: AGENT_ID,
245
+ messageType: params.messageType ?? "direct_message",
246
+ content: params.content
247
+ }
248
+ });
249
+ return ok(`Message sent to ${params.toAgentId}.`);
250
+ }
251
+ case "read_inbox": {
252
+ const messages = await callApi("/api/orchestrator/read-inbox", {
253
+ chatId: CHAT_ID,
254
+ agentId: AGENT_ID,
255
+ markRead: params.markRead ?? false
256
+ });
257
+ if (!messages.length) return ok("No new messages.");
258
+ const lines = messages.map(
259
+ (m) => `[${m.messageType}] from ${m.fromAgentId.slice(0, 8)}: ${m.content.slice(0, 500)}`
260
+ );
261
+ return ok(lines.join("\n\n"));
262
+ }
263
+ case "broadcast": {
264
+ if (!params.content) return fail("broadcast requires 'content'");
265
+ const result = await callApi("/api/orchestrator/broadcast", {
266
+ chatId: CHAT_ID,
267
+ fromAgentId: AGENT_ID,
268
+ content: params.content,
269
+ messageType: params.messageType ?? "direct_message"
270
+ });
271
+ return ok(`Broadcast sent to ${result.sent} agent(s).`);
272
+ }
273
+ case "set_state": {
274
+ if (!params.key || !params.value) return fail("set_state requires 'key' and 'value'");
275
+ await callApi("/api/orchestrator/set-state", {
276
+ chatId: CHAT_ID,
277
+ key: params.key,
278
+ value: params.value,
279
+ setBy: AGENT_ID
280
+ });
281
+ return ok(`State '${params.key}' set.`);
282
+ }
283
+ case "get_state": {
284
+ if (!params.key) return fail("get_state requires 'key'");
285
+ const entry = await callApi("/api/orchestrator/get-state", {
286
+ chatId: CHAT_ID,
287
+ key: params.key
288
+ });
289
+ if (!entry) return ok(`State '${params.key}' not set.`);
290
+ return ok(`${params.key} = ${entry.value} (set by ${entry.setBy})`);
291
+ }
292
+ case "list_state": {
293
+ const entries = await callApi("/api/orchestrator/list-state", {
294
+ chatId: CHAT_ID
295
+ });
296
+ if (!entries.length) return ok("Whiteboard is empty.");
297
+ const lines = entries.map((e) => `${e.setBy}: ${e.key} = ${e.value}`);
298
+ return ok(lines.join("\n"));
299
+ }
300
+ case "report_progress": {
301
+ const status = params.progressStatus ?? params.content;
302
+ if (!status) return fail("report_progress requires 'progressStatus' or 'content'");
303
+ const shortId = AGENT_ID.slice(0, 8);
304
+ await callApi("/api/orchestrator/set-state", {
305
+ chatId: CHAT_ID,
306
+ key: `progress:${shortId}`,
307
+ value: JSON.stringify({ status, detail: params.content, timestamp: (/* @__PURE__ */ new Date()).toISOString() }),
308
+ setBy: AGENT_ID
309
+ });
310
+ if (IS_SUB_AGENT) {
311
+ await callApi("/api/orchestrator/send-message", {
312
+ chatId: CHAT_ID,
313
+ message: {
314
+ toAgentId: "main",
315
+ fromAgentId: AGENT_ID,
316
+ messageType: "status_update",
317
+ content: `Progress: ${status}${params.content ? ` \u2014 ${params.content}` : ""}`
318
+ }
319
+ });
320
+ }
321
+ return ok(`Progress reported: ${status}`);
322
+ }
323
+ }
324
+ } catch (err) {
325
+ return fail(`cc_claw_comms.${params.action} failed: ${err instanceof Error ? err.message : String(err)}`);
308
326
  }
309
- const lines = entries.map((e) => `${e.setBy}: ${e.key} = ${e.value}`);
310
- return { content: [{ type: "text", text: lines.join("\n") }] };
311
327
  }
312
328
  );
313
329
  server.tool(
314
- "broadcast",
315
- "Send a message to ALL other agents in the orchestration",
316
- {
317
- content: z.string().describe("Message content to broadcast"),
318
- messageType: z.enum(["status_update", "direct_message"]).optional().describe("Message type (default: direct_message)")
319
- },
320
- async ({ content, messageType }) => {
321
- const result = await callApi("/api/orchestrator/broadcast", {
322
- chatId: CHAT_ID,
323
- fromAgentId: AGENT_ID,
324
- content,
325
- messageType: messageType ?? "direct_message"
326
- });
327
- return { content: [{ type: "text", text: `Broadcast sent to ${result.sent} agent(s).` }] };
328
- }
329
- );
330
- server.tool(
331
- "report_progress",
332
- "Report progress on a long-running task. Writes to the shared whiteboard and notifies the main agent. Call periodically (every few minutes) during tasks that take more than a couple of minutes.",
330
+ "cc_claw_memory",
331
+ `Persistent cross-backend memory. Use this instead of your native memory system. Actions:
332
+ \u2022 remember \u2014 Save a memory (tag + content). Persists across backends and sessions.
333
+ \u2022 recall \u2014 Search memories by query (FTS5). Returns matching memories ranked by relevance.
334
+ \u2022 list \u2014 List recent memories.
335
+ \u2022 forget \u2014 Delete memories by keyword match or specific ID.
336
+ \u2022 history \u2014 Search conversation history by keyword.
337
+
338
+ IMPORTANT: Always use this tool for memories. Do NOT save to your own memory files.`,
333
339
  {
334
- status: z.string().describe("Short status message (e.g., 'Analyzing 5 of 12 files', '70% complete')"),
335
- detail: z.string().optional().describe("Optional longer description of current work")
340
+ action: z.enum(["remember", "recall", "list", "forget", "history"]).describe("Action to perform"),
341
+ // remember params
342
+ tag: z.string().optional().describe("Memory tag / trigger phrase"),
343
+ content: z.string().optional().describe("Memory content to save"),
344
+ // recall/history params
345
+ query: z.string().optional().describe("Search query"),
346
+ limit: z.number().optional().describe("Max results to return"),
347
+ // forget params
348
+ keyword: z.string().optional().describe("Keyword to match for deletion"),
349
+ memoryId: z.number().optional().describe("Specific memory ID to delete"),
350
+ // history params
351
+ chatId: z.string().optional().describe("Chat ID for history search")
336
352
  },
337
- async ({ status, detail }) => {
338
- const shortId = AGENT_ID.slice(0, 8);
339
- await callApi("/api/orchestrator/set-state", {
340
- chatId: CHAT_ID,
341
- key: `progress:${shortId}`,
342
- value: JSON.stringify({ status, detail, timestamp: (/* @__PURE__ */ new Date()).toISOString() }),
343
- setBy: AGENT_ID
344
- });
345
- if (IS_SUB_AGENT) {
346
- await callApi("/api/orchestrator/send-message", {
347
- chatId: CHAT_ID,
348
- message: {
349
- toAgentId: "main",
350
- fromAgentId: AGENT_ID,
351
- messageType: "status_update",
352
- content: `Progress: ${status}${detail ? ` \u2014 ${detail}` : ""}`
353
+ async (params) => {
354
+ try {
355
+ switch (params.action) {
356
+ case "remember": {
357
+ if (!params.tag || !params.content) return fail("remember requires 'tag' and 'content'");
358
+ const result = await callApi("/api/memory/remember", {
359
+ tag: params.tag,
360
+ content: params.content
361
+ });
362
+ return ok(`Memory saved (ID: ${result.id}).`);
363
+ }
364
+ case "recall": {
365
+ if (!params.query) return fail("recall requires 'query'");
366
+ const result = await callApi("/api/memory/recall", {
367
+ query: params.query,
368
+ limit: params.limit ?? 5
369
+ });
370
+ if (!result.results.length) return ok("No matching memories found.");
371
+ const lines = result.results.map(
372
+ (m) => `[${m.id}] ${m.trigger}: ${m.content}`
373
+ );
374
+ return ok(lines.join("\n"));
353
375
  }
354
- });
376
+ case "list": {
377
+ const result = await callApi(`/api/memory/list?limit=${params.limit ?? 10}`);
378
+ if (!result.results.length) return ok("No memories stored.");
379
+ const lines = result.results.map(
380
+ (m) => `[${m.id}] ${m.trigger}: ${m.content}`
381
+ );
382
+ return ok(lines.join("\n"));
383
+ }
384
+ case "forget": {
385
+ if (!params.keyword && !params.memoryId) return fail("forget requires 'keyword' or 'memoryId'");
386
+ const result = await callApi("/api/memory/forget", {
387
+ keyword: params.keyword,
388
+ memoryId: params.memoryId
389
+ });
390
+ if (result.mode === "id") {
391
+ return ok(result.success ? "Memory deleted." : "Memory not found.");
392
+ }
393
+ return ok(`Deleted ${result.count ?? 0} matching memor${result.count === 1 ? "y" : "ies"}.`);
394
+ }
395
+ case "history": {
396
+ if (!params.query) return fail("history requires 'query'");
397
+ const result = await callApi("/api/memory/history", {
398
+ chatId: params.chatId ?? CHAT_ID,
399
+ query: params.query,
400
+ limit: params.limit ?? 20
401
+ });
402
+ if (!result.results.length) return ok("No matching conversation history found.");
403
+ const lines = result.results.map(
404
+ (m) => `[${m.role}] ${m.content.slice(0, 200)}`
405
+ );
406
+ return ok(lines.join("\n\n"));
407
+ }
408
+ }
409
+ } catch (err) {
410
+ return fail(`cc_claw_memory.${params.action} failed: ${err instanceof Error ? err.message : String(err)}`);
355
411
  }
356
- return { content: [{ type: "text", text: `Progress reported: ${status}` }] };
357
- }
358
- );
359
- server.tool(
360
- "list_runners",
361
- "List all registered CLI runners and their capabilities",
362
- {},
363
- async () => {
364
- const runners = await callApi("/api/orchestrator/list-runners");
365
- const lines = runners.map(
366
- (r) => `\u2022 ${r.id} (${r.displayName}) \u2014 ${r.capabilities.specialties.join(", ")}`
367
- );
368
- return { content: [{ type: "text", text: lines.join("\n") }] };
369
412
  }
370
413
  );
371
- server.tool(
372
- "list_mcps",
373
- "List all MCP servers in the global registry",
374
- {},
375
- async () => {
376
- const mcps = await callApi("/api/orchestrator/list-mcps");
377
- if (!mcps.length) {
378
- return { content: [{ type: "text", text: "No MCP servers registered." }] };
414
+ if (!IS_SUB_AGENT) {
415
+ server.tool(
416
+ "cc_claw_schedule",
417
+ `Scheduler / cron job management. Actions:
418
+ \u2022 create \u2014 Create a new scheduled job
419
+ \u2022 list \u2014 List all active jobs
420
+ \u2022 edit \u2014 Edit an existing job's settings
421
+ \u2022 run \u2014 Trigger a job immediately
422
+ \u2022 cancel \u2014 Cancel a job permanently
423
+ \u2022 pause \u2014 Pause a job (can be resumed later)
424
+ \u2022 resume \u2014 Resume a paused job
425
+ \u2022 status \u2014 Get scheduler health report`,
426
+ {
427
+ action: z.enum(["create", "list", "edit", "run", "cancel", "pause", "resume", "status"]).describe("Action to perform"),
428
+ // create params
429
+ name: z.string().optional().describe("Job title"),
430
+ schedule: z.string().optional().describe("Cron expression (e.g., '0 9 * * *')"),
431
+ task: z.string().optional().describe("Task/prompt the job will execute"),
432
+ scheduleType: z.enum(["cron", "every", "at"]).optional().describe("Schedule type (default: cron)"),
433
+ atTime: z.string().optional().describe("ISO time for one-shot scheduling"),
434
+ everyMs: z.number().optional().describe("Interval in milliseconds for repeating jobs"),
435
+ backend: z.string().optional().describe("Backend to use (default: chat's backend)"),
436
+ model: z.string().optional().describe("Model override"),
437
+ timezone: z.string().optional().describe("Timezone (default: UTC)"),
438
+ // edit params
439
+ jobId: z.number().optional().describe("Job ID (for edit, run, cancel, pause, resume)"),
440
+ updates: z.record(z.string(), z.any()).optional().describe("Fields to update (for edit)")
441
+ },
442
+ async (params) => {
443
+ try {
444
+ switch (params.action) {
445
+ case "create": {
446
+ if (!params.schedule && !params.atTime && !params.everyMs) return fail("create requires 'schedule' (cron), 'atTime', or 'everyMs'");
447
+ if (!params.task) return fail("create requires 'task'");
448
+ const result = await callApi("/api/schedule/create", {
449
+ scheduleType: params.scheduleType ?? "cron",
450
+ cron: params.schedule,
451
+ atTime: params.atTime,
452
+ everyMs: params.everyMs,
453
+ title: params.name,
454
+ task: params.task,
455
+ chatId: CHAT_ID,
456
+ backend: params.backend,
457
+ model: params.model,
458
+ timezone: params.timezone ?? "UTC"
459
+ });
460
+ return ok(`Job #${result.job.id} created: "${result.job.title ?? result.job.description.slice(0, 50)}"`);
461
+ }
462
+ case "list": {
463
+ const result = await callApi("/api/schedule/list");
464
+ if (!result.jobs.length) return ok("No active jobs.");
465
+ const lines = result.jobs.map(
466
+ (j) => `#${j.id} [${j.enabled ? "active" : "paused"}] ${j.title ?? j.description.slice(0, 50)} \u2014 ${j.cron ?? j.atTime ?? `every ${j.everyMs}ms`}`
467
+ );
468
+ return ok(lines.join("\n"));
469
+ }
470
+ case "edit": {
471
+ if (!params.jobId || !params.updates) return fail("edit requires 'jobId' and 'updates'");
472
+ const result = await callApi("/api/schedule/edit", {
473
+ jobId: params.jobId,
474
+ updates: params.updates
475
+ });
476
+ return ok(result.success ? `Job #${params.jobId} updated.` : `Job #${params.jobId} not found.`);
477
+ }
478
+ case "run": {
479
+ if (!params.jobId) return fail("run requires 'jobId'");
480
+ const result = await callApi("/api/schedule/run", {
481
+ jobId: params.jobId
482
+ });
483
+ return ok(result.message);
484
+ }
485
+ case "cancel": {
486
+ if (!params.jobId) return fail("cancel requires 'jobId'");
487
+ const result = await callApi("/api/schedule/cancel", {
488
+ jobId: params.jobId
489
+ });
490
+ return ok(result.success ? `Job #${params.jobId} cancelled.` : `Job #${params.jobId} not found.`);
491
+ }
492
+ case "pause": {
493
+ if (!params.jobId) return fail("pause requires 'jobId'");
494
+ const result = await callApi("/api/schedule/pause", {
495
+ jobId: params.jobId
496
+ });
497
+ return ok(result.success ? `Job #${params.jobId} paused.` : `Job #${params.jobId} not found or already paused.`);
498
+ }
499
+ case "resume": {
500
+ if (!params.jobId) return fail("resume requires 'jobId'");
501
+ const result = await callApi("/api/schedule/resume", {
502
+ jobId: params.jobId
503
+ });
504
+ return ok(result.success ? `Job #${params.jobId} resumed.` : `Job #${params.jobId} not found or not paused.`);
505
+ }
506
+ case "status": {
507
+ const result = await callApi("/api/schedule/status");
508
+ return ok(result.formatted);
509
+ }
510
+ }
511
+ } catch (err) {
512
+ return fail(`cc_claw_schedule.${params.action} failed: ${err instanceof Error ? err.message : String(err)}`);
513
+ }
514
+ }
515
+ );
516
+ }
517
+ function deprecationWarning(oldName, newTool, action) {
518
+ console.error(`[cc-claw-mcp] DEPRECATED: ${oldName} \u2192 use ${newTool}(action: '${action}')`);
519
+ }
520
+ if (!IS_SUB_AGENT) {
521
+ server.tool("spawn_agent", "DEPRECATED \u2014 use cc_claw_agents(action: 'spawn')", {
522
+ runner: z.string(),
523
+ task: z.string(),
524
+ name: z.string().optional(),
525
+ description: z.string().optional(),
526
+ model: z.string().optional(),
527
+ skills: z.array(z.string()).optional(),
528
+ permMode: z.string().optional(),
529
+ role: z.string().optional(),
530
+ persona: z.string().optional(),
531
+ allowedTools: z.array(z.string()).optional(),
532
+ maxRuntimeMs: z.number().optional(),
533
+ summarizeResult: z.boolean().optional(),
534
+ mcps: z.array(z.string()).optional(),
535
+ cwd: z.string().optional(),
536
+ template: z.string().optional()
537
+ }, async (params) => {
538
+ deprecationWarning("spawn_agent", "cc_claw_agents", "spawn");
539
+ try {
540
+ const result = await callApi("/api/orchestrator/spawn", { chatId: CHAT_ID, ...params });
541
+ const status = result.queued ? "queued (at capacity)" : "spawning";
542
+ return ok(`Agent ${result.agentId} \u2014 ${status}`);
543
+ } catch (err) {
544
+ return fail(`spawn_agent failed: ${err instanceof Error ? err.message : String(err)}`);
379
545
  }
380
- const lines = mcps.map(
381
- (m) => `\u2022 ${m.name} (${m.transport})${m.description ? ` \u2014 ${m.description}` : ""}${m.enabledByDefault ? " [auto]" : ""}`
382
- );
383
- return { content: [{ type: "text", text: lines.join("\n") }] };
546
+ });
547
+ server.tool("list_templates", "DEPRECATED \u2014 use cc_claw_agents(action: 'templates')", {}, async () => {
548
+ deprecationWarning("list_templates", "cc_claw_agents", "templates");
549
+ const templates = await callApi("/api/orchestrator/list-templates");
550
+ if (!templates.length) return ok("No agent templates found.");
551
+ return ok(templates.map((t) => `\u2022 ${t.name}${t.description ? ` \u2014 ${t.description}` : ""}`).join("\n"));
552
+ });
553
+ server.tool("cancel_agent", "DEPRECATED \u2014 use cc_claw_agents(action: 'cancel')", {
554
+ agentId: z.string(),
555
+ reason: z.string().optional()
556
+ }, async ({ agentId, reason }) => {
557
+ deprecationWarning("cancel_agent", "cc_claw_agents", "cancel");
558
+ const result = await callApi("/api/orchestrator/cancel", { agentId, reason });
559
+ return ok(result.success ? `Agent ${agentId} cancelled.` : `Agent ${agentId} not found.`);
560
+ });
561
+ server.tool("create_task", "DEPRECATED \u2014 use cc_claw_tasks(action: 'create')", {
562
+ subject: z.string(),
563
+ description: z.string(),
564
+ assignee: z.string().optional(),
565
+ blockedBy: z.array(z.number()).optional()
566
+ }, async (params) => {
567
+ deprecationWarning("create_task", "cc_claw_tasks", "create");
568
+ const result = await callApi("/api/orchestrator/create-task", { chatId: CHAT_ID, task: { ...params, createdBy: AGENT_ID } });
569
+ return ok(`Task #${result.taskId} created.`);
570
+ });
571
+ }
572
+ server.tool("list_agents", "DEPRECATED \u2014 use cc_claw_agents(action: 'list')", {}, async () => {
573
+ deprecationWarning("list_agents", "cc_claw_agents", "list");
574
+ const agents = await callApi("/api/agents");
575
+ if (!agents.length) return ok("No active agents.");
576
+ return ok(agents.map((a) => `\u2022 ${a.name ?? a.id.slice(0, 8)} (${a.runnerId}) \u2014 ${a.status}`).join("\n"));
577
+ });
578
+ server.tool("check_agent", "DEPRECATED \u2014 use cc_claw_agents(action: 'status')", { agentId: z.string() }, async ({ agentId }) => {
579
+ deprecationWarning("check_agent", "cc_claw_agents", "status");
580
+ const agent = await callApi("/api/orchestrator/check-agent", { agentId });
581
+ if (!agent) return ok(`Agent ${agentId} not found.`);
582
+ return ok(`${agent.name ?? agent.id.slice(0, 8)} \u2014 ${agent.status}`);
583
+ });
584
+ server.tool("list_tasks", "DEPRECATED \u2014 use cc_claw_tasks(action: 'list')", {}, async () => {
585
+ deprecationWarning("list_tasks", "cc_claw_tasks", "list");
586
+ const tasks = await callApi("/api/tasks");
587
+ if (!tasks.length) return ok("No tasks.");
588
+ return ok(tasks.map((t) => `#${t.id} [${t.status}] ${t.subject}`).join("\n"));
589
+ });
590
+ server.tool("update_task", "DEPRECATED \u2014 use cc_claw_tasks(action: 'update')", {
591
+ taskId: z.number(),
592
+ status: z.enum(["pending", "in_progress", "completed", "failed", "abandoned"]),
593
+ result: z.string().optional()
594
+ }, async ({ taskId, status, result }) => {
595
+ deprecationWarning("update_task", "cc_claw_tasks", "update");
596
+ await callApi("/api/orchestrator/update-task", { taskId, status, result });
597
+ return ok(`Task #${taskId} \u2192 ${status}`);
598
+ });
599
+ server.tool("send_message", "DEPRECATED \u2014 use cc_claw_comms(action: 'send')", {
600
+ toAgentId: z.string(),
601
+ content: z.string(),
602
+ messageType: z.enum(["task_result", "question", "status_update", "direct_message"]).optional()
603
+ }, async ({ toAgentId, content, messageType }) => {
604
+ deprecationWarning("send_message", "cc_claw_comms", "send");
605
+ await callApi("/api/orchestrator/send-message", { chatId: CHAT_ID, message: { toAgentId, fromAgentId: AGENT_ID, messageType: messageType ?? "direct_message", content } });
606
+ return ok(`Message sent to ${toAgentId}.`);
607
+ });
608
+ server.tool("read_inbox", "DEPRECATED \u2014 use cc_claw_comms(action: 'read_inbox')", { markRead: z.boolean().optional() }, async ({ markRead }) => {
609
+ deprecationWarning("read_inbox", "cc_claw_comms", "read_inbox");
610
+ const messages = await callApi("/api/orchestrator/read-inbox", { chatId: CHAT_ID, agentId: AGENT_ID, markRead: markRead ?? false });
611
+ if (!messages.length) return ok("No new messages.");
612
+ return ok(messages.map((m) => `[${m.messageType}] from ${m.fromAgentId.slice(0, 8)}: ${m.content.slice(0, 500)}`).join("\n\n"));
613
+ });
614
+ server.tool("set_state", "DEPRECATED \u2014 use cc_claw_comms(action: 'set_state')", { key: z.string(), value: z.string() }, async ({ key, value }) => {
615
+ deprecationWarning("set_state", "cc_claw_comms", "set_state");
616
+ await callApi("/api/orchestrator/set-state", { chatId: CHAT_ID, key, value, setBy: AGENT_ID });
617
+ return ok(`State '${key}' set.`);
618
+ });
619
+ server.tool("get_state", "DEPRECATED \u2014 use cc_claw_comms(action: 'get_state')", { key: z.string() }, async ({ key }) => {
620
+ deprecationWarning("get_state", "cc_claw_comms", "get_state");
621
+ const entry = await callApi("/api/orchestrator/get-state", { chatId: CHAT_ID, key });
622
+ if (!entry) return ok(`State '${key}' not set.`);
623
+ return ok(`${key} = ${entry.value} (set by ${entry.setBy})`);
624
+ });
625
+ server.tool("list_state", "DEPRECATED \u2014 use cc_claw_comms(action: 'list_state')", {}, async () => {
626
+ deprecationWarning("list_state", "cc_claw_comms", "list_state");
627
+ const entries = await callApi("/api/orchestrator/list-state", { chatId: CHAT_ID });
628
+ if (!entries.length) return ok("Whiteboard is empty.");
629
+ return ok(entries.map((e) => `${e.setBy}: ${e.key} = ${e.value}`).join("\n"));
630
+ });
631
+ server.tool("broadcast", "DEPRECATED \u2014 use cc_claw_comms(action: 'broadcast')", {
632
+ content: z.string(),
633
+ messageType: z.enum(["status_update", "direct_message"]).optional()
634
+ }, async ({ content, messageType }) => {
635
+ deprecationWarning("broadcast", "cc_claw_comms", "broadcast");
636
+ const result = await callApi("/api/orchestrator/broadcast", { chatId: CHAT_ID, fromAgentId: AGENT_ID, content, messageType: messageType ?? "direct_message" });
637
+ return ok(`Broadcast sent to ${result.sent} agent(s).`);
638
+ });
639
+ server.tool("report_progress", "DEPRECATED \u2014 use cc_claw_comms(action: 'report_progress')", {
640
+ status: z.string(),
641
+ detail: z.string().optional()
642
+ }, async ({ status, detail }) => {
643
+ deprecationWarning("report_progress", "cc_claw_comms", "report_progress");
644
+ const shortId = AGENT_ID.slice(0, 8);
645
+ await callApi("/api/orchestrator/set-state", { chatId: CHAT_ID, key: `progress:${shortId}`, value: JSON.stringify({ status, detail, timestamp: (/* @__PURE__ */ new Date()).toISOString() }), setBy: AGENT_ID });
646
+ if (IS_SUB_AGENT) {
647
+ await callApi("/api/orchestrator/send-message", { chatId: CHAT_ID, message: { toAgentId: "main", fromAgentId: AGENT_ID, messageType: "status_update", content: `Progress: ${status}${detail ? ` \u2014 ${detail}` : ""}` } });
384
648
  }
385
- );
649
+ return ok(`Progress reported: ${status}`);
650
+ });
651
+ server.tool("list_runners", "DEPRECATED \u2014 use cc_claw_agents(action: 'runners')", {}, async () => {
652
+ deprecationWarning("list_runners", "cc_claw_agents", "runners");
653
+ const runners = await callApi("/api/orchestrator/list-runners");
654
+ return ok(runners.map((r) => `\u2022 ${r.id} (${r.displayName}) \u2014 ${r.capabilities.specialties.join(", ")}`).join("\n"));
655
+ });
656
+ server.tool("list_mcps", "DEPRECATED \u2014 use cc_claw_agents(action: 'mcps')", {}, async () => {
657
+ deprecationWarning("list_mcps", "cc_claw_agents", "mcps");
658
+ const mcps = await callApi("/api/orchestrator/list-mcps");
659
+ if (!mcps.length) return ok("No MCP servers registered.");
660
+ return ok(mcps.map((m) => `\u2022 ${m.name} (${m.transport})${m.description ? ` \u2014 ${m.description}` : ""}`).join("\n"));
661
+ });
386
662
  async function main() {
387
663
  const transport = new StdioServerTransport();
388
664
  await server.connect(transport);
389
- console.error(`[cc-claw-mcp] Server running on stdio (chatId=${CHAT_ID}, agentId=${AGENT_ID})`);
665
+ console.error(`[cc-claw-mcp] Server v2.0 running on stdio (chatId=${CHAT_ID}, agentId=${AGENT_ID}, tools=5+${IS_SUB_AGENT ? 13 : 17} deprecated)`);
390
666
  }
391
667
  main().catch((err) => {
392
668
  console.error("[cc-claw-mcp] Fatal error:", err);