Metadata-Version: 2.4
Name: boxkite-mcp
Version: 0.1.0
Summary: MCP server exposing a hosted boxkite control-plane (sandbox lifecycle, exec, files) as native tools for MCP-compatible clients (Claude Code, Claude Desktop, Cursor, etc.).
License: FSL-1.1-Apache-2.0
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: boxkite-client
Requires-Dist: mcp>=1.2
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"

# boxkite-mcp

An MCP (Model Context Protocol) server over a hosted boxkite control-plane --
lets any MCP-compatible client (Claude Code, Claude Desktop, Cursor, etc.)
attach a real sandboxed code-execution backend as a native tool source, with
zero custom integration code.

Built on top of [`boxkite-client`](../sdk-python), the existing Python SDK for
the hosted control-plane API -- this package adds no HTTP logic of its own,
only the MCP tool surface.

## Install

Not yet published to PyPI -- install both packages from this repo:

```bash
pip install -e ../sdk-python
pip install -e .
```

## Configuration

Two environment variables, read at startup. The process fails fast with a
clear error if either is missing:

| Variable | Meaning |
|---|---|
| `BOXKITE_BASE_URL` | Base URL of the boxkite control-plane, e.g. `https://boxkite-control-plane-316016512837.us-central1.run.app` |
| `BOXKITE_API_KEY` | A `bxk_live_...` API key for your account |

## Run

```bash
BOXKITE_BASE_URL=https://your-control-plane.example.com \
BOXKITE_API_KEY=bxk_live_... \
boxkite-mcp
```

Speaks MCP over stdio -- point an MCP client's config at the `boxkite-mcp`
command (see the hosted docs site's MCP page for Claude Desktop / Claude Code
config snippets).

## Tools

Every per-sandbox tool takes `session_id` as a parameter (unlike
`boxkite_client.langchain_tools.create_sandbox_tools`, which binds one
pre-created session at factory time) -- the calling agent owns the full
lifecycle: create a sandbox, run several things in it, destroy it, all
within one conversation.

- `create_sandbox(label?, size?, storage_gb?, lifetime_minutes?, count?)` --
  create a sandbox (or a batch of `count` sandboxes), returns id/status per
  sandbox. `size` is a CPU/memory preset (`"small"` default, `"medium"`, or
  `"large"`); `storage_gb` and `lifetime_minutes` override the sandbox's
  volume size and how long it stays alive. All four are optional and bounded
  by fair-use ceilings on the account, never a paid upgrade.
- `destroy_sandbox(session_id)` -- tear one down
- `get_sandbox(session_id)` -- look up one sandbox's status
- `list_sandboxes(active_only?)` -- list the account's sandboxes
- `exec(session_id, command, timeout?)` -- run a shell command
- `file_create(session_id, path, content)` -- create/overwrite a file
- `view(session_id, path, view_range?)` -- view a file or list a directory
- `str_replace(session_id, path, old_str, new_str, replace_all?)` -- edit a file
- `ls(session_id, path?)` -- list the direct children of a directory
- `glob(session_id, pattern, path?)` -- find files by name pattern (e.g. `**/*.py`)
- `grep(session_id, pattern, path?, glob?, max_matches?)` -- search file
  contents by regex

## Security: the sandbox's own isolation is the trust boundary

`exec` runs arbitrary shell commands inside the sandbox with no client-side
allowlist, confirmation step, or command-scoping; `view`/`file_create`/
`str_replace` operate on any path within the sandbox with no additional
restriction at this layer. That's the correct design for a code-execution
sandbox product -- the isolation boundary is the Kubernetes sandbox itself
(see the root repo's `SECURITY.md`/README for what that isolation actually
provides), **not** these MCP tools' argument validation. Two things follow
from that:

- Whatever the `BOXKITE_API_KEY` you configure can reach (network access,
  mounted storage, any other capability your control-plane deployment grants
  that account's sandboxes) is reachable by any MCP client wired up to this
  server, unconditionally. Don't point this at an account/control-plane
  whose sandboxes have broader privileges than you want an LLM agent to have
  unsupervised access to.
- `exec`/`view` results are returned to the calling LLM as plain tool-result
  text, unsanitized. If a sandbox command produces attacker-influenced
  output (e.g. the agent clones an untrusted repo and then views a file from
  it), that content flows into the LLM's context like any other tool result
  -- treat it as untrusted input, same as you would for a web-fetch or
  file-read tool, not as trusted system output.

## Error handling

Every tool catches `BoxkiteApiError`/`BoxkiteConnectionError` and returns a
descriptive string as the tool result instead of raising -- mirrors the
defensive pattern in `boxkite_client.langchain_tools` so a control-plane
error (quota hit, sandbox not found, network failure) shows up as normal
tool output the calling agent can react to, not a server crash.

## Development

```bash
pip install -e ".[dev]"
pytest tests/
```

Unit tests mock the control-plane with `httpx.MockTransport` (same pattern as
`sdk-python/tests/`) -- no real deployment needed. `tests/live_smoke.py` is a
separate, non-CI manual script that drives the real server process over
stdio via `mcp`'s own `ClientSession` against a live control-plane:

```bash
BOXKITE_BASE_URL=https://your-control-plane.example.com \
BOXKITE_API_KEY=bxk_live_... \
python tests/live_smoke.py
```
