Ctrl K
ComunidadGuías…

server/README.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# sandstorm-mcp (self-hosted)

FastMCP server that exposes 20 file/terminal/git/workspace tools to AI agents
over MCP streamable-HTTP. Runs on any Debian/Ubuntu host, fronted by Caddy.

## Architecture

```
claude.ai / Claude Code / any MCP client
        │  (MCP, streamable HTTP)
        ▼
  https://<your-domain>
        │  (Caddy TLS termination, Let's Encrypt)
        ▼
  http://127.0.0.1:8080   ← FastMCP process (this package)
        │  (asyncio.subprocess)
        ▼
  /bin/bash, git, pathlib, … on the host
```

No CF Workers, no KV, no D1, no sidecar agent. The FastMCP process is the executor.

## Tool surface (20 tools)

File ops (6): `file_read`, `file_write`, `file_delete`, `file_list`, `file_move`, `file_mkdir`.
Terminal (3): `terminal_exec`, `terminal_exec_background`, `terminal_kill`.
Git (9): `git_clone`, `git_status`, `git_commit`, `git_push`, `git_pull`, `git_branch`, `git_checkout`, `git_diff`, `git_log`.
Workspace (2): `workspace_info`, `workspace_install`.

Names and schemas mirror the prior Cloudflare Worker implementation exactly.

## Non-MCP endpoints

| Path                                      | Auth | Purpose |
|-------------------------------------------|------|---------|
| `GET /health`                             | none | Node health + active session count |
| `GET /.well-known/oauth-protected-resource` | none | RFC 9728 metadata |
| `GET /.well-known/oauth-authorization-server` | none | RFC 8414 metadata |
| `POST /oauth/register`                    | none | RFC 7591 DCR |
| `GET/POST /oauth/authorize`               | password form | OAuth 2.0 authorize |
| `POST /oauth/token`                       | PKCE | OAuth 2.0 token |
| `POST /session/create` + related          | optional bearer `SESSION_AUTH_TOKEN` | Session API (orchestrator) |

## Running locally

```bash
# Requires Python 3.12+
uv sync

# Set the bearer token (same one claude.ai clients will send)
export SANDSTORM_MCP_TOKEN=dev-token-change-me
export SANDSTORM_MCP_HOST=127.0.0.1
export SANDSTORM_MCP_PORT=8080
# Optional:
export SANDSTORM_WORKSPACE_ROOT=$HOME/sandstorm-sandbox   # defaults to /home/dev
export SANDSTORM_STATE_DIR=$HOME/sandstorm-sandbox/.sandstorm-mcp

# Start the server
uv run python -m sandstorm_mcp.main
```

Smoke test:

```bash
# No auth → 401
curl -i http://localhost:8080/mcp

# With auth → MCP discovery (JSON-RPC initialize)
curl -s http://localhost:8080/mcp \
  -X POST \
  -H "Authorization: Bearer $SANDSTORM_MCP_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

# List tools
curl -s http://localhost:8080/mcp \
  -X POST \
  -H "Authorization: Bearer $SANDSTORM_MCP_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
```

## Adding a new tool

1. Pick the right module under `src/sandstorm_mcp/tools/`
   (`file_ops.py`, `git_ops.py`, `terminal.py`, `workspace.py`) or create a new one
   and wire it into `tools/__init__.py:register_all`.
2. Decorate the function with `@mcp.tool(name=..., description=...)`. Use `Annotated[T, Field(description="...")]` for parameters.
3. Return a string on success. Raise `fastmcp.exceptions.ToolError(msg)` to produce `isError: true`.
4. Add a test in `tests/test_tools.py`.

## Rotating the auth token

```bash
# 1. Pick a new strong token, e.g.:
NEW=$(python -c 'import secrets; print(secrets.token_urlsafe(32))')

# 2. Update the env file and restart
sudo sed -i "s|^SANDSTORM_MCP_TOKEN=.*|SANDSTORM_MCP_TOKEN=$NEW|" /etc/sandstorm-mcp.env
sudo systemctl restart sandstorm-mcp

# 3. Update the claude.ai custom connector with the new token
# 4. Save the new token to Proton Pass (vault: Claude)
```

Rotation invalidates every existing OAuth access token (they are HMAC-signed with
the server token). Clients using the raw bearer just need the new value.

## Logs

All logs are JSON on stdout → systemd-journald. Tail live:

```bash
sudo journalctl -u sandstorm-mcp -f
sudo journalctl -u sandstorm-mcp -n 200 --no-pager
```

Caddy access logs live at `/var/log/caddy/sandstorm-mcp.log` (JSON).