Skip to content

platools CLI

The SDK ships a single platools binary with three subcommands:

platools doctor [module] [--json]
platools test [tool] [--module ...] [--params ...] [--file ...] [--coverage]
platools serve [--module ...] [--stdio | --http] [--host ... --port ... --auth-token ...]
[--tool NAME ...] [--list]

All three are pure argparse - the SDK takes no runtime dependency on click or typer, so the wheel stays small.

platools doctor

Static tool-graph analyzer. From platools/cli/doctor.py:

Loads tool definitions either from an explicit Python module path (my_app.tools - same convention pytest uses for --rootdir) or from the calling-process registry. Exit code: 0 if there are no error-severity findings, 1 if any error finding is present (CI-friendly).

Terminal window
# Analyze every @platools.tool() in my_app.tools
platools doctor my_app.tools
# Machine-readable output for CI
platools doctor my_app.tools --json

How it finds your tools

platools doctor my_app.tools imports the given module and scans its top-level attributes for Platools instances. The registries on each instance are merged into a fresh composite registry the analyzer walks. That means you can split tools across modules and point doctor at the top-level barrel:

my_app/tools/__init__.py
from .billing import platools as _billing # re-export is enough
from .support import platools as _support
Terminal window
platools doctor my_app.tools

Checks

The analyzer runs every rule in platools/doctor/checks.py:

  • check_descriptions - tool and every parameter must have a non-empty description.
  • check_return_schema - every tool must declare an output type (otherwise agents guess).
  • check_param_sources - no unreachable / shadowed parameters.
  • check_permission_gaps - auth="admin" tools must also declare roles.
  • check_destructive_annotations - verbs like delete_*, freeze_*, cancel_* need annotations={"destructive": True}.
  • check_overly_broad - flags tools whose description is shorter than 10 characters.
  • check_orphan_tools - warns on tools with no test coverage (when a platools-tests.yaml is present).
  • check_circular_dependencies - walks the annotation graph for cycles.

Every finding has a severity (error / warning / info) and may be attached to a specific tool + param. See PRD §5.1 - doctor is the SDK’s ship gate, and it must stay green in CI.

Exit code

  • 0 - no error-severity findings (warnings/infos are allowed).
  • 1 - one or more error findings (CI fails the build).

platools test

Invoke tools locally without going through the platform. From platools/cli/test.py:

Terminal window
# Run a batch from ./platools-tests.yaml
platools test --module my_app.tools
# Run a specific tool with inline params
platools test process_refund --module my_app.tools --params '{"order_id": "ord_1", "reason": "duplicate"}'
# Coverage checklist - which tools have at least one test case?
platools test --module my_app.tools --coverage
# Explicit batch file
platools test --module my_app.tools --file smoke.yaml

Batch file format (platools-tests.yaml)

The top level is a mapping with a tests key whose value is a list of cases:

tests:
- tool: process_refund
params:
order_id: ord_1
reason: duplicate
expect_success: true
- tool: freeze_account
params:
account_id: acct_999
reason: fraud
expect_error: true # this case must fail

Each case is a BatchTestCase:

FieldTypeDefaultMeaning
toolstringrequiredName of the registered tool.
paramsobjectrequiredParams dict passed to the tool.
expect_successbooltrueCase passes when the call returns without raising.
expect_errorboolfalseCase passes when the call raises. Mutually exclusive with expect_success: true.

load_batch_file rejects contradictory combinations at parse time so the runtime path never sees an ambiguous case.

Output

Tests: 2 passed, 0 failed
Latency: p50 4.2ms, p95 11.8ms
✓ process_refund (3.8ms)
✓ freeze_account (12.1ms)

Exit codes: 0 on full pass, 1 on any failure, 2 on argparse / file errors.

platools serve

Runs a local MCP server from your registry. Full walkthrough in Local mode. Quick reference:

Terminal window
# Stdio (default) - for Claude Desktop, Cursor, etc.
platools serve --module my_app.tools
# HTTP transport
platools serve --module my_app.tools --http \
--port 3001 --auth-token "$PLATOOLS_SERVE_TOKEN"
# Dry run - list tools + exit
platools serve --module my_app.tools --list
# Allowlist specific tools
platools serve --module my_app.tools \
--tool process_refund --tool list_invoices

platools serve runs platools doctor against the registry before starting the transport. Any error finding aborts the start with exit code 1. A broken tool surface should never be silently exposed over MCP.

Next steps

  • Local mode - full platools serve walkthrough with Claude Desktop and Cursor config.
  • Python SDK overview - architecture and public surface.
  • Decorator - the @platools.tool() kwargs that doctor validates.