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 noerror-severity findings, 1 if any error finding is present (CI-friendly).
# Analyze every @platools.tool() in my_app.toolsplatools doctor my_app.tools
# Machine-readable output for CIplatools doctor my_app.tools --jsonHow 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:
from .billing import platools as _billing # re-export is enoughfrom .support import platools as _supportplatools doctor my_app.toolsChecks
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 declareroles.check_destructive_annotations- verbs likedelete_*,freeze_*,cancel_*needannotations={"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 aplatools-tests.yamlis 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- noerror-severity findings (warnings/infos are allowed).1- one or moreerrorfindings (CI fails the build).
platools test
Invoke tools locally without going through the platform. From platools/cli/test.py:
# Run a batch from ./platools-tests.yamlplatools test --module my_app.tools
# Run a specific tool with inline paramsplatools 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 fileplatools test --module my_app.tools --file smoke.yamlBatch 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 failEach case is a BatchTestCase:
| Field | Type | Default | Meaning |
|---|---|---|---|
tool | string | required | Name of the registered tool. |
params | object | required | Params dict passed to the tool. |
expect_success | bool | true | Case passes when the call returns without raising. |
expect_error | bool | false | Case 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 failedLatency: 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:
# Stdio (default) - for Claude Desktop, Cursor, etc.platools serve --module my_app.tools
# HTTP transportplatools serve --module my_app.tools --http \ --port 3001 --auth-token "$PLATOOLS_SERVE_TOKEN"
# Dry run - list tools + exitplatools serve --module my_app.tools --list
# Allowlist specific toolsplatools serve --module my_app.tools \ --tool process_refund --tool list_invoicesplatools 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 servewalkthrough with Claude Desktop and Cursor config. - Python SDK overview - architecture and public surface.
- Decorator - the
@platools.tool()kwargs that doctor validates.