cc-claw 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jacob Ben David
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,246 @@
1
+ # CC-Claw — Coding CLI Claw
2
+
3
+ <p align="center">
4
+ <img src="assets/cc_claw.png" alt="CC-Claw Logo" width="300" />
5
+ </p>
6
+
7
+ Your personal AI assistant on Telegram, powered by the best coding CLIs. CC-Claw uses **Claude Code**, **Gemini CLI**, and **Codex** as its backend intelligence — giving you a full personal and productivity assistant accessible from any device.
8
+
9
+ Send text, voice messages, photos, documents, or videos. The agent handles coding, research, email, scheduling, content creation, document analysis, and any task you need. Switch backends on the fly with `/claude`, `/gemini`, or `/codex`. Spawn sub-agents across different CLIs to work in parallel.
10
+
11
+ ## Supported Backends
12
+
13
+ | Backend | CLI | Models |
14
+ |---------|-----|--------|
15
+ | **Claude** | `claude` | Opus 4.6, Sonnet 4.6, Haiku 4.5 |
16
+ | **Gemini** | `gemini` | 3.1 Pro Preview, 3 Flash |
17
+ | **Codex** | `codex` | GPT-5.4, GPT-5.3 Codex, GPT-5.2 Codex |
18
+
19
+ Each backend supports per-model thinking/reasoning level control, session resumption, and configurable permission modes.
20
+
21
+ ## Prerequisites
22
+
23
+ - **Node.js 20+**
24
+ - **At least one coding CLI** installed:
25
+ - Claude Code CLI — `npm install -g @anthropic-ai/claude-code`
26
+ - Gemini CLI — `npm install -g @google/gemini-cli` (or via Homebrew)
27
+ - Codex CLI — `npm install -g @openai/codex` (or via Homebrew)
28
+ - **Telegram bot token** from [@BotFather](https://t.me/BotFather)
29
+
30
+ Backend-specific setup (e.g., Vertex AI for Claude, Google Cloud for Gemini, ChatGPT subscription for Codex) is handled during `cc-claw setup`.
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ npm install -g cc-claw
36
+ ```
37
+
38
+ ## Setup
39
+
40
+ ```bash
41
+ cc-claw setup
42
+ ```
43
+
44
+ The interactive wizard walks you through:
45
+
46
+ 1. Creating a Telegram bot and pasting the token
47
+ 2. Getting your Telegram chat ID (for security — only you can use the bot)
48
+ 3. Selecting your default backend and configuring its credentials
49
+ 4. Optional features: voice (Groq STT + ElevenLabs TTS), web dashboard, video analysis (Gemini)
50
+ 5. Installing as a background service
51
+
52
+ Configuration is saved to `~/.cc-claw/.env`. Data is organized under `~/.cc-claw/`:
53
+
54
+ ```
55
+ ~/.cc-claw/
56
+ .env config (Telegram token, backend credentials)
57
+ data/cc-claw.db SQLite database
58
+ logs/cc-claw.log stdout log
59
+ logs/cc-claw.error.log stderr log
60
+ runners/ config-driven CLI runner definitions (JSON)
61
+ workspace/
62
+ SOUL.md agent identity and personality (customizable)
63
+ USER.md your profile and preferences
64
+ CLAUDE.md / GEMINI.md native CLI awareness notes (auto-generated)
65
+ context/ on-demand context files
66
+ skills/ skill repository (available to all backends)
67
+ ```
68
+
69
+ ## Run
70
+
71
+ ```bash
72
+ # Foreground (see output in terminal)
73
+ cc-claw start
74
+
75
+ # Or install as a background service (launchd on macOS, systemd on Linux)
76
+ cc-claw install
77
+ ```
78
+
79
+ ## CLI
80
+
81
+ CC-Claw has a comprehensive CLI with 50+ commands. Every command supports `--json` for AI agents and scripts.
82
+
83
+ ```bash
84
+ cc-claw status # System status (backend, model, usage)
85
+ cc-claw doctor # Health checks + auto-fix
86
+ cc-claw logs -f # Follow daemon logs
87
+ cc-claw chat send "hello" # Talk to the AI from terminal
88
+ cc-claw tui # Interactive terminal chat
89
+ cc-claw backend set gemini # Switch backend
90
+ cc-claw model list # List models
91
+ cc-claw cron list # List scheduled jobs
92
+ cc-claw agents list # List active sub-agents
93
+ cc-claw agents spawn --runner claude --task "review code"
94
+ cc-claw db stats # Database health
95
+ cc-claw completion --shell zsh # Shell completions
96
+ cc-claw --ai # Generate AI skill file
97
+ ```
98
+
99
+ | Command Group | Description |
100
+ |---------------|-------------|
101
+ | `service` | start, stop, restart, status, install, uninstall |
102
+ | `status` / `doctor` / `logs` | Diagnostics and monitoring |
103
+ | `chat` / `tui` | Talk to the AI from terminal |
104
+ | `backend` / `model` / `thinking` | Backend and model management |
105
+ | `memory` | list, search, add, forget, history |
106
+ | `cron` / `schedule` | Scheduler management (create, cancel, pause, resume, run) |
107
+ | `agents` / `tasks` / `runners` | Agent orchestration |
108
+ | `config` | Runtime and static configuration |
109
+ | `usage` / `cost` | Cost tracking and usage limits |
110
+ | `permissions` / `tools` / `verbose` | Permission and tool management |
111
+ | `skills` / `mcps` | Skills and MCP servers |
112
+ | `db` | Database stats, backup, prune |
113
+ | `completion` | Shell completions (bash/zsh/fish) |
114
+ | `setup` | Interactive configuration wizard |
115
+
116
+ Read commands work offline (direct DB). Write commands require the daemon running.
117
+
118
+ ### Telegram Commands
119
+
120
+ Once running, send these to your bot:
121
+
122
+ | Command | Description |
123
+ |---------|-------------|
124
+ | `/help` | Show available commands |
125
+ | `/backend` | Switch AI backend (Claude / Gemini / Codex) |
126
+ | `/model` | Switch model for active backend (with thinking level picker) |
127
+ | `/summarizer` | Configure session summarization model |
128
+ | `/status` | Backend, model, thinking level, session, context usage |
129
+ | `/cost` | Estimated API cost for this chat (per-model breakdown) |
130
+ | `/cost all` | All-time cost breakdown by model across all chats |
131
+ | `/usage` | Usage per backend with limit warnings |
132
+ | `/limits` | Configure usage limits per backend |
133
+ | `/newchat` | Start a fresh conversation |
134
+ | `/cwd <path>` | Set the working directory |
135
+ | `/memory` | List stored memories |
136
+ | `/forget <keyword>` | Remove a memory |
137
+ | `/voice` | Toggle voice responses |
138
+ | `/cron <desc>` | Schedule a task (or `/schedule`) |
139
+ | `/cron` | List scheduled jobs (or `/jobs`) |
140
+ | `/cron cancel <id>` | Cancel a scheduled job |
141
+ | `/cron pause <id>` | Pause a job |
142
+ | `/cron resume <id>` | Resume a job |
143
+ | `/cron run <id>` | Trigger a job now |
144
+ | `/cron runs [id]` | View run history |
145
+ | `/cron edit <id>` | Edit a job |
146
+ | `/cron health` | Scheduler health |
147
+ | `/skills` | List skills from all backends |
148
+ | `/skill-install <url>` | Install a skill from GitHub |
149
+ | `/setup-profile` | Set up your user profile |
150
+ | `/chats` | List authorized chats and aliases |
151
+ | `/heartbeat` | Proactive awareness (on/off/interval/hours) |
152
+ | `/history` | List recent session summaries |
153
+ | `/stop` | Cancel the current task |
154
+ | `/tools` | Configure which tools the agent can use |
155
+ | `/permissions` | Switch permission mode |
156
+ | `/verbose` | Tool visibility (off / normal / verbose) |
157
+ | `/claude` `/gemini` `/codex` | Quick backend switch |
158
+ | `/agents` | List active sub-agents |
159
+ | `/tasks` | Show task board for current orchestration |
160
+ | `/stopagent <id>` | Cancel a specific sub-agent |
161
+ | `/stopall` | Cancel all sub-agents |
162
+ | `/runners` | List registered CLI runners |
163
+ | `/mcp` | List all MCP servers (central + backends) |
164
+
165
+ ### Permission Modes
166
+
167
+ - **yolo** — Full autopilot, all tools auto-approved
168
+ - **safe** — Only user-configured tools allowed
169
+ - **readonly** — Read, Glob, Grep only
170
+ - **plan** — Plan only, no execution
171
+
172
+ ## Architecture
173
+
174
+ ```
175
+ Telegram --> TelegramChannel --> router (handleMessage)
176
+ |-- command --> handleCommand
177
+ |-- voice --> Groq Whisper STT --> askAgent
178
+ |-- photo/document/video --> handleMedia --> askAgent
179
+ '-- text --> askAgent --> sendResponse
180
+ '--> [SEND_FILE:/path] --> channel.sendFile
181
+ ```
182
+
183
+ The core `askAgent()` function delegates to the active backend's adapter, which handles CLI arg construction, NDJSON parsing, and env setup. All state is persisted in SQLite with FTS5 full-text search.
184
+
185
+ ### Agent Orchestration
186
+
187
+ CC-Claw can spawn and manage multiple sub-agents across different coding CLIs simultaneously:
188
+
189
+ ```
190
+ User: "Have Claude review the code while Gemini researches the API docs"
191
+ → Main agent calls spawn_agent(runner: "claude", task: "Review...", role: "reviewer")
192
+ → Main agent calls spawn_agent(runner: "gemini", task: "Research...", role: "worker")
193
+ → Sub-agents communicate via inbox, share data via whiteboard
194
+ → Results flow back to the main agent for synthesis
195
+ ```
196
+
197
+ Sub-agents support task dependencies, role-based prompts (worker/reviewer/planner), idle detection, timeout enforcement, and per-orchestration cost tracking. See `docs/plans/2026-03-09-agent-orchestration-design.md` for the full design.
198
+
199
+ ### MCP Management
200
+
201
+ MCP (Model Context Protocol) servers are managed centrally. `/mcp` shows all MCPs across all backends. Each backend's native MCPs are available when running through CC-Claw. The orchestrator MCP server gives agents tools for spawning sub-agents, messaging, and task management.
202
+
203
+ ### Adding a CLI Runner
204
+
205
+ Users can add any coding CLI as a sub-agent runner without code changes:
206
+
207
+ 1. Tell CC-Claw: *"Onboard cursor CLI as a sub-agent"*
208
+ 2. The agent discovers capabilities, tests the CLI, and creates a JSON config at `~/.cc-claw/runners/`
209
+ 3. The new runner is immediately available via `/runners`
210
+
211
+ For developers: create a JSON runner config (see `src/agents/runners/config-types.ts`) or a TypeScript adapter.
212
+
213
+ ### Adding a Channel
214
+
215
+ 1. Create `src/channels/<name>.ts` implementing the `Channel` interface
216
+ 2. Register it in `src/index.ts`
217
+
218
+ The router handles all message logic — the channel only delivers and sends messages.
219
+
220
+ ## Environment Variables
221
+
222
+ Set in `~/.cc-claw/.env` (created by `cc-claw setup`):
223
+
224
+ | Variable | Required | Description |
225
+ |----------|----------|-------------|
226
+ | `TELEGRAM_BOT_TOKEN` | Yes | From @BotFather |
227
+ | `ALLOWED_CHAT_ID` | Yes | Comma-separated chat IDs (your user ID + group IDs) |
228
+ | `CLAUDE_CODE_USE_VERTEX` | For Claude | Set to `1` |
229
+ | `CLOUD_ML_REGION` | For Claude | e.g. `us-east5` |
230
+ | `ANTHROPIC_VERTEX_PROJECT_ID` | For Claude | Your GCP project |
231
+ | `GROQ_API_KEY` | No | Voice transcription |
232
+ | `ELEVENLABS_API_KEY` | No | Voice replies (TTS) |
233
+ | `GEMINI_API_KEY` | No | Video analysis |
234
+ | `DASHBOARD_ENABLED` | No | Set to `1` for web dashboard |
235
+ | `DASHBOARD_TOKEN` | No | Fixed auth token for dashboard API (auto-generated if not set) |
236
+ | `CLAUDE_CODE_EXECUTABLE` | No | Path to claude CLI (auto-detected) |
237
+ | `GEMINI_CLI_EXECUTABLE` | No | Path to gemini CLI (auto-detected) |
238
+ | `CODEX_EXECUTABLE` | No | Path to codex CLI (auto-detected) |
239
+ | `EMBEDDING_PROVIDER` | No | Embedding provider: `ollama` (default), `gemini`, `openai`, `off` |
240
+ | `MEMORY_VECTOR_WEIGHT` | No | Vector vs keyword weight for memory search (default: `0.7`) |
241
+ | `OLLAMA_URL` | No | Ollama server URL (default: `http://localhost:11434`) |
242
+ | `CC_CLAW_HOME` | No | Config dir (default: `~/.cc-claw`) |
243
+
244
+ ## License
245
+
246
+ MIT
@@ -0,0 +1,342 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/agents/mcp-server.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z } from "zod";
7
+ var BASE_URL = `http://127.0.0.1:${process.env.CC_CLAW_PORT ?? "3141"}`;
8
+ var CHAT_ID = process.env.CC_CLAW_CHAT_ID ?? "";
9
+ var AGENT_ID = process.env.CC_CLAW_AGENT_ID ?? "main";
10
+ var DASHBOARD_TOKEN = process.env.CC_CLAW_DASHBOARD_TOKEN ?? "";
11
+ var IS_SUB_AGENT = AGENT_ID !== "main";
12
+ async function callApi(path, body) {
13
+ const url = `${BASE_URL}${path}`;
14
+ const headers = { "Content-Type": "application/json" };
15
+ if (DASHBOARD_TOKEN) {
16
+ headers["Authorization"] = `Bearer ${DASHBOARD_TOKEN}`;
17
+ }
18
+ const opts = body ? { method: "POST", headers, body: JSON.stringify(body) } : { method: "GET", headers };
19
+ const res = await fetch(url, opts);
20
+ if (!res.ok) {
21
+ const text = await res.text();
22
+ throw new Error(`API error ${res.status}: ${text}`);
23
+ }
24
+ return res.json();
25
+ }
26
+ var server = new McpServer({
27
+ name: "cc-claw-orchestrator",
28
+ version: "1.0.0"
29
+ });
30
+ if (!IS_SUB_AGENT) {
31
+ server.tool(
32
+ "spawn_agent",
33
+ `Spawn a sub-agent using a specific CLI runner to execute a task.
34
+
35
+ CRITICAL \u2014 Path verification:
36
+ Before calling this tool with a cwd, you MUST verify the path exists using your file system tools (Glob, Read, ls, etc.).
37
+ User-provided paths are often approximate (wrong name, missing suffix, typo). Always confirm the exact path yourself.
38
+ A non-existent cwd will cause the agent to fail immediately. Never pass an unverified path.
39
+
40
+ Typical workflow:
41
+ 1. User says "scan the foo-bar folder under dev_projects"
42
+ 2. You run: ls /Users/.../dev_projects/ (or Glob) to find the actual folder name
43
+ 3. You confirm the exact path exists (e.g., it might be foo-bars, foobar, foo_bar, etc.)
44
+ 4. Only then call spawn_agent with the verified cwd`,
45
+ {
46
+ runner: z.string().describe("CLI runner ID (e.g., 'claude', 'gemini', 'codex', 'opencode')"),
47
+ task: z.string().describe("Task description for the sub-agent"),
48
+ name: z.string().optional().describe("Human-readable agent name (e.g., 'security-reviewer', 'linkedin-writer')"),
49
+ description: z.string().optional().describe("Short description of what this agent does"),
50
+ model: z.string().optional().describe("Model override for the sub-agent"),
51
+ skills: z.array(z.string()).optional().describe("Skills to inject into the sub-agent"),
52
+ permMode: z.string().optional().describe("Permission mode: yolo, safe, readonly, plan (defaults to chat's mode)"),
53
+ role: z.string().optional().describe("Agent role. Presets: worker, reviewer, planner, researcher, writer, analyst, debugger, critic, synthesizer. Custom roles accepted as labels."),
54
+ persona: z.string().optional().describe("Custom system prompt for the agent. Replaces the role's default prompt while preserving orchestrator tools and task."),
55
+ allowedTools: z.array(z.string()).optional().describe("Restrict agent to specific tools (Claude-only). E.g., ['Read', 'Grep', 'Glob']"),
56
+ maxRuntimeMs: z.number().optional().describe("Max runtime in ms before timeout (default: 600000 = 10 min)"),
57
+ summarizeResult: z.boolean().optional().describe("Summarize agent output before posting to inbox"),
58
+ mcps: z.array(z.string()).optional().describe("MCP servers to inject into the sub-agent"),
59
+ cwd: z.string().optional().describe("Working directory for the sub-agent. MUST be a verified, existing absolute path \u2014 check it before calling."),
60
+ 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.")
61
+ },
62
+ async (params) => {
63
+ try {
64
+ const result = await callApi("/api/orchestrator/spawn", { chatId: CHAT_ID, ...params });
65
+ const status = result.queued ? "queued (at capacity, will start when a slot opens)" : "spawning";
66
+ return {
67
+ content: [{ type: "text", text: `Agent ${result.agentId} \u2014 ${status}` }]
68
+ };
69
+ } catch (err) {
70
+ const msg = err instanceof Error ? err.message : String(err);
71
+ return {
72
+ content: [{ type: "text", text: `spawn_agent failed: ${msg}` }],
73
+ isError: true
74
+ };
75
+ }
76
+ }
77
+ );
78
+ server.tool(
79
+ "list_templates",
80
+ "List available agent templates from ~/.cc-claw/agents/. Use a template name with spawn_agent's template parameter to spawn a pre-configured agent.",
81
+ {},
82
+ async () => {
83
+ const templates = await callApi("/api/orchestrator/list-templates");
84
+ if (!templates.length) {
85
+ return { content: [{ type: "text", text: "No agent templates found. Create .md files in ~/.cc-claw/agents/ with YAML frontmatter." }] };
86
+ }
87
+ const lines = templates.map(
88
+ (t) => `\u2022 **${t.name}**${t.description ? ` \u2014 ${t.description}` : ""}${t.runner ? ` (runner: ${t.runner})` : ""}${t.model ? ` [${t.model}]` : ""}`
89
+ );
90
+ return { content: [{ type: "text", text: lines.join("\n") }] };
91
+ }
92
+ );
93
+ server.tool(
94
+ "cancel_agent",
95
+ "Cancel a specific sub-agent",
96
+ {
97
+ agentId: z.string().describe("The agent ID to cancel"),
98
+ reason: z.string().optional().describe("Reason for cancellation")
99
+ },
100
+ async ({ agentId, reason }) => {
101
+ const result = await callApi("/api/orchestrator/cancel", { agentId, reason });
102
+ return {
103
+ content: [{ type: "text", text: result.success ? `Agent ${agentId} cancelled.` : `Agent ${agentId} not found or already completed.` }]
104
+ };
105
+ }
106
+ );
107
+ server.tool(
108
+ "create_task",
109
+ "Add a task to the shared task list",
110
+ {
111
+ subject: z.string().describe("Short imperative title for the task"),
112
+ description: z.string().describe("Detailed task requirements"),
113
+ assignee: z.string().optional().describe("Agent ID to assign the task to"),
114
+ blockedBy: z.array(z.number()).optional().describe("Task IDs that must complete before this task can start")
115
+ },
116
+ async (params) => {
117
+ const result = await callApi("/api/orchestrator/create-task", {
118
+ chatId: CHAT_ID,
119
+ task: { ...params, createdBy: AGENT_ID }
120
+ });
121
+ return {
122
+ content: [{ type: "text", text: `Task #${result.taskId} created.` }]
123
+ };
124
+ }
125
+ );
126
+ }
127
+ server.tool(
128
+ "list_agents",
129
+ "List all active and queued sub-agents",
130
+ {},
131
+ async () => {
132
+ const agents = await callApi("/api/agents");
133
+ if (!agents.length) {
134
+ return { content: [{ type: "text", text: "No active agents." }] };
135
+ }
136
+ const lines = agents.map((a) => {
137
+ const label = a.name ?? a.id.slice(0, 8);
138
+ return `\u2022 ${label} (${a.runnerId}) \u2014 ${a.status}${a.task ? ` \u2192 ${a.task.slice(0, 80)}` : ""}`;
139
+ });
140
+ return { content: [{ type: "text", text: lines.join("\n") }] };
141
+ }
142
+ );
143
+ server.tool(
144
+ "check_agent",
145
+ "Get detailed status of a specific agent",
146
+ {
147
+ agentId: z.string().describe("The agent ID to check")
148
+ },
149
+ async ({ agentId }) => {
150
+ const agent = await callApi("/api/orchestrator/check-agent", { agentId });
151
+ if (!agent) {
152
+ return { content: [{ type: "text", text: `Agent ${agentId} not found.` }] };
153
+ }
154
+ return {
155
+ content: [{
156
+ type: "text",
157
+ text: [
158
+ `Agent: ${agent.name ?? agent.id.slice(0, 8)} (${agent.runnerId})`,
159
+ agent.name ? `ID: ${agent.id.slice(0, 8)}` : null,
160
+ agent.description ? `Description: ${agent.description}` : null,
161
+ `Status: ${agent.status}`,
162
+ `Task: ${agent.task ?? "none"}`,
163
+ `Role: ${agent.role}`,
164
+ `Tokens: ${agent.tokenInput} in / ${agent.tokenOutput} out`,
165
+ agent.resultSummary ? `Result: ${agent.resultSummary.slice(0, 500)}` : null
166
+ ].filter(Boolean).join("\n")
167
+ }]
168
+ };
169
+ }
170
+ );
171
+ server.tool(
172
+ "list_tasks",
173
+ "Get the full task board",
174
+ {},
175
+ async () => {
176
+ const tasks = await callApi("/api/tasks");
177
+ if (!tasks.length) {
178
+ return { content: [{ type: "text", text: "No tasks." }] };
179
+ }
180
+ const lines = tasks.map(
181
+ (t) => `#${t.id} [${t.status}] ${t.subject}${t.assignee ? ` (\u2192 ${t.assignee.slice(0, 8)})` : ""}`
182
+ );
183
+ return { content: [{ type: "text", text: lines.join("\n") }] };
184
+ }
185
+ );
186
+ server.tool(
187
+ "update_task",
188
+ "Update a task's status",
189
+ {
190
+ taskId: z.number().describe("The task ID to update"),
191
+ status: z.enum(["pending", "in_progress", "completed", "failed", "abandoned"]).describe("New status"),
192
+ result: z.string().optional().describe("Result summary (for completed/failed)")
193
+ },
194
+ async ({ taskId, status, result }) => {
195
+ await callApi("/api/orchestrator/update-task", { taskId, status, result });
196
+ return { content: [{ type: "text", text: `Task #${taskId} \u2192 ${status}` }] };
197
+ }
198
+ );
199
+ server.tool(
200
+ "send_message",
201
+ "Send a message to another agent's inbox",
202
+ {
203
+ toAgentId: z.string().describe("Recipient agent ID (or 'main' for the main agent)"),
204
+ content: z.string().describe("Message content"),
205
+ messageType: z.enum(["task_result", "question", "status_update", "direct_message"]).optional().describe("Message type (default: direct_message)")
206
+ },
207
+ async ({ toAgentId, content, messageType }) => {
208
+ await callApi("/api/orchestrator/send-message", {
209
+ chatId: CHAT_ID,
210
+ message: {
211
+ toAgentId,
212
+ fromAgentId: AGENT_ID,
213
+ messageType: messageType ?? "direct_message",
214
+ content
215
+ }
216
+ });
217
+ return { content: [{ type: "text", text: `Message sent to ${toAgentId}.` }] };
218
+ }
219
+ );
220
+ server.tool(
221
+ "read_inbox",
222
+ "Read pending messages from your inbox. Messages remain unread unless markRead is true.",
223
+ {
224
+ markRead: z.boolean().optional().describe("Mark messages as read after returning them (default: false)")
225
+ },
226
+ async ({ markRead }) => {
227
+ const messages = await callApi("/api/orchestrator/read-inbox", {
228
+ chatId: CHAT_ID,
229
+ agentId: AGENT_ID,
230
+ markRead: markRead ?? false
231
+ });
232
+ if (!messages.length) {
233
+ return { content: [{ type: "text", text: "No new messages." }] };
234
+ }
235
+ const lines = messages.map(
236
+ (m) => `[${m.messageType}] from ${m.fromAgentId.slice(0, 8)}: ${m.content.slice(0, 500)}`
237
+ );
238
+ return { content: [{ type: "text", text: lines.join("\n\n") }] };
239
+ }
240
+ );
241
+ server.tool(
242
+ "set_state",
243
+ "Write a key-value pair to the shared state whiteboard",
244
+ {
245
+ key: z.string().describe("State key (1-256 chars)"),
246
+ value: z.string().describe("State value (JSON-serialized data)")
247
+ },
248
+ async ({ key, value }) => {
249
+ await callApi("/api/orchestrator/set-state", {
250
+ chatId: CHAT_ID,
251
+ key,
252
+ value,
253
+ setBy: AGENT_ID
254
+ });
255
+ return { content: [{ type: "text", text: `State '${key}' set.` }] };
256
+ }
257
+ );
258
+ server.tool(
259
+ "get_state",
260
+ "Read a value from the shared state whiteboard",
261
+ {
262
+ key: z.string().describe("State key to read")
263
+ },
264
+ async ({ key }) => {
265
+ const entry = await callApi("/api/orchestrator/get-state", {
266
+ chatId: CHAT_ID,
267
+ key
268
+ });
269
+ if (!entry) {
270
+ return { content: [{ type: "text", text: `State '${key}' not set.` }] };
271
+ }
272
+ return { content: [{ type: "text", text: `${key} = ${entry.value} (set by ${entry.setBy})` }] };
273
+ }
274
+ );
275
+ server.tool(
276
+ "list_state",
277
+ "List all key-value entries on the shared whiteboard for this orchestration",
278
+ {},
279
+ async () => {
280
+ const entries = await callApi("/api/orchestrator/list-state", {
281
+ chatId: CHAT_ID
282
+ });
283
+ if (!entries.length) {
284
+ return { content: [{ type: "text", text: "Whiteboard is empty." }] };
285
+ }
286
+ const lines = entries.map((e) => `${e.setBy}: ${e.key} = ${e.value}`);
287
+ return { content: [{ type: "text", text: lines.join("\n") }] };
288
+ }
289
+ );
290
+ server.tool(
291
+ "broadcast",
292
+ "Send a message to ALL other agents in the orchestration",
293
+ {
294
+ content: z.string().describe("Message content to broadcast"),
295
+ messageType: z.enum(["status_update", "direct_message"]).optional().describe("Message type (default: direct_message)")
296
+ },
297
+ async ({ content, messageType }) => {
298
+ const result = await callApi("/api/orchestrator/broadcast", {
299
+ chatId: CHAT_ID,
300
+ fromAgentId: AGENT_ID,
301
+ content,
302
+ messageType: messageType ?? "direct_message"
303
+ });
304
+ return { content: [{ type: "text", text: `Broadcast sent to ${result.sent} agent(s).` }] };
305
+ }
306
+ );
307
+ server.tool(
308
+ "list_runners",
309
+ "List all registered CLI runners and their capabilities",
310
+ {},
311
+ async () => {
312
+ const runners = await callApi("/api/orchestrator/list-runners");
313
+ const lines = runners.map(
314
+ (r) => `\u2022 ${r.id} (${r.displayName}) \u2014 ${r.capabilities.specialties.join(", ")}`
315
+ );
316
+ return { content: [{ type: "text", text: lines.join("\n") }] };
317
+ }
318
+ );
319
+ server.tool(
320
+ "list_mcps",
321
+ "List all MCP servers in the global registry",
322
+ {},
323
+ async () => {
324
+ const mcps = await callApi("/api/orchestrator/list-mcps");
325
+ if (!mcps.length) {
326
+ return { content: [{ type: "text", text: "No MCP servers registered." }] };
327
+ }
328
+ const lines = mcps.map(
329
+ (m) => `\u2022 ${m.name} (${m.transport})${m.description ? ` \u2014 ${m.description}` : ""}${m.enabledByDefault ? " [auto]" : ""}`
330
+ );
331
+ return { content: [{ type: "text", text: lines.join("\n") }] };
332
+ }
333
+ );
334
+ async function main() {
335
+ const transport = new StdioServerTransport();
336
+ await server.connect(transport);
337
+ console.error(`[cc-claw-mcp] Server running on stdio (chatId=${CHAT_ID}, agentId=${AGENT_ID})`);
338
+ }
339
+ main().catch((err) => {
340
+ console.error("[cc-claw-mcp] Fatal error:", err);
341
+ process.exit(1);
342
+ });