MCP
Poetry's coding-agent surface is an MCP server: poetry-agent
(the poetry-agent gem)
gives any MCP-capable coding agent the live component contract, so the
agent queries what your app actually ships instead of guessing, and
verifies its own markup before you review it.
The server
poetry-agent is a standard stdio MCP server with no SDK
dependency. It is boot-free: it reads the registry committed in the
gems rather than booting Rails, so it starts instantly. And it is
read-only by design: every tool describes or verifies, none can edit
your app.
bundle exec poetry-agent # stdio MCP server; run it from your app directory
Run it from the app directory: the guided build's probe step reads
your config/poetry_components.yml, installed theme, and
JS setup from there, and degrades gracefully anywhere else. Per-editor
configuration (and the generator that writes it) lives on the
Editors page; this page covers what
the server does once connected.
The loop it closes
composeroutes the brief to a starting point.describe_componentordescribe_blockloads the one contract the work needs.- The agent writes ERB.
checkreturns a verdict, so the agent corrects itself before the code is rendered or reviewed.
The point is verification, not just reading: the same linter that runs
as bin/rails poetry:check in your test suite is one tool
call away inside the agent's own loop, so the agent's gate and your
CI gate agree.
The ten tools
| Tool | Returns | When an agent reaches for it |
|---|---|---|
compose | the starting point for a brief: a matching vetted block with full source, or the matching components | first call for every brief |
build_page | a five-step guided workflow, one step per call | a whole screen, page, or dashboard |
list_components | every component, its poetry_* helper, and whether it is interactive | browsing the catalog |
describe_component | one component's contract at brief, detailed, or full depth | before using a component |
check | a PASS or FAIL verdict with line-numbered findings | the last action after the final edit |
list_blocks | the vetted composed-screen catalog | browsing screen starting points |
describe_block | one block's contract and its full ERB source | starting a screen from a block |
list_recipes | the installable multi-file payloads (skill bundles, scaffold templates, screen slices) | work beyond a single component |
get_skill | an installed Poetry skill, served at runtime | hosts that cannot write skill files |
guidance | curated composition guidance by topic | deciding which component fits |
Over HTTP
The same server answers POST JSON-RPC at a same-origin path, which is
where in-page bridges look for a site's own MCP server - this site
serves it at /mcp. It is the stdio server's exact
surface (one assembly, two transports), read-only by construction,
and refuses foreign origins. Pair it with the
WebMCP runtime for the browser side.
# config/routes.rb - the same server over HTTP (POST JSON-RPC), read-only
mount Poetry::Agent::MCP::HTTP.new => "/mcp"
Routing a brief
compose is the first move for any brief. Give it the task
text verbatim; the routing is deterministic. A strong match returns a
vetted block's full source to adapt in place, which is the reliable
path for pages, with runners-up listed. Anything smaller routes to
the matching components. Either way the answer ends with the same
instruction: run check as the last action.
For a whole screen, build_page runs a guided workflow
instead of a single route. Each call returns one step and the exact
next call:
- probe: reads the host app's Poetry config, theme, CSS mode, and importmap-versus-bundler setup, and reports gaps.
- plan: matches the intent to a page architecture: section order, the states a real screen handles (loading, empty, error), the edge cases, and the components.
- direct: creative direction derived from the installed theme, since the theme's vocabulary is the direction.
- snippets: the block or components to start from, routed exactly as
compose. - verify: runs
check; the workflow is done only on a PASS, an executable verdict rather than a claim.
The entry routes on your verb: a review or audit request stays read-only (an audit never becomes an edit), a harden request likewise, and a plan or shape verb returns the architecture without probing. Out-of-order steps are answered, never refused, so calling one step on its own keeps working.
The check verdict
check lints ERB source against the contracts without
rendering it: unknown components, options, variants, and wiring; raw
colors; icon names validated by membership in the installed set; enum
values; slot and helper arity; required content blocks and required
slots; and any-of contracts such as a Button's visible content.
Findings carry line numbers and a did-you-mean suggestion where one
exists. Run it after the final edit; an edit made after the last
check is unverified.
Skills and guidance at runtime
get_skill serves the Poetry skills over the wire for
hosts where installed skill files are absent (hosted agents, or a
session that never ran the generator): poetry (component
usage, generated from the registry), poetry-design (page
composition), and poetry-component (authoring app-owned
components). It returns SKILL.md plus a file index first; pass
file: to fetch one reference. The text is identical to
the installed files, so when the skills are installed the agent reads
them from disk instead:
bin/rails g poetry:skill # installs the same skill files under .claude/skills/
guidance(topic: "deciding") returns the which-component
decision tree from the usage skill, reachable when only the MCP is
connected. The installable skills catalog, including web install,
lives on the Agent Skills page,
and the payloads list_recipes surfaces are documented on
the Recipes page.
One source, every surface
The committed registry is the single source: the MCP tools, the
poetry:check linter, the editor snippets, and the
machine-readable catalog are all projections of it, so none of them
can drift from what ships. In your app the mounted engine serves that
catalog directly:
/poetry/llms.txt # the index: every component, one line each, plus the blocks catalog
/poetry/llms-full.txt # the full contracts
An agent without MCP reads those files; an agent with MCP gets the
same contract tool-shaped, with progressive disclosure
(describe_component loads one component at the depth
asked for, not the whole catalog) plus the check verdict
no static file can give.