contree-mcp 0.1.0__py3-none-any.whl
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.
- contree_mcp/__init__.py +0 -0
- contree_mcp/__main__.py +25 -0
- contree_mcp/app.py +240 -0
- contree_mcp/arguments.py +35 -0
- contree_mcp/auth/__init__.py +2 -0
- contree_mcp/auth/registry.py +236 -0
- contree_mcp/backend_types.py +301 -0
- contree_mcp/cache.py +208 -0
- contree_mcp/client.py +711 -0
- contree_mcp/context.py +53 -0
- contree_mcp/docs.py +1203 -0
- contree_mcp/file_cache.py +381 -0
- contree_mcp/prompts.py +238 -0
- contree_mcp/py.typed +0 -0
- contree_mcp/resources/__init__.py +17 -0
- contree_mcp/resources/guide.py +715 -0
- contree_mcp/resources/image_lineage.py +46 -0
- contree_mcp/resources/image_ls.py +32 -0
- contree_mcp/resources/import_operation.py +52 -0
- contree_mcp/resources/instance_operation.py +52 -0
- contree_mcp/resources/read_file.py +33 -0
- contree_mcp/resources/static.py +12 -0
- contree_mcp/server.py +77 -0
- contree_mcp/tools/__init__.py +39 -0
- contree_mcp/tools/cancel_operation.py +36 -0
- contree_mcp/tools/download.py +128 -0
- contree_mcp/tools/get_guide.py +54 -0
- contree_mcp/tools/get_image.py +30 -0
- contree_mcp/tools/get_operation.py +26 -0
- contree_mcp/tools/import_image.py +99 -0
- contree_mcp/tools/list_files.py +80 -0
- contree_mcp/tools/list_images.py +50 -0
- contree_mcp/tools/list_operations.py +46 -0
- contree_mcp/tools/read_file.py +47 -0
- contree_mcp/tools/registry_auth.py +71 -0
- contree_mcp/tools/registry_token_obtain.py +80 -0
- contree_mcp/tools/rsync.py +46 -0
- contree_mcp/tools/run.py +97 -0
- contree_mcp/tools/set_tag.py +31 -0
- contree_mcp/tools/upload.py +50 -0
- contree_mcp/tools/wait_operations.py +79 -0
- contree_mcp-0.1.0.dist-info/METADATA +450 -0
- contree_mcp-0.1.0.dist-info/RECORD +46 -0
- contree_mcp-0.1.0.dist-info/WHEEL +4 -0
- contree_mcp-0.1.0.dist-info/entry_points.txt +2 -0
- contree_mcp-0.1.0.dist-info/licenses/LICENSE +176 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from contree_mcp.backend_types import FileResponse
|
|
5
|
+
from contree_mcp.context import CLIENT
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def upload(
|
|
9
|
+
path: str | None = None, content: str | None = None, content_base64: str | None = None
|
|
10
|
+
) -> FileResponse:
|
|
11
|
+
"""
|
|
12
|
+
Upload file to Contree for use in containers. Free (no VM).
|
|
13
|
+
|
|
14
|
+
TL;DR:
|
|
15
|
+
- PURPOSE: Upload single file, get UUID for run's tool files param
|
|
16
|
+
- PREFER RSYNC: For multiple files or directories (has caching)
|
|
17
|
+
- BINARY: Use content_base64 for binary files
|
|
18
|
+
|
|
19
|
+
USAGE:
|
|
20
|
+
- Upload single file for injection into containers
|
|
21
|
+
- Pass returned UUID to run's tool via files parameter
|
|
22
|
+
- Use rsync instead for multiple files or directories
|
|
23
|
+
|
|
24
|
+
RETURNS: uuid, sha256
|
|
25
|
+
|
|
26
|
+
GUIDES:
|
|
27
|
+
- [USEFUL] contree://guide/quickstart - File sync + execute patterns
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
if not path and not content and not content_base64:
|
|
31
|
+
raise ValueError("One of 'path', 'content', or 'content_base64' is required")
|
|
32
|
+
|
|
33
|
+
if path:
|
|
34
|
+
path = os.path.expanduser(path)
|
|
35
|
+
if not os.path.exists(path):
|
|
36
|
+
raise ValueError(f"File not found: {path}")
|
|
37
|
+
|
|
38
|
+
client = CLIENT.get()
|
|
39
|
+
|
|
40
|
+
if path:
|
|
41
|
+
with open(path, "rb") as f:
|
|
42
|
+
result = await client.upload_file(f)
|
|
43
|
+
return FileResponse(uuid=result.uuid, sha256=result.sha256)
|
|
44
|
+
elif content_base64:
|
|
45
|
+
data = base64.b64decode(content_base64)
|
|
46
|
+
else:
|
|
47
|
+
data = content.encode("utf-8") # type: ignore[union-attr]
|
|
48
|
+
|
|
49
|
+
result = await client.upload_file(data)
|
|
50
|
+
return FileResponse(uuid=result.uuid, sha256=result.sha256)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
from contree_mcp.backend_types import OperationKind, OperationResponse, OperationStatus
|
|
7
|
+
from contree_mcp.context import CLIENT
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class WaitOperationsOutput(BaseModel):
|
|
11
|
+
results: dict[str, OperationResponse] = Field(description="Map of operation_id to result")
|
|
12
|
+
completed: list[str] = Field(description="List of completed operation IDs")
|
|
13
|
+
cancelled: list[str] = Field(description="List of timed out and cancelled operation IDs")
|
|
14
|
+
timed_out: bool = Field(default=False, description="True if wait exceeded timeout")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def wait_operations(
|
|
18
|
+
operation_ids: list[str],
|
|
19
|
+
timeout: float = 300.0,
|
|
20
|
+
mode: Literal["all", "any"] = "all",
|
|
21
|
+
) -> WaitOperationsOutput:
|
|
22
|
+
"""
|
|
23
|
+
Wait for multiple operations to complete. Free (no VM).
|
|
24
|
+
|
|
25
|
+
TL;DR:
|
|
26
|
+
- PURPOSE: Block until async operations finish
|
|
27
|
+
- MODES: 'all' waits for all, 'any' returns on first completion but cancels others
|
|
28
|
+
- COST: Free (no VM)
|
|
29
|
+
|
|
30
|
+
USAGE:
|
|
31
|
+
- Wait for parallel commands launched with wait=false
|
|
32
|
+
- Use mode='any' for race conditions (first result wins, others cancelled)
|
|
33
|
+
- Use mode='all' (default) to collect all results
|
|
34
|
+
|
|
35
|
+
RETURNS: results dict, completed list, pending list, timed_out bool
|
|
36
|
+
|
|
37
|
+
GUIDES:
|
|
38
|
+
- [ESSENTIAL] contree://guide/async - Parallel execution patterns
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
client = CLIENT.get()
|
|
42
|
+
|
|
43
|
+
results: dict[str, OperationResponse] = {}
|
|
44
|
+
|
|
45
|
+
async def wait_one(op_id: str) -> None:
|
|
46
|
+
nonlocal results
|
|
47
|
+
try:
|
|
48
|
+
result = await client.wait_for_operation(op_id, max_wait=timeout)
|
|
49
|
+
results[op_id] = result
|
|
50
|
+
except Exception as e:
|
|
51
|
+
# On error (timeout, connection error, etc.), mark as failed
|
|
52
|
+
results[op_id] = OperationResponse(
|
|
53
|
+
uuid=op_id,
|
|
54
|
+
status=OperationStatus.FAILED,
|
|
55
|
+
kind=OperationKind.INSTANCE,
|
|
56
|
+
error=str(e),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
done, pending = await asyncio.wait(
|
|
60
|
+
list(map(asyncio.create_task, map(wait_one, set(operation_ids)))),
|
|
61
|
+
return_when=asyncio.ALL_COMPLETED if mode == "all" else asyncio.FIRST_COMPLETED,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
for task in pending:
|
|
65
|
+
if not task.done():
|
|
66
|
+
task.cancel()
|
|
67
|
+
|
|
68
|
+
await asyncio.gather(*pending, return_exceptions=True)
|
|
69
|
+
|
|
70
|
+
cancelled_ids = [op_id for op_id in operation_ids if op_id not in results]
|
|
71
|
+
# timed_out is True only if we have pending tasks AND mode was "all"
|
|
72
|
+
# For mode="any", having pending tasks is expected behavior
|
|
73
|
+
timed_out = len(pending) > 0 and mode == "all"
|
|
74
|
+
return WaitOperationsOutput(
|
|
75
|
+
results=results,
|
|
76
|
+
completed=list(results.keys()),
|
|
77
|
+
cancelled=cancelled_ids,
|
|
78
|
+
timed_out=timed_out,
|
|
79
|
+
)
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: contree-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for Contree container management system
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: aiosqlite>=0.19.0
|
|
9
|
+
Requires-Dist: argclass>=1.0.0
|
|
10
|
+
Requires-Dist: httpx>=0.28.0
|
|
11
|
+
Requires-Dist: mcp>=1.0.0
|
|
12
|
+
Requires-Dist: pydantic>=2.0.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: furo>=2024.0.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: myst-parser>=3.0.0; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: ruff>=0.2.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: sphinx-copybutton>=0.5.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: sphinx-design>=0.5.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: sphinx>=7.0.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: sphinxcontrib-mermaid>=0.9.0; extra == 'dev'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# Contree MCP Server
|
|
27
|
+
|
|
28
|
+
[](https://pypi.org/project/contree-mcp/)
|
|
29
|
+
[](https://github.com/nebius/contree-mcp/actions/workflows/tests.yml)
|
|
30
|
+
[](LICENSE)
|
|
31
|
+
|
|
32
|
+
Run code in isolated cloud containers. Contree gives AI agents secure sandboxed execution environments with full root access, network, and persistent images.
|
|
33
|
+
|
|
34
|
+
## Why Contree?
|
|
35
|
+
|
|
36
|
+
**Fearless experimentation.** Agents can:
|
|
37
|
+
- Run destructive commands (`rm -rf /`, `dd`, kernel exploits) - nothing escapes the sandbox
|
|
38
|
+
- Make mistakes freely - revert to any previous image UUID at zero cost
|
|
39
|
+
- Execute potentially dangerous user requests - Contree IS the safe runtime for risky operations
|
|
40
|
+
- Break things on purpose - corrupt filesystems, crash kernels, test failure modes
|
|
41
|
+
|
|
42
|
+
Every container is isolated. Every image is immutable. Branching is cheap. Mistakes are free.
|
|
43
|
+
|
|
44
|
+
## Quick Setup
|
|
45
|
+
|
|
46
|
+
### 1. Create Config File
|
|
47
|
+
|
|
48
|
+
Store credentials in `~/.config/contree/mcp.ini`:
|
|
49
|
+
|
|
50
|
+
```ini
|
|
51
|
+
[DEFAULT]
|
|
52
|
+
url = https://contree.dev/
|
|
53
|
+
token = <TOKEN HERE>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Configure Your MCP Client
|
|
57
|
+
|
|
58
|
+
#### Claude Code
|
|
59
|
+
|
|
60
|
+
Add to `~/.claude/settings.json`:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{"mcpServers": {"contree": {"command": "uvx", "args": ["contree-mcp"]}}}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Restart Claude Code or run `/mcp` to verify.
|
|
67
|
+
|
|
68
|
+
#### OpenAI Codex CLI
|
|
69
|
+
|
|
70
|
+
Add to `~/.codex/config.toml`:
|
|
71
|
+
|
|
72
|
+
```toml
|
|
73
|
+
[mcp_servers.contree]
|
|
74
|
+
command = "uvx"
|
|
75
|
+
args = ["contree-mcp"]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### Claude Desktop
|
|
79
|
+
|
|
80
|
+
Add to config file:
|
|
81
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
82
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{"mcpServers": {"contree": {"command": "uvx", "args": ["contree-mcp"]}}}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
> **Note:** You can alternatively pass credentials via environment variables (`CONTREE_MCP_TOKEN`, `CONTREE_MCP_URL`) in your MCP client config, but this is not recommended as tokens may appear in process listings.
|
|
89
|
+
|
|
90
|
+
## Manual Installation
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Using uv
|
|
94
|
+
uv pip install contree-mcp
|
|
95
|
+
|
|
96
|
+
# Using pip
|
|
97
|
+
pip install contree-mcp
|
|
98
|
+
|
|
99
|
+
# Run manually
|
|
100
|
+
contree-mcp --token YOUR_TOKEN
|
|
101
|
+
|
|
102
|
+
# HTTP mode (for network access)
|
|
103
|
+
contree-mcp --mode http --http-port 9452 --token YOUR_TOKEN
|
|
104
|
+
|
|
105
|
+
# Visit http://localhost:9452/ for interactive documentation with
|
|
106
|
+
# setup guides, tool reference, and best practices.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Container Installation (Alpine/Ubuntu/Debian)
|
|
110
|
+
|
|
111
|
+
PEP 668 requires additional flags:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pip install --break-system-packages contree-mcp
|
|
115
|
+
uv pip install --break-system-packages --python /usr/bin/python3 contree-mcp
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Configuration
|
|
119
|
+
|
|
120
|
+
| Argument | Environment Variable | Default |
|
|
121
|
+
|----------|---------------------|---------|
|
|
122
|
+
| - | `CONTREE_MCP_CONFIG` | `~/.config/contree/mcp.ini` |
|
|
123
|
+
| `--token` | `CONTREE_MCP_TOKEN` | (required) |
|
|
124
|
+
| `--url` | `CONTREE_MCP_URL` | `https://contree.dev/` |
|
|
125
|
+
| `--mode` | `CONTREE_MCP_MODE` | `stdio` |
|
|
126
|
+
| `--http-port` | `CONTREE_MCP_HTTP_PORT` | `9452` |
|
|
127
|
+
| `--log-level` | `CONTREE_MCP_LOG_LEVEL` | `warning` |
|
|
128
|
+
|
|
129
|
+
## Available Tools
|
|
130
|
+
|
|
131
|
+
### Command Execution
|
|
132
|
+
|
|
133
|
+
| Tool | Description |
|
|
134
|
+
|------|-------------|
|
|
135
|
+
| `contree_run` | Execute command in container (spawns microVM). Supports `wait=false` for async execution. |
|
|
136
|
+
|
|
137
|
+
### Image Management
|
|
138
|
+
|
|
139
|
+
| Tool | Description |
|
|
140
|
+
|------|-------------|
|
|
141
|
+
| `contree_list_images` | List available container images |
|
|
142
|
+
| `contree_get_image` | Get image details by UUID or tag |
|
|
143
|
+
| `contree_import_image` | Import OCI image from registry (requires authentication) |
|
|
144
|
+
| `contree_registry_token_obtain` | Open browser to create PAT for registry authentication |
|
|
145
|
+
| `contree_registry_auth` | Validate and store registry credentials |
|
|
146
|
+
| `contree_set_tag` | Set or remove a tag for an image |
|
|
147
|
+
|
|
148
|
+
### File Transfer
|
|
149
|
+
|
|
150
|
+
| Tool | Description |
|
|
151
|
+
|------|-------------|
|
|
152
|
+
| `contree_upload` | Upload a file to Contree for use in containers |
|
|
153
|
+
| `contree_download` | Download a file from a container image to local filesystem |
|
|
154
|
+
| `contree_rsync` | Sync local files to Contree with caching and deduplication |
|
|
155
|
+
|
|
156
|
+
### Image Inspection
|
|
157
|
+
|
|
158
|
+
| Tool | Description |
|
|
159
|
+
|------|-------------|
|
|
160
|
+
| `contree_list_files` | List files and directories in an image (no VM needed) |
|
|
161
|
+
| `contree_read_file` | Read a file from an image (no VM needed) |
|
|
162
|
+
|
|
163
|
+
### Operations
|
|
164
|
+
|
|
165
|
+
| Tool | Description |
|
|
166
|
+
|------|-------------|
|
|
167
|
+
| `contree_list_operations` | List operations (running or completed) |
|
|
168
|
+
| `contree_get_operation` | Get operation status and result |
|
|
169
|
+
| `contree_wait_operations` | Wait for multiple async operations to complete |
|
|
170
|
+
| `contree_cancel_operation` | Cancel a running operation |
|
|
171
|
+
|
|
172
|
+
### Documentation
|
|
173
|
+
|
|
174
|
+
| Tool | Description |
|
|
175
|
+
|------|-------------|
|
|
176
|
+
| `contree_get_guide` | Get agent guide sections (workflow, quickstart, async, etc.) |
|
|
177
|
+
|
|
178
|
+
## Resource Templates
|
|
179
|
+
|
|
180
|
+
MCP resource templates expose image files and documentation directly via URIs. Fast operations, no VM required.
|
|
181
|
+
|
|
182
|
+
| Resource | URI Template | Description |
|
|
183
|
+
|----------|--------------|-------------|
|
|
184
|
+
| `contree_image_read` | `contree://image/{image}/read/{path}` | Read a file from an image |
|
|
185
|
+
| `contree_image_ls` | `contree://image/{image}/ls/{path}` | List directory in an image |
|
|
186
|
+
| `contree_image_lineage` | `contree://image/{image}/lineage` | View image parent-child relationships |
|
|
187
|
+
| `contree_guide` | `contree://guide/{section}` | Agent guide and best practices |
|
|
188
|
+
|
|
189
|
+
**URI Examples:**
|
|
190
|
+
- `contree://image/abc-123-uuid/read/etc/passwd` - Read file by image UUID
|
|
191
|
+
- `contree://image/tag:alpine:latest/read/etc/os-release` - Read file by tag
|
|
192
|
+
- `contree://image/abc-123-uuid/ls/.` - List root directory
|
|
193
|
+
- `contree://image/tag:python:3.11/ls/usr/local/lib` - List nested directory
|
|
194
|
+
- `contree://image/abc-123-uuid/lineage` - View image ancestry and children
|
|
195
|
+
- `contree://guide/reference` - Tool reference
|
|
196
|
+
- `contree://guide/quickstart` - Common workflow patterns
|
|
197
|
+
|
|
198
|
+
**Guide Sections:** `workflow`, `reference`, `quickstart`, `state`, `async`, `tagging`, `errors`
|
|
199
|
+
|
|
200
|
+
## Examples
|
|
201
|
+
|
|
202
|
+
### Prepare a Reusable Environment (Recommended First Step)
|
|
203
|
+
|
|
204
|
+
**Step 1: Check for existing environment**
|
|
205
|
+
```json
|
|
206
|
+
// contree_list_images
|
|
207
|
+
{"tag_prefix": "common/python-ml"}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Step 2: If not found, build and tag it**
|
|
211
|
+
```json
|
|
212
|
+
// contree_import_image
|
|
213
|
+
{"registry_url": "docker://docker.io/python:3.11-slim"}
|
|
214
|
+
|
|
215
|
+
// contree_run (install packages)
|
|
216
|
+
{"command": "pip install numpy pandas scikit-learn", "image": "<result_image>", "disposable": false}
|
|
217
|
+
|
|
218
|
+
// contree_set_tag
|
|
219
|
+
{"image_uuid": "<result_image>", "tag": "common/python-ml/python:3.11-slim"}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Step 3: Use the prepared environment**
|
|
223
|
+
```json
|
|
224
|
+
// contree_run
|
|
225
|
+
{"command": "python train_model.py", "image": "tag:common/python-ml/python:3.11-slim"}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Run a command
|
|
229
|
+
|
|
230
|
+
**contree_run:**
|
|
231
|
+
```json
|
|
232
|
+
{"command": "python -c 'print(\"Hello from Contree!\")'", "image": "tag:python:3.11"}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Parallel Execution (Async Pattern)
|
|
236
|
+
|
|
237
|
+
Launch multiple instances simultaneously with `wait: false`, then poll for results:
|
|
238
|
+
|
|
239
|
+
**contree_run** (x3):
|
|
240
|
+
```json
|
|
241
|
+
{"command": "python experiment_a.py", "image": "tag:python:3.11", "wait": false}
|
|
242
|
+
{"command": "python experiment_b.py", "image": "tag:python:3.11", "wait": false}
|
|
243
|
+
{"command": "python experiment_c.py", "image": "tag:python:3.11", "wait": false}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Each returns immediately with `operation_id`. Poll with **contree_get_operation**:
|
|
247
|
+
```json
|
|
248
|
+
{"operation_id": "op-1"}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Trie-like Exploration Tree
|
|
252
|
+
|
|
253
|
+
Build branching structures where results become new source images.
|
|
254
|
+
|
|
255
|
+
**contree_run** - create branch point with `disposable: false`:
|
|
256
|
+
```json
|
|
257
|
+
{"command": "pip install numpy pandas", "image": "tag:python:3.11", "disposable": false}
|
|
258
|
+
```
|
|
259
|
+
Returns `result_image: "img-with-deps"`.
|
|
260
|
+
|
|
261
|
+
**contree_run** - branch into parallel experiments:
|
|
262
|
+
```json
|
|
263
|
+
{"command": "python test_numpy.py", "image": "img-with-deps", "wait": false}
|
|
264
|
+
{"command": "python test_pandas.py", "image": "img-with-deps", "wait": false}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Sync Local Files to Container
|
|
268
|
+
|
|
269
|
+
**contree_rsync** - sync a project directory:
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"source": "/path/to/project",
|
|
273
|
+
"destination": "/app",
|
|
274
|
+
"exclude": ["__pycache__", "*.pyc", ".git", "node_modules"]
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
Returns `directory_state_id: "ds_abc123"`.
|
|
278
|
+
|
|
279
|
+
**contree_run** - run with injected files:
|
|
280
|
+
```json
|
|
281
|
+
{
|
|
282
|
+
"command": "python /app/main.py",
|
|
283
|
+
"image": "tag:python:3.11",
|
|
284
|
+
"directory_state_id": "ds_abc123"
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### List images
|
|
289
|
+
|
|
290
|
+
**contree_list_images:**
|
|
291
|
+
```json
|
|
292
|
+
{"tag_prefix": "python"}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Read a file (Resource Template)
|
|
296
|
+
|
|
297
|
+
Use the `contree_image_file` resource template:
|
|
298
|
+
```
|
|
299
|
+
contree://image/tag:busybox:latest/read/etc/passwd
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Import an image
|
|
303
|
+
|
|
304
|
+
**Step 1: Authenticate with registry (first time only)**
|
|
305
|
+
|
|
306
|
+
```json
|
|
307
|
+
// contree_registry_token_obtain - opens browser for PAT creation
|
|
308
|
+
{"registry_url": "docker://docker.io/alpine:latest"}
|
|
309
|
+
|
|
310
|
+
// contree_registry_auth - validate and store credentials
|
|
311
|
+
{"registry_url": "docker://docker.io/alpine:latest", "username": "myuser", "token": "dckr_pat_xxx"}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Step 2: Import the image**
|
|
315
|
+
|
|
316
|
+
```json
|
|
317
|
+
// contree_import_image
|
|
318
|
+
{"registry_url": "docker://docker.io/alpine:latest"}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
To make it reusable, tag after importing:
|
|
322
|
+
```json
|
|
323
|
+
// contree_set_tag
|
|
324
|
+
{"image_uuid": "<result_image>", "tag": "common/base/alpine:latest"}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Track Image Lineage
|
|
328
|
+
|
|
329
|
+
View parent-child relationships and navigate image history using the `contree_image_lineage` resource:
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
contree://image/abc-123-uuid/lineage
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
```json
|
|
337
|
+
{
|
|
338
|
+
"image": "abc-123-uuid",
|
|
339
|
+
"parent": {"image": "parent-uuid", "command": "pip install numpy", "exit_code": 0},
|
|
340
|
+
"children": [{"image": "child-uuid", "command": "python test.py", ...}],
|
|
341
|
+
"ancestors": [/* parent chain up to root */],
|
|
342
|
+
"root": {"image": "root-uuid", "registry_url": "docker://python:3.11", "is_import": true},
|
|
343
|
+
"depth": 2,
|
|
344
|
+
"is_known": true
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Use this to rollback to any ancestor or understand how an image was created.
|
|
349
|
+
|
|
350
|
+
### Download a build artifact
|
|
351
|
+
|
|
352
|
+
**contree_download:**
|
|
353
|
+
```json
|
|
354
|
+
{"image": "img-build-result", "path": "/app/dist/binary", "destination": "./binary", "executable": true}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Dependencies
|
|
358
|
+
|
|
359
|
+
- `mcp` - Model Context Protocol SDK
|
|
360
|
+
- `httpx` - Async HTTP client
|
|
361
|
+
- `argclass` - Argument parsing
|
|
362
|
+
- `aiosqlite` - Async SQLite database
|
|
363
|
+
- `pydantic` - Data validation
|
|
364
|
+
|
|
365
|
+
## Development
|
|
366
|
+
|
|
367
|
+
**Requirements:** Python 3.10+
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
# Clone and install in dev mode
|
|
371
|
+
git clone https://github.com/nebius/contree-mcp.git
|
|
372
|
+
cd contree-mcp
|
|
373
|
+
uv sync --group dev
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Development Workflow
|
|
377
|
+
|
|
378
|
+
Follow this sequence when making changes:
|
|
379
|
+
|
|
380
|
+
1. **Make code changes** - Edit files in `contree_mcp/`
|
|
381
|
+
|
|
382
|
+
2. **Run tests** - Ensure all tests pass
|
|
383
|
+
```bash
|
|
384
|
+
uv run pytest tests/ -v
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
3. **Run linter** - Fix any style issues
|
|
388
|
+
```bash
|
|
389
|
+
uv run ruff check contree_mcp
|
|
390
|
+
uv run ruff format contree_mcp # Auto-fix formatting
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
4. **Type check** (optional but recommended)
|
|
394
|
+
```bash
|
|
395
|
+
uv run mypy contree_mcp
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
5. **Update documentation** - Keep docs in sync with code
|
|
399
|
+
- `README.md` - User-facing docs, examples, tool descriptions
|
|
400
|
+
- `llm.txt` - Shared context for AI agents (architecture, class hierarchy, internals)
|
|
401
|
+
|
|
402
|
+
### Quick Commands
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
# Full validation cycle
|
|
406
|
+
uv run pytest tests/ -q && uv run ruff check contree_mcp && echo "All checks passed"
|
|
407
|
+
|
|
408
|
+
# Run specific test file
|
|
409
|
+
uv run pytest tests/test_tools/test_run.py -v
|
|
410
|
+
|
|
411
|
+
# Auto-fix linting issues
|
|
412
|
+
uv run ruff check contree_mcp --fix
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Testing GitHub Actions Locally
|
|
416
|
+
|
|
417
|
+
Use [act](https://github.com/nektos/act) to run GitHub Actions workflows locally before pushing:
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
# Install act
|
|
421
|
+
brew install act # macOS
|
|
422
|
+
sudo pacman -S act # Arch Linux
|
|
423
|
+
sudo apt install act # Debian/Ubuntu (via nix or manual install)
|
|
424
|
+
|
|
425
|
+
# List available jobs
|
|
426
|
+
act -l
|
|
427
|
+
|
|
428
|
+
# Run lint and typecheck jobs (fast)
|
|
429
|
+
act -j lint
|
|
430
|
+
act -j typecheck
|
|
431
|
+
|
|
432
|
+
# Run tests for Linux only (act simulates Linux)
|
|
433
|
+
act -j test --matrix os:ubuntu-latest
|
|
434
|
+
|
|
435
|
+
# Run specific Python version
|
|
436
|
+
act -j test --matrix os:ubuntu-latest --matrix python-version:3.12
|
|
437
|
+
|
|
438
|
+
# Run all jobs sequentially (stop on first failure)
|
|
439
|
+
act -j lint && act -j typecheck && act -j test --matrix os:ubuntu-latest
|
|
440
|
+
|
|
441
|
+
# Dry run (show what would execute)
|
|
442
|
+
act -n
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Note:** act uses Docker containers that simulate Linux runners. macOS/Windows matrix jobs will run in Linux
|
|
446
|
+
containers, so use `--matrix os:ubuntu-latest` for accurate local testing.
|
|
447
|
+
|
|
448
|
+
# Copyright
|
|
449
|
+
|
|
450
|
+
Nebius B.V. 2026, Licensed under the Apache License, Version 2.0 (see "LICENSE" file).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
contree_mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
contree_mcp/__main__.py,sha256=5Tq-hL2bbaZr7-fPQqdAbiidKhyku2xNqX1gZlAEkuk,616
|
|
3
|
+
contree_mcp/app.py,sha256=vQERw5bvgpKviEeHTibO0WA1tfBtj-htxswYDzGRq0Y,9132
|
|
4
|
+
contree_mcp/arguments.py,sha256=AtIO5xx2nqhZHQ2EJ1QS1Z6yVW3k86nHc4tTXcxjpfQ,1047
|
|
5
|
+
contree_mcp/backend_types.py,sha256=Rx_Wn4gtsJ_zR4RB5hSefbO56ljf7BQeJWLGdxknYA0,9375
|
|
6
|
+
contree_mcp/cache.py,sha256=5_ItLP-An1nKHlezlmSgpsA8AwgTPR-uNxcy8PzI4dw,8487
|
|
7
|
+
contree_mcp/client.py,sha256=dXNY7Uc2hcv3XmrCwWfqEG9bjTdHWsIdPdpGxKe3FxI,26506
|
|
8
|
+
contree_mcp/context.py,sha256=YGMXp4FT31MIRBvfihE-YCw5kPkJqTH-a_tTLIrq98o,1749
|
|
9
|
+
contree_mcp/docs.py,sha256=WOciz0aZPLrHNjjRgR61-cSxBbPPCI_BmoF5o5wxeTQ,29436
|
|
10
|
+
contree_mcp/file_cache.py,sha256=ZS9D5MGM2ACblHTidUw2zRSalpfC5NmHv_9uECcqu5M,14055
|
|
11
|
+
contree_mcp/prompts.py,sha256=EzFLqCHWBjpVshvHfmtmB300gb6mpboUp7nVB6Cuokk,8250
|
|
12
|
+
contree_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
contree_mcp/server.py,sha256=3D5v-WqzV-HGkPIckDDGQCrSoWZ8CEqeU-QgQYQeVCs,2721
|
|
14
|
+
contree_mcp/auth/__init__.py,sha256=vhtYeBrAl3363anBmk5-Fa23Wdl9m6_1Gn_VKyDK9-E,104
|
|
15
|
+
contree_mcp/auth/registry.py,sha256=3gmV4v_Dn2eYchum56s1gQYl2kSvII_6thvWKz4OyX0,8237
|
|
16
|
+
contree_mcp/resources/__init__.py,sha256=WzGWOxspHVfBhkLLlVDlZU7nsUpntoY3Td_NMNOgNcc,423
|
|
17
|
+
contree_mcp/resources/guide.py,sha256=JEm94_wq1yRJtILDgoXK98eTKvBdFKA19xDJP99pabc,20164
|
|
18
|
+
contree_mcp/resources/image_lineage.py,sha256=CBb5ZOpVTx11RGv06-ToDN3YdcRnZvTaC34HrHfc4xI,1594
|
|
19
|
+
contree_mcp/resources/image_ls.py,sha256=W_DoesnFTLNCwOSmi0j9wtSnpC0xtwzS96RQkhyYq5Q,1108
|
|
20
|
+
contree_mcp/resources/import_operation.py,sha256=ocfnEF0ey4RvI_ia6KA-DlNXdFdYqoPG1_vohXxqago,1572
|
|
21
|
+
contree_mcp/resources/instance_operation.py,sha256=kCHO0fGQ__j136y2LfHf6Ghf5y8rHZJTXxnrl5AeDpA,1939
|
|
22
|
+
contree_mcp/resources/read_file.py,sha256=39kXv1FpAzKYrB67ba_r1IZBE-nGTYdNbuVH0svpwjc,1132
|
|
23
|
+
contree_mcp/resources/static.py,sha256=kHH1u910C1DsorCzVlRm5h6_JPENSE1OU0wvuuNANpg,306
|
|
24
|
+
contree_mcp/tools/__init__.py,sha256=SR3Z22vV_AvXnT58q_3_I4YkLwK1ysdM8dC13Xpe-ok,978
|
|
25
|
+
contree_mcp/tools/cancel_operation.py,sha256=QdGb956PEoy9oYmOIWHuxYlm0hakHg4uv_NIgrX3i9k,1111
|
|
26
|
+
contree_mcp/tools/download.py,sha256=ZpRzRYVwpAA-TMzA1c-2cWaujR1PPdimolc4ZNS3wng,4351
|
|
27
|
+
contree_mcp/tools/get_guide.py,sha256=5PNi_hNXAFCquS5xEZ9fH6_16jchlpT20K1YRJjxn_w,1853
|
|
28
|
+
contree_mcp/tools/get_image.py,sha256=3VipR0OLWkDeggoY4rh-1l8U93s83D_x-wO8R_e0H8c,922
|
|
29
|
+
contree_mcp/tools/get_operation.py,sha256=QDLCR0dmiOSAMI_a1VufQcn2Il2FjJmPCBehIKxzK-0,814
|
|
30
|
+
contree_mcp/tools/import_image.py,sha256=gB4JwXd1jpgVayCYq6ktWKWwwpBG9G7SsQ13fmQE1sk,3884
|
|
31
|
+
contree_mcp/tools/list_files.py,sha256=xMJeQPpkjJVDEnRPiicqYsgPEDbOxOQIzDae-MkjqQo,2202
|
|
32
|
+
contree_mcp/tools/list_images.py,sha256=2Z65nnW2yItGHsh10v7tOwJmUQupp3lnVAzcoMY72Vk,1531
|
|
33
|
+
contree_mcp/tools/list_operations.py,sha256=2p31Xqt4YY_3QSKU2yrOxv4vqS_L-sD4aaGM5Acix8U,1300
|
|
34
|
+
contree_mcp/tools/read_file.py,sha256=f5c88J08rtdFBbCd4o9MU47-qMYX_9ORqYX9OpM-qKw,1328
|
|
35
|
+
contree_mcp/tools/registry_auth.py,sha256=9piWm9fhLhEjngIaMURhLXrnr4h_rpoT21GRi4sQr0w,2066
|
|
36
|
+
contree_mcp/tools/registry_token_obtain.py,sha256=pS2dmXBGJTJRjvheiGDhjjVS1dThqHOmK8UNFKdRsHU,2628
|
|
37
|
+
contree_mcp/tools/rsync.py,sha256=ZE6pbKjclEdsuuaI7pbCg6QfeEj-L2-scWZBmQ7hhQk,1333
|
|
38
|
+
contree_mcp/tools/run.py,sha256=QS-QgLVQB2_-z9kBfYgE0gu-cnu-nLB6w8uyNb_J8QM,3614
|
|
39
|
+
contree_mcp/tools/set_tag.py,sha256=WFlGPSHthOA4UbfjI9FVbA2tZN0p-Qvw_i8x13uefUI,1145
|
|
40
|
+
contree_mcp/tools/upload.py,sha256=IdXDiy8VZENbyu7APYPe7wC2af7QkEVdU4EI2GpdJl0,1566
|
|
41
|
+
contree_mcp/tools/wait_operations.py,sha256=BEexOTYWQ-a5sGxyfKDASHUVFoDiBuqYzo_6JGuw8YA,2745
|
|
42
|
+
contree_mcp-0.1.0.dist-info/METADATA,sha256=ZKfIuWSUzItsucpklEgNcYdK7PiXoTMyRLGlVoJucZE,13141
|
|
43
|
+
contree_mcp-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
44
|
+
contree_mcp-0.1.0.dist-info/entry_points.txt,sha256=Jv9r9PfrtYFtYkYu5usTpZelyQwNbbv_XAivokhcyT8,58
|
|
45
|
+
contree_mcp-0.1.0.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
|
46
|
+
contree_mcp-0.1.0.dist-info/RECORD,,
|