* feat: MCP cluster-ops example — reference MCP server + SDK implementation
Standalone MCP server under examples/mcp-cluster-ops/ that exposes
tools for executing commands across a Turnstone cluster via the MQ
client SDK. Serves as a reference implementation for both MCP server
patterns (FastMCP, lifespan, tool handlers) and TurnstoneClient usage.
4 tools: list_nodes, run_on_node, run_on_nodes, run_on_all_nodes.
Parallel dispatch via asyncio.gather, raw ToolResultEvent output
capture, UTF-8 safe truncation, input validation, concurrency caps.
35 tests, ruff clean, mypy --strict clean.
* fix: address review feedback on MCP cluster-ops example
- Remove REDIS_SSL support (RedisBroker doesn't accept ssl kwarg)
- Move max-nodes check from _dispatch_parallel into tool handlers
for consistent error shape (always returns {"error": ...} object)
- Propagate KeyboardInterrupt/SystemExit from asyncio.gather instead
of swallowing them as per-node failures
- Fix _truncate omitted bytes count to reflect actual bytes dropped
after multi-byte boundary adjustment
- Apply strip/dedup to node IDs in run_on_all_nodes (matching
run_on_nodes behavior)
- Add __name__ guard to __main__.py
- Fix misleading UTF-8 byte count comment in tests
MCP Cluster Ops
An MCP server that exposes tools for executing commands across a Turnstone cluster. Serves as a reference implementation for both MCP server patterns and Turnstone MQ client SDK usage.
How it works
This server uses Turnstone's MQ client (TurnstoneClient) to dispatch shell commands to specific nodes via Redis. Remote agents execute the command and the raw bash output is captured directly from the ToolResultEvent stream — bypassing the costly "agent reads output → re-generates output as completion tokens" round-trip.
Multi-node dispatches run in parallel via asyncio.gather, so total wall time is bounded by the slowest node rather than the sum.
Tools
| Tool | Description |
|---|---|
list_nodes |
Discover active nodes in the cluster |
run_on_node |
Execute a command on a specific node |
run_on_nodes |
Execute a command on selected nodes in parallel |
run_on_all_nodes |
Execute a command on ALL active nodes in parallel |
Prerequisites
- A running Turnstone cluster (at least one
turnstone-server+turnstone-bridge) - Redis accessible from wherever this MCP server runs
- Python 3.11+
Installation
# From the turnstone repo root:
pip install -e ./examples/mcp-cluster-ops
# Or install turnstone with MQ support first, then the example:
pip install -e ".[mq]"
pip install -e ./examples/mcp-cluster-ops
Configuration
Environment Variables
| Variable | Default | Description |
|---|---|---|
REDIS_HOST |
localhost |
Redis host |
REDIS_PORT |
6379 |
Redis port |
REDIS_PASSWORD |
(none) | Redis password (use env vars, not config files) |
MCP_CLUSTER_OPS_TIMEOUT |
120 |
Default command timeout (seconds, clamped 5-3600) |
MCP_CLUSTER_OPS_MAX_OUTPUT |
8192 |
Max output bytes per node (0 = unlimited) |
MCP_CLUSTER_OPS_MAX_NODES |
32 |
Max concurrent node dispatches |
MCP_CLUSTER_OPS_MAX_COMMAND |
65536 |
Max command string length |
Register with Turnstone
TOML (~/.config/turnstone/config.toml):
[mcp.servers.cluster-ops]
command = "mcp-cluster-ops"
[mcp.servers.cluster-ops.env]
REDIS_HOST = "redis.example.com"
JSON (via --mcp-config):
{
"mcpServers": {
"cluster-ops": {
"command": "mcp-cluster-ops",
"env": {
"REDIS_HOST": "redis.example.com"
}
}
}
}
Usage Examples
Once registered, the tools appear in any Turnstone session. The model can:
> Check disk usage across the cluster
[calls list_nodes → discovers node-1, node-2, node-3]
[calls run_on_all_nodes with "df -h /"]
node-1: /dev/sda1 500G 320G 180G 64% /
node-2: /dev/sda1 500G 410G 90G 82% /
node-3: /dev/sda1 1.0T 200G 800G 20% /
Why MQ client instead of HTTP SDK?
The HTTP SDK (TurnstoneServer) talks to a single server instance. The MQ client (TurnstoneClient) routes through Redis with target_node support, which is the entire point of cross-node cluster operations.
Security Considerations
This MCP server grants the calling agent shell access to cluster nodes.
- Commands are executed with
auto_approve=Trueand the privileges of the Turnstone server process on the target node. - Command output (which may contain secrets, credentials, or sensitive data) is returned through the MCP tool result and becomes part of the LLM context.
- The security boundary is at the MCP host layer -- use Turnstone's tool policy system to restrict which agents can invoke these tools.
- Set
REDIS_PASSWORDvia your environment or a secrets manager -- avoid hardcoding passwords in config files.
Development
cd examples/mcp-cluster-ops
# Run tests
pip install -e ".[test]"
pytest
# Lint
pip install -e ".[dev]"
ruff check mcp_cluster_ops/
mypy --strict mcp_cluster_ops/