Apps Phase 1: docs, examples, app-only tool filtering#3593
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 055825fbf6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| tools = await apply_session_transforms([tool]) | ||
| if tools and is_enabled(tools[0]): | ||
| if tools and is_enabled(tools[0]) and _is_model_visible(tools[0]): |
There was a problem hiding this comment.
Keep app-only tools reachable during middleware auth checks
Filtering get_tool() by _is_model_visible() breaks authenticated app UI calls before they ever reach the new get_app_tool() path. FastMCP.call_tool(..., app_name=...) still runs middleware first, and AuthMiddleware.on_call_tool() resolves the target through fastmcp.get_tool() (src/fastmcp/server/middleware/authorization.py:139-143), so any HTTP/SSE server with global auth middleware now rejects every @app.tool() invocation as “tool not found” even when the caller is authorized.
Useful? React with 👍 / 👎.
| all_tools = [t for t in await super().list_tools() if t.name == name] | ||
| all_tools = list(await apply_session_transforms(all_tools)) | ||
| enabled = [t for t in all_tools if is_enabled(t)] | ||
| enabled = [t for t in all_tools if is_enabled(t) and _is_model_visible(t)] |
There was a problem hiding this comment.
Preserve app-only tools for task result lookup
This same get_tool() filter also breaks task-enabled app tools. FastMCP.tool() still allows app= and task= together (src/fastmcp/server/server.py:1490-1491), and get_tasks() still registers those tools (src/fastmcp/server/server.py:463-480), but tasks/getResult reconstructs the finished component via server.get_tool() (src/fastmcp/server/tasks/requests.py:316-320). After this change, any app-only background tool resolves to None, so fetching the task result fails with “Component not found”.
Useful? React with 👍 / 👎.
- Charts import: clarify they must come from prefab_ui.components.charts - DataTable: searchable→search (the actual prop name), remove nonexistent table-level sortable prop - Select: remove nonexistent options prop, show SelectOption children - Tabs: default_value→value - Fix search=True in inventory, patterns, datatable examples
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f5634c9224
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| DataTableColumn(key="price", header="Price ($)", sortable=True), | ||
| DataTableColumn(key="last_updated", header="Updated", sortable=True), | ||
| ], | ||
| rows=list(_inventory), |
There was a problem hiding this comment.
Bind the main table to reactive inventory state
rows=list(_inventory) bakes a one-time snapshot into the rendered app instead of reading from state. After add_item, update_quantity, or delete_item mutates the inventory, the "All Items" tab keeps showing the original 10 rows until the user reopens the app, so the flagship CRUD example appears not to persist edits.
Useful? React with 👍 / 👎.
| on_success=[ | ||
| SetState("recent_additions", RESULT), | ||
| ShowToast("Item added!", variant="success"), |
There was a problem hiding this comment.
Refresh visible inventory state after add_item succeeds
This callback stores the updated inventory in recent_additions, but that state key is never rendered anywhere in this view. In the current example, submitting the add form only shows a toast; the new item does not appear in either the filter list or the main inventory view until the app is reloaded.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7affca97aa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| update_quantity, | ||
| arguments={"item_id": STATE.adjust_id, "delta": -1}, | ||
| on_success=[ | ||
| SetState("filtered_items", RESULT), |
There was a problem hiding this comment.
Preserve active category filters after quantity or delete actions
When a user has already applied a category filter in the Actions tab, this writes RESULT back into filtered_items, but update_quantity() and delete_item() return list(_inventory) rather than the currently filtered subset. After any stock adjustment or delete, the UI jumps back to showing all items even though selected_category is still set, so the example no longer reflects the chosen filter.
Useful? React with 👍 / 👎.
| count = sum(1 for it in _inventory if it["category"] == cat) | ||
| total_qty = sum( | ||
| it["quantity"] for it in _inventory if it["category"] == cat | ||
| ) |
There was a problem hiding this comment.
Drive inventory summary cards from reactive state
These totals are computed once from _inventory during render instead of from app state, so add/update/delete operations never refresh the three category summary cards until the app is reopened. That leaves the example showing contradictory counts immediately after the user edits stock.
Useful? React with 👍 / 👎.
strawgate
left a comment
There was a problem hiding this comment.
very cool
might be worth reviewing fstrings and docstrings across the example servers
| A dropdown for choosing from a list of options. Pass a flat list of strings, or structured `SelectOption` objects for custom labels. | ||
|
|
||
| ```python | ||
| from prefab_ui.components import Select |
There was a problem hiding this comment.
add SelectOption
We should really wire up pytest-examples...
| Renders vertical or horizontal bar charts. Each `ChartSeries` maps a key from your data to a colored bar group. Multiple series produce grouped (or stacked) bars. | ||
|
|
||
| ```python | ||
| from prefab_ui.components import BarChart, ChartSeries |
There was a problem hiding this comment.
Earlier we say charts must be imported from components.charts
| Displays proportional data as slices. Set `inner_radius` for a donut chart. Unlike bar/line charts, `PieChart` uses `data_key` for the numeric value and `name_key` for the label — it doesn't use `ChartSeries`. | ||
|
|
||
| ```python | ||
| from prefab_ui.components import PieChart |
There was a problem hiding this comment.
Earlier we say charts must be imported from components.charts
| Plots data points connected by lines. Shares the same API as `BarChart` — use `series`, `x_axis`, and optionally `curve` to control interpolation. | ||
|
|
||
| ```python | ||
| from prefab_ui.components import LineChart, ChartSeries |
There was a problem hiding this comment.
Earlier we say charts must be imported from components.charts
| ) | ||
|
|
||
| with Row(gap=2, css_class="mt-4"): | ||
| Badge(f"Region: {STATE.selected_region}") |
There was a problem hiding this comment.
is this the right syntax for pulling from state? wouldnt this evaluate at call time?
| mcp = FastMCP("Approvals Server", providers=[app]) | ||
|
|
||
| if __name__ == "__main__": | ||
| mcp.run(transport="http") |
There was a problem hiding this comment.
we say this script takes a --stdio arg in the docstring
| mcp = FastMCP("Contacts Server", providers=[app]) | ||
|
|
||
| if __name__ == "__main__": | ||
| mcp.run(transport="http") |
There was a problem hiding this comment.
we say passing --stdio makes it a stdio server in the docs
|
Re: the All other review comments addressed in baa49c9: chart imports fixed to |
Test Failure AnalysisSummary: A single test in Root Cause: This is a flaky test on Windows, not a regression introduced by this PR. The failure is a timing/concurrency issue with uvicorn's ASGI lifecycle on Windows — the runner logs show Suggested Solution: No code fix is needed — this is pre-existing Windows flakiness. The codebase already acknowledges this with the Detailed AnalysisFailing job: Log excerpt: The timeout hit a test that was using the Evidence this is flaky (not a regression):
Related Files
|
This is the foundation work for making FastMCP the definitive resource for MCP app development.
Documentation — Three new pages: a component cheat sheet for quick reference, an architecture deep-dive explaining how structured content flows from Python to pixels, and a "Give Your Tool a UI" section in the quickstart. The sidebar now flows: Overview → Prefab Apps → FastMCPApp → Components → Patterns → Development → Architecture → Custom HTML.
Examples — Three new complete application examples beyond the existing pattern demos:
All existing examples updated from
{{ }}template strings toRx()/set_initial_state()to match the docs. Fixedpatterns_server.pyfor prefab-ui 0.13.x (chart imports moved).App-only tool filtering — Tools with
visibility: ["app"]no longer appear in the model's tool list. They're still callable viaget_app_toolwhen the request carries_meta.fastmcp.app. This is the correct behavior: backend tools are for the UI, not the model.