@pyreon/mcp — API Reference
Generated from
mcp'ssrc/manifest.ts— the same source that powersllms.txtand MCPget_api. Do not edit this page by hand; edit the manifest. For the conceptual guide, see mcp.
MCP server (stdio transport) that exposes Pyreon's structured knowledge to AI coding assistants (Claude Code, Cursor, VS Code, Gemini CLI, etc.). Tools: mcp_overview (start here — markdown table of every tool with "when to use" + example, read straight from this manifest), get_api (look up any Pyreon API), validate (catch React + Pyreon-specific anti-patterns in a snippet), migrate_react (auto-convert React code), diagnose (parse a Pyreon error into structured fix info; optional componentSource + reactiveTrace for causal diagnosis), explain_error (assemble a failure dossier from a full error report), get_routes / get_components (project introspection), get_content_collection / get_content_entry (enumerate @pyreon/zero-content collections + drill into one entry's frontmatter + heading outline), get_browser_smoke_status (which packages need a browser smoke test), get_pattern (canonical "how do I do X" docs), get_anti_patterns (the catalog from .agents/rules/anti-patterns.md), get_changelog (recent release notes per package), audit_test_environment (mock-vnode test scanner — PR #197 bug class), and audit_islands (project-wide islands cross-file audit — duplicate names, dead islands, registry drift, nested islands, never-with-registry).
Multiplatform
Tier: Web-only — the browser package; the native story is stated below
the MCP server — dev/AI tooling, not app runtime
See Multiplatform for the capability matrix and Multiplatform libraries for every package's tier.
Features
Tools covering discovery, lookup, validation, migration, diagnosis, introspection, audit
stdio transport — drop-in compatible with every MCP client
Project context cached per server instance, auto-invalidates on cwd change
Manifest-driven —
get_apireadsapi-reference.ts, regenerated from package manifestsAST-based detectors —
validatecatches React + Pyreon-specific patterns staticallyReal-repo audit tools (
audit_test_environment,audit_islands,get_browser_smoke_status) walk packages/
Complete example
A full, end-to-end usage of the package:
// .mcp/config.json — register the server with any MCP-aware client
{
"mcpServers": {
"pyreon": {
"command": "bunx",
"args": ["@pyreon/mcp"]
}
}
}
// Then from the client (Claude Code, Cursor, etc.):
// mcp_overview()
// → markdown table: tool | when_to_use | example (start here)
// get_api({ package: 'flow', symbol: 'createFlow' })
// → signature, example, common mistakes
// validate({ code: '<MyButton onClick={handler}>...' })
// → React-pattern + Pyreon-pattern diagnostics with line/col
// get_pattern({ name: 'controllable-state' })
// → canonical pattern body from docs/patterns/
// get_anti_patterns({ category: 'reactivity' })
// → reactivity foot-guns from .agents/rules/anti-patterns.md
// get_changelog({ package: 'flow', limit: 5 })
// → recent release notes filtered through ceremonial-bump removal
// audit_test_environment({ minRisk: 'medium' })
// → mock-vnode test files ranked HIGH / MEDIUM / LOW
// audit_islands({})
// → project-wide islands audit (5 cross-file foot-guns)Exports
| Symbol | Kind | Summary |
|---|---|---|
mcp_overview | constant | Returns a markdown table of every registered MCP tool with a one-sentence "when to use" description and a one-line examp |
get_browser_smoke_status | constant | Companion to the pyreon/require-browser-smoke-test lint rule. |
get_api | constant | Look up any Pyreon API by package (e.g. |
validate | constant | Two AST-based detectors run in parallel: detectReactPatterns flags "coming from React" mistakes (useState, `useEffec |
explain_reactivity | constant | The compiler's per-expression reactivity VERDICT for a snippet. |
migrate_react | constant | Convert React code to idiomatic Pyreon. |
migrate_pyreon | constant | The Pyreon → correct-Pyreon codemod (parallel to migrate_react). |
diagnose | constant | Parse a Pyreon runtime / build error into structured fix information. |
explain_error | constant | The rich-context sibling of diagnose. |
get_routes | constant | List every route in the current project — path, loader presence, guards, params, and named-route name. |
get_components | constant | List every component in the current project with its props and signal usage. |
get_content_collection | constant | List the @pyreon/zero-content collections declared in the project's content.config.{ts,mts,js,mjs} (name, type, entr |
get_content_entry | constant | Fetch one content entry: its path, title and size, the parsed frontmatter as key/value pairs, and the heading outline (c |
get_atlas_catalog | constant | Serve the VERIFIED component catalog atlas scan writes (atlas-catalog.json) — every component with its real props, a |
get_dependency_fabric | constant | Serve the workspace dependency graph loom scan writes (loom-report.json): the shape (packages, edges, depth), runtim |
get_atlas_component | constant | Prescriptive usage for ONE catalogued component: required and optional props with their exact allowed values, which prop |
get_api_client | constant | Serve the generated API client lathe generate wrote — every operation with its method, path, summary and the exact sym |
get_api_operation | constant | One generated operation's TYPED signature: each parameter with its location (path/query) and whether it is required, the |
explain_api_diff | constant | The client-contract diff between two versions of an API — each side a spec (JSON/YAML), an api-surface.json, or `<git- |
get_pattern | constant | Fetch a canonical "how do I do X" pattern body from docs/src/content/docs/patterns/ (the same files the docs site rend |
get_anti_patterns | constant | Browse the anti-patterns catalog from .agents/rules/anti-patterns.md, token-frugal by default. |
get_changelog | constant | Recent release notes for any @pyreon/* package without scraping git log. |
audit_test_environment | constant | Scan every *.test.{ts,tsx} under packages/ for the mock-vnode anti-pattern that caused PR #197's silent metadata dr |
audit_islands | constant | Project-wide cross-file islands audit (PR C of the islands DX roadmap). |
API
mcp_overview constant
tool: mcp_overview() → MarkdownTableReturns a markdown table of every registered MCP tool with a one-sentence "when to use" description and a one-line example. Reads from this same manifest at runtime — single source of truth (the same data feeds api-reference.ts, llms-full.txt, and docs/src/content/docs/mcp.md). Intended as the first call for any AI agent connecting to the server: enumerates the surface so the agent can navigate by intent (e.g. "I need release notes" → get_changelog) rather than guessing tool names from tools/list.
Example
mcp_overview()
// → | Tool | When to use | Example |
// |------|-------------|---------|
// | mcp_overview | Returns a markdown table of every registered MCP tool... | mcp_overview() |
// | get_api | Look up any Pyreon API by package and symbol... | get_api({ package: 'flow', symbol: 'createFlow' }) |
// | ...Common mistakes
Skipping this tool and calling
tools/listinstead — that returns names + parameter schemas but no "when to use" guidance, so an agent has to call multiple tools to figure out which one fits the task.
See also: get_api
get_browser_smoke_status constant
tool: get_browser_smoke_status — no argsCompanion to the pyreon/require-browser-smoke-test lint rule. Reports which browser-categorized Pyreon packages have at least one *.browser.test.{ts,tsx} file under src/. Uses the same .agents/rules/browser-packages.json single source of truth as the rule + the CI script. Lets an AI agent check coverage before writing a new browser package (so it adds a smoke test in the same PR) instead of discovering the failure when CI runs. Falls back with a clear message if the JSON isn't present (e.g. consumer apps that don't ship the Pyreon monorepo layout).
Example
// Ask the MCP server:
// "which Pyreon packages are missing browser smoke coverage?"
// Tool walks packages/, matches against .agents/rules/browser-packages.json,
// returns a coverage report.Common mistakes
Using the tool's output as a substitute for running the CI script — this tool only checks file existence, not the self-expiring-exemption check that
bun run lint:browser-smokeperforms
See also: audit_test_environment
get_api constant
tool: get_api({ package: string; symbol: string }) → APIEntryLook up any Pyreon API by package (e.g. "flow" or "@pyreon/flow") and symbol (e.g. "createFlow"). Returns the canonical signature, example, foot-gun catalogue, and cross-references — drawn from api-reference.ts, which is regenerated from each package's manifest.ts. The single agent-facing entry point for "what does this API do and how do I avoid the common mistakes."
Example
// Agent-side
get_api({ package: 'flow', symbol: 'createFlow' })
get_api({ package: '@pyreon/router', symbol: 'useTypedSearchParams' })Common mistakes
Passing the package name with a typo or wrong scope —
get_api({ package: "pyreon-flow", ... })returns nothing. Use"flow"or"@pyreon/flow"; the tool accepts both.Expecting
symbolto match a method on a returned instance (e.g.Posts.useList) — only TOP-LEVEL exports are in api-reference. Method-on-instance APIs are documented in the parent symbol'ssummary/example.Treating a 404 as "the API doesn't exist" — it may exist but the package's manifest is not yet on the MCP pipeline (57 of 76 published packages have one as of this writing — see AGENTS.md "Manifest-driven docs pipeline" for the live count). Check the docs page or source as a fallback when get_api returns empty.
Forgetting that
summarymay contain the answer to a follow-up question — read the full body before falling back toget_pattern/validate/ source diving.
See also: validate · get_pattern
validate constant
tool: validate({ code: string; filename?: string }) → Diagnostics[]Two AST-based detectors run in parallel: detectReactPatterns flags "coming from React" mistakes (useState, useEffect, className, onChange on inputs, React-package imports), and detectPyreonPatterns flags "using Pyreon wrong" mistakes (<For> missing by, props destructured at component signature, typeof process dev gates, raw addEventListener, Date.now() + Math.random() IDs). Diagnostics are merged + sorted by line / column for top-down reading.
Example
validate({ code: `
function MyComp(props) {
const { value } = props // → props-destructured
return <For each={items}>{...}</For> // → for-missing-by
}
` })Common mistakes
Treating zero diagnostics as "the code is correct" —
validateis a STATIC detector. It catches the documented anti-patterns from.agents/rules/anti-patterns.mdbut does NOT verify runtime semantics, cross-file consistency, type correctness, or compiler output. Pair withtsc+ tests for full coverage.Omitting the
filenamearg for path-sensitive detectors — some detectors (e.g.pyreon/no-window-in-ssrwith itsexemptPathsoption) need the path to know whether the file is server-only-exempt. Without it the diagnostic may misfire or fail to fire.Running
validateon a snippet that is NOT a full file — detectors expect complete syntax (everyimport, everyfunction). Passing a partial expression yields no diagnostics, which can be mistaken for "clean".Calling
validateafter the code is already merged — it's a pre-commit / before-paste tool. After-the-fact use is fine but the maximum value is catching the bug BEFORE it ships.
See also: get_anti_patterns · migrate_react
explain_reactivity constant
tool: explain_reactivity({ code: string; filename?: string }) → ReactivityMapThe compiler's per-expression reactivity VERDICT for a snippet. The Pyreon compiler already decides, while emitting codegen, whether each JSX expression is reactive or baked static — explain_reactivity surfaces that ground truth via analyzeReactivity: every expression classified live / live prop / live attr / baked once / hoisted static, merged with the detectPyreonPatterns footguns, over an annotated source view. Where validate reports BUGS, this reports the whole MAP: an agent sees that <div>{qty}</div> compiled to baked once (dead) BEFORE it ships the stale-closure / destructured-props / static-when-meant-reactive bug. The reactivity 'type-check' surface for AI agents.
Example
explain_reactivity({ code: `
function Cart(props) {
const { qty } = props // → footgun: props-destructured-body
const price = signal(9.99)
return <div>{qty} × {price()}</div> // {qty} → baked once (dead), {price()} → live
}
` })Common mistakes
Confusing it with
validate—validatelists anti-patterns;explain_reactivityclassifies EVERY expression live/static so you can spot a binding that silently won't update even when no footgun fires.Passing a partial expression instead of a full component — the compiler needs complete JSX to classify bindings; a fragment yields "No reactive expressions detected".
Reading a
baked onceverdict as an error — static is often correct (literal text, one-time content). It is only a bug when that expression was MEANT to update; the tool flags the shape, you decide intent.
See also: validate · get_anti_patterns
migrate_react constant
tool: migrate_react({ code: string; filename?: string }) → MigrationResultConvert React code to idiomatic Pyreon. Handles useState → signal(), useEffect → effect(), className → class, onChange → onInput, useMemo → computed(), React imports → Pyreon imports. Reports per-edit fixable diagnostics so callers can apply or review.
Example
migrate_react({ code: `
import { useState, useEffect } from 'react'
function Counter() {
const [count, setCount] = useState(0)
useEffect(() => { console.log(count) }, [count])
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
` })Common mistakes
Expecting the migration to handle every React feature — currently covers the most common hooks/JSX patterns. Class components, Concurrent React APIs, Suspense boundaries, and React-specific libs (react-router, redux) are NOT migrated automatically; the result will flag remaining issues but won't rewrite them.
Running
migrate_reacton a file that's already mostly Pyreon — it's idempotent against already-migrated code (nothing flagged → nothing changed), so the cost is just the parse pass; safe to re-run.Forgetting that
useEffect(() => fn, [deps])→effect(() => fn)changes semantics: Pyreon effects auto-track via signal reads, the explicit deps array is dropped. Verify your effects read the same signals the React deps array listed.Trusting the migration to produce idiomatic Pyreon — the output is CORRECT but mechanical. Pair with
get_patternafter migration to apply Pyreon-native shapes (e.g.<Show when={() => …}>instead of ternaries;<For>instead of.map()).
See also: validate
migrate_pyreon constant
tool: migrate_pyreon({ code: string; filename?: string }) → PyreonMigrationResultThe Pyreon → correct-Pyreon codemod (parallel to migrate_react). Auto-fixes ONLY the mechanically-safe footguns validate / explain_reactivity flag — sig(v) → sig.set(v), <For key={k}> → <For by={k}>, and dropping x as unknown as VNodeChild — and returns every OTHER detected footgun (props-destructured, on-click-undefined, raw-add-event-listener, …) as a manual-fix list. This is what makes those three detectPyreonPatterns codes report fixable: true; a conservative codemod (span-based, idempotent, non-overlapping) that never mangles code, so an agent can apply the result verbatim.
Example
migrate_pyreon({ code: `
const count = signal(0)
count(1) // → count.set(1)
const list = <For each={a} key={k}>{…}</For> // → <For each={a} by={k}>
const node = (x as unknown as VNodeChild) // → x
` })Common mistakes
Expecting it to fix everything
validateflags — only the three mechanically-safe codes are auto-fixed; the rest (props-destructured, on-click-undefined, raw-add-event-listener, date-math-random-id) need human judgement and come back inremaining.Running it as a formatter — it only rewrites the flagged footgun spans; whitespace / style elsewhere is untouched (pair with your formatter).
Skipping
validateafterwards —migrate_pyreonclears the mechanical footguns, but re-runvalidateto confirm theremaining(human) issues are addressed.
See also: validate · explain_reactivity · migrate_react
diagnose constant
tool: diagnose({ error: string, componentSource?: string, reactiveTrace?: ReactiveTraceEntry[], filename?: string, phase?: string }) → DiagnoseResultParse a Pyreon runtime / build error into structured fix information. String-only call is unchanged (probable cause + fix + related docs from the regex pattern table — fully backward-compatible). v2 adds OPTIONAL structured context for richer, causal diagnosis: pass componentSource and the tool runs the static Pyreon detectors over it and maps each hit to the documented anti-pattern catalog entry (the detectorCodes bridge); pass reactiveTrace (the ErrorContext.reactiveTrace from @pyreon/core, populated in dev) and the tool formats the causal sequence of signal writes leading to the crash. The tool is deterministic — it assembles structured context, the calling agent reasons over it (no embedded LLM). Use the enriched form when you have the failing component + the error report; use the bare string form for a quick "what does this error mean".
Example
// v1 — unchanged, backward-compatible
diagnose({ error: 'Cannot redefine property X on object [object Object]' })
// → cause: configurable: false on a getter; fix: set configurable: true
// v2 — structured context → causal diagnosis
diagnose({
error: 'name is stale after parent update',
componentSource: 'function G({ name }) { return <div>{name}</div> }',
reactiveTrace: [{ name: 'name', prev: '"a"', next: '"b"', timestamp: 1 }],
})
// → base diagnosis + "Static detector findings: props-destructured"
// + matched anti-pattern entry + the reactive run-upCommon mistakes
Assuming v2 changed the string-only behaviour — it did not; an error-only call returns byte-identical output to before. The enrichment sections appear ONLY when componentSource / reactiveTrace are supplied
Expecting the tool to return a fix patch — it returns structured CONTEXT (regex diagnosis + detector hits + matched anti-patterns + reactive run-up). The agent reasons over it; the tool does not embed a model
Passing a production error report and expecting
reactiveTracecontent — the trace is dev-only (it tree-shakes out of prod builds), so prod reports carryreactiveTrace: undefinedand the tool degrades to the v1 base diagnosis
See also: validate · get_anti_patterns · explain_error
explain_error constant
tool: explain_error({ report: string; componentSource?: string }) → FailureDossierThe rich-context sibling of diagnose. diagnose matches an error STRING against known footguns; explain_error takes a full ErrorContext-shaped report — crucially the reactiveTrace (the causal SEQUENCE of signal writes from @pyreon/core's error reports) — and assembles a structured failure dossier: the reactive run-up + heuristic findings (empty-trace / nullish-then-crash / write-storm / last-write-correlation / type-flip), optional static detectPyreonPatterns on the component source, and correlated anti-pattern catalogue entries. The server only assembles + applies cheap heuristics; the consuming agent reasons over the dossier and a human gates any patch (the tool returns text only — no mutation, no LLM dependency). Use it when an agent has a captured Pyreon crash and the stack trace alone is not enough — the reactive sequence shows how the app reached the failing state.
Example
explain_error({ report: JSON.stringify(errorContext) })
// errorContext from registerErrorHandler(ctx => …) in dev;
// ctx.reactiveTrace is the high-signal fieldCommon mistakes
Passing only an error string — that is what
diagnoseis for.explain_errorwants the structured report (phase, component, props, reactiveTrace) to be worth more thandiagnoseExpecting it to apply a fix — it returns a dossier + suspected cause only. Repair is human-gated by construction (the tool has no write capability)
Capturing the report in production —
reactiveTraceis dev-only (tree-shaken in prod), so the highest-signal section will be empty. Capture in dev
See also: diagnose · validate · get_anti_patterns
get_routes constant
tool: get_routes() → Route[]List every route in the current project — path, loader presence, guards, params, and named-route name. Walks the project source from process.cwd() down. Cached per server instance with auto-invalidation on cwd change.
Example
get_routes()
// → [{ path: '/', name: 'home', hasLoader: true, params: [] }, ...]Common mistakes
Calling
get_routesfrom outside a Pyreon project (nopackage.jsonwith@pyreon/routeror@pyreon/zeroreachable fromprocess.cwd()) — returns an empty array. Run from the project root, not from~/or a parent directory.Expecting the route list to update mid-session after file changes — the scanner caches per server-instance + cwd. Restart the MCP server or change cwd to refresh.
Treating
hasLoader: falseas "no data" — the route may load data viauseQueryin the component body.hasLoaderreflects theexport const loader = …convention only.
See also: get_components
get_components constant
tool: get_components() → ComponentInfo[]List every component in the current project with its props and signal usage. Same scanner as get_routes. Useful for an agent before generating new code that needs to reference existing components.
Example
get_components()
// → [{ name: 'Button', file: 'src/Button.tsx', props: ['onClick', 'children'], signals: ['count'] }, ...]Common mistakes
Trusting the
propslist to be complete — the scanner extracts props from the FIRST parameter type annotation or destructure. Components using prop spread (<Comp {...rest}>) or computed prop shapes won't have their forwarded keys listed.Expecting
signalsto count signals declared INSIDE the component body — yes, those are listed; but signals imported from another module and used here are NOT listed (the scanner is per-file).Calling outside a Pyreon project — same caveat as
get_routes: returns empty if the scanner can't find a project root.
See also: get_routes
get_content_collection constant
tool: get_content_collection({ name? }) → markdownList the @pyreon/zero-content collections declared in the project's content.config.{ts,mts,js,mjs} (name, type, entry count, content directory), or — with name — one collection's entries (slug, title, file). Reads from process.cwd().
Example
get_content_collection()
// → # Content collections (2) — `docs` (markdown) — 42 entries at `src/content/docs`
get_content_collection({ name: 'docs' })
// → one line per entry: `getting-started` — Getting started (`getting-started.md`)Common mistakes
Calling it outside a
@pyreon/zero-contentapp — with nocontent.config.*it answers "No content.config found", not an empty list.Passing a collection name that is not declared — the answer lists the known collection names instead.
See also: get_content_entry · get_routes
get_content_entry constant
tool: get_content_entry({ collection, slug }) → markdownFetch one content entry: its path, title and size, the parsed frontmatter as key/value pairs, and the heading outline (code-fence aware). Use slug: "" for a collection's index entry. When the slug is not found, the answer suggests up to five nearby slugs.
Example
get_content_entry({ collection: 'docs', slug: 'getting-started' })
// → path, title, bytes, ## Frontmatter, ## Heading outlineCommon mistakes
Guessing slugs — call
get_content_collection({ name })first to list the real ones.Using the file name with its extension as the slug — slugs are derived the same way the runtime derives them, without
.md.
See also: get_content_collection
get_atlas_catalog constant
tool: get_atlas_catalog({ tag?: string }) → stringServe the VERIFIED component catalog atlas scan writes (atlas-catalog.json) — every component with its real props, allowed values and scenario counts, read from your source rather than guessed. Filter with tag. Each line states how many scenarios are verified, failing and UNVERIFIED, because most Atlas checks are still stubs and a catalogued component is not automatically a checked one. With no catalog present the tool returns instructions to run atlas scan instead of an invented answer.
Example
get_atlas_catalog({})
// → # Atlas catalog — 12 component(s) … scenarios: 2 (1 verified, 0 failing, 1 unverified)
get_atlas_catalog({ tag: 'form' })
// → only components tagged `form`Common mistakes
Treating a catalogued component as a verified one — the counts on each line are the point.
unverifiedmeans nothing examined that scenario; it is not a pass.Calling it before
atlas scanhas run — the catalog is a build artifact, so the tool returns setup instructions rather than a stale or invented list.Expecting per-prop detail here — the index is deliberately token-frugal. Use
get_atlas_componentfor one component's exact prop values.
See also: get_atlas_component · get_api
get_dependency_fabric constant
tool: get_dependency_fabric({ package?: string }) → stringServe the workspace dependency graph loom scan writes (loom-report.json): the shape (packages, edges, depth), runtime cycles, gating findings, and the blast-radius ranking that answers "what does changing this reach?". Pass package for one package's declared runtime deps, its dependents, its depth and reach, and its own findings. Every rendering carries loom's honesty rule — DECLARED truth only, so it cannot say which version is INSTALLED, and unused-dep is lexical evidence rather than proof. A report older than a day is flagged; with none present the tool returns instructions to run loom scan rather than an invented graph.
Example
get_dependency_fabric({})
// → # Dependency fabric — 142 workspace package(s) … Blast radius: @pyreon/core → 96 dependent(s)
get_dependency_fabric({ package: '@pyreon/router' })
// → declares, depended-on-by, depth, reach, and that package's findingsCommon mistakes
Reading
unused-depas "safe to delete" — it is lexical evidence with INFO severity; bins, plugin autoloads and CSS imports load without an import statement, so verify before removing.Asking it which version is installed — loom reads manifests and source, never a lockfile or the registry. Declared ranges are all it can honestly report.
Trusting a stale report — the fabric changes with every dependency edit, so the tool prints the artifact age and you should re-run
loom scanafter one.Expecting dev-dependency cycles — loom excludes dev edges from cycle detection on purpose, because monorepos legitimately share test utilities both ways.
See also: get_atlas_catalog · get_api
get_atlas_component constant
tool: get_atlas_component({ name: string }) → stringPrescriptive usage for ONE catalogued component: required and optional props with their exact allowed values, which props are reactive (pass a signal accessor, not a value), a known-good example when a scenario has actually been VERIFIED, and avoid: lines for scenarios that genuinely failed a check. An unverified example is offered but labelled UNVERIFIED rather than presented as correct — the consumer of this answer cannot check it, so the claim has to be earned. Unknown names get near-match suggestions.
Example
get_atlas_component({ name: 'Button' })
// → required: label(text)
// optional: state(primary|secondary)
// reactive (pass a signal accessor, not a value): onClick
// correct (verified): {"label":"Save"}Common mistakes
Reading
example (UNVERIFIED …)as a known-good example — it is args from a scenario nothing has checked. Onlycorrect (verified)carries evidence.Passing a resolved value to a prop listed as reactive — those take an accessor (
() => count()), and passing the value captures it once.Inventing a value for a prop whose allowed set is printed —
state(primary|secondary)is the complete list for that component.
See also: get_atlas_catalog · validate
get_api_client constant
tool: get_api_client({ search?: string, path?: string }) → stringServe the generated API client lathe generate wrote — every operation with its method, path, summary and the exact symbols it exports (endpoint, use<Op> hook, <op>Stream / use<Op>Stream for streaming operations), grouped by the module they live in, plus every model. Read from the api-surface.json beside the generated code, so it describes the client the agent will actually import rather than a re-reading of the spec. Filter with search; point path at a generated directory when a project has several.
Example
get_api_client({})
// → # Shop — 3 operation(s), 2 model(s)
// ## orders
// - `getOrder` GET /orders/:id — One order → `getOrder`, `useGetOrder`
get_api_client({ search: 'order' })Common mistakes
Guessing a hook name from the spec's operationId — the generated name is normalized (and a stream-only operation has no
use<Op>at all). The symbols listed here are the real exports.Calling it before
lathe generatehas run — the surface is a generation artifact, so the tool returns setup instructions rather than a guessed client.Expecting parameter detail from the index — it is deliberately compact. Use
get_api_operationfor one operation's typed signature and a call.
See also: get_api_operation · explain_api_diff
get_api_operation constant
tool: get_api_operation({ operation: string, path?: string }) → stringOne generated operation's TYPED signature: each parameter with its location (path/query) and whether it is required, the request body, the response and stream event types, the fields of every model they name, and example calls shaped by those types — the direct endpoint call, the query or mutation hook, and a for await over the stream when there is one. Imports are written relative to the working directory. Unknown names get near-match suggestions.
Example
get_api_operation({ operation: 'getOrder' })
// → - `id` (path, required): string
// - response: Order
// const q = useGetOrder(() => ({ params: { id: '…' } }))Common mistakes
Passing a path parameter under
query(or the reverse) — the location is printed per parameter; path params go inparams.Calling a
use<Op>hook with a value instead of an accessor — generated hooks take() => argsso signal reads stay reactive; returnundefinedto hold the query disabled.Awaiting a stream function —
<op>Stream(...)returns an async iterable; iterate it withfor await, andbreakcloses the connection.
See also: get_api_client · explain_api_diff
explain_api_diff constant
tool: explain_api_diff({ before: string, after?: string }) → stringThe client-contract diff between two versions of an API — each side a spec (JSON/YAML), an api-surface.json, or <git-rev>:<path> (main:openapi.yaml); after defaults to the generated client in the project. Uses @pyreon/lathe's own classifier (the same one lathe diff and lathe check run), so severities are from the CLIENT's point of view: a response field turning optional is breaking, a request field doing so is not. Breaking first, each change naming the generated symbols it reaches (model changes are traced transitively to operations), followed by what to check in the code for every breaking change.
Example
explain_api_diff({ before: 'main:openapi.yaml', after: 'openapi.yaml' })
// → ### API contract: 1 breaking, 0 additive
// | `field-now-optional` | `Customer.email` | required → optional | `getOrder`, `useGetOrder` (orders) |
// - `Customer.email` (`field-now-optional`): guard every read.Common mistakes
Trusting a green typecheck after regenerating — breaking changes here are exactly the ones that still COMPILE (a field that is now sometimes absent) and fail at runtime.
Diffing generated TypeScript instead of the contract — formatting and ordering move for non-contract reasons; this compares only what a caller can observe.
Reading
member-addedas harmless — aswitchover that value can now receive a member it does not handle.
See also: get_api_client · get_api_operation
get_pattern constant
tool: get_pattern({ name?: string }) → PatternBody | string[]Fetch a canonical "how do I do X" pattern body from docs/src/content/docs/patterns/ (the same files the docs site renders — one source, two surfaces). 17 foundational patterns ship: controllable-state, data-fetching, dev-warnings, dynamic-fields, event-listeners, form-fields, imperative-toasts, islands, keyed-lists, multiplatform, reactive-context, reactive-spread, routing-setup, signal-writes, ssr-safe-hooks, state-management, styler-theming. Omit name to list available patterns. Drop a new docs/src/content/docs/patterns/<slug>.md file to add one — picked up on next call (no code change, no bundling step in dev; scripts/copy-content.ts mirrors it into this package's bundled content/patterns/ for the published bunx @pyreon/mcp consumer case at build time).
Example
get_pattern({ name: 'controllable-state' })
// → full canonical pattern body
get_pattern({})
// → [{ name: 'controllable-state', summary: '...' }, ...]Common mistakes
Passing a name in CamelCase or PascalCase — pattern names are kebab-case (
controllable-state, notControllableState). A wrong-case name 404s.Expecting the pattern list to include every Pyreon idiom —
get_patterncovers the 17 foundational shapes (data fetching, forms, signal writes, multiplatform, etc.). Specialized topics (PMTC internals, native compat shims, devtools wiring) live in the wider docs site, not as aget_patternslug.Confusing patterns with anti-patterns —
get_patternreturns "how to do X correctly";get_anti_patternsreturns "what to avoid". They're complementary.
See also: get_anti_patterns
get_anti_patterns constant
tool: get_anti_patterns({ category?: 'reactivity'|'jsx'|'context'|'architecture'|'islands'|'ssr'|'ssg'|'bundling'|'testing'|'lifecycle'|'build'|'ci'|'best-practices'|'library-api'|'documentation'|'all'; name?: string; full?: boolean; page?: number }) → stringBrowse the anti-patterns catalog from .agents/rules/anti-patterns.md, token-frugal by default. No args → a COMPACT INDEX (one line per entry: title + [detector: <code>] tag + one-sentence hook), kept at least ~60% smaller than { full: true } by a density gate rather than a pinned size (the catalog has grown past 400 entries and past a single page — the index is PAGINATED at 240 entries/page, footer names the next). Drill in deliberately: { name } → the single matching entry's full body (cheapest); { category } → full bodies for one of the 15 real categories (reactivity, jsx, context, architecture, islands, ssr, ssg, bundling, testing, lifecycle, build, ci, best-practices, library-api, documentation); { full: true } → the entire catalog (tens of thousands of tokens, explicit opt-in). The index keeps per-category ## <Heading> markers so categories are still discoverable in one call; each [detector: <code>] tag pairs the entry with the live validate detector.
Example
get_anti_patterns()
// → compact index (paginated — "page 1 of 2" on this catalog's current size): titles + detector tags + one-line hooks
get_anti_patterns({ name: 'Destructuring props' }) // → that entry's full body
get_anti_patterns({ category: 'reactivity' }) // → full bodies, one category
get_anti_patterns({ category: 'islands' }) // → any of the 15 real categories works, not just the first 8
get_anti_patterns({ full: true }) // → entire catalog (tens of thousands of tokens)Common mistakes
Reaching for
{ full: true }to "see the anti-patterns" — that is the multi-tens-of-thousands-of-token dump. The no-arg index is the orient call; pull full bodies with{ name }once you know which entry mattersExpecting no-arg to return full bodies — it returns the index (behaviour changed in the token-slim PR). Full bodies need
{ name },{ category }, or{ full: true }
See also: validate · get_pattern
get_changelog constant
tool: get_changelog({ package?: string; limit?: number; includeDependencyUpdates?: boolean; since?: string }) → ChangelogEntry[]Recent release notes for any @pyreon/* package without scraping git log. Parses packages/**/CHANGELOG.md into version entries ({ version, changes[], dependencyUpdates[], empty }) and returns the N most recent substantive versions (default 5). Filters out ceremonial version bumps (pure dependency-update releases with no user-facing body) by default — opt back in with includeDependencyUpdates: true. since: "0.12.0" returns the delta from a known floor — useful when an agent knows the version it was trained against.
Example
get_changelog({ package: 'flow', limit: 5 })
get_changelog({ package: '@pyreon/router', since: '0.12.0' })Common mistakes
Forgetting that ceremonial version bumps are filtered by default — if you NEED the dep-only releases (e.g. tracking when a transitive Pyreon dep flipped), pass
includeDependencyUpdates: true. Otherwise the gap between "what changed" and "what shipped" can confuse a coverage analysis.Passing
since: "0.27"(without patch) — the parser does a semver-aware comparison and treats"0.27"as"0.27.0". Be explicit ("0.27.0") to avoid silent off-by-one.Omitting
packageand expecting a multi-package digest — the tool is per-package. For a cross-package release survey, call once per package or read the release notes on GitHub.Trusting changelog entries to spell out the migration — they describe WHAT changed, not always HOW to migrate. Pair with
get_pattern/get_apifor shape changes.
See also: get_api
audit_test_environment constant
tool: audit_test_environment({ minRisk?: 'high' | 'medium' | 'low'; limit?: number }) → AuditReportScan every *.test.{ts,tsx} under packages/ for the mock-vnode anti-pattern that caused PR #197's silent metadata drop. Files are classified HIGH / MEDIUM / LOW based on the balance of mock-vnode literals + helpers + helper-call sites vs real h() calls + @pyreon/core import. Three context-aware skips (helper-def vs binding discrimination, type-guard call-arg skip, template-string fixture mask) keep the false-positive rate low. Run before merging a new test file or after a framework change.
Example
audit_test_environment({ minRisk: 'medium', limit: 10 })
// → grouped report with HIGH / MEDIUM / LOW sectionsCommon mistakes
Treating a HIGH finding as "this test is broken" — HIGH means the test relies HEAVILY on mock vnodes. The test may still be correct given its scope (e.g. testing a helper that only operates on vnode shapes); review the file and pair with a real-
h()companion test if the contract assertion matters.Calling with
minRisk: "low"and getting overwhelmed — LOW includes any file that even mentions a mock vnode helper. Usemediumfor actionable signal,highfor "would have prevented PR #197"-tier risk.Running outside the monorepo root — the scanner walks
packages/fromprocess.cwd(). From a subpackage dir, you get a partial result.Expecting it to flag missing tests — it ONLY scans existing test files. Missing test coverage is a separate concern (coverage gate, not audit_test_environment).
See also: get_browser_smoke_status · audit_islands
audit_islands constant
tool: audit_islands({ json?: boolean }) → IslandAuditReportProject-wide cross-file islands audit (PR C of the islands DX roadmap). Walks packages/ + examples/ and runs five detectors that auto-registry can't reach (manual hydrateIslands({...}) for non-Vite consumers / library authors) AND PR G's per-file island-never-with-registry-entry detector misses (it only catches the same-file shape): duplicate-name, never-with-registry-entry, registry-mismatch, nested-island, dead-island. Each finding ships with file path + line/column + actionable fix suggestion. Companion to the pyreon doctor --check-islands CLI flag (same scanner, same five detectors). Run before merging an island PR; CI gate by piping --json and grepping findings.length > 0.
Example
audit_islands({})
// → markdown-grouped report with one section per finding code
audit_islands({ json: true })
// → machine-readable { root, findings: [...], summary: {...} }Common mistakes
Running outside a project that uses islands — the audit walks
packages/+examples/fromprocess.cwd(). A project with zeroisland()declarations returns an empty findings array (not an error).Treating
registry-mismatchas a hard error in auto-registry apps — it only fires for MANUALhydrateIslands({ ... })calls. Apps usinghydrateIslandsAuto()(Vite plugin default) won't see this finding even if they'd be vulnerable to the same drift in a manual setup.Expecting
dead-islandto catch every never-used island — the detector tracks static imports of the loader path. Dynamic-import chains routed through a registry indirection may not be statically traceable; verify by source-grepping the loader path before deleting.Confusing
nested-islandwith intentional island composition — the outer island'shydrateRootREPLACES the inner subtree before the inner can hydrate. If you genuinely need nested islands, flatten or use a different boundary primitive.
See also: audit_test_environment · get_anti_patterns
Package-level notes
Project-context caching: Each
createServer()instance maintains its own cached context (routes, components, islands). The cache auto-resets whenprocess.cwd()changes between tool invocations, so the same server can operate across multiple projects in one session.
Manifest-driven:
get_apireadsapi-reference.ts, which is generated from each package'smanifest.ts. The marker-pair protocol (<gen-docs:api-reference:start @pyreon/<name>>) lets some packages be generated and others stay hand-written during incremental migration.