Commit f01ce5f
feat(mcp): render Prefab UI for the find/view/decide/mutate cluster (#20)
* feat(mcp): add Prefab UI foundation (utils, templates, schemas)
Lay the scaffolding for MCP-Apps rendering without changing any tool
behavior yet. Ported from katana-openapi-client's mature Prefab UI
pipeline — specifically the contract established in katana commit
5b373fca (fix(mcp): Prefab UI was built but never rendered in Claude
Desktop).
Added:
- tools/tool_result_utils.py — make_tool_result(), UI_META,
format_md_table, iso_or_none, enum_to_str. Pass raw PrefabApp as
structured_content; FastMCP's ToolResult.__init__ converts via
_prefab_to_json on isinstance check. Do NOT call .to_json() manually
(that's the bug katana #350 fixed).
- templates/{orders_list,order_detail,viable_statuses,status_change_preview}.md —
markdown fallback for the 4 tools that will get UI in a follow-up commit.
- tests/tools/test_tool_result_utils.py — pins the contract: Pydantic
dump when ui=None, Prefab envelope (with "view" key) when ui is a
PrefabApp, and UI_META == {"ui": True}. Prevents silent regressions
the next time FastMCP or prefab-ui change shape.
Updated:
- tools/schemas.py — added typed response models (OrderSummary,
OrderDetail, OrderList, HistoryEntry, StatusEntry, ViableStatusesResponse,
StatusChangePreview, StatusChangeResult) so make_tool_result's `response`
argument has a well-typed home for every UI tool in the follow-up.
OrderSummary/StatusEntry are duplicated with orders.py/statuses.py
until that commit swaps the imports over.
No behavior change to registered tools — follow-up commit wires them up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(mcp): render Prefab UI for find/view/decide/mutate cluster
Four tools now emit a PrefabApp via ``make_tool_result`` and are tagged
with ``meta=UI_META`` so FastMCP's ``_maybe_apply_prefab_ui`` registers
``ui://prefab/…`` resources with MIME ``text/html;profile=mcp-app`` —
the contract Claude Desktop uses to render MCP-Apps UI. Non-Prefab
clients still see the markdown fallback rendered from the templates
added in the previous commit.
Added tools/prefab_ui.py with four builders:
- build_orders_table_ui: sortable DataTable, row click fires
CallTool("get_order") for drill-down
- build_order_detail_ui: Card + history timeline (ForEach) + footer
buttons that fire get_viable_statuses / add_order_comment
- build_viable_statuses_ui: color-coded status buttons; click sends a
SendMessage follow-up asking Claude to update_order_status
- build_status_change_preview_ui: current → new status chips, comment
with visibility badge, Confirm button that sends the confirm=true
follow-up
Plus helpers ``_color_to_variant`` (StatusPro's free-form Status.color
→ Prefab Badge variant; lossy mapping, falls back to ``outline``),
``_status_chip`` (shared status Badge renderer), ``_is_overdue``
(ISO-8601 due-date parser with UTC fallback).
Wired in orders.py and statuses.py:
- list_orders, get_order, update_order_status (preview branch),
get_viable_statuses — return ToolResult and include meta=UI_META
- lookup_order, add_order_comment, update_order_due_date,
bulk_update_order_status, list_statuses — unchanged behaviour;
deferred for a future pass since their payloads are simple enough
that a UI adds little over JSON
Cleanup bundled in:
- Inline ``from statuspro_public_api_client.api.orders import …``
imports (previously inside tool function bodies) lifted to module
top so the import graph is obvious and the cache-read tools don't
pay import cost per call
- OrderSummary, StatusEntry definitions moved into schemas.py (were
duplicated locally); typed StatusChangePreview / StatusChangeResult
replace the ad-hoc ``dict[str, Any]`` preview/result shapes for the
update_order_status flow
Verification — server boots cleanly and a probe via mcp.list_tools() +
mcp.list_resources() confirms:
- 4/9 tools carry meta expanded to the full AppConfig dict (not the
raw {"ui": True}), proving FastMCP's _maybe_apply_prefab_ui ran
- 4 ``ui://prefab/tool/<hash>/renderer.html`` resources are
registered with mime=``text/html;profile=mcp-app``
Plus test_prefab_ui.py with 6 smoke tests exercising every builder
against minimal sample dicts; each asserts ``app.to_json()`` returns a
dict containing the ``"view"`` envelope key.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mcp): status-change confirm branch gets its own template
Two self-review follow-ups before review:
1. update_order_status preview branch was fetching
``statuses.list()`` twice — once via the shared
``_status_color_catalog`` helper and again to resolve the new
status's display name. Combine into one pass that builds the
``{code: color}`` map and finds the new name at the same time.
2. Confirm branches were reusing the ``status_change_preview`` template
with placeholder dashes for the now-irrelevant preview fields — the
resulting markdown said "Preview: …" after the update had already
run, which is misleading. Add a dedicated
``status_change_result.md`` and route both the declined-by-user and
executed outcomes through it. The preview path is unaffected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(mcp): simplify pass on the Prefab UI wiring
Five fixes from the /simplify review pass:
1. Use ``iso_or_none`` (already in tool_result_utils) consistently in
``_to_summary``, ``_to_detail``, and ``_history_entry`` instead of
inline ``str()``/``isoformat()`` patterns. ``UNSET`` is falsy so
``iso_or_none(getattr(order, "due_date", None))`` short-circuits
correctly for unset attrs fields.
2. Run the two independent fetches in ``get_order`` concurrently via
``asyncio.gather`` — ``orders.get`` and ``_status_color_catalog``
have no dependency on each other, so this halves the latency on
uncached catalog calls.
3. ``list_orders`` now serializes ``OrderSummary`` to dict once and
reuses the result for both the Prefab DataTable and the markdown
table rows. Saves N ``model_dump`` calls per page.
4. ``StatusChangePreview`` gains a ``recipients_text()`` method so
both ``orders.py`` (for the markdown template var) and
``prefab_ui.build_status_change_preview_ui`` (for the UI metric)
share one implementation. Drops the duplicated three-line
``customer / additional contacts / nobody`` block from each site.
5. ``test_build_orders_table_ui`` and
``test_build_status_change_preview_ui`` now assert against the
serialized envelope for the action wiring that drives the loop —
``"get_order"`` for the row-click drill-down,
``"confirm=true"`` and the new status code for the Confirm button.
Pure shape checks were silently allowing those follow-up payloads
to regress.
Also tightened the ``_status_color_catalog`` docstring — the
``ResponseCachingMiddleware`` caches MCP tool calls, not the
underlying ``client.statuses.list()`` call this helper makes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 9aa5db8 commit f01ce5f
13 files changed
Lines changed: 1137 additions & 86 deletions
File tree
- statuspro_mcp_server
- src/statuspro_mcp
- templates
- tools
- tests/tools
Lines changed: 10 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
Lines changed: 5 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
Lines changed: 9 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
Lines changed: 5 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
Lines changed: 6 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
0 commit comments