Skip to content

platools serve - Local MCP mode

platools serve spins up a local MCP server directly from your @platools.tool() registry, with no Platos platform in the loop. It’s the right choice for:

  • IDE integrations - Claude Desktop, Cursor, and any other MCP client that launches servers as child processes.
  • Quick demos - show your tools to stakeholders without deploying the platform.
  • CI dry-runs - spin up an HTTP server, hit tools/list, assert the shape, tear down.
  • Local development - iterate on a tool without waiting for a cloud round-trip.

Implementation lives in platools/cli/serve.py, platools/serve/http.py, platools/serve/stdio.py, and platools/serve/dispatcher.py.

Two transports

Terminal window
# Stdio (default) - JSON-RPC over stdin/stdout, ideal for subprocess launch
platools serve --module my_app.tools
# HTTP transport - JSON-RPC over HTTP/1.1 on 127.0.0.1:3001 by default
platools serve --module my_app.tools --http \
--port 3001 \
--auth-token "$PLATOOLS_SERVE_TOKEN"

Rules of the road (from cli/serve.py):

  • Tool discovery mirrors platools doctor / platools test - scan an imported module for Platools instances, merge their registries. Zero tools -> refuse to start with exit code 1.
  • Doctor gate. The CLI runs platools doctor against the composite registry before the transport starts. Any error-severity finding aborts the start with exit code 1.
  • HTTP requires a bearer token. Passed as --auth-token or PLATOOLS_SERVE_TOKEN. Stdio is trusted by its parent process and carries no token.
  • HTTP binds 127.0.0.1 by default. Use --host 0.0.0.0 if you know what you’re doing (e.g. exposing a dev server to a container on the same host). The server uses hmac.compare_digest on the bearer token so timing attacks can’t walk it out.
  • 1 MiB request body cap. HTTP mode rejects larger requests with 413 before reading them into memory.
  • No chunked transfer encoding. HTTP mode speaks a narrow subset of HTTP/1.1 - clients must send Content-Length. Chunked requests get a 411.

Using it with Claude Desktop

Add a server entry to your claude_desktop_config.json:

{
"mcpServers": {
"my-app": {
"command": "platools",
"args": ["serve", "--module", "my_app.tools"],
"cwd": "/path/to/my_app"
}
}
}

Restart Claude Desktop. The tools from my_app.tools show up in the tool picker immediately. Because the stdio transport is trusted by its parent, no auth token is needed - Claude Desktop owns the subprocess.

Using it with Cursor

Cursor’s MCP config takes the same shape - command + args + cwd. Wire it up exactly like the Claude Desktop example above.

HTTP mode for CI

#!/usr/bin/env bash
set -euo pipefail
export PLATOOLS_SERVE_TOKEN="$(openssl rand -hex 32)"
platools serve --module my_app.tools --http --port 3001 &
SERVER_PID=$!
trap "kill $SERVER_PID" EXIT
# Give the server a beat to bind the port
sleep 0.5
curl -fsS \
-H "Authorization: Bearer $PLATOOLS_SERVE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}' \
http://127.0.0.1:3001/mcp | jq '.result.tools | length'

Use the bearer token for any production-ish deploy - even on localhost, an unauthenticated tool surface is an invitation to a misbehaving neighbor process.

Tool allowlisting

Only expose a subset of your registry:

Terminal window
platools serve --module my_app.tools \
--tool process_refund \
--tool list_invoices

Repeat --tool for each allowlisted name. Typos fail loudly at startup, not silently at tools/list time - the CLI validates every name against the live registry before starting the transport.

Exit codes

CodeMeaning
0Clean shutdown (stdio EOF, HTTP stop_event fired).
1Startup refused - empty registry, doctor errors, missing auth token, bad host/port/path.
2Argparse / config errors (bad --tool name, invalid --transport).

Development vs. production

platools serve (local)Platos platform (production)
Transportstdio or HTTP on localhostOutbound WebSocket to wss://
AuthOptional bearer token (HTTP) / none (stdio)JWT via PLATOS_SECRET
MonitoringNoneTool call logs, latency, quality checks
Multi-agentSingle client at a timeMultiple agents, concurrent sessions
Use caseDevelopment, IDE integration, CIProduction workloads

Use platools serve to iterate locally, then switch to platools.connect() for production.

Next steps