claude-control 0.1.0__tar.gz

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.
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.4
2
+ Name: claude-control
3
+ Version: 0.1.0
4
+ Summary: MCP server for coordinating Claude Code instances across multiple projects
5
+ Author-email: Red Duck Labs <support@redducklabs.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/redducklabs/claude-control
8
+ Project-URL: Repository, https://github.com/redducklabs/claude-control
9
+ Project-URL: Issues, https://github.com/redducklabs/claude-control/issues
10
+ Keywords: mcp,claude,claude-code,automation,orchestration
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: claude-code-sdk>=0.0.25
22
+ Requires-Dist: mcp>=1.12.0
23
+
24
+ # Claude Control
25
+
26
+ MCP server that lets a Claude Code session coordinate with Claude Code instances running in other project directories.
27
+
28
+ ## How It Works
29
+
30
+ Claude Control is an MCP server (stdio transport) that exposes tools for dispatching prompts to Claude Code instances in configured project directories. Each remote instance:
31
+
32
+ - Runs as a persistent subprocess with conversation context preserved across calls
33
+ - Loads the target project's own `CLAUDE.md`, `.mcp.json`, hooks, and settings
34
+ - Runs with `bypassPermissions` for fully autonomous operation
35
+
36
+ ## Installation
37
+
38
+ ### From PyPI
39
+
40
+ ```bash
41
+ pip install claude-control
42
+ ```
43
+
44
+ Or with [uv](https://docs.astral.sh/uv/):
45
+
46
+ ```bash
47
+ uv tool install claude-control
48
+ ```
49
+
50
+ ### From Source
51
+
52
+ ```bash
53
+ git clone https://github.com/redducklabs/claude-control.git
54
+ cd claude-control
55
+ pip install .
56
+ ```
57
+
58
+ ## Setup
59
+
60
+ ### 1. Configure Projects
61
+
62
+ Create a `projects.json` file (see `projects.json.example`):
63
+
64
+ ```json
65
+ {
66
+ "projects": [
67
+ {
68
+ "name": "my-backend",
69
+ "path": "D:\\repos\\my-backend",
70
+ "description": "Backend API service"
71
+ },
72
+ {
73
+ "name": "my-frontend",
74
+ "path": "D:\\repos\\my-frontend",
75
+ "description": "Frontend web application"
76
+ }
77
+ ]
78
+ }
79
+ ```
80
+
81
+ ### 2. Register as an MCP Server
82
+
83
+ Add to the `.mcp.json` of the project where you want coordination tools available.
84
+
85
+ **Using `uvx` (recommended — no global install needed):**
86
+
87
+ ```json
88
+ {
89
+ "mcpServers": {
90
+ "claude_control": {
91
+ "command": "uvx",
92
+ "args": ["claude-control"],
93
+ "env": {
94
+ "CLAUDE_CONTROL_PROJECTS": "/path/to/your/projects.json"
95
+ }
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ **Using a pip install:**
102
+
103
+ ```json
104
+ {
105
+ "mcpServers": {
106
+ "claude_control": {
107
+ "command": "claude-control",
108
+ "env": {
109
+ "CLAUDE_CONTROL_PROJECTS": "/path/to/your/projects.json"
110
+ }
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ **Using `python -m`:**
117
+
118
+ ```json
119
+ {
120
+ "mcpServers": {
121
+ "claude_control": {
122
+ "command": "python",
123
+ "args": ["-m", "claude_control"],
124
+ "env": {
125
+ "CLAUDE_CONTROL_PROJECTS": "/path/to/your/projects.json"
126
+ }
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ ### 3. Restart Claude Code
133
+
134
+ The tools will appear as `mcp__claude_control__send_command`, `mcp__claude_control__list_projects`, etc.
135
+
136
+ ## Tools
137
+
138
+ ### `send_command`
139
+
140
+ Send a prompt to a Claude Code instance in the specified project directory.
141
+
142
+ | Parameter | Type | Description |
143
+ |-----------|------|-------------|
144
+ | `project` | string | Project name (from projects.json) |
145
+ | `prompt` | string | The prompt to send |
146
+
147
+ Returns the full text response from the remote instance. Sessions persist across calls — follow-up prompts have access to prior context.
148
+
149
+ ### `list_projects`
150
+
151
+ List all configured projects with their paths, descriptions, and session status.
152
+
153
+ ### `reset_session`
154
+
155
+ Tear down a project's Claude Code session. The next `send_command` call creates a fresh session with no prior context.
156
+
157
+ | Parameter | Type | Description |
158
+ |-----------|------|-------------|
159
+ | `project` | string | Project name to reset |
160
+
161
+ ### `get_session_status`
162
+
163
+ Check whether a project has an active session, its ID, and turn count.
164
+
165
+ | Parameter | Type | Description |
166
+ |-----------|------|-------------|
167
+ | `project` | string | Project name to check |
168
+
169
+ ## Dependencies
170
+
171
+ - Python >= 3.11
172
+ - `claude-code-sdk >= 0.0.25`
173
+ - `mcp >= 1.12.0`
174
+ - Claude Code CLI installed and on PATH
175
+
176
+ ## Configuration
177
+
178
+ The `CLAUDE_CONTROL_PROJECTS` environment variable points to your `projects.json`. If unset, defaults to `projects.json` in the package's parent directory.
@@ -0,0 +1,155 @@
1
+ # Claude Control
2
+
3
+ MCP server that lets a Claude Code session coordinate with Claude Code instances running in other project directories.
4
+
5
+ ## How It Works
6
+
7
+ Claude Control is an MCP server (stdio transport) that exposes tools for dispatching prompts to Claude Code instances in configured project directories. Each remote instance:
8
+
9
+ - Runs as a persistent subprocess with conversation context preserved across calls
10
+ - Loads the target project's own `CLAUDE.md`, `.mcp.json`, hooks, and settings
11
+ - Runs with `bypassPermissions` for fully autonomous operation
12
+
13
+ ## Installation
14
+
15
+ ### From PyPI
16
+
17
+ ```bash
18
+ pip install claude-control
19
+ ```
20
+
21
+ Or with [uv](https://docs.astral.sh/uv/):
22
+
23
+ ```bash
24
+ uv tool install claude-control
25
+ ```
26
+
27
+ ### From Source
28
+
29
+ ```bash
30
+ git clone https://github.com/redducklabs/claude-control.git
31
+ cd claude-control
32
+ pip install .
33
+ ```
34
+
35
+ ## Setup
36
+
37
+ ### 1. Configure Projects
38
+
39
+ Create a `projects.json` file (see `projects.json.example`):
40
+
41
+ ```json
42
+ {
43
+ "projects": [
44
+ {
45
+ "name": "my-backend",
46
+ "path": "D:\\repos\\my-backend",
47
+ "description": "Backend API service"
48
+ },
49
+ {
50
+ "name": "my-frontend",
51
+ "path": "D:\\repos\\my-frontend",
52
+ "description": "Frontend web application"
53
+ }
54
+ ]
55
+ }
56
+ ```
57
+
58
+ ### 2. Register as an MCP Server
59
+
60
+ Add to the `.mcp.json` of the project where you want coordination tools available.
61
+
62
+ **Using `uvx` (recommended — no global install needed):**
63
+
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "claude_control": {
68
+ "command": "uvx",
69
+ "args": ["claude-control"],
70
+ "env": {
71
+ "CLAUDE_CONTROL_PROJECTS": "/path/to/your/projects.json"
72
+ }
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ **Using a pip install:**
79
+
80
+ ```json
81
+ {
82
+ "mcpServers": {
83
+ "claude_control": {
84
+ "command": "claude-control",
85
+ "env": {
86
+ "CLAUDE_CONTROL_PROJECTS": "/path/to/your/projects.json"
87
+ }
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ **Using `python -m`:**
94
+
95
+ ```json
96
+ {
97
+ "mcpServers": {
98
+ "claude_control": {
99
+ "command": "python",
100
+ "args": ["-m", "claude_control"],
101
+ "env": {
102
+ "CLAUDE_CONTROL_PROJECTS": "/path/to/your/projects.json"
103
+ }
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ ### 3. Restart Claude Code
110
+
111
+ The tools will appear as `mcp__claude_control__send_command`, `mcp__claude_control__list_projects`, etc.
112
+
113
+ ## Tools
114
+
115
+ ### `send_command`
116
+
117
+ Send a prompt to a Claude Code instance in the specified project directory.
118
+
119
+ | Parameter | Type | Description |
120
+ |-----------|------|-------------|
121
+ | `project` | string | Project name (from projects.json) |
122
+ | `prompt` | string | The prompt to send |
123
+
124
+ Returns the full text response from the remote instance. Sessions persist across calls — follow-up prompts have access to prior context.
125
+
126
+ ### `list_projects`
127
+
128
+ List all configured projects with their paths, descriptions, and session status.
129
+
130
+ ### `reset_session`
131
+
132
+ Tear down a project's Claude Code session. The next `send_command` call creates a fresh session with no prior context.
133
+
134
+ | Parameter | Type | Description |
135
+ |-----------|------|-------------|
136
+ | `project` | string | Project name to reset |
137
+
138
+ ### `get_session_status`
139
+
140
+ Check whether a project has an active session, its ID, and turn count.
141
+
142
+ | Parameter | Type | Description |
143
+ |-----------|------|-------------|
144
+ | `project` | string | Project name to check |
145
+
146
+ ## Dependencies
147
+
148
+ - Python >= 3.11
149
+ - `claude-code-sdk >= 0.0.25`
150
+ - `mcp >= 1.12.0`
151
+ - Claude Code CLI installed and on PATH
152
+
153
+ ## Configuration
154
+
155
+ The `CLAUDE_CONTROL_PROJECTS` environment variable points to your `projects.json`. If unset, defaults to `projects.json` in the package's parent directory.
@@ -0,0 +1 @@
1
+ """Claude Control — MCP server for coordinating Claude Code instances across projects."""
@@ -0,0 +1,5 @@
1
+ """Allow running as `python -m claude_control`."""
2
+
3
+ from claude_control.server import main
4
+
5
+ main()
@@ -0,0 +1,70 @@
1
+ """Load and validate project configuration."""
2
+
3
+ import json
4
+ import os
5
+ import sys
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+
10
+ @dataclass
11
+ class ProjectConfig:
12
+ name: str
13
+ path: str
14
+ description: str = ""
15
+
16
+
17
+ def load_projects(config_path: str | None = None) -> dict[str, ProjectConfig]:
18
+ """Load project configurations from JSON file.
19
+
20
+ Resolution order for config path:
21
+ 1. Explicit config_path argument
22
+ 2. CLAUDE_CONTROL_PROJECTS environment variable
23
+ 3. projects.json next to this package's parent directory
24
+ """
25
+ if config_path is None:
26
+ config_path = os.environ.get("CLAUDE_CONTROL_PROJECTS")
27
+
28
+ if config_path is None:
29
+ # Default: projects.json in the repo root (parent of claude_control/)
30
+ package_dir = Path(__file__).resolve().parent
31
+ config_path = str(package_dir.parent / "projects.json")
32
+
33
+ config_file = Path(config_path)
34
+ if not config_file.exists():
35
+ print(
36
+ f"Config file not found: {config_file}. "
37
+ f"Create it or set CLAUDE_CONTROL_PROJECTS env var.",
38
+ file=sys.stderr,
39
+ )
40
+ return {}
41
+
42
+ with open(config_file) as f:
43
+ data = json.load(f)
44
+
45
+ projects: dict[str, ProjectConfig] = {}
46
+ for entry in data.get("projects", []):
47
+ name = entry.get("name")
48
+ path = entry.get("path")
49
+
50
+ if not name or not path:
51
+ print(
52
+ f"Skipping invalid project entry (missing name or path): {entry}",
53
+ file=sys.stderr,
54
+ )
55
+ continue
56
+
57
+ resolved_path = Path(path).resolve()
58
+ if not resolved_path.exists():
59
+ print(
60
+ f"Warning: path does not exist for project '{name}': {resolved_path}",
61
+ file=sys.stderr,
62
+ )
63
+
64
+ projects[name] = ProjectConfig(
65
+ name=name,
66
+ path=str(resolved_path),
67
+ description=entry.get("description", ""),
68
+ )
69
+
70
+ return projects
@@ -0,0 +1,138 @@
1
+ """Claude Control MCP Server — coordinate Claude Code instances across projects."""
2
+
3
+ import json
4
+ import logging
5
+ import sys
6
+ from typing import Any, Dict, Optional
7
+
8
+ from mcp.server.fastmcp import FastMCP
9
+
10
+ from .config import load_projects
11
+ from .session_manager import SessionManager
12
+
13
+ logging.basicConfig(
14
+ level=logging.INFO,
15
+ format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
16
+ stream=sys.stderr,
17
+ )
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # Initialize MCP server
21
+ mcp = FastMCP("Claude Control")
22
+
23
+ # Module-level session manager (initialized lazily)
24
+ _session_manager: SessionManager | None = None
25
+
26
+
27
+ def _get_manager() -> SessionManager:
28
+ global _session_manager
29
+ if _session_manager is None:
30
+ projects = load_projects()
31
+ if not projects:
32
+ logger.warning("No projects configured. Tools will return errors.")
33
+ else:
34
+ logger.info("Loaded %d project(s): %s", len(projects), ", ".join(projects.keys()))
35
+ _session_manager = SessionManager(projects)
36
+ return _session_manager
37
+
38
+
39
+ @mcp.tool()
40
+ async def send_command(project: str, prompt: str) -> Dict[str, Any]:
41
+ """Send a prompt to a Claude Code instance running in the specified project directory.
42
+
43
+ The remote instance maintains conversation context across calls — follow-up
44
+ prompts will have access to prior context. Use reset_session to start fresh.
45
+
46
+ The remote instance runs with full permissions and loads the target project's
47
+ own CLAUDE.md, MCP servers, and hooks.
48
+
49
+ Args:
50
+ project: Name of the project (as defined in projects.json)
51
+ prompt: The prompt/command to send to the remote Claude instance
52
+ """
53
+ manager = _get_manager()
54
+ try:
55
+ result = await manager.send(project, prompt)
56
+ response: Dict[str, Any] = {"text": result.text}
57
+ if result.session_id:
58
+ response["session_id"] = result.session_id
59
+ if result.is_error:
60
+ response["is_error"] = True
61
+ if result.cost_usd is not None:
62
+ response["cost_usd"] = result.cost_usd
63
+ if result.num_turns:
64
+ response["num_turns"] = result.num_turns
65
+ return response
66
+ except Exception as e:
67
+ logger.exception("send_command failed for project '%s'", project)
68
+ return {"text": f"Error: {e}", "is_error": True}
69
+
70
+
71
+ @mcp.tool()
72
+ async def list_projects() -> Dict[str, Any]:
73
+ """List all configured projects and their session status.
74
+
75
+ Returns project names, directory paths, descriptions, and whether
76
+ each project has an active Claude Code session.
77
+ """
78
+ manager = _get_manager()
79
+ projects = []
80
+ for name, config in manager.projects.items():
81
+ status = manager.get_status(name)
82
+ projects.append({
83
+ "name": name,
84
+ "path": config.path,
85
+ "description": config.description,
86
+ "active_session": status["active"],
87
+ "session_id": status["session_id"],
88
+ "turn_count": status["turn_count"],
89
+ })
90
+ return {"projects": projects}
91
+
92
+
93
+ @mcp.tool()
94
+ async def reset_session(project: str) -> Dict[str, Any]:
95
+ """Reset a project's Claude Code session, starting fresh on the next command.
96
+
97
+ Disconnects the persistent Claude Code subprocess for the named project.
98
+ The next send_command call will create a new session with no prior context.
99
+
100
+ Args:
101
+ project: Name of the project whose session to reset
102
+ """
103
+ manager = _get_manager()
104
+ try:
105
+ existed = await manager.reset(project)
106
+ if existed:
107
+ return {"status": "reset", "project": project}
108
+ return {"status": "no_session", "project": project, "message": "No active session to reset"}
109
+ except Exception as e:
110
+ logger.exception("reset_session failed for project '%s'", project)
111
+ return {"status": "error", "project": project, "message": str(e)}
112
+
113
+
114
+ @mcp.tool()
115
+ async def get_session_status(project: str) -> Dict[str, Any]:
116
+ """Check whether a project has an active Claude Code session.
117
+
118
+ Returns session activity status, session ID, and turn count.
119
+
120
+ Args:
121
+ project: Name of the project to check
122
+ """
123
+ manager = _get_manager()
124
+ return manager.get_status(project)
125
+
126
+
127
+ def main():
128
+ try:
129
+ mcp.run()
130
+ except KeyboardInterrupt:
131
+ pass
132
+ except Exception as e:
133
+ logger.error("Server failed: %s", e)
134
+ sys.exit(1)
135
+
136
+
137
+ if __name__ == "__main__":
138
+ main()
@@ -0,0 +1,160 @@
1
+ """Manage persistent ClaudeSDKClient sessions for each project."""
2
+
3
+ import logging
4
+ import sys
5
+ from dataclasses import dataclass, field
6
+
7
+ from claude_code_sdk import (
8
+ AssistantMessage,
9
+ ClaudeCodeOptions,
10
+ ClaudeSDKClient,
11
+ ResultMessage,
12
+ TextBlock,
13
+ )
14
+ from claude_code_sdk._internal.message_parser import parse_message
15
+
16
+ from .config import ProjectConfig
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ @dataclass
22
+ class SessionInfo:
23
+ client: ClaudeSDKClient
24
+ session_id: str | None = None
25
+ turn_count: int = 0
26
+
27
+
28
+ @dataclass
29
+ class SendResult:
30
+ text: str
31
+ session_id: str | None = None
32
+ is_error: bool = False
33
+ num_turns: int = 0
34
+ cost_usd: float | None = None
35
+
36
+
37
+ class SessionManager:
38
+ """Manages persistent Claude Code sessions, one per project."""
39
+
40
+ def __init__(self, projects: dict[str, ProjectConfig]) -> None:
41
+ self.projects = projects
42
+ self._sessions: dict[str, SessionInfo] = {}
43
+
44
+ def _make_options(self, project: ProjectConfig) -> ClaudeCodeOptions:
45
+ return ClaudeCodeOptions(
46
+ cwd=project.path,
47
+ permission_mode="bypassPermissions",
48
+ )
49
+
50
+ async def _ensure_session(self, project_name: str) -> SessionInfo:
51
+ """Get existing session or create a new one."""
52
+ if project_name not in self.projects:
53
+ raise ValueError(
54
+ f"Unknown project: '{project_name}'. "
55
+ f"Available: {', '.join(sorted(self.projects.keys()))}"
56
+ )
57
+
58
+ if project_name in self._sessions:
59
+ return self._sessions[project_name]
60
+
61
+ project = self.projects[project_name]
62
+ options = self._make_options(project)
63
+ client = ClaudeSDKClient(options)
64
+
65
+ try:
66
+ await client.connect()
67
+ except Exception as e:
68
+ logger.error("Failed to connect to Claude for project '%s': %s", project_name, e)
69
+ raise
70
+
71
+ info = SessionInfo(client=client)
72
+ self._sessions[project_name] = info
73
+ logger.info("Created new session for project '%s' (cwd=%s)", project_name, project.path)
74
+ return info
75
+
76
+ async def send(self, project_name: str, prompt: str) -> SendResult:
77
+ """Send a prompt to a project's Claude instance and collect the response."""
78
+ info = await self._ensure_session(project_name)
79
+
80
+ try:
81
+ await info.client.query(prompt)
82
+ except Exception as e:
83
+ # Connection may have died — try once to reconnect
84
+ logger.warning("Query failed for '%s', attempting reconnect: %s", project_name, e)
85
+ await self.reset(project_name)
86
+ info = await self._ensure_session(project_name)
87
+ await info.client.query(prompt)
88
+
89
+ text_parts: list[str] = []
90
+ result_msg: ResultMessage | None = None
91
+
92
+ # Read raw messages from the internal query stream and parse them
93
+ # ourselves. This lets us skip unknown message types (e.g.
94
+ # rate_limit_event) that the SDK's parse_message() doesn't handle,
95
+ # without killing the iteration.
96
+ async for raw in info.client._query.receive_messages():
97
+ try:
98
+ message = parse_message(raw)
99
+ except Exception as e:
100
+ logger.debug("Skipping unparseable message (type=%s): %s", raw.get("type"), e)
101
+ continue
102
+
103
+ if isinstance(message, AssistantMessage):
104
+ for block in message.content:
105
+ if isinstance(block, TextBlock):
106
+ text_parts.append(block.text)
107
+ elif isinstance(message, ResultMessage):
108
+ result_msg = message
109
+ break
110
+
111
+ info.turn_count += 1
112
+
113
+ if result_msg:
114
+ info.session_id = result_msg.session_id
115
+ return SendResult(
116
+ text="\n".join(text_parts) if text_parts else "(no text response)",
117
+ session_id=result_msg.session_id,
118
+ is_error=result_msg.is_error,
119
+ num_turns=result_msg.num_turns,
120
+ cost_usd=result_msg.total_cost_usd,
121
+ )
122
+
123
+ return SendResult(
124
+ text="\n".join(text_parts) if text_parts else "(no response received)",
125
+ is_error=True,
126
+ )
127
+
128
+ async def reset(self, project_name: str) -> bool:
129
+ """Disconnect and remove a project's session. Returns True if a session existed."""
130
+ info = self._sessions.pop(project_name, None)
131
+ if info is None:
132
+ return False
133
+
134
+ try:
135
+ await info.client.disconnect()
136
+ except Exception:
137
+ # The SDK's disconnect can throw cancel scope errors due to
138
+ # anyio/asyncio interaction. The subprocess still gets cleaned up.
139
+ pass
140
+
141
+ logger.info("Reset session for project '%s'", project_name)
142
+ return True
143
+
144
+ def get_status(self, project_name: str) -> dict:
145
+ """Get session status for a project."""
146
+ if project_name not in self.projects:
147
+ return {"error": f"Unknown project: '{project_name}'"}
148
+
149
+ info = self._sessions.get(project_name)
150
+ return {
151
+ "project": project_name,
152
+ "active": info is not None,
153
+ "session_id": info.session_id if info else None,
154
+ "turn_count": info.turn_count if info else 0,
155
+ }
156
+
157
+ async def shutdown(self) -> None:
158
+ """Disconnect all sessions."""
159
+ for name in list(self._sessions.keys()):
160
+ await self.reset(name)
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.4
2
+ Name: claude-control
3
+ Version: 0.1.0
4
+ Summary: MCP server for coordinating Claude Code instances across multiple projects
5
+ Author-email: Red Duck Labs <support@redducklabs.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/redducklabs/claude-control
8
+ Project-URL: Repository, https://github.com/redducklabs/claude-control
9
+ Project-URL: Issues, https://github.com/redducklabs/claude-control/issues
10
+ Keywords: mcp,claude,claude-code,automation,orchestration
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: claude-code-sdk>=0.0.25
22
+ Requires-Dist: mcp>=1.12.0
23
+
24
+ # Claude Control
25
+
26
+ MCP server that lets a Claude Code session coordinate with Claude Code instances running in other project directories.
27
+
28
+ ## How It Works
29
+
30
+ Claude Control is an MCP server (stdio transport) that exposes tools for dispatching prompts to Claude Code instances in configured project directories. Each remote instance:
31
+
32
+ - Runs as a persistent subprocess with conversation context preserved across calls
33
+ - Loads the target project's own `CLAUDE.md`, `.mcp.json`, hooks, and settings
34
+ - Runs with `bypassPermissions` for fully autonomous operation
35
+
36
+ ## Installation
37
+
38
+ ### From PyPI
39
+
40
+ ```bash
41
+ pip install claude-control
42
+ ```
43
+
44
+ Or with [uv](https://docs.astral.sh/uv/):
45
+
46
+ ```bash
47
+ uv tool install claude-control
48
+ ```
49
+
50
+ ### From Source
51
+
52
+ ```bash
53
+ git clone https://github.com/redducklabs/claude-control.git
54
+ cd claude-control
55
+ pip install .
56
+ ```
57
+
58
+ ## Setup
59
+
60
+ ### 1. Configure Projects
61
+
62
+ Create a `projects.json` file (see `projects.json.example`):
63
+
64
+ ```json
65
+ {
66
+ "projects": [
67
+ {
68
+ "name": "my-backend",
69
+ "path": "D:\\repos\\my-backend",
70
+ "description": "Backend API service"
71
+ },
72
+ {
73
+ "name": "my-frontend",
74
+ "path": "D:\\repos\\my-frontend",
75
+ "description": "Frontend web application"
76
+ }
77
+ ]
78
+ }
79
+ ```
80
+
81
+ ### 2. Register as an MCP Server
82
+
83
+ Add to the `.mcp.json` of the project where you want coordination tools available.
84
+
85
+ **Using `uvx` (recommended — no global install needed):**
86
+
87
+ ```json
88
+ {
89
+ "mcpServers": {
90
+ "claude_control": {
91
+ "command": "uvx",
92
+ "args": ["claude-control"],
93
+ "env": {
94
+ "CLAUDE_CONTROL_PROJECTS": "/path/to/your/projects.json"
95
+ }
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ **Using a pip install:**
102
+
103
+ ```json
104
+ {
105
+ "mcpServers": {
106
+ "claude_control": {
107
+ "command": "claude-control",
108
+ "env": {
109
+ "CLAUDE_CONTROL_PROJECTS": "/path/to/your/projects.json"
110
+ }
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ **Using `python -m`:**
117
+
118
+ ```json
119
+ {
120
+ "mcpServers": {
121
+ "claude_control": {
122
+ "command": "python",
123
+ "args": ["-m", "claude_control"],
124
+ "env": {
125
+ "CLAUDE_CONTROL_PROJECTS": "/path/to/your/projects.json"
126
+ }
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ ### 3. Restart Claude Code
133
+
134
+ The tools will appear as `mcp__claude_control__send_command`, `mcp__claude_control__list_projects`, etc.
135
+
136
+ ## Tools
137
+
138
+ ### `send_command`
139
+
140
+ Send a prompt to a Claude Code instance in the specified project directory.
141
+
142
+ | Parameter | Type | Description |
143
+ |-----------|------|-------------|
144
+ | `project` | string | Project name (from projects.json) |
145
+ | `prompt` | string | The prompt to send |
146
+
147
+ Returns the full text response from the remote instance. Sessions persist across calls — follow-up prompts have access to prior context.
148
+
149
+ ### `list_projects`
150
+
151
+ List all configured projects with their paths, descriptions, and session status.
152
+
153
+ ### `reset_session`
154
+
155
+ Tear down a project's Claude Code session. The next `send_command` call creates a fresh session with no prior context.
156
+
157
+ | Parameter | Type | Description |
158
+ |-----------|------|-------------|
159
+ | `project` | string | Project name to reset |
160
+
161
+ ### `get_session_status`
162
+
163
+ Check whether a project has an active session, its ID, and turn count.
164
+
165
+ | Parameter | Type | Description |
166
+ |-----------|------|-------------|
167
+ | `project` | string | Project name to check |
168
+
169
+ ## Dependencies
170
+
171
+ - Python >= 3.11
172
+ - `claude-code-sdk >= 0.0.25`
173
+ - `mcp >= 1.12.0`
174
+ - Claude Code CLI installed and on PATH
175
+
176
+ ## Configuration
177
+
178
+ The `CLAUDE_CONTROL_PROJECTS` environment variable points to your `projects.json`. If unset, defaults to `projects.json` in the package's parent directory.
@@ -0,0 +1,13 @@
1
+ README.md
2
+ pyproject.toml
3
+ claude_control/__init__.py
4
+ claude_control/__main__.py
5
+ claude_control/config.py
6
+ claude_control/server.py
7
+ claude_control/session_manager.py
8
+ claude_control.egg-info/PKG-INFO
9
+ claude_control.egg-info/SOURCES.txt
10
+ claude_control.egg-info/dependency_links.txt
11
+ claude_control.egg-info/entry_points.txt
12
+ claude_control.egg-info/requires.txt
13
+ claude_control.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ claude-control = claude_control.server:main
@@ -0,0 +1,2 @@
1
+ claude-code-sdk>=0.0.25
2
+ mcp>=1.12.0
@@ -0,0 +1 @@
1
+ claude_control
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "claude-control"
7
+ version = "0.1.0"
8
+ description = "MCP server for coordinating Claude Code instances across multiple projects"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ authors = [
13
+ {name = "Red Duck Labs", email = "support@redducklabs.com"}
14
+ ]
15
+ keywords = ["mcp", "claude", "claude-code", "automation", "orchestration"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Software Development :: Libraries :: Python Modules",
25
+ ]
26
+ dependencies = [
27
+ "claude-code-sdk>=0.0.25",
28
+ "mcp>=1.12.0",
29
+ ]
30
+
31
+ [project.scripts]
32
+ claude-control = "claude_control.server:main"
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/redducklabs/claude-control"
36
+ Repository = "https://github.com/redducklabs/claude-control"
37
+ Issues = "https://github.com/redducklabs/claude-control/issues"
38
+
39
+ [tool.setuptools.packages.find]
40
+ where = ["."]
41
+ include = ["claude_control*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+