2026-07-24 16:56:47 +01:00
2026-07-15 14:42:24 +01:00
2026-07-24 16:43:46 +01:00
2026-07-15 19:00:59 +01:00
2026-07-24 16:56:47 +01:00
2026-07-24 16:43:46 +01:00
2026-07-15 14:42:24 +01:00

Turnstone MCP Server

A Model Context Protocol server providing five sandboxed tools for LLM-driven workflows. Built with FastMCP and Playwright, it exposes code execution, headless browser automation, file-to-Markdown conversion, and ephemeral file downloads 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
generate_download_link Creates a single-use, time-limited HTTPS download URL for a file in /workspace

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)
PUBLIC_BASE_URL http://localhost:8000 Public HTTPS base URL for ephemeral download links
WORKSPACE_MOUNT /workspace Absolute path to the shared workspace root (used as the Docker volume name in compose.yaml)

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 PlaywrightBrowserManager holds 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 by subprocess.run.
  • Path traversal protectionextract_to_markdown and generate_download_link resolve all paths against WORKSPACE_MOUNT and reject anything that escapes it.
  • Ephemeral download linksgenerate_download_link mints a 256-bit single-use token linked to a file path; clicking the URL streams the file and immediately invalidates the token. Expired tokens are garbage-collected automatically.
  • 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_workspace Docker volume at /workspace, giving the MCP tools read/write access to files used by other Turnstone services.

Credential Leak Guardrail — Action Required

The Turnstone credential leak guardrail intercepts outbound messages and flags URLs it considers suspicious. Because generate_download_link returns a URL containing a token, the guardrail will block it unless a policy explicitly allows the pattern.

Action: Add the following pattern to the Turnstone credential leak policy:

PUBLIC_BASE_URL/download?

Replace PUBLIC_BASE_URL with the actual value of the environment variable (e.g., https://turnstone.example.com/download?).

Without this policy, any attempt by the agent to present a download link to the user will be silently blocked by the credential leak filter.

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"}
GET /download?token= Streams a file identified by a single-use token (see generate_download_link tool)

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:

  1. Define the function with @mcp.tool()
  2. Add docstring with Parameters and Returns sections
  3. Implement input validation and error handling
  4. Return a string result (success or error message)

License

See the repository root for licensing information.

S
Description
No description provided
Readme
58 KiB
Languages
Python 97.8%
Dockerfile 2.2%