The pyRPC local MCP server exposes exactly three tools. Not four, not six, three. This is not a shipping deadline that cut the backlog short. Each tool was prototyped, tested against real agent workflows, and either earned its place or got cut. The test for inclusion was simple: could a competent model accomplish this some other way? If yes, the tool does not exist.
introspect_project
This is the one thing grep cannot reconstruct. The tool reads pyrpc.json, imports your backend module, walks the live registry, and returns every registered procedure with kind (query or mutation), parameter names, types, requiredness, defaults, docstrings, and full input/output JSON Schemas.
Static parsing cannot resolve conditional registration, decorator metadata, or runtime-computed defaults. The tool gives the agent the same view of your API that the code generator sees, which is the only view that matters for writing correct client code.
check_call
Answers the question: would these arguments be accepted? The tool validates hypothetical arguments against real Python types, including pydantic models, dataclasses, attrs classes, and manual validation logic, without executing anything. It returns structured per-parameter errors that an agent can act on immediately.
Without this tool, an agent guesses at the argument shape, writes client code, and waits for CI to tell it the guess was wrong. With it, the agent validates before writing, and the first attempt at client code is usually correct.
run_codegen
Wraps the existing code generation pipeline because regeneration is genuinely stateful work. The tool imports the module, renders templates, compares output against existing files, and writes only when the bytes differ. dry_run=true by default, so the agent can ask “is the TypeScript current?” without touching any files.
An agent doing this by hand would re-implement the CLI badly. The tool exists because the pipeline is complex enough that reimplementing it in-context costs more tokens than calling it.
The pipeline
introspect_project
|
v
[agent sees full registry with types, kinds, defaults]
|
v
check_call (with hypothetical arguments)
|
v
[agent validates payload before writing code]
|
v
run_codegen (dry_run=true, then dry_run=false)
|
v
[client TypeScript is generated and up to date]Why each tool is read-only or safe-dry-first
introspect_project reads files and imports modules. It never writes, never executes procedures, and never makes network calls. check_call validates arguments in-memory and discards them. No side effects, no database queries, no HTTP requests.
run_codegen is the only tool that writes files, and it defaults to dry run. The agent must explicitly set dry_run=false to modify anything. Even then, it only touches files under the configured codegen output directory. The write scope is narrow and predictable.
The anti-pattern of too many tools
Every tool name, description, and parameter schema lands in the model’s context window on every request. More tools means more tokens, more ambiguity, and more opportunities for the agent to pick the wrong one. The failure mode is quiet: the model does not crash, it just makes worse decisions as the tool list grows.
We prototyped six additional tools during development: project diagnostics, link inspection, configuration linting, procedure execution, schema export, and client scaffolding. Each would be easy to add and hard to remove once agents depend on them. Each was cut because the existing three tools, composed correctly, cover the same ground.
How this compares
The industry’s most disciplined MCP servers converge on two to four tools:
- Better Auth exposes exactly two tools. Public documentation, zero authentication.
- Prisma ships two tools: one for schema operations, one for query execution.
- pyRPC ships three: inspect, validate, generate. The middle tool (validate) is what most servers skip, and it is the one that prevents the most CI failures.
Three is a statement about restraint. Restraint is a feature agents can feel, even if they cannot name it.
Annotations carry the policy
Every tool declares MCP ToolAnnotations. The two read-only tools set readOnlyHint true and openWorldHint false. Codegen sets readOnlyHint false with destructiveHint false and idempotentHint true, because running it twice converges to the same bytes. These are hints, not security boundaries. The real guarantee for the read tools is architectural: they never call your function. The real limit for codegen is the dry_run default plus the narrow file scope.

pyRPC