Turnstone MCP Server
A Model Context Protocol server providing four sandboxed tools for LLM-driven workflows. Built with FastMCP and Playwright, it exposes code execution, headless browser automation, and file-to-Markdown conversion over HTTP.
Designed to run as a Docker container alongside the Turnstone platform, sharing the turnstone_workspace volume so tools can read and write files that other services in the ecosystem use.
Tools
| Tool | Description |
|---|---|
execute_python_code |
Runs Python source in a subprocess with timeout, memory, and output-size limits |
playwright_navigate |
Navigates a persistent headless Chromium session to a URL |
playwright_get_page_text |
Extracts rendered text from the current browser page |
extract_to_markdown |
Converts supported files (PDF, DOCX, images, CSV, etc.) to Markdown via markitdown |
Quick Start
Run the server with Docker Compose:
docker compose up --build
The server starts on port 8000. The MCP health endpoint is available at http://localhost:8000/health.
Running in the background
docker compose up -d --build
Configuration
All limits are configurable via environment variables with sensible defaults. Set them in your .env file or pass them through docker compose:
| Variable | Default | Description |
|---|---|---|
PYTHON_EXEC_TIMEOUT |
30 |
Python execution timeout (seconds) |
PYTHON_MAX_OUTPUT |
65536 |
Maximum stdout/stderr capture (bytes) |
PYTHON_MAX_MEMORY_MB |
256 |
Address-space memory limit per subprocess (MB) |
PLAYWRIGHT_NAV_TIMEOUT |
30000 |
Browser navigation timeout (ms) |
PLAYWRIGHT_TEXT_TIMEOUT |
15000 |
Page text extraction timeout (ms) |
EXTRACT_MAX_FILE_SIZE |
10485760 |
Maximum file size for extraction (10 MB) |
WORKSPACE_MOUNT |
workspace |
Docker volume name for the shared workspace |
Example with custom limits:
PYTHON_EXEC_TIMEOUT=60 PYTHON_MAX_MEMORY_MB=512 docker compose up --build
Architecture
┌──────────────┐ HTTP/MCP ┌──────────────────┐
│ LLM Client │ ────────────────► │ Turnstone MCP │
│ │ │ Container :8000 │
└──────────────┘ └────────┬─────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
┌─────▼─────┐ ┌──────▼──────┐ ┌───────▼───────┐
│ subprocess │ │ Playwright │ │ markitdown │
│ (Python) │ │ Chromium │ │ (file → MD) │
└───────────┘ └─────────────┘ └───────────────┘
│ │ │
└───────────┬───────────┴───────────────────────┘
│
┌─────────▼──────────┐
│ turnstone_workspace│
│ (shared volume) │
└────────────────────┘
Key design decisions
- Persistent browser session — A singleton
PlaywrightBrowserManagerholds the Chromium process alive across tool calls, preventing garbage-collection teardowns. - Sandboxed execution — Python code runs in a separate process with a per-subprocess memory limit (
resource.setrlimit) and a wall-clock timeout enforced bysubprocess.run. - Path traversal protection —
extract_to_markdownresolves all paths againstWORKSPACE_ROOTand rejects anything that escapes it. - Structured JSON logging — All tool invocations and responses are logged as structured JSON for observability.
- CORS enabled — The server accepts requests from any origin with standard MCP headers.
- Shared workspace volume — The container mounts the
turnstone_workspaceDocker volume at/workspace, giving the MCP tools read/write access to files used by other Turnstone services.
Docker Configuration
The service is defined in compose.yaml:
| Setting | Value |
|---|---|
| Port mapping | 8000:8000 |
| User | 1000:1000 |
| Restart policy | unless-stopped |
| Health check | HTTP GET /health every 30 s (timeout 10 s, 3 retries) |
| Workspace mount | ${WORKSPACE_MOUNT:-workspace}:/workspace |
Endpoints
| Method | Path | Description |
|---|---|---|
POST |
/ |
MCP protocol endpoint (default) |
GET |
/health |
Returns {"status": "healthy", "service": "turnstone-mcp"} |
Project Structure
compose.yaml # Docker Compose service definition
Dockerfile # Container build instructions
server.py # Complete MCP server — all logic in a single module
Development
No test framework or build step is required. To add a new tool, follow the existing pattern:
- Define the function with
@mcp.tool() - Add docstring with
ParametersandReturnssections - Implement input validation and error handling
- Return a string result (success or error message)
License
See the repository root for licensing information.