Compare commits
13 commits
main
...
refresh-la
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a8daf0970 |
||
|
|
f6ed5970b8 |
||
|
|
ad77e4986c |
||
|
|
f5a615b872 | ||
|
|
b0e4688b19 |
||
|
|
372ac05dcb | ||
|
|
f56b561fd4 |
||
|
|
da00eea3c4 | ||
|
|
3a9717e6be |
||
|
|
ec7a83f5f9 |
||
|
|
bcbe407c6c | ||
|
|
328afe0fdb |
||
|
|
243df39f04 |
|
|
@ -19,6 +19,13 @@ Be friendly and welcoming while maintaining high standards. Call out what works
|
|||
|
||||
Even perfect code for unwanted features should be rejected.
|
||||
|
||||
### Dependency version compatibility
|
||||
|
||||
When a PR adapts code to a new version of a dependency (e.g., removing a parameter that was dropped upstream, using a new API):
|
||||
- **The version pin in `pyproject.toml` must match.** If the change breaks compatibility with the previously-pinned minimum version, the minimum version must be bumped. Otherwise users on the old version get a regression.
|
||||
- **If backwards compatibility with the old version is desired**, the code must handle both versions (e.g., try/except, version check). Simply deleting the old API usage without bumping the pin is always wrong — it silently breaks users on the old version.
|
||||
- **Lock file (`uv.lock`) changes should be scoped to the PR's purpose.** A PR fixing a ty compatibility issue should not also include unrelated dependency version bumps (anthropic, google-auth, etc.) from running `uv sync --upgrade`. These create noise and make the diff harder to review.
|
||||
|
||||
### API design and naming
|
||||
|
||||
Identify confusing patterns or non-idiomatic code:
|
||||
|
|
|
|||
102
.claude/skills/review-pr/SKILL.md
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
---
|
||||
name: review-pr
|
||||
description: Monitor and respond to automated PR reviews (Codex bot). Use when pushing a PR, checking review status, or responding to bot feedback. Handles the full cycle of push -> wait for review -> evaluate comments -> fix -> re-push.
|
||||
---
|
||||
|
||||
# PR Review Workflow
|
||||
|
||||
This repo has `chatgpt-codex-connector[bot]` configured as an automated reviewer. After every push to a PR branch, Codex reviews the diff and either:
|
||||
- Reacts with a thumbs-up on its review body (no suggestions — PR is clean)
|
||||
- Posts inline comments with suggestions (each tagged with a priority badge)
|
||||
|
||||
## Checking review status
|
||||
|
||||
After pushing, check whether Codex has reviewed the latest commit:
|
||||
|
||||
```bash
|
||||
# Get the latest commit SHA on the branch
|
||||
LATEST=$(git rev-parse HEAD)
|
||||
|
||||
# Check if Codex has reviewed that specific commit
|
||||
gh api repos/PrefectHQ/fastmcp/pulls/{PR_NUMBER}/reviews \
|
||||
| jq "[.[] | select(.user.login == \"chatgpt-codex-connector[bot]\" and .commit_id == \"$LATEST\")] | length"
|
||||
```
|
||||
|
||||
If the count is 0, Codex hasn't reviewed the latest push yet. Wait and check again.
|
||||
|
||||
If the count is > 0, check for inline comments on the latest review:
|
||||
|
||||
```bash
|
||||
# Get the review body to check for thumbs-up
|
||||
gh api repos/PrefectHQ/fastmcp/pulls/{PR_NUMBER}/reviews \
|
||||
| jq '[.[] | select(.user.login == "chatgpt-codex-connector[bot]") | {state, body: .body[:300], commit_id: .commit_id}] | last'
|
||||
```
|
||||
|
||||
A clean review from Codex looks like a review body that contains a thumbs-up reaction or says "no suggestions." If the body contains "Here are some automated review suggestions," there are inline comments to evaluate.
|
||||
|
||||
## Evaluating Codex comments
|
||||
|
||||
Fetch all inline comments from Codex:
|
||||
|
||||
```bash
|
||||
gh api repos/PrefectHQ/fastmcp/pulls/{PR_NUMBER}/comments \
|
||||
| jq '[.[] | select(.user.login == "chatgpt-codex-connector[bot]") | {body, path, line, created_at}]'
|
||||
```
|
||||
|
||||
Codex comments include priority badges:
|
||||
- `P0` (red) — Critical issue, likely a real bug
|
||||
- `P1` (orange) — Important, worth fixing
|
||||
- `P2` (yellow) — Moderate, evaluate on merit
|
||||
|
||||
**How to evaluate Codex comments:**
|
||||
|
||||
1. **Treat Codex as a competent but sometimes overzealous reviewer.** It catches real bugs (cache eviction ordering, silent data loss, missing validation) but also suggests scope expansions and hypothetical improvements.
|
||||
|
||||
2. **Fix real bugs** — issues in code you actually changed where behavior is incorrect or data is silently lost.
|
||||
|
||||
3. **Dismiss scope expansion** — if a comment points out a pre-existing limitation unrelated to your diff, note it as a potential follow-up but don't block the PR.
|
||||
|
||||
4. **Dismiss speculative concerns** — if a comment describes a scenario that requires very specific conditions and the existing behavior is acceptable, dismiss it.
|
||||
|
||||
5. **When fixing, be proactive** — if Codex found one instance of a pattern bug (e.g., missing role validation in one handler), check all similar code paths before pushing. Codex will find the next instance on the next review cycle, so get ahead of it.
|
||||
|
||||
## Responding to every comment
|
||||
|
||||
**Every Codex comment must get a visible response** — either a fix or a reply explaining why it was dismissed. The maintainer can't see your reasoning otherwise.
|
||||
|
||||
- **If fixing**: The fix itself is the response. No reply needed unless the fix is non-obvious.
|
||||
- **If dismissing**: Reply to the comment thread with a brief explanation of why. Keep it to 1-2 sentences. Examples:
|
||||
- "This is pre-existing behavior unrelated to this diff — the scope lookup fallback existed before caching was added. Worth a follow-up issue but not blocking this PR."
|
||||
- "The AsyncExitStack handles cleanup when the session exits, so the subprocess isn't leaked — just kept alive slightly longer than necessary in this edge case."
|
||||
- "Gemini supports a much wider range of media types than OpenAI/Anthropic, so a restrictive allowlist would be inaccurate here."
|
||||
|
||||
Use `gh api` to reply (note: use `in_reply_to`, not a `/replies` sub-path):
|
||||
|
||||
```bash
|
||||
# Reply to a specific review comment
|
||||
gh api repos/PrefectHQ/fastmcp/pulls/{PR_NUMBER}/comments \
|
||||
-f body="Your reply here" \
|
||||
-F in_reply_to={COMMENT_ID}
|
||||
```
|
||||
|
||||
## The fix-push-review cycle
|
||||
|
||||
After evaluating comments:
|
||||
|
||||
1. Fix all real issues in one batch
|
||||
2. Reply to all dismissed comments with reasoning
|
||||
3. Think about what patterns Codex might flag next — check similar code paths proactively
|
||||
4. Commit and push
|
||||
5. Check that Codex reviews the new commit
|
||||
6. Repeat until Codex gives a clean review (thumbs-up) or only has dismissible comments
|
||||
|
||||
## Responding to stale comments
|
||||
|
||||
Codex sometimes re-posts old comments that reference code you've already fixed (they appear on the old commit's diff). These are stale — verify the fix is in the latest commit and reply noting the fix is already in place.
|
||||
|
||||
## When a PR is ready
|
||||
|
||||
A PR is ready for human review when:
|
||||
- All Codex comments are either fixed or replied to with dismissal reasoning
|
||||
- CI checks pass
|
||||
- The diff is clean and focused on the stated purpose
|
||||
29
.github/ISSUE_TEMPLATE/bug.yml
vendored
|
|
@ -3,33 +3,30 @@ description: Report a bug or unexpected behavior in FastMCP
|
|||
labels: [bug, pending]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thanks for contributing to FastMCP! 🙏
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for reporting a bug!
|
||||
|
||||
A good bug report is one of the most valuable contributions you can make — see [CONTRIBUTING.md](../../CONTRIBUTING.md). If the fix is straightforward, a PR is also welcome.
|
||||
|
||||
### Before you submit
|
||||
|
||||
To help us help you, please:
|
||||
|
||||
- 🔄 **Make sure you're testing on the latest version of FastMCP** - many issues are already fixed in newer versions
|
||||
- 🔍 **Check if someone else has already reported this issue** or if it's been fixed on the main branch
|
||||
- 📋 **You MUST include a copy/pasteable and properly formatted MRE** (minimal reproducible example) below or your issue may be closed without response
|
||||
- 💡 **The ideal issue is a clear problem description and an MRE — that's it.** If you've done a genuine investigation and have a non-obvious insight into the root cause, include it. But please don't speculate or ask an LLM to generate a diagnosis or proposed fix. We have LLMs too, and an incorrect analysis is harder to work with than none at all.
|
||||
- ✂️ **Keep it short.** A one-paragraph description and a working MRE is the ideal bug report. Issues that are difficult to parse — due to length, speculation, or generated content — may be closed without response.
|
||||
|
||||
Thanks for helping to make FastMCP better! 🚀
|
||||
- Make sure you're testing on the **latest version** of FastMCP — many issues are already fixed in newer releases
|
||||
- Check if someone else has **already reported this** or if it's been fixed on the main branch
|
||||
- You **must** include a copy/pasteable, properly formatted MRE (minimal reproducible example) or your issue may be closed without response
|
||||
- **The ideal issue is a clear problem description and an MRE — that's it.** If you've done genuine investigation and have a non-obvious insight into the root cause, include it. But please don't speculate or ask an LLM to generate a diagnosis. We have LLMs too, and an incorrect analysis is harder to work with than none at all.
|
||||
- **Keep it short.** A clear description plus a concise MRE is ideal — aim to fit in a single screen. Issues that include unsolicited root cause analysis, proposed fixes, or multi-section diagnostic writeups will be labeled `too-long` and not triaged until condensed.
|
||||
- **Using an LLM?** Great — but it must follow these guidelines. Generic LLM output that ignores our contributing conventions will be closed. See [CONTRIBUTING.md](../../CONTRIBUTING.md).
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
label: What happened?
|
||||
description: |
|
||||
Please explain what you're experiencing and what you would expect to happen instead.
|
||||
Describe the bug in a few sentences. What did you do, what happened, and what did you expect instead?
|
||||
|
||||
Provide as much detail as possible to help us understand and solve your problem quickly.
|
||||
Do NOT include root cause analysis, proposed fixes, or diagnostic writeups — just describe the problem.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
|
|
|||
26
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
|
|
@ -3,33 +3,27 @@ description: Suggest an idea or improvement for FastMCP
|
|||
labels: [enhancement, pending]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thanks for contributing to FastMCP! 🙏
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for suggesting an improvement to FastMCP!
|
||||
|
||||
Enhancement issues are the **primary way** features and improvements get into FastMCP. Maintainers use well-written issues to implement changes that fit the codebase's patterns and ship quickly. A clear issue here is more impactful than a PR — see [CONTRIBUTING.md](../../CONTRIBUTING.md) for why.
|
||||
|
||||
### Before you submit
|
||||
|
||||
To help us evaluate your enhancement request:
|
||||
|
||||
- 🔍 **Check if this has already been requested** - search existing issues first
|
||||
- 💭 **Think about the broader impact** - how would this affect other users?
|
||||
- 📋 **Consider implementation complexity** - is this a small change or a major feature?
|
||||
- ✂️ **Keep it short.** Describe the problem you're trying to solve and why existing behavior falls short. Skip proposed implementations unless you have a specific, well-considered suggestion — we don't need LLM-generated API designs. Requests that are difficult to parse may be closed without response.
|
||||
|
||||
Thanks for helping to make FastMCP better! 🚀
|
||||
- 🔍 **Check if this has already been requested** — search existing issues first
|
||||
- 🎯 **Describe the problem you're trying to solve**, not the solution you want — we'll figure out the best implementation
|
||||
- ✂️ **Keep it short.** A motivating description and a concrete use case is the ideal request — aim to fit in a single screen. Skip proposed implementations, API designs, or multi-option analyses — maintainers will figure out the approach. Requests that are difficult to parse will be labeled `too-long` and not triaged until condensed.
|
||||
- 🤖 **Using an LLM?** Great — but it must follow these guidelines. Generic LLM output that ignores our contributing conventions will be closed. See [CONTRIBUTING.md](../../CONTRIBUTING.md).
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Enhancement
|
||||
description: |
|
||||
Please describe the enhancement:
|
||||
What problem or use case does this solve? How does current behavior fall short?
|
||||
|
||||
- What problem or use case would it solve?
|
||||
- How would it improve your workflow or experience with FastMCP?
|
||||
- Are there any alternative solutions you've considered?
|
||||
Focus on the *what* and *why* — the motivating scenario. You don't need to propose an API or implementation.
|
||||
validations:
|
||||
required: true
|
||||
|
|
|
|||
10
.github/actions/run-pytest/action.yml
vendored
|
|
@ -3,7 +3,7 @@ description: "Run pytest with appropriate flags for the test type and platform"
|
|||
|
||||
inputs:
|
||||
test-type:
|
||||
description: "Type of tests to run: unit, integration, or client_process"
|
||||
description: "Type of tests to run: unit, integration, client_process, or conformance"
|
||||
required: false
|
||||
default: "unit"
|
||||
|
||||
|
|
@ -23,8 +23,13 @@ runs:
|
|||
TIMEOUT="5"
|
||||
MAX_PROCS="0"
|
||||
EXTRA_FLAGS="-x"
|
||||
elif [ "${{ inputs.test-type }}" == "conformance" ]; then
|
||||
MARKER="conformance"
|
||||
TIMEOUT="120"
|
||||
MAX_PROCS="0"
|
||||
EXTRA_FLAGS="-x"
|
||||
else
|
||||
MARKER="not integration and not client_process"
|
||||
MARKER="not integration and not client_process and not conformance"
|
||||
TIMEOUT="5"
|
||||
MAX_PROCS="4"
|
||||
EXTRA_FLAGS=""
|
||||
|
|
@ -38,6 +43,7 @@ runs:
|
|||
uv run --no-sync pytest \
|
||||
--inline-snapshot=disable \
|
||||
--timeout=$TIMEOUT \
|
||||
--durations=50 \
|
||||
-m "$MARKER" \
|
||||
$PARALLEL_FLAGS \
|
||||
$EXTRA_FLAGS \
|
||||
|
|
|
|||
34
.github/pull_request_template.md
vendored
|
|
@ -1,28 +1,22 @@
|
|||
## Description
|
||||
<!--
|
||||
Please provide a clear and concise description of the changes made in this pull request.
|
||||
|
||||
Using AI to generate code? Please include a note in the description with which AI tool you used.
|
||||
-->
|
||||
<!-- What does this PR do? Link to the issue it addresses. -->
|
||||
|
||||
**Contributors Checklist**
|
||||
<!--
|
||||
NOTE:
|
||||
1. You must create an issue in the repository before making a Pull Request.
|
||||
2. You must not create a Pull Request for an issue that is already assigned to someone else.
|
||||
Closes #
|
||||
|
||||
If you do not follow these steps, your Pull Request will be closed without review.
|
||||
-->
|
||||
## Contribution type
|
||||
|
||||
- [ ] My change closes #(issue number)
|
||||
- [ ] I have followed the repository's development workflow
|
||||
- [ ] I have tested my changes manually and by adding relevant tests
|
||||
- [ ] I have performed all required documentation updates
|
||||
<!-- Check the one that applies. If you're unsure whether your change is welcome, please open an issue first — see CONTRIBUTING.md. -->
|
||||
|
||||
**Review Checklist**
|
||||
<!-- Your Pull Request will not be reviewed if tests are failing, you have not self-reviewed your changes, or you have not checked all of the following: -->
|
||||
- [ ] Bug fix (simple, well-scoped fix for a clearly broken behavior)
|
||||
- [ ] Documentation improvement
|
||||
- [ ] Enhancement (maintainers typically implement enhancements — see [CONTRIBUTING.md](../CONTRIBUTING.md))
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] This PR addresses an existing issue (or fixes a self-evident bug)
|
||||
- [ ] I have read [CONTRIBUTING.md](../CONTRIBUTING.md)
|
||||
- [ ] I have added tests that cover my changes
|
||||
- [ ] I have run `uv run prek run --all-files` and all checks pass
|
||||
- [ ] I have self-reviewed my changes
|
||||
- [ ] My Pull Request is ready for review
|
||||
|
||||
---
|
||||
- [ ] If I used an LLM, it followed the repo's contributing conventions (not generic output)
|
||||
|
|
|
|||
26
.github/release.yml
vendored
|
|
@ -8,12 +8,25 @@ changelog:
|
|||
labels:
|
||||
- feature
|
||||
|
||||
- title: Enhancements 🔧
|
||||
- title: Breaking Changes ⚠️
|
||||
labels:
|
||||
- breaking change
|
||||
exclude:
|
||||
labels:
|
||||
- contrib
|
||||
- security
|
||||
|
||||
- title: Enhancements ✨
|
||||
labels:
|
||||
- enhancement
|
||||
exclude:
|
||||
labels:
|
||||
- breaking change
|
||||
- security
|
||||
|
||||
- title: Security 🔒
|
||||
labels:
|
||||
- security
|
||||
|
||||
- title: Fixes 🐞
|
||||
labels:
|
||||
|
|
@ -21,13 +34,7 @@ changelog:
|
|||
exclude:
|
||||
labels:
|
||||
- contrib
|
||||
|
||||
- title: Breaking Changes 🛫
|
||||
labels:
|
||||
- breaking change
|
||||
exclude:
|
||||
labels:
|
||||
- contrib
|
||||
- security
|
||||
|
||||
- title: Docs 📚
|
||||
labels:
|
||||
|
|
@ -41,6 +48,9 @@ changelog:
|
|||
- title: Dependencies 📦
|
||||
labels:
|
||||
- dependencies
|
||||
exclude:
|
||||
labels:
|
||||
- security
|
||||
|
||||
- title: Other Changes 🦾
|
||||
labels:
|
||||
|
|
|
|||
2
.github/workflows/auto-close-duplicates.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
|
||||
- name: Generate Marvin App token
|
||||
id: marvin-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ secrets.MARVIN_APP_ID }}
|
||||
private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }}
|
||||
|
|
|
|||
2
.github/workflows/auto-close-needs-mre.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
|
||||
- name: Generate Marvin App token
|
||||
id: marvin-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ secrets.MARVIN_APP_ID }}
|
||||
private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }}
|
||||
|
|
|
|||
2
.github/workflows/martian-test-failure.yml
vendored
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
|
||||
- name: Generate Marvin App token
|
||||
id: marvin-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ secrets.MARVIN_APP_ID }}
|
||||
private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }}
|
||||
|
|
|
|||
2
.github/workflows/martian-triage-issue.yml
vendored
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
|
||||
- name: Generate Marvin App token
|
||||
id: marvin-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ secrets.MARVIN_APP_ID }}
|
||||
private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }}
|
||||
|
|
|
|||
|
|
@ -36,14 +36,9 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: uv sync --python 3.12
|
||||
|
||||
- name: Run prek
|
||||
uses: j178/prek-action@v1
|
||||
env:
|
||||
SKIP: no-commit-to-branch
|
||||
|
||||
- name: Generate Marvin App token
|
||||
id: marvin-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ secrets.MARVIN_APP_ID }}
|
||||
private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }}
|
||||
|
|
|
|||
7
.github/workflows/marvin-comment-on-pr.yml
vendored
|
|
@ -38,14 +38,9 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: uv sync --python 3.12
|
||||
|
||||
- name: Run prek
|
||||
uses: j178/prek-action@v1
|
||||
env:
|
||||
SKIP: no-commit-to-branch
|
||||
|
||||
- name: Generate Marvin App token
|
||||
id: marvin-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ secrets.MARVIN_APP_ID }}
|
||||
private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }}
|
||||
|
|
|
|||
2
.github/workflows/marvin-dedupe-issues.yml
vendored
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
|
||||
- name: Generate Marvin App token
|
||||
id: marvin-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ secrets.MARVIN_APP_ID }}
|
||||
private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }}
|
||||
|
|
|
|||
17
.github/workflows/marvin-label-triage.yml
vendored
|
|
@ -36,7 +36,7 @@ jobs:
|
|||
|
||||
- name: Generate Marvin App token
|
||||
id: marvin-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ secrets.MARVIN_APP_ID }}
|
||||
private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }}
|
||||
|
|
@ -49,7 +49,7 @@ jobs:
|
|||
PROMPT<<PROMPT_END
|
||||
You're an issue triage assistant for FastMCP, a Python framework for building Model Context Protocol servers and clients. Your task is to analyze issues/PRs and apply appropriate labels.
|
||||
|
||||
IMPORTANT: Your ONLY action should be to apply labels using mcp__github__update_issue. DO NOT post any comments.
|
||||
IMPORTANT: Your primary action should be to apply labels using mcp__github__update_issue. DO NOT post comments EXCEPT when applying the too-long label (see below).
|
||||
|
||||
Issue/PR Information:
|
||||
- REPO: ${{ github.repository }}
|
||||
|
|
@ -68,7 +68,7 @@ jobs:
|
|||
|
||||
3. Analyze and apply labels based on these guidelines:
|
||||
|
||||
CORE CATEGORIES (apply EXACTLY ONE - these are mutually exclusive):
|
||||
CORE CATEGORIES (apply EXACTLY ONE - these are mutually exclusive; skip if applying too-long):
|
||||
- bug: Reports of broken functionality OR PRs that fix bugs
|
||||
- enhancement: New functions/endpoints, improvements to existing features, internal tooling, workflow improvements, minor new capabilities
|
||||
- feature: ONLY for major headline functionality worthy of a blog post announcement (2-4 per release, never for issues)
|
||||
|
|
@ -96,8 +96,12 @@ jobs:
|
|||
|
||||
STATUS (apply if applicable):
|
||||
- needs more info: Issue lacks reproduction steps, error messages, or clear description
|
||||
- good first issue: ONLY if it's clearly scoped, has obvious solution, and touches limited files
|
||||
- invalid: Spam, completely off-topic, or nonsensical (often LLM-generated)
|
||||
- too-long: Apply when an issue or PR doesn't conform to CONTRIBUTING.md. Issues should be a short problem description, an MRE, and expected vs. actual behavior — not a design document. PRs should have a focused description of the change — not a report. We don't need proposed solutions or design alternatives (the issue should describe the problem and let maintainers architect the fix), summaries of what tests cover, explanations of code we can read ourselves, or speculative root-cause analysis. Common LLM failure modes to watch for: verbose "diagnostic" writeups, large proposed patches in issue bodies, multi-section reports restating what's visible in the diff, numbered lists of possible approaches or solutions, "suggested" schemas/shapes/APIs, generic analysis that doesn't reference specific code, and "Notes" sections. But these are heuristics, not rules — a complex PR may legitimately need more context, and a brief submission can still be low-quality. Judge by whether the content helps a reviewer or just adds noise. When applying, do not apply other triage labels. The author needs to condense before triage is worthwhile.
|
||||
|
||||
WHEN APPLYING too-long: After labeling, post a brief comment using mcp__github__add_issue_comment:
|
||||
"Thanks for the report. This issue goes beyond what our contributor guidelines ask for — we just need a short problem description and an MRE. Please see our [contributing guidelines](https://github.com/PrefectHQ/fastmcp/blob/main/CONTRIBUTING.md) and condense this issue. We'll triage it once it's trimmed down."
|
||||
Use this exact text (or very close to it). Do not editorialize or add details.
|
||||
|
||||
AREA LABELS (apply ONLY when thematically central to the issue):
|
||||
- cli: Issues primarily about FastMCP CLI commands (run, dev, install)
|
||||
|
|
@ -108,6 +112,7 @@ jobs:
|
|||
- http: HTTP transport or networking is the main issue
|
||||
- contrib: Specifically about community contributions in src/contrib/
|
||||
- tests: Issues primarily about testing infrastructure, CI/CD workflows, or test coverage
|
||||
- security: Apply ONLY when the issue/PR addresses an exploitable vulnerability or hardens against one. Examples: SSRF, LFI, path traversal, injection, auth bypass allowing unauthorized access, scope escalation, open redirects. Do NOT apply for ordinary auth bugs (wrong scopes returned, token refresh logic, OAuth flow correctness) unless an attacker could exploit the bug to bypass access controls or escalate privileges. The key question: "Could a malicious actor exploit this?" If the answer is just "it breaks for legitimate users," that's a bug, not a security issue.
|
||||
|
||||
IMPORTANT LABELING RULES:
|
||||
- Be selective - only apply labels that are clearly relevant
|
||||
|
|
@ -122,7 +127,7 @@ jobs:
|
|||
|
||||
4. Apply selected labels:
|
||||
Use mcp__github__update_issue to apply your selected labels
|
||||
DO NOT post any comments
|
||||
DO NOT post any comments unless applying too-long (see above)
|
||||
PROMPT_END
|
||||
EOF
|
||||
|
||||
|
|
@ -139,7 +144,7 @@ jobs:
|
|||
allowed_non_write_users: "*"
|
||||
allowed_bots: "marvin-context-protocol"
|
||||
claude_args: |
|
||||
--allowedTools Bash(gh label list),mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__update_issue,mcp__github__get_pull_request_files
|
||||
--allowedTools Bash(gh label list),mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__update_issue,mcp__github__add_issue_comment,mcp__github__get_pull_request_files
|
||||
settings: |
|
||||
{
|
||||
"model": "claude-sonnet-4-6",
|
||||
|
|
|
|||
2
.github/workflows/run-static.yml
vendored
|
|
@ -35,6 +35,6 @@ jobs:
|
|||
resolution: locked
|
||||
|
||||
- name: Run prek
|
||||
uses: j178/prek-action@v1
|
||||
uses: j178/prek-action@v2
|
||||
env:
|
||||
SKIP: no-commit-to-branch
|
||||
|
|
|
|||
23
.github/workflows/run-tests.yml
vendored
|
|
@ -73,6 +73,29 @@ jobs:
|
|||
with:
|
||||
test-type: client_process
|
||||
|
||||
run_conformance_tests:
|
||||
name: "MCP conformance tests"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup uv
|
||||
uses: ./.github/actions/setup-uv
|
||||
with:
|
||||
resolution: locked
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Run conformance tests
|
||||
uses: ./.github/actions/run-pytest
|
||||
with:
|
||||
test-type: conformance
|
||||
|
||||
run_integration_tests:
|
||||
name: "Integration tests"
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
28
.github/workflows/run-upgrade-checks.yml
vendored
|
|
@ -38,7 +38,7 @@ jobs:
|
|||
resolution: upgrade
|
||||
|
||||
- name: Run prek
|
||||
uses: j178/prek-action@v1
|
||||
uses: j178/prek-action@v2
|
||||
env:
|
||||
SKIP: no-commit-to-branch
|
||||
|
||||
|
|
@ -105,7 +105,7 @@ jobs:
|
|||
uses: jayqi/failed-build-issue-action@v1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
label: "build-failure"
|
||||
label: "build failed"
|
||||
title-template: "Upgrade checks failing on main branch"
|
||||
body-template: |
|
||||
## Upgrade Checks Failure on Main Branch
|
||||
|
|
@ -131,3 +131,27 @@ jobs:
|
|||
|
||||
---
|
||||
*This issue was automatically created by a GitHub Action.*
|
||||
|
||||
close-on-success:
|
||||
name: Close issue on success
|
||||
needs: [static_analysis, run_tests, run_integration_tests]
|
||||
if: success() && github.event.pull_request == null && github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Close resolved failure issue
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
issue=$(gh issue list \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--label "build failed" \
|
||||
--state open \
|
||||
--json number \
|
||||
--jq '.[0].number // empty')
|
||||
|
||||
if [ -n "$issue" ]; then
|
||||
gh issue close "$issue" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--comment "Upgrade checks are passing again as of [\`${GITHUB_SHA::7}\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA})."
|
||||
fi
|
||||
|
|
|
|||
2
.github/workflows/update-config-schema.yml
vendored
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
steps:
|
||||
- name: Generate Marvin App token
|
||||
id: marvin-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ secrets.MARVIN_APP_ID }}
|
||||
private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }}
|
||||
|
|
|
|||
2
.github/workflows/update-sdk-docs.yml
vendored
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
steps:
|
||||
- name: Generate Marvin App token
|
||||
id: marvin-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ secrets.MARVIN_APP_ID }}
|
||||
private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }}
|
||||
|
|
|
|||
1
.gitignore
vendored
|
|
@ -65,6 +65,7 @@ dmypy.json
|
|||
|
||||
# Claude worktree management
|
||||
.claude-wt/worktrees
|
||||
.claude/worktrees/
|
||||
|
||||
# Agents
|
||||
/PLAN.md
|
||||
|
|
|
|||
34
CLAUDE.md
|
|
@ -52,6 +52,8 @@ When modifying MCP functionality, changes typically need to be applied across al
|
|||
|
||||
## Development Rules
|
||||
|
||||
**Read `CONTRIBUTING.md` before opening issues or PRs.** It describes when PRs are appropriate, what we expect from enhancement proposals, and what we'll close without review.
|
||||
|
||||
### Git & CI
|
||||
|
||||
- Prek hooks are required (run automatically on commits)
|
||||
|
|
@ -62,6 +64,28 @@ When modifying MCP functionality, changes typically need to be applied across al
|
|||
- **ALWAYS** run prek before PRs
|
||||
- **NEVER** create a release, comment on an issue, or open a PR unless specifically instructed to do so.
|
||||
|
||||
### Releases
|
||||
|
||||
Only cut releases when the maintainer explicitly asks. Tags follow `v<version>` (e.g., `v3.2.0`). Always pass `--generate-notes` so the auto-generated changelog appears at the bottom.
|
||||
|
||||
**The title pun is critical.** Titles follow `v<version>: <pun>` where the pun relates to the most important theme of the release. Propose multiple options and let the maintainer choose — never pick one yourself. Look at recent releases for tone (e.g., "Code to Joy" for the code mode release, "Three at Last" for 3.0).
|
||||
|
||||
Write the maintainer-approved handwritten notes to a temporary file, then create the release. `--generate-notes` appends the auto-generated changelog after the handwritten content.
|
||||
|
||||
```bash
|
||||
gh release create v3.2.0 --target main --title "v3.2.0: Theme Here" --generate-notes --notes-file /tmp/release-notes.md
|
||||
```
|
||||
|
||||
Most releases target `main`, but maintenance or backport releases may target a different branch (e.g., `release/2.x`). Confirm the target with the maintainer if there's any ambiguity.
|
||||
|
||||
The handwritten notes are prepended above the auto-generated changelog and are the part that matters. Do not include a title in the notes body — the release title (`v{version}: {pun}`) already serves as the heading. Work with the maintainer to draft the notes — propose a draft, get feedback, iterate. Do not publish without the maintainer's sign-off.
|
||||
|
||||
**Before drafting, always read recent existing releases** (`gh release list` then `gh release view <tag>`) to absorb the voice, structure, and level of detail. Each release builds on the tone of previous ones — don't guess at the style from these instructions alone.
|
||||
|
||||
**Point releases** (3.0, 3.1, 3.2) get narrative prose: open with the theme of the release, then walk through headline features conceptually — what they enable, why they matter, how they fit together. Write it the way a blog post reads, not a changelog. Multiple paragraphs, code examples where they clarify.
|
||||
|
||||
**Patch releases** (3.1.1, 3.0.2) get 1-2 sentences explaining what broke and what the fix does. Keep it minimal — the auto-generated changelog has the details.
|
||||
|
||||
### Commit Messages and Agent Attribution
|
||||
|
||||
- **Agents NOT acting on behalf of @jlowin MUST identify themselves** (e.g., "🤖 Generated with Claude Code" in commits/PRs)
|
||||
|
|
@ -79,6 +103,14 @@ When modifying MCP functionality, changes typically need to be applied across al
|
|||
- Minor fixes: keep body short and concise
|
||||
- No "test plan" sections or testing summaries
|
||||
|
||||
### Code Review Guidelines
|
||||
|
||||
- **Fix causes, not symptoms.** When a PR works around a problem instead of addressing why it occurs, that's a red flag. A side-channel that compensates for a missing step adds permanent complexity. If the fix doesn't change the code path where the bug actually happens, ask why not.
|
||||
- Focus on API design and naming clarity
|
||||
- Identify confusing patterns (e.g., parameter values that contradict defaults) or non-idiomatic code (mutable defaults, etc.). Contributed code will need to be maintained indefinitely, and by someone other than the author (unless the author is a maintainer).
|
||||
- Suggest specific improvements, not generic "add more tests" comments
|
||||
- Think about API ergonomics from a user perspective
|
||||
|
||||
### Code Standards
|
||||
|
||||
- Python ≥ 3.10 with full type annotations
|
||||
|
|
@ -102,6 +134,7 @@ When modifying MCP functionality, changes typically need to be applied across al
|
|||
- Do not manually modify `docs/python-sdk/**` — these files are auto-generated from source code by a bot and maintained via a long-lived PR. Do not include changes to these files in contributor PRs.
|
||||
- Do not manually modify `docs/public/schemas/**` or `src/fastmcp/utilities/mcp_server_config/v1/schema.json` — these are auto-generated and maintained via a long-lived PR.
|
||||
- **Core Principle:** A feature doesn't exist unless it is documented!
|
||||
- When adding or modifying settings in `src/fastmcp/settings.py`, update `docs/more/settings.mdx` to match.
|
||||
|
||||
### Documentation Guidelines
|
||||
|
||||
|
|
@ -110,6 +143,7 @@ When modifying MCP functionality, changes typically need to be applied across al
|
|||
- **Structure:** Headers form navigation guide, logical H2/H3 hierarchy
|
||||
- **Content:** User-focused sections, motivate features (why) before mechanics (how)
|
||||
- **Style:** Prose over code comments for important information
|
||||
- **Docstrings:** FastMCP docstrings are automatically compiled into MDX documents. Use markdown (single backticks, fenced code blocks), not RST (no double backticks). Bare `{}` in examples will be interpreted as JSX — wrap in backticks instead.
|
||||
|
||||
## Critical Patterns
|
||||
|
||||
|
|
|
|||
53
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# Contributing to FastMCP
|
||||
|
||||
FastMCP is an actively maintained, high-traffic project. We welcome contributions — but the most impactful way to contribute might not be what you expect.
|
||||
|
||||
## The best contribution is a great issue
|
||||
|
||||
FastMCP is an opinionated framework, and its maintainers use AI-assisted tooling that is deeply tuned to those opinions — the design philosophy, the API patterns, the way the framework is meant to evolve. A well-written issue with a clear problem description is often more valuable than a pull request, because it lets maintainers produce a solution that isn't just correct, but consistent with how the framework wants to work. That matters more than speed, though it's faster too.
|
||||
|
||||
**A great issue looks like this:**
|
||||
|
||||
1. A short, motivating description of the problem or gap
|
||||
2. A minimal reproducible example (for bugs) or a concrete use case (for enhancements)
|
||||
3. A brief note on expected vs. actual behavior
|
||||
|
||||
That's it. No need to diagnose root causes, propose API designs, or suggest implementations. If you've done genuine investigation and have a non-obvious insight, include it.
|
||||
|
||||
## Using AI to contribute
|
||||
|
||||
We encourage you to use LLMs to help identify bugs, write MREs, and prepare contributions. But if you do, your LLM must take into account the conventions and contributing guidelines of this repo — including how we want issues formatted and when it's appropriate to open a PR. Generic LLM output that ignores these guidelines tells us the contribution wasn't made thoughtfully, and we will close it. A good AI-assisted contribution is indistinguishable from a good human one. A bad one is obvious.
|
||||
|
||||
## When to open a pull request
|
||||
|
||||
An open issue is not an invitation to submit a PR. Issues track problems; whether and how to solve them is a separate decision. If you want to work on something, propose your approach in the issue first — especially for anything beyond a trivial fix.
|
||||
|
||||
**Bug fixes** — PRs are welcome for simple, well-scoped bug fixes where the problem and solution are both straightforward. "The function raises `TypeError` when passed `None` because of a missing guard" is a good candidate. If the fix requires design decisions or touches multiple subsystems, open an issue with a design proposal instead.
|
||||
|
||||
**Documentation** — Typo fixes, clarifications, and improvements to examples are always welcome as PRs.
|
||||
|
||||
**Enhancements and features** — We welcome enhancement PRs, but our experience is that most contributors — even when using LLMs — implement fixes that address the one instance of a problem they encountered rather than understanding why the framework produces that problem and fixing it at the right layer. This creates branching, patch-style code that's difficult to maintain and makes it impossible to reason about the framework as a coherent system. For this reason, enhancements need a design proposal in the issue before code is written. The proposal doesn't need to be long — just enough to show you've thought about how the change fits into the framework, not just how it solves your immediate case.
|
||||
|
||||
**Integrations** — FastMCP generally does not accept PRs that add third-party integrations (custom middleware, provider-specific adapters, etc.). If you're building something for your users, ship it as a standalone package — that's a feature, not a limitation. Authentication providers are an exception, since auth is tightly coupled to the framework.
|
||||
|
||||
## PR guidelines
|
||||
|
||||
If you do open a PR:
|
||||
|
||||
- **Reference an issue.** Every PR should address a tracked issue. If there isn't one, open an issue first. This isn't a permission step — you don't need to wait for a response. But the issue gives us context on the problem, and if a maintainer is already working on it, we can let you know before you invest time in code.
|
||||
- **Keep it focused.** One logical change per PR. Don't bundle unrelated fixes or refactors.
|
||||
- **Match existing patterns.** Follow the code style, type annotation conventions, and test patterns you see in the codebase. Run `uv run prek run --all-files` before submitting.
|
||||
- **Write tests.** Bug fixes should include a test that fails without the fix. Enhancements should include tests for the new behavior.
|
||||
- **Fix the cause, not the symptom.** If the bug is that a code path skips a step, the fix should make it stop skipping that step — not add compensation elsewhere. Workaround-style fixes will be sent back for revision.
|
||||
- **Don't submit generated boilerplate.** We review every line. PRs that read like unedited LLM output — verbose descriptions, speculative changes, shotgun-style fixes — will be closed.
|
||||
|
||||
## What we'll close without review
|
||||
|
||||
To keep the project maintainable, we will close PRs that:
|
||||
|
||||
- Don't reference an issue or address a clearly self-evident bug
|
||||
- Make sweeping changes without prior discussion
|
||||
- Add third-party integrations that belong in a separate package
|
||||
- Are difficult to review due to size, scope, or generated content
|
||||
|
||||
This isn't personal — contributing to a framework is different from contributing to an application. In an application, a fix that works is a good fix. In a framework, a fix that works but doesn't fit the framework's design creates maintenance burden that compounds over time. Every patch that works around a problem instead of solving it at the right layer makes the system harder for *everyone* to reason about — maintainers, contributors, and users. We hold contributions to this standard because the alternative is a codebase that's a series of patches rather than a coherent system. A good issue is often the best thing you can do for the project.
|
||||
|
|
@ -77,7 +77,13 @@ FastMCP has three pillars:
|
|||
|
||||
**[Servers](https://gofastmcp.com/servers/server)** wrap your Python functions into MCP-compliant tools, resources, and prompts. **[Clients](https://gofastmcp.com/clients/client)** connect to any server with full protocol support. And **[Apps](https://gofastmcp.com/apps/overview)** give your tools interactive UIs rendered directly in the conversation.
|
||||
|
||||
Ready to build? Start with the [installation guide](https://gofastmcp.com/getting-started/installation) or jump straight to the [quickstart](https://gofastmcp.com/getting-started/quickstart). When you're ready to deploy, [Prefect Horizon](https://www.prefect.io/horizon) offers free hosting for FastMCP users.
|
||||
Ready to build? Start with the [installation guide](https://gofastmcp.com/getting-started/installation) or jump straight to the [quickstart](https://gofastmcp.com/getting-started/quickstart).
|
||||
|
||||
## Run FastMCP in production with Horizon
|
||||
|
||||
FastMCP is how teams build MCP servers. **[Prefect Horizon](https://www.prefect.io/horizon)** is how enterprises run them in production. Register any MCP server behind a managed gateway with SSO, tool-level RBAC, audit logs, and observability. Deploy FastMCP servers and go from PR to preview in 60 seconds, then remix tools from across your registry into use-case-specific, permissioned endpoints. Horizon is everything we've learned about MCP at scale from building the world's most popular MCP framework. Free for individuals, built for teams.
|
||||
|
||||
[Deploy FastMCP with Horizon →](https://www.prefect.io/horizon)
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
|
|||
30
SECURITY.md
|
|
@ -2,15 +2,33 @@
|
|||
|
||||
## Supported Versions
|
||||
|
||||
FastMCP v2.x receives security updates. Earlier versions are no longer supported.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.x | :white_check_mark: |
|
||||
| < 2.0 | :x: |
|
||||
| 3.x | :white_check_mark: |
|
||||
| 2.x | :x: |
|
||||
| 1.x | :x: |
|
||||
| 0.x | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report security vulnerabilities privately using [GitHub's security advisory feature](https://github.com/PrefectHQ/fastmcp/security/advisories/new).
|
||||
Please report security vulnerabilities privately using [GitHub's security advisory feature](https://github.com/PrefectHQ/fastmcp/security/advisories/new). Do not open public issues for security concerns.
|
||||
|
||||
Do not open public issues for security concerns.
|
||||
## Scope
|
||||
|
||||
We accept reports for vulnerabilities in FastMCP itself — the library code in this repository.
|
||||
|
||||
The following are **out of scope**:
|
||||
|
||||
- Vulnerabilities in third-party dependencies or the MCP SDK itself. We'll bump version floors for known CVEs, but the fix belongs upstream.
|
||||
- Limitations of upstream identity providers that FastMCP cannot control.
|
||||
- Issues that require the attacker to already have server-side access or control of the MCP server configuration.
|
||||
|
||||
## Disclosure Process
|
||||
|
||||
When we receive a valid report:
|
||||
|
||||
1. We triage the report and determine whether it affects FastMCP directly.
|
||||
2. We develop and test a fix on a private branch.
|
||||
3. We coordinate CVE assignment through GitHub's advisory process when warranted.
|
||||
4. We publish the advisory and release a patched version.
|
||||
5. We credit the reporter in the advisory (unless they prefer otherwise).
|
||||
|
|
|
|||
119
docs/apps/architecture.mdx
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
---
|
||||
title: App Architecture
|
||||
sidebarTitle: Architecture
|
||||
description: How FastMCP apps work under the hood — from Python to pixels.
|
||||
icon: sitemap
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
This page explains how Prefab apps work under the hood — how your Python code becomes an interactive UI inside a host client's conversation. You don't need any of this to build apps, but the mental model is useful when something isn't rendering the way you expect, when tool calls from the UI aren't reaching your server, or when you're building [custom HTML apps](/apps/low-level) and need to understand the protocol directly.
|
||||
|
||||
## The Pipeline
|
||||
|
||||
An MCP App moves through five stages from Python to pixels:
|
||||
|
||||
```
|
||||
Python components → JSON tree → structuredContent → Renderer iframe → Host UI
|
||||
```
|
||||
|
||||
You write Prefab components in Python. FastMCP serializes them to a JSON component tree and delivers it as `structuredContent` on the tool result. The host loads the Prefab renderer in a sandboxed iframe, pushes the JSON into it, and the renderer paints the UI. If the UI needs to call server tools, it talks back through the same `postMessage` channel.
|
||||
|
||||
The following sections walk through each stage.
|
||||
|
||||
## Tool Registration
|
||||
|
||||
When you mark a tool with `app=True` or `@app.ui()`, FastMCP wires up the metadata and renderer resource that the protocol requires.
|
||||
|
||||
### The `app=True` Flag
|
||||
|
||||
The `app` parameter on `@mcp.tool` accepts `True`, an `AppConfig` object, or a dict. When you pass `True`, FastMCP checks whether the tool's return type is a Prefab type (`PrefabApp`, `Component`, or unions containing them). If the tool qualifies, FastMCP expands `True` into a full `AppConfig` — setting the renderer URI, CSP headers, and visibility — and stores it in the tool's `meta["ui"]` dict.
|
||||
|
||||
This expansion also triggers registration of the shared Prefab renderer resource (discussed below). The tool and the renderer are linked through a `resourceUri` field in the metadata: the tool says "render me with `ui://prefab/renderer.html`", and the host fetches that resource when it needs to display the result.
|
||||
|
||||
Type inference works the same way. If your return type annotation is a Prefab type and you haven't set `app` explicitly, FastMCP auto-wires the metadata as if you'd written `app=True`.
|
||||
|
||||
### FastMCPApp Registration
|
||||
|
||||
`FastMCPApp` uses the same underlying mechanism but adds two things. First, it tags every tool — both `@app.ui()` entry points and `@app.tool()` backends — with `meta["fastmcp"]["app"]` set to the app's name. This tag is how the server identifies which app a tool belongs to when routing calls from the UI.
|
||||
|
||||
Second, it sets `meta["ui"]["visibility"]` to control who can see each tool. Entry points default to `["model"]` (visible to the LLM). Backend tools default to `["app"]` (visible only to the UI). Hosts use this to filter the tool list — the model sees entry points, and the UI sees backends.
|
||||
|
||||
## Serialization
|
||||
|
||||
When a Prefab tool runs, its return value — a `PrefabApp` or a raw `Component` — needs to become a JSON blob that the renderer can interpret.
|
||||
|
||||
### PrefabApp.to_json()
|
||||
|
||||
The serialization entry point is `PrefabApp.to_json()`. This method walks the component tree and produces a JSON object with three top-level keys: `view` (the component tree), `state` (initial state values), and `_meta` (routing metadata).
|
||||
|
||||
FastMCP passes a `tool_resolver` callback to `to_json()`. Whenever the component tree contains a `CallTool` action that references a function (not a string), the resolver converts it to a `ResolvedTool` with the function's registered name. This is how `CallTool(save_contact)` becomes `CallTool("save_contact")` in the wire format. The resolver also handles `unwrap_result` — a flag that tells the renderer to unwrap single-value results from the `{"result": value}` envelope that FastMCP uses for schema compliance.
|
||||
|
||||
### The _meta.fastmcp.app Tag
|
||||
|
||||
After `to_json()` produces the JSON tree, FastMCP injects `_meta.fastmcp.app` with the app's name (if the tool belongs to a `FastMCPApp`). This tag rides along inside `structuredContent` all the way to the renderer.
|
||||
|
||||
When the renderer calls a backend tool, it includes `_meta.fastmcp.app` in the `CallTool` request. The server sees this tag and routes the call through a special path that bypasses transforms — more on this in the next section.
|
||||
|
||||
### ToolResult Assembly
|
||||
|
||||
The final tool result has two parts: `content` (a list of `TextContent` blocks for the LLM) and `structuredContent` (the JSON tree for the renderer). By default, Prefab tools send `"[Rendered Prefab UI]"` as the text content — just enough for the LLM to know something was rendered. If you return a `ToolResult` explicitly, you control both halves.
|
||||
|
||||
## Tool Call Routing
|
||||
|
||||
When a host calls a tool, the server needs to find it. Normal tool calls go through the provider chain, which applies transforms (namespace prefixes, visibility filters, etc.) before resolving the tool by name. But app UI calls need a different path.
|
||||
|
||||
### The get_app_tool Bypass
|
||||
|
||||
Backend tools registered with `@app.tool()` are typically hidden from the model (`visibility=["app"]`). Visibility transforms would filter them out of normal resolution. And namespace transforms might rename them — `save_contact` becomes `contacts_save_contact` — but the renderer still uses the original name.
|
||||
|
||||
`get_app_tool` solves both problems. When the server sees `_meta.fastmcp.app` on an incoming `CallTool` request, it calls `get_app_tool(app_name, tool_name)` instead of the normal `get_tool(name)`. This method walks the provider tree directly, skipping the transform chain entirely. It finds the tool by its original registered name and verifies that its `meta["fastmcp"]["app"]` matches the expected app identity.
|
||||
|
||||
This is why `CallTool("save_contact")` keeps working even when the server is mounted under a namespace prefix. The renderer sends the original name plus the app identity; the server uses `get_app_tool` to find the tool without transforms getting in the way.
|
||||
|
||||
Authorization checks still apply — `get_app_tool` bypasses transforms, but it runs auth checks against the tool's `auth` configuration before executing.
|
||||
|
||||
### Provider Delegation
|
||||
|
||||
The `get_app_tool` method is defined on the `Provider` base class and overridden by aggregate and wrapped providers. Aggregate providers fan out the lookup across all child providers in parallel. Wrapped providers (like `FastMCPProvider`, which wraps a nested `FastMCP` server) delegate to the inner server's `get_app_tool`. This means backend tools are reachable through any depth of server composition.
|
||||
|
||||
## The Renderer
|
||||
|
||||
The Prefab renderer is a self-contained JavaScript application that interprets the JSON component tree and renders it as a React UI.
|
||||
|
||||
### The Shared Resource
|
||||
|
||||
FastMCP registers the renderer as a `ui://prefab/renderer.html` resource with MIME type `text/html;profile=mcp-app`. The renderer HTML is bundled inside the `prefab-ui` Python package — `get_renderer_html()` returns it as a string. All Prefab tools on a server share this single resource, regardless of how many tools or apps are registered.
|
||||
|
||||
The resource also carries CSP metadata (via `get_renderer_csp()`) declaring which CDN domains the renderer needs to load its JavaScript dependencies. Hosts use this to configure the iframe's Content Security Policy.
|
||||
|
||||
### postMessage Communication
|
||||
|
||||
The renderer lives in a sandboxed iframe. It communicates with the host using `postMessage` — the standard browser API for cross-origin iframe communication. The protocol follows the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps) specification:
|
||||
|
||||
The host pushes the tool result (including `structuredContent`) into the iframe. The renderer parses the JSON component tree, initializes state, and renders the UI. When the user interacts with the UI — submitting a form, clicking a button — and that interaction triggers a `CallTool` action, the renderer sends a `callServerTool` message back to the host via `postMessage`. The host forwards this as a regular MCP `tools/call` request to the server, including `_meta.fastmcp.app` for routing.
|
||||
|
||||
The response flows back the same way: server to host, host to iframe via `postMessage`, renderer updates state with the result.
|
||||
|
||||
### AppBridge
|
||||
|
||||
The `@modelcontextprotocol/ext-apps` JavaScript SDK provides the `App` class (sometimes called AppBridge) that manages the `postMessage` handshake. It handles connection negotiation, tool result delivery, server tool calls, and host context (like safe area insets and theme preferences). The Prefab renderer uses this SDK internally — you only interact with it directly when building [custom HTML apps](/apps/low-level).
|
||||
|
||||
## The Dev Server
|
||||
|
||||
`fastmcp dev apps` provides a local preview environment that simulates the host-side behavior without requiring a real MCP host client.
|
||||
|
||||
### Proxy Architecture
|
||||
|
||||
The dev server runs two HTTP servers. Your MCP server starts on port 8000 (configurable) with the Streamable HTTP transport. The dev UI runs on port 8080 and serves a picker page that lists your app tools.
|
||||
|
||||
A reverse proxy at `/mcp` on the dev server forwards requests to your MCP server. This is important because the renderer iframe runs on `localhost:8080`, and your MCP server runs on `localhost:8000`. Without the proxy, the renderer's `callServerTool` requests would be cross-origin and blocked by the browser. The proxy makes everything same-origin from the iframe's perspective.
|
||||
|
||||
### The Launch Flow
|
||||
|
||||
When you select a tool and click launch, the dev UI calls the tool through the proxy, receives the `structuredContent` response, and opens a new tab. That tab loads the tool's renderer resource (fetched from the proxy) in an iframe, creates an AppBridge instance, and pushes the tool result into the renderer. From this point forward, the experience matches what a real host would provide — the renderer displays the UI, and any `CallTool` actions route back through the proxy to your MCP server.
|
||||
|
||||
Auto-reload is enabled by default, so changes to your server code restart the MCP server automatically. The dev UI stays running — just re-launch the tool to see your changes.
|
||||
66
docs/apps/development.mdx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
title: Development
|
||||
sidebarTitle: Development
|
||||
description: Preview and test your app tools locally without a full MCP host.
|
||||
icon: flask
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
<Frame>
|
||||
<img src="/apps/images/dev-app.png" alt="The dev UI showing a rendered Prefab app with the MCP inspector panel" />
|
||||
</Frame>
|
||||
|
||||
`fastmcp dev apps` launches a browser-based preview for your app tools. It starts your MCP server and a local dev UI side by side — you pick a tool, fill in its arguments, and see the rendered result in a new tab. No MCP host client needed.
|
||||
|
||||
This works with both [Prefab apps](/apps/prefab) and [custom HTML apps](/apps/low-level).
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
fastmcp dev apps server.py
|
||||
```
|
||||
|
||||
The dev UI opens at `http://localhost:8080`. Your MCP server runs on port 8000 with auto-reload enabled by default — save a file and the server restarts automatically.
|
||||
|
||||
## How It Works
|
||||
|
||||
The dev server does three things:
|
||||
|
||||
The **picker page** connects to your MCP server, finds all tools with UI metadata, and renders a form for each one. The forms are auto-generated from the tool's input schema — text fields, dropdowns, checkboxes, all wired up.
|
||||
|
||||
When you submit a form, the dev server **calls your tool** via the MCP protocol and opens the result in a new tab. The result page loads the tool's UI resource (the Prefab renderer or your custom HTML) inside an AppBridge — the same protocol that real MCP hosts use.
|
||||
|
||||
A **reverse proxy** on `/mcp` forwards requests from the browser to your MCP server, avoiding CORS issues that would otherwise block the iframe-based renderer from talking to a different port.
|
||||
|
||||
## MCP Inspector
|
||||
|
||||
The dev UI includes an inspector panel on the left side that captures MCP traffic in real time. It shows JSON-RPC messages flowing between the browser and your server — requests, responses, and AppBridge `postMessage` traffic.
|
||||
|
||||
Each entry shows direction, method, timing, and a smart summary. Click any entry to expand the full JSON-RPC body. The panel auto-scrolls to new messages unless you've scrolled up to inspect older ones.
|
||||
|
||||
The inspector is useful for debugging: you can see exactly what arguments your tool received, what it returned, and how the AppBridge communicated with the renderer.
|
||||
|
||||
## Options
|
||||
|
||||
```bash
|
||||
fastmcp dev apps server.py:mcp --mcp-port 9000 --dev-port 9090 --no-reload
|
||||
```
|
||||
|
||||
| Option | Flag | Default | Description |
|
||||
| ------ | ---- | ------- | ----------- |
|
||||
| MCP Port | `--mcp-port` | `8000` | Port for your MCP server |
|
||||
| Dev Port | `--dev-port` | `8080` | Port for the dev UI |
|
||||
| Auto-Reload | `--reload` / `--no-reload` | On | Watch files and restart the server on changes |
|
||||
|
||||
## Multiple Tools
|
||||
|
||||
If your server has multiple app tools, the picker shows a dropdown. Each tool gets its own form and launch button. The tool's `title` is displayed when available, falling back to the tool name.
|
||||
|
||||
```bash
|
||||
# Server with multiple app tools
|
||||
fastmcp dev apps examples/apps/contacts/contacts_server.py
|
||||
```
|
||||
140
docs/apps/examples.mdx
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
---
|
||||
title: Examples
|
||||
sidebarTitle: Examples
|
||||
description: Example apps you can run right now.
|
||||
icon: images
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
Every example below is a working FastMCP server you can run with `fastmcp dev apps` or connect to from any MCP host. The source is in `examples/apps/` in the repository.
|
||||
|
||||
<Columns cols={2}>
|
||||
<Tile href="#sales-dashboard" title="Sales Dashboard" description="Metrics, charts, and deal pipeline">
|
||||
<div style={{overflow: "hidden", width: "100%"}}>
|
||||
<img src="/apps/images/app-example-sales-dashboard.png" />
|
||||
</div>
|
||||
</Tile>
|
||||
<Tile href="#system-monitor" title="System Monitor" description="Live CPU, memory, disk with auto-refresh">
|
||||
<img src="/apps/images/app-example-system-dashboard.png" />
|
||||
</Tile>
|
||||
<Tile href="#quiz" title="Quiz" description="LLM-generated trivia with scoring">
|
||||
<img src="/apps/images/app-example-quiz.png" />
|
||||
</Tile>
|
||||
<Tile href="#interactive-map" title="Interactive Map" description="Geocoded addresses on Leaflet">
|
||||
<img src="/apps/images/app-example-map.png" />
|
||||
</Tile>
|
||||
<Tile href="/apps/providers/file-upload" title="File Upload" description="Drag-and-drop upload provider">
|
||||
<img src="/apps/images/app-file-upload.png" />
|
||||
</Tile>
|
||||
<Tile href="/apps/providers/approval" title="Approval" description="Human-in-the-loop confirmation">
|
||||
<img src="/apps/images/app-approval.png" />
|
||||
</Tile>
|
||||
<Tile href="/apps/providers/choice" title="Choice" description="Clickable option selection">
|
||||
<img src="/apps/images/app-choice.png" />
|
||||
</Tile>
|
||||
<Tile href="/apps/providers/form" title="Form Input" description="Pydantic model forms">
|
||||
<img src="/apps/images/app-form.png" />
|
||||
</Tile>
|
||||
<Tile href="/apps/generative" title="Generative UI" description="LLM writes the UI at runtime">
|
||||
<img src="/apps/images/app-showcase.png" />
|
||||
</Tile>
|
||||
</Columns>
|
||||
|
||||
## Running Examples
|
||||
|
||||
Preview any example in your browser with the dev server:
|
||||
|
||||
```bash
|
||||
pip install "fastmcp[apps]"
|
||||
fastmcp dev apps examples/apps/sales_dashboard/sales_dashboard_server.py
|
||||
```
|
||||
|
||||
The dev server opens an interactive browser UI where you can select a tool and provide arguments. In a real deployment, the LLM provides these arguments on the fly based on the conversation. For example, the quiz example works best when connected to an MCP host like Goose or Claude Desktop, where the LLM generates the questions itself.
|
||||
|
||||
## Standalone Examples
|
||||
|
||||
### Sales Dashboard
|
||||
|
||||
A full dashboard with KPI metrics, revenue trends, segment breakdown, and a deal pipeline table. Shows what you can build with a single `app=True` tool and Prefab's chart and data components.
|
||||
|
||||
```bash
|
||||
fastmcp dev apps examples/apps/sales_dashboard/sales_dashboard_server.py
|
||||
```
|
||||
|
||||
### System Monitor
|
||||
|
||||
Reads live CPU, memory, and disk stats from your machine using `psutil`. Auto-refreshes via `SetInterval` calling a backend tool, with a dropdown to control the refresh rate. The chart accumulates 100 data points over time.
|
||||
|
||||
```bash
|
||||
pip install psutil
|
||||
fastmcp dev apps examples/apps/system_monitor/system_monitor_server.py
|
||||
```
|
||||
|
||||
### Quiz
|
||||
|
||||
The LLM generates trivia questions and passes them to the tool. The user answers via buttons, sees correct/incorrect feedback, and tracks score across questions. Demonstrates multi-turn client-side state with FastMCPApp.
|
||||
|
||||
```bash
|
||||
fastmcp dev apps examples/apps/quiz/quiz_server.py
|
||||
```
|
||||
|
||||
### Interactive Map
|
||||
|
||||
Accepts addresses or place names, geocodes them via OpenStreetMap Nominatim (free, no API key), and renders an interactive Leaflet map using Prefab's `Embed` component with inline HTML. Proves that Prefab apps aren't limited to built-in components.
|
||||
|
||||
```bash
|
||||
fastmcp dev apps examples/apps/map/map_server.py
|
||||
```
|
||||
|
||||
## Built-in Providers
|
||||
|
||||
These are ready-made capabilities you add with a single `add_provider()` call.
|
||||
|
||||
### [File Upload](/apps/providers/file-upload)
|
||||
|
||||
Drag-and-drop file upload. The user drops files, clicks Upload, and the server stores them. The LLM can list and read uploaded files through model-visible tools.
|
||||
|
||||
```python
|
||||
from fastmcp.apps.file_upload import FileUpload
|
||||
mcp.add_provider(FileUpload())
|
||||
```
|
||||
|
||||
### [Approval](/apps/providers/approval)
|
||||
|
||||
Human-in-the-loop confirmation. The LLM presents what it's about to do, the user clicks Approve or Reject, and the decision flows back as a message.
|
||||
|
||||
```python
|
||||
from fastmcp.apps.approval import Approval
|
||||
mcp.add_provider(Approval())
|
||||
```
|
||||
|
||||
### [Choice](/apps/providers/choice)
|
||||
|
||||
Present clickable options instead of asking users to type. Clean structured input without parsing free text.
|
||||
|
||||
```python
|
||||
from fastmcp.apps.choice import Choice
|
||||
mcp.add_provider(Choice())
|
||||
```
|
||||
|
||||
### [Form Input](/apps/providers/form)
|
||||
|
||||
Generate a validated form from a Pydantic model. Submission is validated against the model before being returned.
|
||||
|
||||
```python
|
||||
from fastmcp.apps.form import FormInput
|
||||
mcp.add_provider(FormInput(model=MyModel))
|
||||
```
|
||||
|
||||
### [Generative UI](/apps/providers/generative)
|
||||
|
||||
The LLM writes Prefab Python code at runtime and the result renders as a streaming interactive UI. Tailored visualizations for any data. See the [full guide](/apps/generative) for details.
|
||||
|
||||
```python
|
||||
from fastmcp.apps.generative import GenerativeUI
|
||||
mcp.add_provider(GenerativeUI())
|
||||
```
|
||||
133
docs/apps/generative.mdx
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
---
|
||||
title: Generative UI
|
||||
sidebarTitle: Generative UI
|
||||
description: Let the LLM build custom Prefab UIs on the fly.
|
||||
icon: wand-magic-sparkles
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
Generative UI means the LLM writes the UI code at runtime. Instead of calling a pre-built tool with a fixed interface, the model writes Prefab Python code tailored to the current data and request. The user watches the UI build up in real time as the model generates code.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.generative import GenerativeUI
|
||||
|
||||
mcp = FastMCP("Prefab Studio")
|
||||
mcp.add_provider(GenerativeUI())
|
||||
```
|
||||
|
||||
That's it. The `GenerativeUI` provider registers everything:
|
||||
|
||||
- **`generate_prefab_ui`** — a tool that accepts Python code, executes it in a Pyodide sandbox, and renders the result as a Prefab app
|
||||
- **`search_prefab_components`** — a tool that lets the LLM search the Prefab component library to discover what's available
|
||||
- **The generative renderer** — a `ui://` resource with browser-side Pyodide for streaming progressive rendering
|
||||
|
||||
## How It Works
|
||||
|
||||
When the LLM decides to call `generate_prefab_ui`, it writes Prefab Python code into the `code` argument. The MCP Apps protocol creates the renderer iframe in parallel with the tool call, so the app is already running when partial arguments start flowing.
|
||||
|
||||
As the LLM generates each token:
|
||||
|
||||
1. The host forwards partial arguments to the app via `ontoolinputpartial`
|
||||
2. The renderer extracts the growing `code` string
|
||||
3. Browser-side Pyodide executes whatever compiles successfully
|
||||
4. The user sees components appear as they're written
|
||||
|
||||
When the LLM finishes, the server runs the complete code in a server-side Pyodide sandbox for validation, and the renderer replaces the streaming preview with the final server-validated result.
|
||||
|
||||
## What the LLM Writes
|
||||
|
||||
The tool description includes code examples that teach the LLM the Prefab patterns. A typical generation looks like:
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Column, Row, Heading, Text, Badge, Card, CardContent
|
||||
from prefab_ui.components.charts import BarChart, ChartSeries
|
||||
from prefab_ui.app import PrefabApp
|
||||
|
||||
with PrefabApp() as app:
|
||||
with Column(gap=6, css_class="p-6"):
|
||||
Heading("Q3 Revenue Report")
|
||||
|
||||
BarChart(
|
||||
data=[
|
||||
{"month": "Jul", "revenue": 42000},
|
||||
{"month": "Aug", "revenue": 51000},
|
||||
{"month": "Sep", "revenue": 63000},
|
||||
],
|
||||
series=[ChartSeries(data_key="revenue", label="Revenue")],
|
||||
x_axis="month",
|
||||
)
|
||||
|
||||
with Row(gap=4):
|
||||
with Card():
|
||||
with CardContent():
|
||||
Text("Total", css_class="text-sm text-muted-foreground")
|
||||
Heading("$156,000")
|
||||
with Card():
|
||||
with CardContent():
|
||||
Text("Growth", css_class="text-sm text-muted-foreground")
|
||||
Badge("+18%", variant="success")
|
||||
```
|
||||
|
||||
The model writes real Python — loops, f-strings, computation, helper functions. Prefab's component library gives it charts, tables, forms, cards, badges, and layout primitives to work with.
|
||||
|
||||
## The Component Search Tool
|
||||
|
||||
Before writing code, the LLM can call `search_prefab_components` to discover what's available:
|
||||
|
||||
```
|
||||
search_prefab_components("Chart")
|
||||
→ 7 components matching 'Chart':
|
||||
AreaChart — from prefab_ui.components.charts import AreaChart
|
||||
BarChart — from prefab_ui.components.charts import BarChart
|
||||
...
|
||||
```
|
||||
|
||||
Passing `detail=True` returns full field descriptions and docstrings. The search tool introspects the actual Prefab classes at runtime, so it's always up to date with the installed version.
|
||||
|
||||
## Passing Data
|
||||
|
||||
The `generate_prefab_ui` tool accepts a `data` parameter. Values passed here become global variables in the sandbox:
|
||||
|
||||
```python
|
||||
# The LLM can reference 'sales_data' directly in its code
|
||||
result = await generate_prefab_ui(
|
||||
code="...",
|
||||
data={"sales_data": [{"month": "Jan", "revenue": 42000}, ...]}
|
||||
)
|
||||
```
|
||||
|
||||
This lets the model use real data from earlier in the conversation to build visualizations.
|
||||
|
||||
## Configuration
|
||||
|
||||
`GenerativeUI` accepts options for customizing tool names:
|
||||
|
||||
```python
|
||||
GenerativeUI(
|
||||
tool_name="generate_prefab_ui", # default
|
||||
components_tool_name="search_prefab_components", # default
|
||||
include_components_tool=True, # default
|
||||
)
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
Generative UI requires `fastmcp[apps]` which installs `prefab-ui`. The Pyodide sandbox (for server-side validation) requires Deno — it installs automatically on first use.
|
||||
|
||||
The streaming renderer loads Pyodide from CDN in the browser. The CSP is configured automatically by the provider — no manual setup needed.
|
||||
|
||||
## Sandbox Limitations
|
||||
|
||||
The Pyodide sandbox includes the Python standard library and Prefab. External packages (NumPy, pandas, requests, etc.) are **not available** — the LLM's code must work with only built-in Python and Prefab components. If the LLM tries to import an unavailable package, the sandbox will raise an `ImportError`.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[GenerativeUI Provider Reference](/apps/providers/generative)** — Configuration options and quick setup
|
||||
- **[Prefab UI](/apps/prefab)** — The component library and state system the LLM writes code against
|
||||
- **[Prefab Component Reference](https://prefab.prefect.io/docs/components)** — Full component library documentation
|
||||
- **[Development](/apps/development)** — Preview generative UI tools locally with `fastmcp dev apps`
|
||||
BIN
docs/apps/images/app-approval.png
Normal file
|
After Width: | Height: | Size: 571 KiB |
BIN
docs/apps/images/app-chart.png
Normal file
|
After Width: | Height: | Size: 6 KiB |
BIN
docs/apps/images/app-choice.png
Normal file
|
After Width: | Height: | Size: 536 KiB |
BIN
docs/apps/images/app-contacts.png
Normal file
|
After Width: | Height: | Size: 587 KiB |
0
docs/apps/images/app-datatable.png
Normal file
BIN
docs/apps/images/app-example-map.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
docs/apps/images/app-example-quiz.png
Normal file
|
After Width: | Height: | Size: 586 KiB |
BIN
docs/apps/images/app-example-sales-dashboard.png
Normal file
|
After Width: | Height: | Size: 683 KiB |
BIN
docs/apps/images/app-example-system-dashboard.png
Normal file
|
After Width: | Height: | Size: 745 KiB |
BIN
docs/apps/images/app-file-upload.png
Normal file
|
After Width: | Height: | Size: 555 KiB |
BIN
docs/apps/images/app-form.png
Normal file
|
After Width: | Height: | Size: 580 KiB |
BIN
docs/apps/images/app-greet.png
Normal file
|
After Width: | Height: | Size: 267 KiB |
BIN
docs/apps/images/app-overview.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
docs/apps/images/app-quickstart-dev-2.png
Normal file
|
After Width: | Height: | Size: 586 KiB |
BIN
docs/apps/images/app-quickstart-dev.png
Normal file
|
After Width: | Height: | Size: 652 KiB |
BIN
docs/apps/images/app-quickstart.png
Normal file
|
After Width: | Height: | Size: 639 KiB |
BIN
docs/apps/images/app-showcase.png
Normal file
|
After Width: | Height: | Size: 1,001 KiB |
BIN
docs/apps/images/dev-app.png
Normal file
|
After Width: | Height: | Size: 322 KiB |
538
docs/apps/interactive-apps.mdx
Normal file
|
|
@ -0,0 +1,538 @@
|
|||
---
|
||||
title: FastMCPApp
|
||||
sidebarTitle: FastMCPApp
|
||||
description: Managed tool binding, visibility, and composition for apps with heavy server interaction.
|
||||
icon: puzzle-piece
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
<Tip>
|
||||
[Prefab](https://prefab.prefect.io) is in early, active development — its API changes frequently and breaking changes can occur with any release. Always pin `prefab-ui` to a specific version in your dependencies.
|
||||
</Tip>
|
||||
|
||||
Any [Prefab app](/apps/prefab) can call server tools — there's nothing stopping you from using `CallTool("tool_name")` in a regular `@mcp.tool(app=True)`. But once you have multiple backend tools, the management overhead adds up: Which tools should the model see vs. only the UI? What happens to string-based tool references when servers are composed under namespaces? How do you keep things wired correctly as the app grows?
|
||||
|
||||
`FastMCPApp` is a class that solves these problems. It gives you two decorators that work together:
|
||||
|
||||
- **`@app.ui()`** — entry-point tools the model calls to open the app. These return a Prefab UI.
|
||||
- **`@app.tool()`** — backend tools the UI calls via `CallTool`. These do the work.
|
||||
|
||||
Backend tools get globally stable identifiers that survive namespacing. Visibility is managed automatically — the model sees entry points, the UI sees backends. And `CallTool` accepts function references instead of strings, so references are refactorable and composition-safe.
|
||||
|
||||
## Your First Interactive App
|
||||
|
||||
Here's a minimal app with a form that saves data:
|
||||
|
||||
```python
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Badge, Button, Column, ForEach, Form,
|
||||
Heading, Input, Row, Separator, Text,
|
||||
)
|
||||
from prefab_ui.rx import RESULT
|
||||
from fastmcp import FastMCP, FastMCPApp
|
||||
|
||||
app = FastMCPApp("Notes")
|
||||
|
||||
notes_db: list[dict] = []
|
||||
|
||||
|
||||
@app.tool()
|
||||
def add_note(title: str, body: str) -> list[dict]:
|
||||
"""Save a note and return all notes."""
|
||||
notes_db.append({"title": title, "body": body})
|
||||
return list(notes_db)
|
||||
|
||||
|
||||
@app.ui()
|
||||
def notes_app() -> PrefabApp:
|
||||
"""Open the notes app."""
|
||||
with Column(gap=6, css_class="p-6") as view:
|
||||
Heading("Notes")
|
||||
|
||||
with ForEach("notes") as note:
|
||||
with Row(gap=2, align="center"):
|
||||
Text(note.title, css_class="font-semibold")
|
||||
Badge(note.body)
|
||||
|
||||
Separator()
|
||||
|
||||
with Form(
|
||||
on_submit=CallTool(
|
||||
"add_note",
|
||||
on_success=[
|
||||
SetState("notes", RESULT),
|
||||
ShowToast("Note saved!", variant="success"),
|
||||
],
|
||||
on_error=ShowToast("Failed to save", variant="error"),
|
||||
)
|
||||
):
|
||||
Input(name="title", label="Title", required=True)
|
||||
Input(name="body", label="Body", required=True)
|
||||
Button("Add Note")
|
||||
|
||||
return PrefabApp(view=view, state={"notes": list(notes_db)})
|
||||
|
||||
|
||||
mcp = FastMCP("Notes Server", providers=[app])
|
||||
```
|
||||
|
||||
When the model calls `notes_app`, the user sees a form. Submitting it calls `add_note` on the server, updates the state with the result, and shows a toast — all without leaving the UI.
|
||||
|
||||
Let's break down the key concepts.
|
||||
|
||||
## Entry Points: @app.ui()
|
||||
|
||||
Entry points are what the model sees and calls to open your app. They return a Prefab UI, just like display tools:
|
||||
|
||||
```python
|
||||
@app.ui()
|
||||
def dashboard() -> PrefabApp:
|
||||
"""The model calls this to open the dashboard."""
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Dashboard")
|
||||
# ... build UI ...
|
||||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
Entry points default to `visibility=["model"]` — they show up in the tool list for the LLM but aren't callable from within the app UI. They support the same options as `@mcp.tool`: `name`, `description`, `title`, `tags`, `icons`, `auth`, and `timeout`.
|
||||
|
||||
```python
|
||||
@app.ui(title="Contact Manager", description="Open the contact management interface")
|
||||
def contact_manager() -> PrefabApp:
|
||||
...
|
||||
```
|
||||
|
||||
## Backend Tools: @app.tool()
|
||||
|
||||
Backend tools do the work. The UI calls them via `CallTool`; they run on the server and return data:
|
||||
|
||||
```python
|
||||
@app.tool()
|
||||
def save_contact(name: str, email: str) -> list[dict]:
|
||||
"""Save a contact and return the updated list."""
|
||||
db.append({"name": name, "email": email})
|
||||
return list(db)
|
||||
```
|
||||
|
||||
By default, backend tools are only visible to the app UI (`visibility=["app"]`). The model doesn't see them in the tool list. If you want a tool callable by both the model and the UI, pass `model=True`:
|
||||
|
||||
```python
|
||||
@app.tool(model=True)
|
||||
def list_contacts() -> list[dict]:
|
||||
"""Both the model and the UI can call this."""
|
||||
return list(db)
|
||||
```
|
||||
|
||||
Backend tools support `name`, `description`, `auth`, and `timeout`:
|
||||
|
||||
```python
|
||||
@app.tool(description="Search contacts by name or email", timeout=10.0)
|
||||
def search(query: str) -> list[dict]:
|
||||
...
|
||||
```
|
||||
|
||||
## Connecting UI to Backend: CallTool
|
||||
|
||||
`CallTool` is the bridge between the UI and the server. Pass the name of a backend tool registered with `@app.tool()`:
|
||||
|
||||
```python
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
|
||||
# Reference a backend tool by name
|
||||
CallTool("save_contact", arguments={"name": "Alice", "email": "alice@example.com"})
|
||||
|
||||
# Arguments can reference state with Rx
|
||||
from prefab_ui.rx import STATE
|
||||
|
||||
CallTool("search", arguments={"query": STATE.search_term})
|
||||
```
|
||||
|
||||
FastMCPApp resolves the name to the tool's stable global key automatically, so `CallTool("save_contact")` keeps working even when the server is mounted under a namespace.
|
||||
|
||||
You can also pass the function directly — `CallTool(save_contact)` — which can be convenient when the tool is defined in the same file. Both forms resolve identically.
|
||||
|
||||
### Handling Results
|
||||
|
||||
Server calls are asynchronous. Use `on_success` and `on_error` callbacks to handle outcomes:
|
||||
|
||||
```python
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
from prefab_ui.rx import RESULT
|
||||
|
||||
CallTool(
|
||||
"save_contact",
|
||||
on_success=[
|
||||
SetState("contacts", RESULT),
|
||||
ShowToast("Saved!", variant="success"),
|
||||
],
|
||||
on_error=ShowToast("Something went wrong", variant="error"),
|
||||
)
|
||||
```
|
||||
|
||||
`RESULT` is a reactive reference to the value the tool returned — available inside `on_success` callbacks. Similarly, `ERROR` (from `prefab_ui.rx`) is available inside `on_error`.
|
||||
|
||||
Callbacks can be a single action or a list of actions. They execute in order, and an error in any action short-circuits the rest.
|
||||
|
||||
### result_key Shorthand
|
||||
|
||||
When a tool returns data that should replace a state key, `result_key` is a convenient shorthand for `on_success=SetState(key, RESULT)`:
|
||||
|
||||
```python
|
||||
CallTool("list_contacts", result_key="contacts")
|
||||
|
||||
# equivalent to:
|
||||
CallTool(
|
||||
"list_contacts",
|
||||
on_success=SetState("contacts", RESULT),
|
||||
)
|
||||
```
|
||||
|
||||
## Actions
|
||||
|
||||
`CallTool` is one of several actions available in Prefab. Actions are events attached to component handlers like `on_click`, `on_submit`, and `on_change`.
|
||||
|
||||
### Client Actions
|
||||
|
||||
These run instantly in the browser — no server round-trip:
|
||||
|
||||
```python
|
||||
from prefab_ui.actions import SetState, ToggleState, AppendState, PopState, ShowToast
|
||||
|
||||
# Set a value
|
||||
SetState("count", 42)
|
||||
|
||||
# Toggle a boolean
|
||||
ToggleState("expanded")
|
||||
|
||||
# Append to a list
|
||||
AppendState("items", {"name": "New Item"})
|
||||
|
||||
# Remove by index
|
||||
PopState("items", 0)
|
||||
|
||||
# Show a notification
|
||||
ShowToast("Done!", variant="success")
|
||||
```
|
||||
|
||||
### Chaining Actions
|
||||
|
||||
Pass a list to execute multiple actions in sequence:
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Button
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
|
||||
Button(
|
||||
"Reset",
|
||||
on_click=[
|
||||
SetState("query", ""),
|
||||
SetState("results", []),
|
||||
ShowToast("Cleared", variant="default"),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
### Loading States
|
||||
|
||||
A common pattern: show a loading indicator while a server call is in flight.
|
||||
|
||||
```python
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.components import Button
|
||||
from prefab_ui.rx import RESULT, Rx
|
||||
|
||||
saving = Rx("saving")
|
||||
|
||||
Button(
|
||||
saving.then("Saving...", "Save"),
|
||||
disabled=saving,
|
||||
on_click=[
|
||||
SetState("saving", True),
|
||||
CallTool(
|
||||
"save_data",
|
||||
on_success=[
|
||||
SetState("saving", False),
|
||||
SetState("result", RESULT),
|
||||
ShowToast("Saved!", variant="success"),
|
||||
],
|
||||
on_error=[
|
||||
SetState("saving", False),
|
||||
ShowToast("Failed", variant="error"),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
# Pass state={"saving": False} to PrefabApp when returning
|
||||
```
|
||||
|
||||
## Forms
|
||||
|
||||
Forms are the most common way to collect input and send it to the server. When a form submits, all named input values are gathered and passed as arguments to the `CallTool` action.
|
||||
|
||||
### Manual Forms
|
||||
|
||||
Build forms with individual input components:
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Form, Input, Select, SelectOption, Textarea, Button
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.actions import ShowToast
|
||||
|
||||
with Form(
|
||||
on_submit=CallTool(
|
||||
"create_ticket",
|
||||
on_success=ShowToast("Ticket created!", variant="success"),
|
||||
)
|
||||
):
|
||||
Input(name="title", label="Title", required=True)
|
||||
with Select(name="priority", label="Priority"):
|
||||
SelectOption("Low", value="low")
|
||||
SelectOption("Medium", value="medium")
|
||||
SelectOption("High", value="high")
|
||||
SelectOption("Critical", value="critical")
|
||||
Textarea(name="description", label="Description")
|
||||
Button("Create Ticket")
|
||||
```
|
||||
|
||||
When submitted, the CallTool receives `{"title": "...", "priority": "...", "description": "..."}` as arguments to `create_ticket`.
|
||||
|
||||
### Pydantic Model Forms
|
||||
|
||||
For structured data, `Form.from_model()` generates the entire form from a Pydantic model — inputs, labels, and submit wiring:
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from prefab_ui.components import Column, Heading, Form
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.rx import RESULT
|
||||
|
||||
class BugReport(BaseModel):
|
||||
title: str = Field(title="Bug Title")
|
||||
severity: Literal["low", "medium", "high", "critical"] = Field(
|
||||
title="Severity", default="medium"
|
||||
)
|
||||
description: str = Field(title="Description")
|
||||
|
||||
|
||||
@app.ui()
|
||||
def report_bug() -> PrefabApp:
|
||||
"""File a bug report."""
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Report a Bug")
|
||||
Form.from_model(
|
||||
BugReport,
|
||||
on_submit=CallTool(
|
||||
"create_bug",
|
||||
on_success=ShowToast("Bug filed!", variant="success"),
|
||||
on_error=ShowToast("Failed to submit", variant="error"),
|
||||
),
|
||||
)
|
||||
return PrefabApp(view=view)
|
||||
|
||||
|
||||
@app.tool()
|
||||
def create_bug(data: BugReport) -> str:
|
||||
"""Create a bug report."""
|
||||
# save to database...
|
||||
return f"Created: {data.title}"
|
||||
```
|
||||
|
||||
`str` fields become text inputs, `Literal` becomes a select dropdown, `bool` becomes a checkbox. Field titles and defaults are respected.
|
||||
|
||||
## Composition and Namespacing
|
||||
|
||||
The reason `FastMCPApp` exists — and why you'd use it instead of plain `@mcp.tool(app=True)` with `CallTool("tool_name")` — is composition safety.
|
||||
|
||||
When you mount a server under a namespace, tool names get prefixed:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
||||
platform = FastMCP("Platform")
|
||||
platform.mount("contacts", contacts_server)
|
||||
|
||||
# "save_contact" becomes "contacts_save_contact"
|
||||
```
|
||||
|
||||
If your UI used `CallTool("save_contact")`, it would break — the tool is now named `contacts_save_contact`. But `CallTool(save_contact)` with a function reference resolves to a globally stable key (like `save_contact-a1b2c3d4`) that bypasses the namespace entirely.
|
||||
|
||||
This is why `FastMCPApp` assigns global keys to backend tools, and why `CallTool` accepts function references. Your app works the same whether it's running standalone or mounted inside a larger platform.
|
||||
|
||||
### Mounting an App
|
||||
|
||||
`FastMCPApp` is a Provider. Add it to a server with `providers=` or `add_provider`:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, FastMCPApp
|
||||
|
||||
app = FastMCPApp("Contacts")
|
||||
|
||||
@app.ui()
|
||||
def contact_manager() -> PrefabApp:
|
||||
...
|
||||
|
||||
@app.tool()
|
||||
def save_contact(name: str, email: str) -> dict:
|
||||
...
|
||||
|
||||
|
||||
# Option 1: providers list
|
||||
mcp = FastMCP("Platform", providers=[app])
|
||||
|
||||
# Option 2: add_provider
|
||||
mcp = FastMCP("Platform")
|
||||
mcp.add_provider(app)
|
||||
```
|
||||
|
||||
Multiple apps can coexist on the same server:
|
||||
|
||||
```python
|
||||
mcp = FastMCP("Platform", providers=[contacts_app, inventory_app, billing_app])
|
||||
```
|
||||
|
||||
Each app's backend tools have their own global keys, so there's no collision even if two apps have a tool named `save`.
|
||||
|
||||
### Running Standalone
|
||||
|
||||
For development, `FastMCPApp` has a convenience `run()` method that wraps itself in a temporary `FastMCP` server:
|
||||
|
||||
```python
|
||||
app = FastMCPApp("Contacts")
|
||||
# ... register tools ...
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
```
|
||||
|
||||
## Complete Example: Contact Manager
|
||||
|
||||
This pulls together everything — entry points, backend tools, callable references, forms (both manual and Pydantic), state management, and actions:
|
||||
|
||||
```python expandable
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Badge, Button, Column, ForEach, Form,
|
||||
Heading, Input, Muted, Row, Separator, Text,
|
||||
)
|
||||
from prefab_ui.rx import RESULT, Rx
|
||||
from pydantic import BaseModel, Field
|
||||
from fastmcp import FastMCP, FastMCPApp
|
||||
|
||||
# Data
|
||||
|
||||
contacts_db: list[dict] = [
|
||||
{"name": "Arthur Dent", "email": "arthur@earth.com", "category": "Customer"},
|
||||
{"name": "Ford Prefect", "email": "ford@betelgeuse.org", "category": "Partner"},
|
||||
]
|
||||
|
||||
|
||||
class ContactModel(BaseModel):
|
||||
name: str = Field(title="Full Name", min_length=1)
|
||||
email: str = Field(title="Email")
|
||||
category: Literal["Customer", "Vendor", "Partner", "Other"] = "Other"
|
||||
|
||||
|
||||
# App
|
||||
|
||||
app = FastMCPApp("Contacts")
|
||||
|
||||
|
||||
@app.tool()
|
||||
def save_contact(data: ContactModel) -> list[dict]:
|
||||
"""Save a new contact and return the updated list."""
|
||||
contacts_db.append(data.model_dump())
|
||||
return list(contacts_db)
|
||||
|
||||
|
||||
@app.tool()
|
||||
def search_contacts(query: str) -> list[dict]:
|
||||
"""Filter contacts by name or email."""
|
||||
q = query.lower()
|
||||
return [
|
||||
c for c in contacts_db
|
||||
if q in c["name"].lower() or q in c["email"].lower()
|
||||
]
|
||||
|
||||
|
||||
@app.tool(model=True)
|
||||
def list_contacts() -> list[dict]:
|
||||
"""Return all contacts. Visible to both the model and the UI."""
|
||||
return list(contacts_db)
|
||||
|
||||
|
||||
@app.ui()
|
||||
def contact_manager() -> PrefabApp:
|
||||
"""Open the contact manager."""
|
||||
with Column(gap=6, css_class="p-6") as view:
|
||||
Heading("Contacts")
|
||||
|
||||
with ForEach("contacts") as contact:
|
||||
with Row(gap=2, align="center"):
|
||||
Text(contact.name, css_class="font-medium")
|
||||
Muted(contact.email)
|
||||
Badge(contact.category)
|
||||
|
||||
Separator()
|
||||
|
||||
Heading("Add Contact", level=3)
|
||||
Form.from_model(
|
||||
ContactModel,
|
||||
on_submit=CallTool(
|
||||
"save_contact",
|
||||
on_success=[
|
||||
SetState("contacts", RESULT),
|
||||
ShowToast("Contact saved!", variant="success"),
|
||||
],
|
||||
on_error=ShowToast("Failed to save", variant="error"),
|
||||
),
|
||||
)
|
||||
|
||||
Separator()
|
||||
|
||||
Heading("Search", level=3)
|
||||
with Form(
|
||||
on_submit=CallTool(
|
||||
"search_contacts",
|
||||
arguments={"query": Rx("query")},
|
||||
on_success=SetState("contacts", RESULT),
|
||||
)
|
||||
):
|
||||
Input(name="query", placeholder="Search by name or email...")
|
||||
Button("Search")
|
||||
|
||||
return PrefabApp(view=view, state={"contacts": list(contacts_db)})
|
||||
|
||||
|
||||
mcp = FastMCP("Contacts Server", providers=[app])
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
This example is also available as a runnable server at `examples/apps/contacts/contacts_server.py`.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Prefab Apps](/apps/prefab)** — Components, state, and reactive displays (the building blocks)
|
||||
- **[Patterns](/apps/patterns)** — Copy-paste examples for common UIs
|
||||
- **[Development](/apps/development)** — Preview and test app tools locally
|
||||
- **[Prefab UI Docs](https://prefab.prefect.io)** — Full component reference and advanced patterns
|
||||
|
|
@ -27,7 +27,7 @@ The tool declares which resource to use via `AppConfig`. When the host calls the
|
|||
import json
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.apps import AppConfig, ResourceCSP
|
||||
from fastmcp.apps import AppConfig, ResourceCSP
|
||||
|
||||
mcp = FastMCP("My App Server")
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ def chart_view() -> str:
|
|||
`AppConfig` controls how a tool or resource participates in the Apps extension. Import it from `fastmcp.server.apps`:
|
||||
|
||||
```python
|
||||
from fastmcp.server.apps import AppConfig
|
||||
from fastmcp.apps import AppConfig
|
||||
```
|
||||
|
||||
On **tools**, you'll typically set `resource_uri` to point to the UI resource:
|
||||
|
|
@ -145,6 +145,8 @@ The `App` object provides:
|
|||
- **`app.onhostcontextchanged`** — callback for host context changes (e.g., safe area insets)
|
||||
- **`app.getHostContext()`** — get current host context
|
||||
|
||||
See the full [ext-apps SDK documentation](https://github.com/modelcontextprotocol/ext-apps) for the complete API reference.
|
||||
|
||||
<Note>
|
||||
If your HTML loads external scripts, styles, or makes API calls, you need to declare those domains in the CSP configuration. See [Security](#security) below.
|
||||
</Note>
|
||||
|
|
@ -158,7 +160,7 @@ Apps run in sandboxed iframes with a deny-by-default Content Security Policy. By
|
|||
If your app needs to load external resources (CDN scripts, API calls, embedded iframes), declare the allowed domains with `ResourceCSP`:
|
||||
|
||||
```python
|
||||
from fastmcp.server.apps import AppConfig, ResourceCSP
|
||||
from fastmcp.apps import AppConfig, ResourceCSP
|
||||
|
||||
@mcp.resource(
|
||||
"ui://my-app/view.html",
|
||||
|
|
@ -185,7 +187,7 @@ def my_view() -> str:
|
|||
If your app needs browser capabilities like camera or clipboard access, request them via `ResourcePermissions`:
|
||||
|
||||
```python
|
||||
from fastmcp.server.apps import AppConfig, ResourcePermissions
|
||||
from fastmcp.apps import AppConfig, ResourcePermissions
|
||||
|
||||
@mcp.resource(
|
||||
"ui://my-app/view.html",
|
||||
|
|
@ -214,7 +216,7 @@ import qrcode
|
|||
from mcp import types
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.apps import AppConfig, ResourceCSP
|
||||
from fastmcp.apps import AppConfig, ResourceCSP
|
||||
from fastmcp.tools import ToolResult
|
||||
|
||||
mcp = FastMCP("QR Code Server")
|
||||
|
|
@ -290,7 +292,7 @@ Not all hosts support the Apps extension. You can check at runtime using the too
|
|||
|
||||
```python
|
||||
from fastmcp import Context
|
||||
from fastmcp.server.apps import AppConfig, UI_EXTENSION_ID
|
||||
from fastmcp.apps import AppConfig, UI_EXTENSION_ID
|
||||
|
||||
@mcp.tool(app=AppConfig(resource_uri="ui://my-app/view.html"))
|
||||
async def my_tool(ctx: Context) -> str:
|
||||
|
|
|
|||
|
|
@ -10,67 +10,172 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
|
|||
|
||||
<VersionBadge version="3.0.0" />
|
||||
|
||||
MCP Apps let your tools return interactive UIs — rendered in a sandboxed iframe right inside the host client's conversation. Instead of returning plain text, a tool can show a chart, a sortable table, a form, or anything you can build with HTML.
|
||||
MCP tools normally return text. That works for answers, but not for data the user wants to *explore* — a revenue chart they can hover over, a sortable employee directory, a form that submits structured input. MCP Apps let your tools return interactive UIs rendered right inside the conversation.
|
||||
|
||||
FastMCP implements the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps) and provides two approaches:
|
||||
<Frame>
|
||||
<img src="/apps/images/app-showcase.png" alt="A Prefab app showing forms, charts, metrics, progress bars, data tables, and interactive controls — all built in Python" />
|
||||
</Frame>
|
||||
|
||||
## Prefab Apps (Recommended)
|
||||
FastMCP builds on the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps) with [Prefab](https://prefab.prefect.io), a Python component library that compiles to interactive UIs. You write Python; the user sees charts, tables, forms, and dashboards.
|
||||
|
||||
<Note>
|
||||
The examples throughout the Apps docs require the `apps` extra:
|
||||
|
||||
```bash
|
||||
pip install "fastmcp[apps]"
|
||||
```
|
||||
|
||||
This installs [Prefab UI](https://prefab.prefect.io), the component library used to build app UIs.
|
||||
</Note>
|
||||
|
||||
<Warning>
|
||||
FastMCP pins a **minimum** version of `prefab-ui` for compatibility but intentionally does **not** pin an upper bound. Prefab is a rapidly evolving library with frequent breaking changes. If you are deploying to production, you **must** pin `prefab-ui` to a specific version in your own dependencies. Without a pin, a fresh deploy could pull a newer Prefab version that changes component APIs, breaking your app.
|
||||
</Warning>
|
||||
|
||||
## Which Approach?
|
||||
|
||||
Most apps start with **[Prefab Apps](/apps/prefab)** — add `app=True` to a tool and return components. That covers charts, tables, dashboards, and client-side interactivity.
|
||||
|
||||
When your UI needs multiple backend tools with managed visibility and composition safety, use **[FastMCPApp](/apps/interactive-apps)**.
|
||||
|
||||
When you want the LLM to design the UI at runtime, use **[Generative UI](/apps/generative)**.
|
||||
|
||||
When you need your own HTML/JS (maps, 3D, video), use **[Custom HTML](/apps/low-level)**.
|
||||
|
||||
FastMCP also includes ready-made **[app providers](/apps/providers/approval)** that add common capabilities with a single `add_provider()` call.
|
||||
|
||||
## Building Apps
|
||||
|
||||
### Prefab Apps
|
||||
|
||||
<VersionBadge version="3.1.0" />
|
||||
|
||||
<Tip>
|
||||
[Prefab](https://prefab.prefect.io) is in extremely early, active development — its API changes frequently and breaking changes can occur with any release. The FastMCP integration is equally new and under rapid development. These docs are included for users who want to work on the cutting edge; production use is not recommended. Always [pin `prefab-ui` to a specific version](/apps/prefab#getting-started) in your dependencies.
|
||||
</Tip>
|
||||
|
||||
[Prefab UI](https://prefab.prefect.io) is a declarative UI framework for Python. You describe layouts, charts, tables, forms, and interactive behaviors using a Python DSL — and the framework compiles them to a JSON protocol that a shared renderer interprets. It started as a component library inside FastMCP and grew into its own framework with [comprehensive documentation](https://prefab.prefect.io).
|
||||
The quickest way to give a tool a visual UI. Add `app=True` to any tool and return a Prefab component — when the host calls it, the user sees an interactive UI instead of a JSON blob:
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Column, Heading, BarChart, ChartSeries
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading
|
||||
from prefab_ui.components.charts import BarChart, ChartSeries
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Dashboard")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def sales_chart(year: int) -> PrefabApp:
|
||||
"""Show sales data as an interactive chart."""
|
||||
data = get_sales_data(year)
|
||||
def revenue_chart(year: int) -> PrefabApp:
|
||||
"""Show annual revenue as an interactive bar chart."""
|
||||
data = [
|
||||
{"quarter": "Q1", "revenue": 42000},
|
||||
{"quarter": "Q2", "revenue": 51000},
|
||||
{"quarter": "Q3", "revenue": 47000},
|
||||
{"quarter": "Q4", "revenue": 63000},
|
||||
]
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading(f"{year} Sales")
|
||||
Heading(f"{year} Revenue")
|
||||
BarChart(
|
||||
data=data,
|
||||
series=[ChartSeries(data_key="revenue", label="Revenue")],
|
||||
x_axis="month",
|
||||
x_axis="quarter",
|
||||
)
|
||||
|
||||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
Install with `pip install "fastmcp[apps]"` and see [Prefab Apps](/apps/prefab) for the integration guide.
|
||||
Prefab apps aren't limited to static displays. Prefab's state system and client-side actions (toggles, tabs, conditionals) all work. You can even call other tools from the UI using `CallTool`. There's no hard wall on what a Prefab app can do.
|
||||
|
||||
## Custom HTML Apps
|
||||
See [Prefab Apps](/apps/prefab) for the full guide.
|
||||
|
||||
The [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps) is an open protocol, and you can use it directly when you need full control. You write your own HTML/CSS/JavaScript and communicate with the host via the [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) SDK.
|
||||
### FastMCPApp
|
||||
|
||||
This is the right choice for custom rendering (maps, 3D, video), specific JavaScript frameworks, or capabilities beyond what the component library offers.
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
When your app has a lot of server-side interaction — forms that save data, search that queries a database, multi-step workflows — managing the connection between UI and backend tools gets complicated fast. Which tools should the model see vs. only the UI? What happens to tool references when servers are composed under namespaces? How do you keep `CallTool("save_contact")` working when the tool name changes?
|
||||
|
||||
`FastMCPApp` is a class that solves these problems. It gives you two decorators that work together:
|
||||
|
||||
- **`@app.ui()`** — entry-point tools the model calls to open the app
|
||||
- **`@app.tool()`** — backend tools the UI calls via `CallTool`
|
||||
|
||||
Backend tools get stable identifiers that survive namespacing, visibility is managed automatically (the model sees entry points, the UI sees backends), and `CallTool` accepts tool names that resolve correctly regardless of how servers are composed:
|
||||
|
||||
```python
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Column, Heading, Form, Input, Button, ForEach, Row, Text, Badge, Separator,
|
||||
)
|
||||
from prefab_ui.rx import RESULT
|
||||
from fastmcp import FastMCP, FastMCPApp
|
||||
|
||||
app = FastMCPApp("Contacts")
|
||||
|
||||
|
||||
@app.tool()
|
||||
def save_contact(name: str, email: str) -> list[dict]:
|
||||
"""Save a contact and return the updated list."""
|
||||
db.append({"name": name, "email": email})
|
||||
return list(db)
|
||||
|
||||
|
||||
@app.ui()
|
||||
def contact_manager() -> PrefabApp:
|
||||
"""Open the contact manager."""
|
||||
with Column(gap=6, css_class="p-6") as view:
|
||||
Heading("Contacts")
|
||||
with ForEach("contacts") as contact:
|
||||
with Row(gap=2):
|
||||
Text(contact.name)
|
||||
Badge(contact.email)
|
||||
Separator()
|
||||
with Form(
|
||||
on_submit=CallTool(
|
||||
"save_contact",
|
||||
on_success=[
|
||||
SetState("contacts", RESULT),
|
||||
ShowToast("Saved!", variant="success"),
|
||||
],
|
||||
)
|
||||
):
|
||||
Input(name="name", label="Name", required=True)
|
||||
Input(name="email", label="Email", required=True)
|
||||
Button("Save")
|
||||
|
||||
return PrefabApp(view=view, state={"contacts": list(db)})
|
||||
|
||||
|
||||
mcp = FastMCP("Server", providers=[app])
|
||||
```
|
||||
|
||||
You *can* build server-interactive UIs without `FastMCPApp` — it's all the same protocol underneath. But once you have multiple tools, composition concerns, or visibility requirements, `FastMCPApp` handles the complexity so you don't have to.
|
||||
|
||||
See [FastMCPApp](/apps/interactive-apps) for the full guide.
|
||||
|
||||
### Generative UI
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
Instead of pre-building a UI, the LLM can write one from scratch. The `GenerativeUI` provider registers tools that let the model write Prefab Python code, execute it in a sandbox, and render the result — with streaming so the user watches the UI build up in real time.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.apps import AppConfig, ResourceCSP
|
||||
from fastmcp.apps.generative import GenerativeUI
|
||||
|
||||
mcp = FastMCP("Custom App")
|
||||
|
||||
@mcp.tool(app=AppConfig(resource_uri="ui://my-app/view.html"))
|
||||
def my_tool() -> str:
|
||||
return '{"values": [1, 2, 3]}'
|
||||
|
||||
@mcp.resource(
|
||||
"ui://my-app/view.html",
|
||||
app=AppConfig(csp=ResourceCSP(resource_domains=["https://unpkg.com"])),
|
||||
)
|
||||
def view() -> str:
|
||||
return "<html>...</html>"
|
||||
mcp = FastMCP("Prefab Studio")
|
||||
mcp.add_provider(GenerativeUI())
|
||||
```
|
||||
|
||||
See [Custom HTML Apps](/apps/low-level) for the full reference.
|
||||
See [Generative UI](/apps/generative) for the full guide, or the [provider reference](/apps/providers/generative) for configuration options.
|
||||
|
||||
### Custom HTML
|
||||
|
||||
All the approaches above use [Prefab UI](https://prefab.prefect.io) to build UIs in pure Python. If you need full control — your own HTML, CSS, JavaScript, a specific framework — you can use the [MCP Apps extension directly](/apps/low-level). You write the HTML yourself and communicate with the host via the [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) SDK.
|
||||
|
||||
## Previewing Apps Locally
|
||||
|
||||
The `fastmcp dev apps` command launches a browser-based preview for your app tools — no MCP host client needed. See [Development](/apps/development).
|
||||
|
||||
```bash
|
||||
fastmcp dev apps server.py
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,30 +1,29 @@
|
|||
---
|
||||
title: Patterns
|
||||
sidebarTitle: Patterns
|
||||
description: Charts, tables, forms, and other common tool UIs.
|
||||
description: Copy-paste examples for common tool UIs.
|
||||
icon: grid-2-plus
|
||||
tag: SOON
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.1.0" />
|
||||
|
||||
<Tip>
|
||||
[Prefab](https://prefab.prefect.io) is in extremely early, active development — its API changes frequently and breaking changes can occur with any release. The FastMCP integration is equally new and under rapid development. These docs are included for users who want to work on the cutting edge; production use is not recommended. Always pin `prefab-ui` to a specific version in your dependencies.
|
||||
</Tip>
|
||||
Each pattern below is a complete, copy-pasteable tool. They're organized by what you're building — pick the one closest to your use case, paste it, and adapt.
|
||||
|
||||
The most common use of Prefab is giving your tools a visual representation — a chart instead of raw numbers, a sortable table instead of a text dump, a status dashboard instead of a list of booleans. Each pattern below is a complete, copy-pasteable tool.
|
||||
For the full set of available components — layout containers, form controls, overlays, and more — see the [Prefab component reference](https://prefab.prefect.io/docs/components).
|
||||
|
||||
## Charts
|
||||
|
||||
Prefab includes [bar, line, area, pie, radar, and radial charts](https://prefab.prefect.io/docs/components/charts). They all render client-side with tooltips, legends, and responsive sizing.
|
||||
Prefab includes [bar, line, area, pie, radar, and radial charts](https://prefab.prefect.io/docs/components/charts). They render client-side with tooltips, legends, and responsive sizing.
|
||||
|
||||
### Bar Chart
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Column, Heading, BarChart, ChartSeries
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading
|
||||
from prefab_ui.components.charts import BarChart, ChartSeries
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Charts")
|
||||
|
|
@ -59,11 +58,12 @@ Multiple `ChartSeries` entries plot different data keys. Add `stacked=True` to s
|
|||
|
||||
### Area Chart
|
||||
|
||||
`LineChart` and `AreaChart` share the same API as `BarChart`, with `curve` for interpolation (`"linear"`, `"smooth"`, `"step"`) and `show_dots` for data points:
|
||||
`LineChart` and `AreaChart` share the same API as `BarChart`, with `curve` for interpolation and `show_dots` for data points:
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Column, Heading, AreaChart, ChartSeries
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading
|
||||
from prefab_ui.components.charts import AreaChart, ChartSeries
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Charts")
|
||||
|
|
@ -95,11 +95,12 @@ def usage_trend() -> PrefabApp:
|
|||
|
||||
### Pie and Donut Charts
|
||||
|
||||
`PieChart` uses `data_key` (the numeric value) and `name_key` (the label) instead of series. Set `inner_radius` for a donut:
|
||||
`PieChart` uses `data_key` (the numeric value) and `name_key` (the label). Set `inner_radius` for a donut:
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Column, Heading, PieChart
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading
|
||||
from prefab_ui.components.charts import PieChart
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Charts")
|
||||
|
|
@ -130,11 +131,11 @@ def ticket_breakdown() -> PrefabApp:
|
|||
|
||||
## Data Tables
|
||||
|
||||
[DataTable](https://prefab.prefect.io/docs/components/data-display/data-table) provides sortable columns, full-text search, and pagination — all running client-side in the browser.
|
||||
[DataTable](https://prefab.prefect.io/docs/components/data-display/data-table) provides sortable columns, full-text search, and pagination — all client-side:
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Column, Heading, DataTable, DataTableColumn
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading, DataTable, DataTableColumn
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Directory")
|
||||
|
|
@ -161,7 +162,7 @@ def employee_directory() -> PrefabApp:
|
|||
DataTableColumn(key="location", header="Office", sortable=True),
|
||||
],
|
||||
rows=employees,
|
||||
searchable=True,
|
||||
search=True,
|
||||
paginated=True,
|
||||
page_size=15,
|
||||
)
|
||||
|
|
@ -169,133 +170,16 @@ def employee_directory() -> PrefabApp:
|
|||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
## Forms
|
||||
|
||||
A form collects input, but it needs somewhere to send that input. The [`CallTool`](https://prefab.prefect.io/docs/concepts/actions) action connects a form to a tool on your MCP server — so you need two tools: one that renders the form, and one that handles the submission.
|
||||
|
||||
```python
|
||||
from prefab_ui.components import (
|
||||
Column, Heading, Row, Muted, Badge, Input, Select,
|
||||
Textarea, Button, Form, ForEach, Separator,
|
||||
)
|
||||
from prefab_ui.actions import ShowToast
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.app import PrefabApp
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Contacts")
|
||||
|
||||
contacts_db: list[dict] = [
|
||||
{"name": "Zaphod Beeblebrox", "email": "zaphod@galaxy.gov", "category": "Partner"},
|
||||
]
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def contact_form() -> PrefabApp:
|
||||
"""Show a contact list with a form to add new contacts."""
|
||||
with Column(gap=6, css_class="p-6") as view:
|
||||
Heading("Contacts")
|
||||
|
||||
with ForEach("contacts"):
|
||||
with Row(gap=2, align="center"):
|
||||
Muted("{{ name }}")
|
||||
Muted("{{ email }}")
|
||||
Badge("{{ category }}")
|
||||
|
||||
Separator()
|
||||
|
||||
with Form(
|
||||
on_submit=CallTool(
|
||||
"save_contact",
|
||||
result_key="contacts",
|
||||
on_success=ShowToast("Contact saved!", variant="success"),
|
||||
on_error=ShowToast("{{ $error }}", variant="error"),
|
||||
)
|
||||
):
|
||||
Input(name="name", label="Full Name", required=True)
|
||||
Input(name="email", label="Email", input_type="email", required=True)
|
||||
Select(
|
||||
name="category",
|
||||
label="Category",
|
||||
options=["Customer", "Vendor", "Partner", "Other"],
|
||||
)
|
||||
Textarea(name="notes", label="Notes", placeholder="Optional notes...")
|
||||
Button("Save Contact")
|
||||
|
||||
return PrefabApp(view=view, state={"contacts": list(contacts_db)})
|
||||
|
||||
|
||||
@mcp.tool
|
||||
def save_contact(
|
||||
name: str,
|
||||
email: str,
|
||||
category: str = "Other",
|
||||
notes: str = "",
|
||||
) -> list[dict]:
|
||||
"""Save a new contact and return the updated list."""
|
||||
contacts_db.append({"name": name, "email": email, "category": category, "notes": notes})
|
||||
return list(contacts_db)
|
||||
```
|
||||
|
||||
When the user submits the form, the renderer calls `save_contact` on the server with all named input values as arguments. Because `result_key="contacts"` is set, the returned list replaces the `contacts` state — and the `ForEach` re-renders with the new data automatically.
|
||||
|
||||
The `save_contact` tool is a regular MCP tool. The LLM can also call it directly in conversation. Your UI actions and your conversational tools are the same thing.
|
||||
|
||||
### Pydantic Model Forms
|
||||
|
||||
For complex forms, `Form.from_model()` generates the entire form from a Pydantic model — inputs, labels, validation, and submit wiring:
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from prefab_ui.components import Column, Heading, Form
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.app import PrefabApp
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Bug Tracker")
|
||||
|
||||
|
||||
class BugReport(BaseModel):
|
||||
title: str = Field(title="Bug Title")
|
||||
severity: Literal["low", "medium", "high", "critical"] = Field(
|
||||
title="Severity", default="medium"
|
||||
)
|
||||
description: str = Field(title="Description")
|
||||
steps_to_reproduce: str = Field(title="Steps to Reproduce")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def report_bug() -> PrefabApp:
|
||||
"""Show a bug report form."""
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Report a Bug")
|
||||
Form.from_model(BugReport, on_submit=CallTool("create_bug_report"))
|
||||
|
||||
return PrefabApp(view=view)
|
||||
|
||||
|
||||
@mcp.tool
|
||||
def create_bug_report(data: dict) -> str:
|
||||
"""Create a bug report from the form submission."""
|
||||
report = BugReport(**data)
|
||||
# save to database...
|
||||
return f"Created bug report: {report.title}"
|
||||
```
|
||||
|
||||
`str` fields become text inputs, `Literal` becomes a select, `bool` becomes a checkbox. The `on_submit` CallTool receives all field values under a `data` key.
|
||||
|
||||
## Status Displays
|
||||
|
||||
Cards, badges, progress bars, and grids combine naturally for dashboards. See the [Prefab layout](https://prefab.prefect.io/docs/concepts/composition) and [container](https://prefab.prefect.io/docs/components/containers) docs for the full set of layout and display components.
|
||||
Cards, badges, progress bars, and grids combine naturally for dashboards:
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Column, Row, Grid, Heading, Text, Muted, Badge,
|
||||
Card, CardContent, Progress, Separator,
|
||||
)
|
||||
from prefab_ui.app import PrefabApp
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Monitoring")
|
||||
|
|
@ -319,9 +203,7 @@ def system_status() -> PrefabApp:
|
|||
"All Healthy" if all_ok else "Degraded",
|
||||
variant="success" if all_ok else "destructive",
|
||||
)
|
||||
|
||||
Separator()
|
||||
|
||||
with Grid(columns=2, gap=4):
|
||||
for svc in services:
|
||||
with Card():
|
||||
|
|
@ -338,13 +220,16 @@ def system_status() -> PrefabApp:
|
|||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
## Conditional Content
|
||||
## Reactive Displays
|
||||
|
||||
[`If`, `Elif`, and `Else`](https://prefab.prefect.io/docs/concepts/composition#conditional-rendering) show or hide content based on state. Changes are instant — no server round-trip.
|
||||
These patterns use state and `Rx()` for client-side interactivity — no server calls needed.
|
||||
|
||||
### Feature Toggles
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Column, Heading, Switch, Separator, Alert, If
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading, Switch, Alert, If, Separator
|
||||
from prefab_ui.rx import Rx
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Flags")
|
||||
|
|
@ -355,47 +240,41 @@ def feature_flags() -> PrefabApp:
|
|||
"""Toggle feature flags with live preview."""
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Feature Flags")
|
||||
|
||||
Switch(name="dark_mode", label="Dark Mode")
|
||||
Switch(name="beta_features", label="Beta Features")
|
||||
|
||||
Switch(name="beta", label="Beta Features")
|
||||
Separator()
|
||||
|
||||
with If("{{ dark_mode }}"):
|
||||
with If(Rx("dark_mode")):
|
||||
Alert(title="Dark mode enabled", description="UI will use dark theme.")
|
||||
with If("{{ beta_features }}"):
|
||||
with If(Rx("beta")):
|
||||
Alert(
|
||||
title="Beta features active",
|
||||
description="Experimental features are now visible.",
|
||||
variant="warning",
|
||||
)
|
||||
|
||||
return PrefabApp(view=view, state={"dark_mode": False, "beta_features": False})
|
||||
return PrefabApp(view=view, state={"dark_mode": False, "beta": False})
|
||||
```
|
||||
|
||||
## Tabs
|
||||
|
||||
[Tabs](https://prefab.prefect.io/docs/components/containers/tabs) organize content into switchable views. Switching is client-side — no server round-trip.
|
||||
### Tabs
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Column, Heading, Text, Muted, Badge, Row,
|
||||
DataTable, DataTableColumn, Tabs, Tab, ForEach,
|
||||
)
|
||||
from prefab_ui.app import PrefabApp
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Projects")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def project_overview(project_id: str) -> PrefabApp:
|
||||
def project_overview() -> PrefabApp:
|
||||
"""Show project details organized in tabs."""
|
||||
project = {
|
||||
"name": "FastMCP v3",
|
||||
"description": "Next generation MCP framework with Apps support.",
|
||||
"status": "Active",
|
||||
"created_at": "2025-01-15",
|
||||
"members": [
|
||||
{"name": "Alice Chen", "role": "Lead"},
|
||||
{"name": "Bob Martinez", "role": "Design"},
|
||||
|
|
@ -408,13 +287,11 @@ def project_overview(project_id: str) -> PrefabApp:
|
|||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading(project["name"])
|
||||
|
||||
with Tabs():
|
||||
with Tab("Overview"):
|
||||
Text(project["description"])
|
||||
with Row(gap=4):
|
||||
Badge(project["status"])
|
||||
Muted(f"Created: {project['created_at']}")
|
||||
|
||||
with Tab("Members"):
|
||||
DataTable(
|
||||
|
|
@ -426,24 +303,22 @@ def project_overview(project_id: str) -> PrefabApp:
|
|||
)
|
||||
|
||||
with Tab("Activity"):
|
||||
with ForEach("activity"):
|
||||
with ForEach("activity") as item:
|
||||
with Row(gap=2):
|
||||
Muted("{{ timestamp }}")
|
||||
Text("{{ message }}")
|
||||
Muted(item.timestamp)
|
||||
Text(item.message)
|
||||
|
||||
return PrefabApp(view=view, state={"activity": project["activity"]})
|
||||
```
|
||||
|
||||
## Accordion
|
||||
|
||||
[Accordion](https://prefab.prefect.io/docs/components/containers/accordion) collapses sections to save space. `multiple=True` lets users expand several items at once:
|
||||
### Accordion
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Column, Heading, Row, Text, Badge, Progress,
|
||||
Accordion, AccordionItem,
|
||||
)
|
||||
from prefab_ui.app import PrefabApp
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("API Monitor")
|
||||
|
|
@ -461,7 +336,6 @@ def api_health() -> PrefabApp:
|
|||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("API Health")
|
||||
|
||||
with Accordion(multiple=True):
|
||||
for ep in endpoints:
|
||||
with AccordionItem(ep["path"]):
|
||||
|
|
@ -477,7 +351,81 @@ def api_health() -> PrefabApp:
|
|||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
## Interactive Patterns
|
||||
|
||||
These patterns call server tools. For context on `FastMCPApp`, `@app.tool()`, and `CallTool`, see [FastMCPApp](/apps/interactive-apps).
|
||||
|
||||
### Contact Form
|
||||
|
||||
```python
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Badge, Button, Column, ForEach, Form, Heading,
|
||||
Input, Muted, Row, Select, SelectOption, Separator, Text, Textarea,
|
||||
)
|
||||
from prefab_ui.rx import RESULT
|
||||
from fastmcp import FastMCP, FastMCPApp
|
||||
|
||||
app = FastMCPApp("Contacts")
|
||||
|
||||
contacts_db: list[dict] = [
|
||||
{"name": "Zaphod Beeblebrox", "email": "zaphod@galaxy.gov", "category": "Partner"},
|
||||
]
|
||||
|
||||
|
||||
@app.tool()
|
||||
def save_contact(
|
||||
name: str, email: str, category: str = "Other", notes: str = "",
|
||||
) -> list[dict]:
|
||||
"""Save a new contact and return the updated list."""
|
||||
contacts_db.append({"name": name, "email": email, "category": category})
|
||||
return list(contacts_db)
|
||||
|
||||
|
||||
@app.ui()
|
||||
def contact_form() -> PrefabApp:
|
||||
"""Contact list with an add form."""
|
||||
with Column(gap=6, css_class="p-6") as view:
|
||||
Heading("Contacts")
|
||||
|
||||
with ForEach("contacts") as contact:
|
||||
with Row(gap=2, align="center"):
|
||||
Text(contact.name, css_class="font-medium")
|
||||
Muted(contact.email)
|
||||
Badge(contact.category)
|
||||
|
||||
Separator()
|
||||
|
||||
with Form(
|
||||
on_submit=CallTool(
|
||||
"save_contact",
|
||||
on_success=[
|
||||
SetState("contacts", RESULT),
|
||||
ShowToast("Contact saved!", variant="success"),
|
||||
],
|
||||
on_error=ShowToast("Failed to save", variant="error"),
|
||||
)
|
||||
):
|
||||
Input(name="name", label="Full Name", required=True)
|
||||
Input(name="email", label="Email", input_type="email", required=True)
|
||||
with Select(name="category", label="Category"):
|
||||
SelectOption("Customer", value="Customer")
|
||||
SelectOption("Vendor", value="Vendor")
|
||||
SelectOption("Partner", value="Partner")
|
||||
SelectOption("Other", value="Other")
|
||||
Textarea(name="notes", label="Notes", placeholder="Optional notes...")
|
||||
Button("Save Contact")
|
||||
|
||||
return PrefabApp(view=view, state={"contacts": list(contacts_db)})
|
||||
|
||||
|
||||
mcp = FastMCP("Server", providers=[app])
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Custom HTML Apps](/apps/low-level)** — When you need your own HTML, CSS, and JavaScript
|
||||
- **[Prefab UI Docs](https://prefab.prefect.io)** — Components, state, expressions, and actions
|
||||
- **[FastMCPApp](/apps/interactive-apps)** — Managed tool binding for server-connected UIs
|
||||
- **[Development](/apps/development)** — Preview app tools locally with `fastmcp dev apps`
|
||||
- **[Prefab UI Docs](https://prefab.prefect.io)** — Full component reference, layout guides, and more
|
||||
|
|
|
|||
|
|
@ -1,44 +1,33 @@
|
|||
---
|
||||
title: Prefab Apps
|
||||
sidebarTitle: Prefab Apps
|
||||
description: Build interactive tool UIs in pure Python — no HTML or JavaScript required.
|
||||
title: Prefab UI
|
||||
sidebarTitle: Prefab UI
|
||||
description: The component library behind FastMCP apps — charts, tables, dashboards, forms, and reactive displays.
|
||||
icon: palette
|
||||
tag: SOON
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.1.0" />
|
||||
|
||||
<Tip>
|
||||
[Prefab](https://prefab.prefect.io) is in extremely early, active development — its API changes frequently and breaking changes can occur with any release. The FastMCP integration is equally new and under rapid development. These docs are included for users who want to work on the cutting edge; production use is not recommended. Always pin `prefab-ui` to a specific version in your dependencies (see below).
|
||||
</Tip>
|
||||
<Warning>
|
||||
[Prefab](https://prefab.prefect.io) is in early, active development — breaking changes can occur with any release. FastMCP pins a minimum version of `prefab-ui` for compatibility but does not pin an upper bound. If you are deploying to production, **pin `prefab-ui` to a specific version** in your own dependencies.
|
||||
</Warning>
|
||||
|
||||
[Prefab UI](https://prefab.prefect.io) is a declarative UI framework for Python. You describe what your interface should look like — a chart, a table, a form — and return it from your tool. FastMCP takes care of everything else: registering the renderer, wiring the protocol metadata, and delivering the component tree to the host.
|
||||
[Prefab UI](https://prefab.prefect.io) is the component library behind all FastMCP app features. You describe layouts, charts, tables, and forms in Python, and Prefab compiles them to interactive UIs that render in the host's conversation.
|
||||
|
||||
Prefab started as a component library inside FastMCP and grew into a full framework for building interactive applications — with its own state management, reactive expression system, and action model. The [Prefab documentation](https://prefab.prefect.io) covers all of this in depth. This page focuses on the FastMCP integration: what you return from a tool, and what FastMCP does with it.
|
||||
The simplest way to use it: add `app=True` to a tool and return Prefab components. The host renders an interactive UI instead of text. This works for everything from static charts to reactive dashboards with client-side state — no server round-trips needed.
|
||||
|
||||
```bash
|
||||
pip install "fastmcp[apps]"
|
||||
```
|
||||
For apps that need server interaction (forms, search, CRUD), see [FastMCPApp](/apps/interactive-apps) which adds managed tool binding on top of Prefab UI. For LLM-generated UIs, see [Generative UI](/apps/generative).
|
||||
|
||||
<Tip>
|
||||
Prefab UI is in active early development and its API changes frequently. We strongly recommend pinning `prefab-ui` to a specific version in your project's dependencies. Installing `fastmcp[apps]` pulls in `prefab-ui` but won't pin it — so a routine `pip install --upgrade` could introduce breaking changes.
|
||||
## Getting Started
|
||||
|
||||
```toml
|
||||
# pyproject.toml
|
||||
dependencies = [
|
||||
"fastmcp[apps]",
|
||||
"prefab-ui==0.8.0", # pin to a known working version
|
||||
]
|
||||
```
|
||||
</Tip>
|
||||
|
||||
Here's the simplest possible Prefab App — a tool that returns a bar chart:
|
||||
Here's a tool that returns a bar chart:
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Column, Heading, BarChart, ChartSeries
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading
|
||||
from prefab_ui.components.charts import BarChart, ChartSeries
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Dashboard")
|
||||
|
|
@ -65,67 +54,240 @@ def revenue_chart(year: int) -> PrefabApp:
|
|||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
That's it — you declare a layout using Python's `with` statement, and return it. When the host calls this tool, the user sees an interactive bar chart instead of a JSON blob. The [Patterns](/apps/patterns) page has more examples: area charts, data tables, forms, status dashboards, and more.
|
||||
The `app=True` flag tells FastMCP this tool returns a UI. When a host calls the tool, the user sees an interactive chart instead of a JSON blob. The [Patterns](/apps/patterns) page has more examples.
|
||||
|
||||
## What You Return
|
||||
## Layout and Components
|
||||
|
||||
### Components
|
||||
|
||||
The simplest way to get started. If you're returning a visual representation of data and don't need Prefab's more advanced features like initial state or stylesheets, just return the components directly. FastMCP wraps them in a `PrefabApp` automatically:
|
||||
Prefab uses Python's `with` statement to express nesting. Containers like `Column`, `Row`, and `Grid` collect their children automatically:
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Column, Heading, Badge
|
||||
from fastmcp import FastMCP
|
||||
from prefab_ui.components import (
|
||||
Column, Row, Grid, Heading, Text, Muted, Badge,
|
||||
Card, CardContent, Separator,
|
||||
)
|
||||
|
||||
mcp = FastMCP("Status")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def status_badge() -> Column:
|
||||
"""Show system status."""
|
||||
with Column(gap=2) as view:
|
||||
Heading("All Systems Operational")
|
||||
Badge("Healthy", variant="success")
|
||||
return view
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Team Status")
|
||||
Separator()
|
||||
with Grid(columns=2, gap=4):
|
||||
with Card():
|
||||
with CardContent():
|
||||
Text("API Gateway", css_class="font-medium")
|
||||
Badge("healthy", variant="success")
|
||||
with Card():
|
||||
with CardContent():
|
||||
Text("Cache", css_class="font-medium")
|
||||
Badge("degraded", variant="destructive")
|
||||
```
|
||||
|
||||
Want a chart? Return a chart. Want a table? Return a table. FastMCP handles the wiring.
|
||||
|
||||
### PrefabApp
|
||||
|
||||
When you need more control — setting initial state values that components can read and react to, or configuring the rendering engine — return a `PrefabApp` explicitly:
|
||||
You can also use Python loops to generate components at build time:
|
||||
|
||||
```python
|
||||
services = [
|
||||
{"name": "API", "status": "healthy", "ok": True},
|
||||
{"name": "Cache", "status": "degraded", "ok": False},
|
||||
]
|
||||
|
||||
with Grid(columns=2, gap=4):
|
||||
for svc in services:
|
||||
with Card():
|
||||
with CardContent():
|
||||
Text(svc["name"])
|
||||
Badge(
|
||||
svc["status"],
|
||||
variant="success" if svc["ok"] else "destructive",
|
||||
)
|
||||
```
|
||||
|
||||
Build-time loops produce static content — the data is baked into the component tree at construction time. For dynamic iteration over state that changes at render time, use `ForEach` (covered below).
|
||||
|
||||
The full component library — layout containers, data display, charts, forms, overlays — is documented in the [Prefab component reference](https://prefab.prefect.io/docs/components).
|
||||
|
||||
## State and Reactivity
|
||||
|
||||
Display tools can be interactive without calling the server. The key is **state** — a client-side key-value store that lives in the browser. Components read from state, actions mutate it, and the UI re-renders automatically.
|
||||
|
||||
### Declaring State
|
||||
|
||||
Pass a `state` dict to `PrefabApp` to declare initial state, then use `Rx("key")` to create reactive references:
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Column, Heading, Text, Button, If, Badge
|
||||
from prefab_ui.actions import ToggleState
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading, Switch, Alert, If
|
||||
from prefab_ui.rx import Rx
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Demo")
|
||||
mcp = FastMCP("Flags")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def toggle_demo() -> PrefabApp:
|
||||
"""Interactive toggle with state."""
|
||||
def feature_flags() -> PrefabApp:
|
||||
"""Toggle feature flags with live preview."""
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Button("Toggle", on_click=ToggleState("show"))
|
||||
with If("{{ show }}"):
|
||||
Badge("Visible!", variant="success")
|
||||
Heading("Feature Flags")
|
||||
Switch(name="dark_mode", label="Dark Mode")
|
||||
Switch(name="beta", label="Beta Features")
|
||||
|
||||
return PrefabApp(view=view, state={"show": False})
|
||||
with If(Rx("dark_mode")):
|
||||
Alert(title="Dark mode enabled")
|
||||
with If(Rx("beta")):
|
||||
Alert(title="Beta features active", variant="warning")
|
||||
|
||||
return PrefabApp(view=view, state={"dark_mode": False, "beta": False})
|
||||
```
|
||||
|
||||
The `state` dict provides the initial values. Components reference state with `{{ expression }}` templates. State mutations like `ToggleState` happen entirely in the browser — no server round-trip. The [Prefab state guide](https://prefab.prefect.io/docs/concepts/state) covers this in detail.
|
||||
Three things to notice here:
|
||||
|
||||
### ToolResult
|
||||
The `state` dict on `PrefabApp` declares the keys and their starting values. `Rx("dark_mode")` creates a reactive reference that compiles to `{{ dark_mode }}` in the wire protocol.
|
||||
|
||||
Every tool result has two audiences: the renderer (which displays the UI) and the LLM (which reads the text content to understand what happened). By default, Prefab Apps send `"[Rendered Prefab UI]"` as the text content, which tells the LLM almost nothing.
|
||||
Interactive components with a `name` prop automatically bind to state. The `Switch(name="dark_mode")` syncs its on/off value to the `dark_mode` state key on every toggle — no event wiring needed.
|
||||
|
||||
If you want the LLM to understand the result — so it can reference the data in conversation, summarize it, or decide what to do next — wrap your return in a `ToolResult` with a meaningful `content` string:
|
||||
`If(Rx("dark_mode"))` shows its children only when the state key is truthy. When the switch flips, the condition re-evaluates instantly in the browser.
|
||||
|
||||
### Reactive References with Rx
|
||||
|
||||
The `Rx` class is how you reference state in component props:
|
||||
|
||||
```python
|
||||
from prefab_ui.rx import Rx
|
||||
|
||||
count = Rx("count")
|
||||
```
|
||||
|
||||
Rx objects support arithmetic, comparisons, and formatting — they compile to expressions the renderer evaluates at render time:
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Column, Heading, BarChart, ChartSeries
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Text, Slider
|
||||
from prefab_ui.rx import Rx
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Calculator")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def tip_calculator() -> PrefabApp:
|
||||
"""Calculate tip with a slider."""
|
||||
tip_pct = Rx("tip_pct")
|
||||
bill = Rx("bill")
|
||||
|
||||
tip_amount = tip_pct / 100 * bill
|
||||
total = bill + tip_amount
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Slider(name="bill", label="Bill Amount", min=0, max=500, step=0.5)
|
||||
Slider(name="tip_pct", label="Tip %", min=0, max=50)
|
||||
Text(f"Tip: {tip_amount.currency()}")
|
||||
Text(f"Total: {total.currency()}")
|
||||
|
||||
return PrefabApp(view=view, state={"bill": 50.00, "tip_pct": 18})
|
||||
```
|
||||
|
||||
`Rx("tip_pct") / 100 * Rx("bill")` builds a compound expression — it doesn't do the math in Python. The renderer evaluates it live as the sliders move. The `.currency()` pipe formats the result as currency.
|
||||
|
||||
#### Pipes
|
||||
|
||||
Rx objects support formatting pipes that transform values at render time:
|
||||
|
||||
```python
|
||||
from prefab_ui.rx import Rx
|
||||
|
||||
price = Rx("price")
|
||||
ratio = Rx("ratio")
|
||||
name = Rx("name")
|
||||
|
||||
price.currency() # $42.50
|
||||
price.currency("EUR") # EUR format
|
||||
ratio.percent() # 85%
|
||||
name.upper() # ALICE
|
||||
name.truncate(10) # alice (or truncated if longer)
|
||||
```
|
||||
|
||||
Number pipes include `currency`, `percent`, `number`, `compact`, `round`, and `abs`. String pipes include `upper`, `lower`, and `truncate`. See the [Prefab expression docs](https://prefab.prefect.io/docs/concepts/expressions) for the full list.
|
||||
|
||||
#### Conditionals
|
||||
|
||||
The `.then()` method creates ternary expressions:
|
||||
|
||||
```python
|
||||
from prefab_ui.rx import Rx
|
||||
|
||||
connected = Rx("connected")
|
||||
|
||||
Badge(
|
||||
connected.then("Online", "Offline"),
|
||||
variant=connected.then("success", "destructive"),
|
||||
)
|
||||
```
|
||||
|
||||
### Dynamic Iteration with ForEach
|
||||
|
||||
Python `for` loops generate static content at build time. When you need to iterate over state that can change — a list that grows, items that get filtered — use `ForEach`:
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading, ForEach, Row, Text, Badge
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Directory")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def team_list() -> PrefabApp:
|
||||
"""Show the current team."""
|
||||
members = [
|
||||
{"name": "Alice", "role": "Engineering"},
|
||||
{"name": "Bob", "role": "Design"},
|
||||
]
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Team")
|
||||
with ForEach("members") as member:
|
||||
with Row(gap=2, align="center"):
|
||||
Text(member.name, css_class="font-medium")
|
||||
Badge(member.role)
|
||||
|
||||
return PrefabApp(view=view, state={"members": members})
|
||||
```
|
||||
|
||||
`ForEach("members")` iterates over the `members` state key. The `as member` gives you an Rx proxy scoped to each item, so `member.name` resolves to `{{ $item.name }}` in the wire protocol. If the `members` state changes (e.g., through an action), the list re-renders automatically.
|
||||
|
||||
### Conditional Rendering
|
||||
|
||||
`If`, `Elif`, and `Else` control what's visible based on state:
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Select, SelectOption, If, Elif, Else, Text
|
||||
from prefab_ui.rx import Rx
|
||||
|
||||
tier = Rx("tier")
|
||||
|
||||
with Column(gap=4) as view:
|
||||
with Select(name="tier", label="Plan"):
|
||||
SelectOption("Free", value="free")
|
||||
SelectOption("Pro", value="pro")
|
||||
SelectOption("Enterprise", value="enterprise")
|
||||
with If(tier == "enterprise"):
|
||||
Text("Full access to all features")
|
||||
with Elif(tier == "pro"):
|
||||
Text("Advanced features unlocked")
|
||||
with Else():
|
||||
Text("Basic features only")
|
||||
|
||||
# Pass state={"tier": "free"} to PrefabApp when returning
|
||||
```
|
||||
|
||||
Changes are instant — switching the dropdown re-evaluates the conditions in the browser.
|
||||
|
||||
## Giving the LLM Context
|
||||
|
||||
By default, Prefab sends `"[Rendered Prefab UI]"` as the text content for the LLM. If the model needs to reason about the data, wrap your return in a `ToolResult` with a meaningful summary:
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading
|
||||
from prefab_ui.components.charts import BarChart, ChartSeries
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.tools import ToolResult
|
||||
|
||||
|
|
@ -148,11 +310,28 @@ def sales_overview(year: int) -> ToolResult:
|
|||
)
|
||||
```
|
||||
|
||||
The user sees the chart. The LLM sees `"Total revenue for 2025: $203,000 across 4 quarters"` and can reason about it.
|
||||
The user sees the chart. The LLM sees the summary string.
|
||||
|
||||
## Type Inference
|
||||
## Advanced
|
||||
|
||||
If your tool's return type annotation is a Prefab type — `PrefabApp`, `Component`, or their `Optional` variants — FastMCP detects this and enables app rendering automatically:
|
||||
<Accordion title="Customizing CSP">
|
||||
`app=True` auto-wires the Prefab renderer with default CSP settings. If your app loads external resources — embedding iframes, fetching from APIs, loading scripts — use `PrefabAppConfig` to add the required domains:
|
||||
|
||||
```python
|
||||
from fastmcp.apps import PrefabAppConfig, ResourceCSP
|
||||
|
||||
@mcp.tool(app=PrefabAppConfig(
|
||||
csp=ResourceCSP(frame_domains=["https://example.com"]),
|
||||
))
|
||||
def dashboard_with_embed() -> PrefabApp:
|
||||
...
|
||||
```
|
||||
|
||||
`PrefabAppConfig()` with no arguments is equivalent to `app=True`. It auto-sets the renderer URI and merges the renderer's CSP with any additional domains you provide.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Type inference">
|
||||
If your return type annotation is a Prefab type — `PrefabApp`, `Component`, or unions containing them — FastMCP enables app rendering automatically, even without `app=True`:
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
|
|
@ -160,24 +339,14 @@ def greet(name: str) -> PrefabApp:
|
|||
return PrefabApp(view=Heading(f"Hello, {name}!"))
|
||||
```
|
||||
|
||||
This is equivalent to `@mcp.tool(app=True)`. Explicit `app=True` is recommended for clarity, and is required when the return type doesn't reveal a Prefab type (e.g., `-> ToolResult`).
|
||||
Explicit `app=True` is recommended for clarity.
|
||||
</Accordion>
|
||||
|
||||
## How It Works
|
||||
|
||||
Behind the scenes, when a tool returns a Prefab component or `PrefabApp`, FastMCP:
|
||||
|
||||
1. **Registers a shared renderer** — a `ui://prefab/renderer.html` resource containing the JavaScript rendering engine, fetched once by the host and reused across all your Prefab tools.
|
||||
2. **Wires the tool metadata** — so the host knows to load the renderer iframe when displaying the tool result.
|
||||
3. **Serializes the component tree** — your Python components become `structuredContent` on the tool result, which the renderer interprets and displays.
|
||||
|
||||
None of this requires any configuration. The `app=True` flag (or type inference) is the only thing you need.
|
||||
|
||||
## Mixing with Custom HTML Apps
|
||||
|
||||
Prefab tools and [custom HTML tools](/apps/low-level) coexist in the same server. Prefab tools share a single renderer resource; custom tools point to their own. Both use the same MCP Apps protocol:
|
||||
<Accordion title="Mixing with custom HTML">
|
||||
Prefab tools and [custom HTML tools](/apps/low-level) coexist on the same server:
|
||||
|
||||
```python
|
||||
from fastmcp.server.apps import AppConfig
|
||||
from fastmcp.apps import AppConfig
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def team_directory() -> PrefabApp:
|
||||
|
|
@ -187,9 +356,11 @@ def team_directory() -> PrefabApp:
|
|||
def map_view() -> str:
|
||||
...
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Patterns](/apps/patterns)** — Charts, tables, forms, and other common tool UIs
|
||||
- **[Custom HTML Apps](/apps/low-level)** — When you need your own HTML, CSS, and JavaScript
|
||||
- **[Prefab UI Docs](https://prefab.prefect.io)** — Components, state, expressions, and actions
|
||||
- **[FastMCPApp](/apps/interactive-apps)** — Managed tool binding for apps with heavy server interaction
|
||||
- **[Patterns](/apps/patterns)** — Charts, tables, dashboards, and other common examples
|
||||
- **[Development](/apps/development)** — Preview app tools locally with `fastmcp dev apps`
|
||||
- **[Prefab UI Docs](https://prefab.prefect.io)** — Full component reference, advanced state patterns, and more
|
||||
|
|
|
|||
80
docs/apps/providers/approval.mdx
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
---
|
||||
title: Approval
|
||||
sidebarTitle: Approval
|
||||
description: Human-in-the-loop approval gates for agent actions
|
||||
icon: shield-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
`Approval` adds a human-in-the-loop confirmation step to any server. The LLM presents what it's about to do, the user approves or rejects via buttons, and the decision flows back into the conversation as a message.
|
||||
|
||||
<Frame>
|
||||
<img src="/apps/images/app-approval.png" alt="The Approval provider shown in Goose, with a payment confirmation card and Approve/Cancel buttons" />
|
||||
</Frame>
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.approval import Approval
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(Approval())
|
||||
```
|
||||
|
||||
This registers a single tool:
|
||||
|
||||
| Tool | Visibility | Purpose |
|
||||
|------|-----------|---------|
|
||||
| `request_approval` | Model | Shows an approval card, sends the user's decision back as a message |
|
||||
|
||||
The LLM calls `request_approval` with a summary (and optional details) whenever it's about to take a significant action. The user sees a card with Approve and Reject buttons. Clicking either sends a message back into the conversation via `SendMessage`, which triggers the LLM's next turn.
|
||||
|
||||
The message looks like it came from the user:
|
||||
|
||||
```
|
||||
"Deploy v3.2 to production" — I selected: Approve
|
||||
```
|
||||
|
||||
<Note>
|
||||
Approval is an advisory gate, not an enforcement mechanism. The conversation isn't blocked while the card is open — the user can keep typing, and a determined LLM could proceed without waiting. Think of it as a strong UX signal that encourages confirmation, not a security boundary. For hard enforcement, implement approval logic server-side in your tool implementations.
|
||||
</Note>
|
||||
|
||||
## Configuration
|
||||
|
||||
The constructor sets defaults; the LLM can override all of these per-call via tool arguments.
|
||||
|
||||
```python
|
||||
Approval(
|
||||
name="Approval", # App name
|
||||
title="Approval Required", # Card heading
|
||||
approve_text="Approve", # Approve button label
|
||||
reject_text="Reject", # Reject button label
|
||||
approve_variant="default", # "default", "destructive", "success", "info"
|
||||
reject_variant="outline", # same options plus "outline"
|
||||
)
|
||||
```
|
||||
|
||||
The LLM can customize each invocation:
|
||||
|
||||
```python
|
||||
request_approval(
|
||||
summary="Delete 47 files from /tmp",
|
||||
details="This cannot be undone.",
|
||||
title="Destructive Action",
|
||||
approve_text="Delete",
|
||||
approve_variant="destructive",
|
||||
reject_text="Keep files",
|
||||
)
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
When the user clicks a button, two things happen:
|
||||
|
||||
1. `SendMessage` pushes the decision into the conversation as a user message
|
||||
2. `SetState("decided", True)` replaces the buttons with "Response sent."
|
||||
|
||||
The tool description instructs the LLM to stop and wait for the "I selected:" message before proceeding. If approved, it continues. If rejected, it acknowledges and asks how to proceed.
|
||||
72
docs/apps/providers/choice.mdx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
title: Choice
|
||||
sidebarTitle: Choice
|
||||
description: Present clickable options instead of free-text responses
|
||||
icon: list-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
`Choice` lets the LLM present a set of options as clickable buttons instead of asking the user to type a response. The selection flows back into the conversation as a message, giving the LLM clean structured input.
|
||||
|
||||
<Frame>
|
||||
<img src="/apps/images/app-choice.png" alt="The Choice provider shown in Goose, with four lunch options as clickable buttons" />
|
||||
</Frame>
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.choice import Choice
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(Choice())
|
||||
```
|
||||
|
||||
This registers a single tool:
|
||||
|
||||
| Tool | Visibility | Purpose |
|
||||
|------|-----------|---------|
|
||||
| `choose` | Model | Shows a card with clickable options, sends the selection back as a message |
|
||||
|
||||
The LLM calls `choose` with a prompt and a list of options. The user sees a card with one button per option. Clicking one sends a message back into the conversation:
|
||||
|
||||
```
|
||||
"Which deployment strategy?" — I selected: Blue-green
|
||||
```
|
||||
|
||||
<Note>
|
||||
This is an advisory interaction, not an enforcement mechanism. The conversation isn't blocked while the card is open — the user can keep typing, and the LLM could proceed without waiting. The tool description instructs the LLM to stop and wait for the "I selected:" response, but for hard enforcement, implement selection logic server-side.
|
||||
</Note>
|
||||
|
||||
## Configuration
|
||||
|
||||
The constructor sets defaults; the LLM can override `title` per-call.
|
||||
|
||||
```python
|
||||
Choice(
|
||||
name="Choice", # App name
|
||||
title="Choose an Option", # Default card heading
|
||||
variant="outline", # Button style for all options
|
||||
)
|
||||
```
|
||||
|
||||
The LLM provides the options per-call:
|
||||
|
||||
```python
|
||||
choose(
|
||||
prompt="What should we have for lunch?",
|
||||
options=["Pizza", "Tacos", "Ramen", "Salad"],
|
||||
title="The Important Questions",
|
||||
)
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
Each option renders as a full-width button in a vertical stack. When the user clicks one:
|
||||
|
||||
1. `SendMessage` pushes the selection into the conversation as a user message
|
||||
2. `SetState("decided", True)` replaces the buttons with "Response sent."
|
||||
|
||||
The tool description instructs the LLM to stop and wait for the "I selected:" message before proceeding with whatever the user chose.
|
||||
129
docs/apps/providers/file-upload.mdx
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
---
|
||||
title: File Upload
|
||||
sidebarTitle: File Upload
|
||||
description: Drag-and-drop file upload for any MCP server
|
||||
icon: upload
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
`FileUpload` adds drag-and-drop file upload to any server. Users upload files through an interactive UI, bypassing the LLM context window entirely. The LLM can then list and read uploaded files through model-visible tools.
|
||||
|
||||
<Frame>
|
||||
<img src="/apps/images/app-file-upload.png" alt="The FileUpload provider shown in Goose, with a drag-and-drop zone for uploading files" />
|
||||
</Frame>
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.file_upload import FileUpload
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(FileUpload())
|
||||
```
|
||||
|
||||
This registers four tools:
|
||||
|
||||
| Tool | Visibility | Purpose |
|
||||
|------|-----------|---------|
|
||||
| `file_manager` | Model | Opens the drag-and-drop upload UI |
|
||||
| `store_files` | App only | Called by the UI when the user clicks Upload |
|
||||
| `list_files` | Model | Returns metadata for all uploaded files |
|
||||
| `read_file` | Model | Returns a file's contents by name |
|
||||
|
||||
The LLM sees `file_manager`, `list_files`, and `read_file`. It calls `file_manager` to show the upload interface, then uses `list_files` and `read_file` to work with whatever the user uploaded. `store_files` is app-only — the UI calls it directly and the LLM never needs to know about it.
|
||||
|
||||
## Configuration
|
||||
|
||||
```python
|
||||
FileUpload(
|
||||
name="Files", # App name (used in tool routing)
|
||||
max_file_size=10 * 1024 * 1024, # 10 MB default, enforced server-side
|
||||
title="File Upload", # Heading shown in the UI
|
||||
description="Drop files to...", # Description text below the heading
|
||||
drop_label="Drop files here", # Label inside the drop zone
|
||||
)
|
||||
```
|
||||
|
||||
The `max_file_size` limit is enforced both in the UI (the DropZone rejects oversized files) and on the server (the `store_files` tool validates before calling `on_store`).
|
||||
|
||||
## Storage Scoping
|
||||
|
||||
By default, files are stored in memory and scoped by MCP session ID. Each session gets its own isolated file store — files uploaded in one conversation aren't visible in another.
|
||||
|
||||
This works with **stdio**, **SSE**, and **stateful HTTP** transports, where sessions persist across requests.
|
||||
|
||||
<Warning>
|
||||
In **stateless HTTP** mode, each request creates a new session object with a new ID. Files stored during one request (e.g. the UI upload) will be invisible to the next request (e.g. the LLM calling `list_files`). You **must** override `_get_scope_key` to use a stable identifier like a user ID from your auth token.
|
||||
</Warning>
|
||||
|
||||
For stateless deployments, override `_get_scope_key` to return a stable identifier. For example, to scope files by authenticated user:
|
||||
|
||||
```python
|
||||
from fastmcp.apps.file_upload import FileUpload
|
||||
|
||||
class UserScopedUpload(FileUpload):
|
||||
def _get_scope_key(self, ctx):
|
||||
return ctx.access_token["sub"]
|
||||
```
|
||||
|
||||
For process-wide shared storage (all users see all files):
|
||||
|
||||
```python
|
||||
class SharedUpload(FileUpload):
|
||||
def _get_scope_key(self, ctx):
|
||||
return "__shared__"
|
||||
```
|
||||
|
||||
## Custom Storage
|
||||
|
||||
The default implementation stores files in memory for the lifetime of the server process. For persistent storage, subclass `FileUpload` and override three methods. Each receives the current `Context`, giving you access to session IDs, auth tokens, and request metadata for partitioning and authorization.
|
||||
|
||||
```python
|
||||
import base64
|
||||
|
||||
from fastmcp.apps.file_upload import FileUpload
|
||||
|
||||
class S3Upload(FileUpload):
|
||||
def on_store(self, files, ctx):
|
||||
user_id = ctx.access_token["sub"]
|
||||
for f in files:
|
||||
s3.put_object(
|
||||
Bucket="uploads",
|
||||
Key=f"{user_id}/{f['name']}",
|
||||
Body=base64.b64decode(f["data"]),
|
||||
)
|
||||
return self.on_list(ctx)
|
||||
|
||||
def on_list(self, ctx):
|
||||
user_id = ctx.access_token["sub"]
|
||||
objects = s3.list_objects(Bucket="uploads", Prefix=f"{user_id}/")
|
||||
return [
|
||||
{
|
||||
"name": obj["Key"].split("/", 1)[1],
|
||||
"type": "application/octet-stream",
|
||||
"size": obj["Size"],
|
||||
"size_display": f"{obj['Size']} B",
|
||||
"uploaded_at": obj["LastModified"].isoformat(),
|
||||
}
|
||||
for obj in objects.get("Contents", [])
|
||||
]
|
||||
|
||||
def on_read(self, name, ctx):
|
||||
user_id = ctx.access_token["sub"]
|
||||
obj = s3.get_object(Bucket="uploads", Key=f"{user_id}/{name}")
|
||||
content = obj["Body"].read()
|
||||
return {
|
||||
"name": name,
|
||||
"size": obj["ContentLength"],
|
||||
"type": obj["ContentType"],
|
||||
"uploaded_at": obj["LastModified"].isoformat(),
|
||||
"content": content.decode("utf-8"),
|
||||
}
|
||||
```
|
||||
|
||||
Each file dict passed to `on_store` contains `name`, `size`, `type`, and `data` (base64-encoded content). The return value from `on_store` and `on_list` should be a list of summary dicts with `name`, `type`, `size`, `size_display`, and `uploaded_at` fields — these populate the file list in the UI.
|
||||
|
||||
`on_read` returns a dict with file metadata and either `content` (decoded text) or `content_base64` (a base64 preview for binary files).
|
||||
105
docs/apps/providers/form.mdx
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
---
|
||||
title: Form Input
|
||||
sidebarTitle: Form Input
|
||||
description: Collect structured data from users via Pydantic models
|
||||
icon: rectangle-list
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
`FormInput` generates a validated form from a Pydantic model. The user fills it out, and the submission is validated against the model before being returned. Structured elicitation that can't be hallucinated.
|
||||
|
||||
<Frame>
|
||||
<img src="/apps/images/app-form.png" alt="The FormInput provider shown in Goose, with a bug report form" />
|
||||
</Frame>
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.form import FormInput
|
||||
|
||||
class BugReport(BaseModel):
|
||||
title: str = Field(description="Brief summary")
|
||||
severity: Literal["low", "medium", "high", "critical"]
|
||||
description: str = Field(
|
||||
description="Detailed description",
|
||||
json_schema_extra={"ui": {"type": "textarea"}},
|
||||
)
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(FormInput(model=BugReport))
|
||||
```
|
||||
|
||||
This registers two tools:
|
||||
|
||||
| Tool | Visibility | Purpose |
|
||||
|------|-----------|---------|
|
||||
| `collect_bugreport` | Model | Opens the form UI |
|
||||
| `submit_form` | App only | Validates and processes the submission |
|
||||
|
||||
The tool name is derived from the model class name, lowercased: `collect_{modelname}`. So `BugReport` becomes `collect_bugreport`, `ShippingAddress` becomes `collect_shippingaddress`. Use `tool_name` to override if needed. The LLM calls it with a prompt explaining what it needs, and the user gets a form with fields matching the model.
|
||||
|
||||
## Field Mapping
|
||||
|
||||
`FormInput` uses Prefab's `Form.from_model()`, which maps Pydantic types to form components:
|
||||
|
||||
| Python type | Form component |
|
||||
|------------|---------------|
|
||||
| `str` | Text input |
|
||||
| `int`, `float` | Number input |
|
||||
| `bool` | Checkbox |
|
||||
| `datetime.date` | Date picker |
|
||||
| `Literal[...]` | Select dropdown |
|
||||
| `SecretStr` | Password input |
|
||||
|
||||
Use `Field()` metadata to control labels (`title`), placeholders (`description`), and validation (`min_length`, `max_length`, `ge`, `le`). Use `json_schema_extra={"ui": {"type": "textarea"}}` for multiline text.
|
||||
|
||||
## Callback
|
||||
|
||||
By default, the validated model is returned as JSON. Provide an `on_submit` callback to process the data server-side:
|
||||
|
||||
```python
|
||||
def save_report(report: BugReport) -> str:
|
||||
db.insert(report.model_dump())
|
||||
return f"Bug #{db.last_id} filed: {report.title}"
|
||||
|
||||
mcp.add_provider(FormInput(model=BugReport, on_submit=save_report))
|
||||
```
|
||||
|
||||
The callback receives a validated model instance and returns a string that becomes the tool result.
|
||||
|
||||
## Configuration
|
||||
|
||||
```python
|
||||
FormInput(
|
||||
model=BugReport, # Required: the Pydantic model
|
||||
name="BugTracker", # App name (default: model name)
|
||||
title="File a Bug", # Card heading (default: model name)
|
||||
tool_name="file_bug", # Tool name (default: collect_{model})
|
||||
submit_text="Submit Report", # Button label (default: "Submit")
|
||||
on_submit=save_report, # Optional callback
|
||||
send_message=True, # Push result as a chat message
|
||||
)
|
||||
```
|
||||
|
||||
Set `send_message=True` to push the result back into the conversation via `SendMessage`, triggering the LLM's next turn. Without it, the result is just the tool return value.
|
||||
|
||||
## Multiple Forms
|
||||
|
||||
Add multiple providers for different models — each gets its own tool:
|
||||
|
||||
```python
|
||||
mcp = FastMCP(
|
||||
"My Server",
|
||||
providers=[
|
||||
FormInput(model=ShippingAddress),
|
||||
FormInput(model=BugReport),
|
||||
FormInput(model=ContactInfo),
|
||||
],
|
||||
)
|
||||
```
|
||||
74
docs/apps/providers/generative.mdx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
---
|
||||
title: Generative UI
|
||||
sidebarTitle: Generative UI
|
||||
description: Let the LLM generate custom UIs at runtime
|
||||
icon: wand-magic-sparkles
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
`GenerativeUI` lets the LLM write Prefab Python code at runtime and render it as a streaming interactive UI. Instead of calling pre-built tools with fixed interfaces, the model creates tailored visualizations for whatever data it's working with.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.generative import GenerativeUI
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(GenerativeUI())
|
||||
```
|
||||
|
||||
This registers:
|
||||
|
||||
| Component | Type | Purpose |
|
||||
|-----------|------|---------|
|
||||
| `generate_prefab_ui` | Tool | Accepts Python code, executes in Pyodide sandbox, renders result |
|
||||
| `search_prefab_components` | Tool | Lets the LLM discover available Prefab components |
|
||||
| Generative renderer | Resource | `ui://` resource with browser-side Pyodide for streaming |
|
||||
|
||||
The LLM writes real Python — loops, f-strings, computation — using Prefab's component library (charts, tables, forms, cards, layout primitives). As the model generates tokens, the host streams partial code to the renderer via `ontoolinputpartial`, so the user watches the UI build up in real time.
|
||||
|
||||
## Configuration
|
||||
|
||||
```python
|
||||
GenerativeUI(
|
||||
tool_name="generate_prefab_ui", # Rename the generation tool
|
||||
components_tool_name="search_prefab_components", # Rename the search tool
|
||||
include_components_tool=True, # Set False to omit the search tool
|
||||
)
|
||||
```
|
||||
|
||||
## What the LLM Sees
|
||||
|
||||
The tool description includes code examples that teach the LLM the Prefab patterns. The LLM calls `generate_prefab_ui` with a `code` argument containing Prefab Python, and optionally a `data` argument to pass in real data from the conversation:
|
||||
|
||||
```python
|
||||
# The LLM generates something like:
|
||||
generate_prefab_ui(
|
||||
code="""
|
||||
from prefab_ui.components import Column, Heading
|
||||
from prefab_ui.components.charts import BarChart, ChartSeries
|
||||
from prefab_ui.app import PrefabApp
|
||||
|
||||
with PrefabApp() as app:
|
||||
with Column(gap=4):
|
||||
Heading("Revenue")
|
||||
BarChart(data=data, series=[ChartSeries(data_key="revenue")], x_axis="quarter")
|
||||
""",
|
||||
data={"data": [{"quarter": "Q1", "revenue": 42000}, ...]}
|
||||
)
|
||||
```
|
||||
|
||||
The component search tool lets the LLM discover what's available before writing code — `search_prefab_components("Chart")` returns matching components with import paths.
|
||||
|
||||
## Requirements
|
||||
|
||||
Requires `fastmcp[apps]` (installs `prefab-ui`). The Pyodide sandbox for server-side validation requires Deno, which installs automatically on first use. The streaming renderer loads Pyodide from CDN in the browser — CSP is configured automatically.
|
||||
|
||||
The sandbox includes the Python standard library and Prefab. External packages (NumPy, pandas, etc.) are not available.
|
||||
|
||||
## Learn More
|
||||
|
||||
The full **[Generative UI guide](/apps/generative)** covers the streaming mechanics in detail, how to pass data, the component search tool, and sandbox limitations.
|
||||
208
docs/apps/quickstart.mdx
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
---
|
||||
title: Quickstart
|
||||
sidebarTitle: Quickstart
|
||||
description: Build your first MCP app in under a minute.
|
||||
icon: rocket
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
MCP tools normally return text. FastMCP apps return interactive UIs rendered directly in the conversation: charts, tables, forms, dashboards. The easiest way to build one is with [Prefab UI](https://prefab.prefect.io), a Python component library designed for exactly this. You describe the UI in Python; Prefab compiles it to something the host can render.
|
||||
|
||||
This tutorial builds a working app from scratch. Here's what you'll have in about a minute:
|
||||
|
||||
<Frame>
|
||||
<img src="/apps/images/app-quickstart.png" alt="A team directory app with a pie chart and sortable data table, rendered inside a conversation in Goose" />
|
||||
</Frame>
|
||||
|
||||
## Setup
|
||||
|
||||
Install FastMCP with the `apps` extra, which pulls in Prefab UI:
|
||||
|
||||
```bash
|
||||
pip install "fastmcp[apps]"
|
||||
```
|
||||
|
||||
## A Tool That Returns a UI
|
||||
|
||||
When your tool has something to *show* (a table of results, a chart, a status dashboard) you can return an interactive UI instead of text. Build the visualization with Prefab components, return it from your tool, and set `app=True` so FastMCP knows to render it. The user sees a live, interactive widget right in the conversation instead of a wall of JSON.
|
||||
|
||||
Create `server.py`:
|
||||
|
||||
```python server.py expandable
|
||||
from collections import Counter
|
||||
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Grid, Heading, DataTable, DataTableColumn
|
||||
from prefab_ui.components.charts import PieChart
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My First App")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def team_directory() -> PrefabApp:
|
||||
"""Browse the team directory."""
|
||||
members = [
|
||||
{"name": "Alice Chen", "role": "Staff Engineer", "office": "San Francisco"},
|
||||
{"name": "Bob Martinez", "role": "Lead Designer", "office": "New York"},
|
||||
{"name": "Carol Johnson", "role": "Senior Engineer", "office": "London"},
|
||||
{"name": "David Kim", "role": "Product Manager", "office": "San Francisco"},
|
||||
{"name": "Eva Mueller", "role": "Engineer", "office": "Berlin"},
|
||||
{"name": "Frank Lee", "role": "Data Scientist", "office": "San Francisco"},
|
||||
{"name": "Grace Park", "role": "Engineering Manager", "office": "New York"},
|
||||
]
|
||||
|
||||
office_counts = [
|
||||
{"office": office, "count": count}
|
||||
for office, count in Counter(m["office"] for m in members).items()
|
||||
]
|
||||
|
||||
with PrefabApp() as app:
|
||||
with Column(gap=4, css_class="p-6"):
|
||||
Heading("Team Directory")
|
||||
with Grid(columns=[1, 2], gap=4):
|
||||
PieChart(
|
||||
data=office_counts,
|
||||
data_key="count",
|
||||
name_key="office",
|
||||
show_legend=True,
|
||||
)
|
||||
DataTable(
|
||||
columns=[
|
||||
DataTableColumn(key="name", header="Name", sortable=True),
|
||||
DataTableColumn(key="role", header="Role", sortable=True),
|
||||
DataTableColumn(key="office", header="Office", sortable=True),
|
||||
],
|
||||
rows=members,
|
||||
search=True,
|
||||
)
|
||||
|
||||
return app
|
||||
```
|
||||
|
||||
That `app=True` is doing a lot behind the scenes. It tells FastMCP to set up everything the MCP Apps protocol requires: the renderer resource, the content security policy, the metadata that tells the host "this tool returns a UI." Without it, you'd wire all of that up by hand. With it, you just return Prefab components and FastMCP handles the rest. The host (Claude Desktop, Goose, etc.) loads the result in a sandboxed iframe where the user can sort columns, search, and interact, all client-side with no round-trips to your server.
|
||||
|
||||
The Prefab code itself reads top-to-bottom like a document. `PrefabApp()` is the root container and everything inside its `with` block becomes the app's UI. `Column` arranges children vertically. `Heading` renders a title. `DataTable` takes rows of data and column definitions, and gives you sorting and search for free. The `with` blocks establish parent-child relationships: nesting components inside each other builds the layout tree.
|
||||
|
||||
## Running It
|
||||
|
||||
FastMCP includes a dev server that renders your app tools in a browser, no MCP host needed:
|
||||
|
||||
```bash
|
||||
fastmcp dev apps server.py
|
||||
```
|
||||
|
||||
This opens `http://localhost:8080` where you can pick a tool and see the rendered UI. Try sorting the table columns and typing in the search box.
|
||||
|
||||
## Making It Interactive
|
||||
|
||||
The table above is a static snapshot that renders once from the data your Python code provides. But Prefab apps can also respond to user interaction in real time, without any server round-trips.
|
||||
|
||||
The key concept is **state**: a client-side key-value store that components read from and write to. When the user interacts with a component, it updates state. Other components that reference that state re-render instantly. See the [Prefab state docs](https://prefab.prefect.io/docs/concepts/state) for the full guide.
|
||||
|
||||
Here's the same directory, but now clicking a row shows that person's details in a card:
|
||||
|
||||
<Frame>
|
||||
<img src="/apps/images/app-quickstart-dev-2.png" alt="The team directory with a detail card showing after clicking Bob Martinez" />
|
||||
</Frame>
|
||||
|
||||
```python expandable server.py
|
||||
from collections import Counter
|
||||
|
||||
from prefab_ui.actions import SetState
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Card, CardContent, CardHeader, Column, Grid, H3, Heading, Muted,
|
||||
Row, DataTable, DataTableColumn, Badge, Small, Text,
|
||||
)
|
||||
from prefab_ui.components.charts import PieChart
|
||||
from prefab_ui.components.control_flow import If
|
||||
from prefab_ui.rx import Rx, STATE
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My First App")
|
||||
|
||||
MEMBERS = [
|
||||
{"name": "Alice Chen", "role": "Staff Engineer", "office": "San Francisco", "email": "alice@company.com", "projects": 3},
|
||||
{"name": "Bob Martinez", "role": "Lead Designer", "office": "New York", "email": "bob@company.com", "projects": 5},
|
||||
{"name": "Carol Johnson", "role": "Senior Engineer", "office": "London", "email": "carol@company.com", "projects": 2},
|
||||
{"name": "David Kim", "role": "Product Manager", "office": "San Francisco", "email": "david@company.com", "projects": 7},
|
||||
{"name": "Eva Mueller", "role": "Engineer", "office": "Berlin", "email": "eva@company.com", "projects": 1},
|
||||
{"name": "Frank Lee", "role": "Data Scientist", "office": "San Francisco", "email": "frank@company.com", "projects": 4},
|
||||
{"name": "Grace Park", "role": "Engineering Manager", "office": "New York", "email": "grace@company.com", "projects": 6},
|
||||
]
|
||||
|
||||
OFFICE_COUNTS = [
|
||||
{"office": office, "count": count}
|
||||
for office, count in Counter(m["office"] for m in MEMBERS).items()
|
||||
]
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def team_directory() -> PrefabApp:
|
||||
"""Browse the team directory."""
|
||||
with PrefabApp(state={"selected": None}) as app:
|
||||
with Column(gap=4, css_class="p-6"):
|
||||
Heading("Team Directory")
|
||||
with Grid(columns=[1, 2], gap=4):
|
||||
PieChart(
|
||||
data=OFFICE_COUNTS,
|
||||
data_key="count",
|
||||
name_key="office",
|
||||
show_legend=True,
|
||||
)
|
||||
DataTable(
|
||||
columns=[
|
||||
DataTableColumn(key="name", header="Name", sortable=True),
|
||||
DataTableColumn(key="role", header="Role", sortable=True),
|
||||
DataTableColumn(key="office", header="Office", sortable=True),
|
||||
],
|
||||
rows=MEMBERS,
|
||||
search=True,
|
||||
on_row_click=SetState("selected", Rx("$event")),
|
||||
)
|
||||
|
||||
with If(STATE.selected):
|
||||
with Card():
|
||||
with CardHeader():
|
||||
with Row(gap=2, align="center"):
|
||||
H3(Rx("selected.name"))
|
||||
Badge(Rx("selected.office"))
|
||||
with CardContent():
|
||||
with Grid(columns=3, gap=4):
|
||||
with Column(gap=0):
|
||||
Small("Role")
|
||||
Text(Rx("selected.role"))
|
||||
with Column(gap=0):
|
||||
Small("Email")
|
||||
Text(Rx("selected.email"))
|
||||
with Column(gap=0):
|
||||
Small("Active Projects")
|
||||
Text(Rx("selected.projects"))
|
||||
|
||||
return app
|
||||
```
|
||||
|
||||
Three new ideas here:
|
||||
|
||||
**`SetState` + `on_row_click`** is the interaction. When the user clicks a table row, `SetState("selected", Rx("$event"))` writes the clicked row's data into the `selected` state key. `$event` is a special variable that contains the event payload (in this case, the row dict).
|
||||
|
||||
**`Rx("selected.name")`** reads from state reactively. It doesn't hold a Python value. It compiles to a browser-side expression that re-evaluates live whenever `selected` changes. So `Text(Rx("selected.name"))` always shows the name of whoever was last clicked.
|
||||
|
||||
**`If(STATE.selected)`** conditionally renders the detail card only when something has been selected. Before any click, `selected` is `None` and the card is hidden.
|
||||
|
||||
The `state` dict on `PrefabApp` sets initial values when the app loads. Run `fastmcp dev apps server.py` again and try clicking a row.
|
||||
|
||||
## Next Steps
|
||||
|
||||
You've built a tool that returns an interactive, reactive UI. This pattern covers a huge range of use cases: build a visualization in Prefab, return it from a tool, and the user gets dashboards, charts, data tables, and status displays right in the conversation.
|
||||
|
||||
When you need the UI to talk back to your server (forms that save data, buttons that trigger actions, search that queries a database) you promote the tool to a **[FastMCPApp](/apps/interactive-apps)**. That gives you managed backend tools, automatic visibility control, and stable routing so your UI's button clicks reach the right server-side code.
|
||||
|
||||
- **[Prefab UI](/apps/prefab)** covers the full component library: charts, forms, badges, progress bars, and the [reactive state system](https://prefab.prefect.io/docs/concepts/state) in depth.
|
||||
- **[FastMCPApp](/apps/interactive-apps)** is the next step when your UI needs to interact with backend logic.
|
||||
- **[App Providers](/apps/providers/approval)** are ready-made capabilities you can add with a single `add_provider()` call.
|
||||
|
|
@ -5,6 +5,86 @@ rss: true
|
|||
tag: NEW
|
||||
---
|
||||
|
||||
<Update label="v3.1.1" description="2026-03-14">
|
||||
|
||||
**[v3.1.1: 'Tis But a Patch](https://github.com/PrefectHQ/fastmcp/releases/tag/v3.1.1)**
|
||||
|
||||
Pins `pydantic-monty` below 0.0.8 to fix a breaking change in Monty that affects code mode. Monty 0.0.8 removed the `external_functions` constructor parameter, causing `MontySandboxProvider` to fail. This patch caps the version so existing installs work correctly.
|
||||
|
||||
### Fixes 🐞
|
||||
* Pin pydantic-monty below 0.0.8 to fix code mode by [@jlowin](https://github.com/jlowin) in [#3497](https://github.com/PrefectHQ/fastmcp/pull/3497)
|
||||
|
||||
**Full Changelog**: [v3.1.0...v3.1.1](https://github.com/PrefectHQ/fastmcp/compare/v3.1.0...v3.1.1)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="v3.1.0" description="2026-03-03">
|
||||
|
||||
**[v3.1.0: Code to Joy](https://github.com/PrefectHQ/fastmcp/releases/tag/v3.1.0)**
|
||||
|
||||
FastMCP 3.1 is the Code Mode release. The 3.0 architecture introduced providers and transforms as the extensibility layer — 3.1 puts that architecture to work, shipping the most requested capability since launch: servers that can find and execute code on behalf of agents, without requiring clients to know what tools exist.
|
||||
|
||||
### New Features 🎉
|
||||
* feat: Search transforms for tool discovery by [@jlowin](https://github.com/jlowin) in [#3154](https://github.com/PrefectHQ/fastmcp/pull/3154)
|
||||
* Add experimental CodeMode transform by [@aaazzam](https://github.com/aaazzam) in [#3297](https://github.com/PrefectHQ/fastmcp/pull/3297)
|
||||
* Add Prefab Apps integration for MCP tool UIs by [@jlowin](https://github.com/jlowin) in [#3316](https://github.com/PrefectHQ/fastmcp/pull/3316)
|
||||
### Enhancements 🔧
|
||||
* Lazy-load heavy imports to reduce import time by [@jlowin](https://github.com/jlowin) in [#3295](https://github.com/PrefectHQ/fastmcp/pull/3295)
|
||||
* Add http_client parameter to all token verifiers for connection pooling by [@jlowin](https://github.com/jlowin) in [#3300](https://github.com/PrefectHQ/fastmcp/pull/3300)
|
||||
* Add in-memory caching for token introspection results by [@jlowin](https://github.com/jlowin) in [#3298](https://github.com/PrefectHQ/fastmcp/pull/3298)
|
||||
* Add SessionStart hook to install gh CLI in cloud sessions by [@jlowin](https://github.com/jlowin) in [#3308](https://github.com/PrefectHQ/fastmcp/pull/3308)
|
||||
* Fix ty 0.0.19 type errors by [@jlowin](https://github.com/jlowin) in [#3310](https://github.com/PrefectHQ/fastmcp/pull/3310)
|
||||
* Code Mode: Add resource limits to MontySandboxProvider by [@jlowin](https://github.com/jlowin) in [#3326](https://github.com/PrefectHQ/fastmcp/pull/3326)
|
||||
* Accept transforms as FastMCP init kwarg by [@jlowin](https://github.com/jlowin) in [#3324](https://github.com/PrefectHQ/fastmcp/pull/3324)
|
||||
* Split large test files to comply with loq line limit by [@jlowin](https://github.com/jlowin) in [#3328](https://github.com/PrefectHQ/fastmcp/pull/3328)
|
||||
* Add -m/--module flag to `fastmcp run` and `dev inspector` by [@dgenio](https://github.com/dgenio) in [#3331](https://github.com/PrefectHQ/fastmcp/pull/3331)
|
||||
* Add search_result_serializer hook and serialize_tools_for_output_markdown by [@MagnusS0](https://github.com/MagnusS0) in [#3337](https://github.com/PrefectHQ/fastmcp/pull/3337)
|
||||
* Add MultiAuth for composing multiple token verification sources by [@jlowin](https://github.com/jlowin) in [#3335](https://github.com/PrefectHQ/fastmcp/pull/3335)
|
||||
* Adds PropelAuth as an AuthProvider by [@andrew-propelauth](https://github.com/andrew-propelauth) in [#3358](https://github.com/PrefectHQ/fastmcp/pull/3358)
|
||||
* Replace vendored DI with uncalled-for by [@chrisguidry](https://github.com/chrisguidry) in [#3301](https://github.com/PrefectHQ/fastmcp/pull/3301)
|
||||
* Decompose CodeMode into composable discovery tools by [@jlowin](https://github.com/jlowin) in [#3354](https://github.com/PrefectHQ/fastmcp/pull/3354)
|
||||
* feat(contrib): auto-sync MCPMixin decorators with from_function signatures by [@AnkeshThakur](https://github.com/AnkeshThakur) in [#3323](https://github.com/PrefectHQ/fastmcp/pull/3323)
|
||||
* Add Google GenAI Sampling Handler by [@strawgate](https://github.com/strawgate) in [#2977](https://github.com/PrefectHQ/fastmcp/pull/2977)
|
||||
* Add ListTools, search limit, and catalog size annotation to CodeMode by [@jlowin](https://github.com/jlowin) in [#3359](https://github.com/PrefectHQ/fastmcp/pull/3359)
|
||||
* Allow configuring FastMCP transport setting in the same way as other configuration by [@jvdmr](https://github.com/jvdmr) in [#1796](https://github.com/PrefectHQ/fastmcp/pull/1796)
|
||||
* Add include_unversioned option to VersionFilter by [@yangbaechu](https://github.com/yangbaechu) in [#3349](https://github.com/PrefectHQ/fastmcp/pull/3349)
|
||||
### Fixes 🐞
|
||||
* Fix docs banner pushing nav down by [@jlowin](https://github.com/jlowin) in [#3282](https://github.com/PrefectHQ/fastmcp/pull/3282)
|
||||
* fix: Replace hardcoded TTL with DEFAULT_TTL_MS - issue #3279 by [@cedric57](https://github.com/cedric57) in [#3280](https://github.com/PrefectHQ/fastmcp/pull/3280)
|
||||
* fix: stop suppressing server stderr in fastmcp call by [@jlowin](https://github.com/jlowin) in [#3283](https://github.com/PrefectHQ/fastmcp/pull/3283)
|
||||
* fix: skip max_completion_tokens when maxTokens is None by [@eon01](https://github.com/eon01) in [#3284](https://github.com/PrefectHQ/fastmcp/pull/3284)
|
||||
* OpenAPI: rewrite $ref under propertyNames and patternProperties in _replace_ref_with_defs; add regression test for dict[StrEnum, Model] by [@manojPal23234](https://github.com/manojPal23234) in [#3306](https://github.com/PrefectHQ/fastmcp/pull/3306)
|
||||
* Remove stale add_resource() key parameter from docs by [@jlowin](https://github.com/jlowin) in [#3309](https://github.com/PrefectHQ/fastmcp/pull/3309)
|
||||
* Handle AuthorizationError as exclusion in AuthMiddleware list hooks by [@yangbaechu](https://github.com/yangbaechu) in [#3338](https://github.com/PrefectHQ/fastmcp/pull/3338)
|
||||
* Fix flaky OpenAPI performance test threshold by [@jlowin](https://github.com/jlowin) in [#3355](https://github.com/PrefectHQ/fastmcp/pull/3355)
|
||||
* Fix flaky SSE timeout test by [@jlowin](https://github.com/jlowin) in [#3343](https://github.com/PrefectHQ/fastmcp/pull/3343)
|
||||
* Remove system role references from docs by [@jlowin](https://github.com/jlowin) in [#3356](https://github.com/PrefectHQ/fastmcp/pull/3356)
|
||||
* Fix session persistence across tool calls in multi-server MCPConfigTransport by [@jer805](https://github.com/jer805) in [#3330](https://github.com/PrefectHQ/fastmcp/pull/3330)
|
||||
### Docs 📚
|
||||
* Add v3.0.2 release notes by [@jlowin](https://github.com/jlowin) in [#3276](https://github.com/PrefectHQ/fastmcp/pull/3276)
|
||||
* Fix "FastMCP Constructor Parameters" in documentation server.mdx (Remove old parameters & Add new parameter) by [@wangyy04](https://github.com/wangyy04) in [#3317](https://github.com/PrefectHQ/fastmcp/pull/3317)
|
||||
* Fix stale docs: tag filtering API and missing output_schema param by [@jlowin](https://github.com/jlowin) in [#3322](https://github.com/PrefectHQ/fastmcp/pull/3322)
|
||||
* Narrate search example clients by [@jlowin](https://github.com/jlowin) in [#3321](https://github.com/PrefectHQ/fastmcp/pull/3321)
|
||||
* Code Mode: Document resource limits and fix docs formatting by [@jlowin](https://github.com/jlowin) in [#3327](https://github.com/PrefectHQ/fastmcp/pull/3327)
|
||||
* Add reverse proxy (nginx) section to HTTP deployment docs by [@dgenio](https://github.com/dgenio) in [#3344](https://github.com/PrefectHQ/fastmcp/pull/3344)
|
||||
* Restructure docs navigation: CLI section, Composition, More by [@jlowin](https://github.com/jlowin) in [#3361](https://github.com/PrefectHQ/fastmcp/pull/3361)
|
||||
### Other Changes 🦾
|
||||
* Don't advertise sampling.tools capability by default by [@jlowin](https://github.com/jlowin) in [#3334](https://github.com/PrefectHQ/fastmcp/pull/3334)
|
||||
|
||||
## New Contributors
|
||||
* @cedric57 made their first contribution in [#3280](https://github.com/PrefectHQ/fastmcp/pull/3280)
|
||||
* @eon01 made their first contribution in [#3284](https://github.com/PrefectHQ/fastmcp/pull/3284)
|
||||
* @manojPal23234 made their first contribution in [#3306](https://github.com/PrefectHQ/fastmcp/pull/3306)
|
||||
* @wangyy04 made their first contribution in [#3317](https://github.com/PrefectHQ/fastmcp/pull/3317)
|
||||
* @yangbaechu made their first contribution in [#3338](https://github.com/PrefectHQ/fastmcp/pull/3338)
|
||||
* @andrew-propelauth made their first contribution in [#3358](https://github.com/PrefectHQ/fastmcp/pull/3358)
|
||||
* @jer805 made their first contribution in [#3330](https://github.com/PrefectHQ/fastmcp/pull/3330)
|
||||
* @jvdmr made their first contribution in [#1796](https://github.com/PrefectHQ/fastmcp/pull/1796)
|
||||
|
||||
**Full Changelog**: [v3.0.2...v3.1.0](https://github.com/PrefectHQ/fastmcp/compare/v3.0.2...v3.1.0)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="v3.0.2" description="2026-02-22">
|
||||
|
||||
**[v3.0.2: Threecovery Mode II](https://github.com/PrefectHQ/fastmcp/releases/tag/v3.0.2)**
|
||||
|
|
@ -663,6 +743,21 @@ Breaking changes are minimal: for most servers, updating the import statement is
|
|||
|
||||
</Update>
|
||||
|
||||
<Update label="v2.14.6" description="2026-03-27">
|
||||
|
||||
**[v2.14.6: $Ref Dead Redemption](https://github.com/PrefectHQ/fastmcp/releases/tag/v2.14.6)**
|
||||
|
||||
v2.14.4 backported `dereference_refs()` but never wired it into the tool schema pipeline — `$ref` and `$defs` were still sent to MCP clients. Now fixed: `compress_schema()` dereferences at both tool schema creation sites, so schemas are fully inlined before reaching clients.
|
||||
|
||||
### Fixes 🐞
|
||||
* Updated deprecation URL for V2 by [@SrzStephen](https://github.com/SrzStephen) in [#3109](https://github.com/PrefectHQ/fastmcp/pull/3109)
|
||||
* Use MemoryStore for OAuth proxy tests by [@SrzStephen](https://github.com/SrzStephen) in [#3111](https://github.com/PrefectHQ/fastmcp/pull/3111)
|
||||
* fix: wire up dereference_refs() in tool schema pipeline by [@jlowin](https://github.com/jlowin) in [#3170](https://github.com/PrefectHQ/fastmcp/pull/3170)
|
||||
|
||||
**Full Changelog**: [v2.14.5...v2.14.6](https://github.com/PrefectHQ/fastmcp/compare/v2.14.5...v2.14.6)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="v2.14.5" description="2026-02-03">
|
||||
|
||||
**[v2.14.5: Sealed Docket](https://github.com/PrefectHQ/fastmcp/releases/tag/v2.14.5)**
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ See [Server Configuration](/deployment/server-configuration) for the full config
|
|||
| Python | `--python` | Python version (e.g., `3.11`) |
|
||||
| Project | `--project` | Run within a uv project directory |
|
||||
| Requirements | `--with-requirements` | Install from a requirements file |
|
||||
| Config Path | `--config-path` | Custom path to Claude Desktop config directory (`claude-desktop` only) |
|
||||
|
||||
## Examples
|
||||
|
||||
|
|
@ -92,6 +93,10 @@ fastmcp install cursor server.py --env-file .env
|
|||
fastmcp install claude-desktop server.py \
|
||||
--python 3.11 \
|
||||
--with-requirements requirements.txt
|
||||
|
||||
# With custom config path (claude-desktop only)
|
||||
fastmcp install claude-desktop server.py \
|
||||
--config-path "C:\Users\username\AppData\Local\Packages\Claude_xyz\LocalCache\Roaming\Claude"
|
||||
```
|
||||
|
||||
## Generating MCP JSON
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ fastmcp --help
|
|||
| Command | What it does |
|
||||
| ------- | ------------ |
|
||||
| [`run`](/cli/running) | Run a server (local file, factory function, remote URL, or config file) |
|
||||
| [`dev apps`](/cli/running#previewing-apps) | Launch a browser-based preview UI for Prefab App tools |
|
||||
| [`dev inspector`](/cli/running#development-with-the-inspector) | Launch a server inside the MCP Inspector for interactive testing |
|
||||
| [`install`](/cli/install-mcp) | Install a server into Claude Code, Claude Desktop, Cursor, Gemini CLI, or Goose |
|
||||
| [`inspect`](/cli/inspecting) | Print a server's tools, resources, and prompts as a summary or JSON report |
|
||||
|
|
|
|||
|
|
@ -96,6 +96,31 @@ By default, `fastmcp run` uses your current Python environment directly. When yo
|
|||
|
||||
The `--skip-env` flag is useful when you're already inside an activated venv, a Docker container with pre-installed dependencies, or a uv-managed project — it prevents uv from trying to set up another environment layer.
|
||||
|
||||
## Previewing Apps
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
`fastmcp dev apps` launches a browser-based preview UI for servers with [Prefab App tools](/apps/prefab). It starts your MCP server on one port and a local dev UI on another — giving you a live, interactive picker where you can call app tools and see their rendered output without needing a full MCP host client.
|
||||
|
||||
```bash
|
||||
fastmcp dev apps server.py
|
||||
fastmcp dev apps server.py:mcp --mcp-port 9000 --dev-port 9090
|
||||
```
|
||||
|
||||
The picker auto-generates a form from each tool's input schema. Submit the form and the result opens in a new tab as a rendered Prefab UI.
|
||||
|
||||
Auto-reload is on by default — save a file and the MCP server restarts automatically.
|
||||
|
||||
<Tip>
|
||||
`fastmcp dev apps` requires `fastmcp[apps]` — install with `pip install "fastmcp[apps]"`.
|
||||
</Tip>
|
||||
|
||||
| Option | Flag | Description |
|
||||
| ------ | ---- | ----------- |
|
||||
| MCP Port | `--mcp-port` | Port for the MCP server (default: `8000`) |
|
||||
| Dev Port | `--dev-port` | Port for the dev UI (default: `8080`) |
|
||||
| Auto-Reload | `--reload` / `--no-reload` | Watch for file changes (default: on) |
|
||||
|
||||
## Development with the Inspector
|
||||
|
||||
`fastmcp dev inspector` launches your server inside the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), a browser-based tool for interactively testing MCP servers. Auto-reload is on by default, so your server restarts when you save changes.
|
||||
|
|
|
|||
|
|
@ -124,6 +124,38 @@ client = Client(
|
|||
)
|
||||
```
|
||||
|
||||
### SSL Verification
|
||||
|
||||
By default, HTTPS connections verify the server's SSL certificate. You can customize this behavior with the `verify` parameter, which accepts the same values as [httpx](https://www.python-httpx.org/advanced/ssl/):
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
# Disable SSL verification (e.g., for self-signed certs in development)
|
||||
client = Client("https://dev-server.internal/mcp", verify=False)
|
||||
|
||||
# Use a custom CA bundle
|
||||
client = Client("https://corp-server.internal/mcp", verify="/path/to/ca-bundle.pem")
|
||||
|
||||
# Use a custom SSL context for full control
|
||||
import ssl
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.load_verify_locations("/path/to/internal-ca.pem")
|
||||
client = Client("https://corp-server.internal/mcp", verify=ctx)
|
||||
```
|
||||
|
||||
The `verify` parameter is also available directly on `StreamableHttpTransport` and `SSETransport`:
|
||||
|
||||
```python
|
||||
from fastmcp.client.transports import StreamableHttpTransport
|
||||
|
||||
transport = StreamableHttpTransport(
|
||||
url="https://dev-server.internal/mcp",
|
||||
verify=False,
|
||||
)
|
||||
client = Client(transport)
|
||||
```
|
||||
|
||||
### SSE Transport
|
||||
|
||||
Server-Sent Events transport is maintained for backward compatibility. Use Streamable HTTP for new deployments unless you have specific infrastructure requirements.
|
||||
|
|
|
|||
|
|
@ -115,6 +115,10 @@ async def health_check(request):
|
|||
|
||||
This health endpoint will be available at `http://localhost:8000/health` and can be used by load balancers, monitoring systems, or deployment platforms to verify your server is running.
|
||||
|
||||
<Note>
|
||||
Custom routes are never protected by the server's authentication middleware, even when an `AuthProvider` is configured. This is by design — the primary use case for custom routes is unauthenticated operational endpoints like health checks and readiness probes. If you need authenticated HTTP endpoints alongside your MCP server, [mount it in a FastAPI app](/integrations/fastapi) and use FastAPI's `Depends()` for auth on your routes.
|
||||
</Note>
|
||||
|
||||
### Custom Middleware
|
||||
|
||||
|
||||
|
|
@ -625,18 +629,18 @@ You might expect sticky sessions (session affinity) to solve this, but they don'
|
|||
|
||||
For horizontally scaled deployments, enable stateless HTTP mode. In stateless mode, each request creates a fresh transport context, eliminating the need for session affinity entirely.
|
||||
|
||||
**Option 1: Via constructor**
|
||||
**Option 1: Via `http_app()`**
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My Server", stateless_http=True)
|
||||
mcp = FastMCP("My Server")
|
||||
|
||||
@mcp.tool
|
||||
def process(data: str) -> str:
|
||||
return f"Processed: {data}"
|
||||
|
||||
app = mcp.http_app()
|
||||
app = mcp.http_app(stateless_http=True)
|
||||
```
|
||||
|
||||
**Option 2: Via `run()`**
|
||||
|
|
|
|||
109
docs/docs.json
|
|
@ -12,7 +12,7 @@
|
|||
"decoration": "gradient"
|
||||
},
|
||||
"banner": {
|
||||
"content": "Deploy FastMCP servers for free on [Prefect Horizon](https://www.prefect.io/horizon)"
|
||||
"content": "Meet [Prefect Horizon](https://prefect.io/horizon?utm_source=gofastmcp&utm_medium=docs&utm_campaign=docs_banner&utm_content=sitewide_banner), the enterprise MCP gateway built by the team behind FastMCP"
|
||||
},
|
||||
"colors": {
|
||||
"dark": "#f72585",
|
||||
|
|
@ -140,8 +140,7 @@
|
|||
"servers/providers/proxy",
|
||||
"servers/providers/skills",
|
||||
"servers/providers/custom"
|
||||
],
|
||||
"tag": "NEW"
|
||||
]
|
||||
},
|
||||
{
|
||||
"collapsed": true,
|
||||
|
|
@ -156,25 +155,30 @@
|
|||
"servers/transforms/tool-search",
|
||||
"servers/transforms/resources-as-tools",
|
||||
"servers/transforms/prompts-as-tools"
|
||||
],
|
||||
"tag": "NEW"
|
||||
]
|
||||
},
|
||||
{
|
||||
"collapsed": true,
|
||||
"group": "Authentication",
|
||||
"icon": "key",
|
||||
"group": "Auth",
|
||||
"icon": "shield-check",
|
||||
"pages": [
|
||||
"servers/auth/authentication",
|
||||
"servers/auth/token-verification",
|
||||
"servers/auth/remote-oauth",
|
||||
"servers/auth/oauth-proxy",
|
||||
"servers/auth/oidc-proxy",
|
||||
"servers/auth/full-oauth-server",
|
||||
"servers/auth/multi-auth"
|
||||
],
|
||||
"tag": "UPDATED"
|
||||
{
|
||||
"collapsed": true,
|
||||
"group": "Authentication",
|
||||
"icon": "key",
|
||||
"pages": [
|
||||
"servers/auth/authentication",
|
||||
"servers/auth/token-verification",
|
||||
"servers/auth/remote-oauth",
|
||||
"servers/auth/oauth-proxy",
|
||||
"servers/auth/oidc-proxy",
|
||||
"servers/auth/full-oauth-server",
|
||||
"servers/auth/multi-auth"
|
||||
]
|
||||
},
|
||||
"servers/authorization"
|
||||
]
|
||||
},
|
||||
"servers/authorization",
|
||||
{
|
||||
"collapsed": true,
|
||||
"group": "Deployment",
|
||||
|
|
@ -192,9 +196,44 @@
|
|||
"group": "Apps",
|
||||
"pages": [
|
||||
"apps/overview",
|
||||
"apps/prefab",
|
||||
"apps/patterns",
|
||||
"apps/low-level"
|
||||
"apps/quickstart",
|
||||
"apps/examples",
|
||||
{
|
||||
"collapsed": true,
|
||||
"group": "Building Apps",
|
||||
"icon": "hammer",
|
||||
"pages": [
|
||||
"apps/prefab",
|
||||
"apps/interactive-apps",
|
||||
"apps/generative",
|
||||
"apps/patterns"
|
||||
],
|
||||
"tag": "NEW"
|
||||
},
|
||||
{
|
||||
"collapsed": true,
|
||||
"group": "Providers",
|
||||
"icon": "layer-group",
|
||||
"pages": [
|
||||
"apps/providers/approval",
|
||||
"apps/providers/choice",
|
||||
"apps/providers/file-upload",
|
||||
"apps/providers/form",
|
||||
"apps/providers/generative"
|
||||
],
|
||||
"tag": "NEW"
|
||||
},
|
||||
{
|
||||
"collapsed": true,
|
||||
"group": "Advanced",
|
||||
"icon": "gear",
|
||||
"pages": [
|
||||
"apps/development",
|
||||
"apps/architecture",
|
||||
"apps/low-level"
|
||||
],
|
||||
"tag": "NEW"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -315,6 +354,7 @@
|
|||
{
|
||||
"group": "More",
|
||||
"pages": [
|
||||
"more/settings",
|
||||
{
|
||||
"collapsed": true,
|
||||
"group": "Upgrading",
|
||||
|
|
@ -362,10 +402,25 @@
|
|||
"python-sdk/fastmcp-mcp_config",
|
||||
"python-sdk/fastmcp-settings",
|
||||
"python-sdk/fastmcp-telemetry",
|
||||
"python-sdk/fastmcp-types",
|
||||
{
|
||||
"group": "fastmcp.apps",
|
||||
"pages": [
|
||||
"python-sdk/fastmcp-apps-__init__",
|
||||
"python-sdk/fastmcp-apps-app",
|
||||
"python-sdk/fastmcp-apps-approval",
|
||||
"python-sdk/fastmcp-apps-choice",
|
||||
"python-sdk/fastmcp-apps-config",
|
||||
"python-sdk/fastmcp-apps-file_upload",
|
||||
"python-sdk/fastmcp-apps-form",
|
||||
"python-sdk/fastmcp-apps-generative"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "fastmcp.cli",
|
||||
"pages": [
|
||||
"python-sdk/fastmcp-cli-__init__",
|
||||
"python-sdk/fastmcp-cli-apps_dev",
|
||||
"python-sdk/fastmcp-cli-auth",
|
||||
"python-sdk/fastmcp-cli-cimd",
|
||||
"python-sdk/fastmcp-cli-cli",
|
||||
|
|
@ -475,16 +530,16 @@
|
|||
"group": "fastmcp.prompts",
|
||||
"pages": [
|
||||
"python-sdk/fastmcp-prompts-__init__",
|
||||
"python-sdk/fastmcp-prompts-function_prompt",
|
||||
"python-sdk/fastmcp-prompts-prompt"
|
||||
"python-sdk/fastmcp-prompts-base",
|
||||
"python-sdk/fastmcp-prompts-function_prompt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "fastmcp.resources",
|
||||
"pages": [
|
||||
"python-sdk/fastmcp-resources-__init__",
|
||||
"python-sdk/fastmcp-resources-base",
|
||||
"python-sdk/fastmcp-resources-function_resource",
|
||||
"python-sdk/fastmcp-resources-resource",
|
||||
"python-sdk/fastmcp-resources-template",
|
||||
"python-sdk/fastmcp-resources-types"
|
||||
]
|
||||
|
|
@ -493,6 +548,7 @@
|
|||
"group": "fastmcp.server",
|
||||
"pages": [
|
||||
"python-sdk/fastmcp-server-__init__",
|
||||
"python-sdk/fastmcp-server-app",
|
||||
"python-sdk/fastmcp-server-apps",
|
||||
{
|
||||
"group": "auth",
|
||||
|
|
@ -521,6 +577,7 @@
|
|||
"python-sdk/fastmcp-server-auth-providers-auth0",
|
||||
"python-sdk/fastmcp-server-auth-providers-aws",
|
||||
"python-sdk/fastmcp-server-auth-providers-azure",
|
||||
"python-sdk/fastmcp-server-auth-providers-clerk",
|
||||
"python-sdk/fastmcp-server-auth-providers-debug",
|
||||
"python-sdk/fastmcp-server-auth-providers-descope",
|
||||
"python-sdk/fastmcp-server-auth-providers-discord",
|
||||
|
|
@ -684,9 +741,9 @@
|
|||
"group": "fastmcp.tools",
|
||||
"pages": [
|
||||
"python-sdk/fastmcp-tools-__init__",
|
||||
"python-sdk/fastmcp-tools-base",
|
||||
"python-sdk/fastmcp-tools-function_parsing",
|
||||
"python-sdk/fastmcp-tools-function_tool",
|
||||
"python-sdk/fastmcp-tools-tool",
|
||||
"python-sdk/fastmcp-tools-tool_transform"
|
||||
]
|
||||
},
|
||||
|
|
@ -734,6 +791,7 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"python-sdk/fastmcp-utilities-mime",
|
||||
{
|
||||
"group": "openapi",
|
||||
"pages": [
|
||||
|
|
@ -750,6 +808,7 @@
|
|||
"python-sdk/fastmcp-utilities-skills",
|
||||
"python-sdk/fastmcp-utilities-tests",
|
||||
"python-sdk/fastmcp-utilities-timeout",
|
||||
"python-sdk/fastmcp-utilities-token_cache",
|
||||
"python-sdk/fastmcp-utilities-types",
|
||||
"python-sdk/fastmcp-utilities-ui",
|
||||
"python-sdk/fastmcp-utilities-version_check",
|
||||
|
|
@ -1050,4 +1109,4 @@
|
|||
"appearance": "light",
|
||||
"background": "/assets/brand/thumbnail-background-4.jpeg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
257
docs/fastmcp-analytics.js
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
(function () {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
// Public browser key for the shared Prefect Amplitude project.
|
||||
// This is intentionally client-side; the secret key must never ship to the browser.
|
||||
var AMPLITUDE_API_KEY = "c361ed56e7bdc1a48a38773c40120b39";
|
||||
var AMPLITUDE_SCRIPT_URL =
|
||||
"https://cdn.amplitude.com/libs/analytics-browser-2.8.1-min.js.gz";
|
||||
var AMPLITUDE_SERVER_URL = "https://api2.amplitude.com/2/httpapi";
|
||||
var PAGE_VIEW_EVENT = "Page View: FastMCP Docs";
|
||||
var OUTBOUND_CLICK_EVENT = "Docs Outbound Clicked";
|
||||
var SOURCE = "docs";
|
||||
var SOURCE_DETAIL = "fastmcp";
|
||||
var SURFACE = "fastmcp_docs";
|
||||
var DEVICE_ID_PARAM = "deviceId";
|
||||
var routeListenersInstalled = false;
|
||||
var amplitudeInitialized = false;
|
||||
var lastTrackedUrl = null;
|
||||
|
||||
var PREFECT_DESTINATION_HOSTNAMES = [
|
||||
"www.prefect.io",
|
||||
"prefect.io",
|
||||
"horizon.prefect.io",
|
||||
"app.prefect.cloud",
|
||||
];
|
||||
|
||||
var routeChangeCallbacks = [];
|
||||
|
||||
function loadScript(src, onload) {
|
||||
var script = document.createElement("script");
|
||||
script.src = src;
|
||||
script.async = true;
|
||||
|
||||
if (typeof onload === "function") {
|
||||
script.addEventListener("load", onload);
|
||||
}
|
||||
|
||||
document.head.appendChild(script);
|
||||
return script;
|
||||
}
|
||||
|
||||
function getAmplitude() {
|
||||
return window.amplitude || window.amplitudeAnalytics;
|
||||
}
|
||||
|
||||
function normalizePathname(pathname) {
|
||||
if (pathname === "/") return pathname;
|
||||
return pathname.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function observeRouteChanges(callback) {
|
||||
routeChangeCallbacks.push(callback);
|
||||
|
||||
if (!routeListenersInstalled) {
|
||||
var fireCallbacks = function () {
|
||||
routeChangeCallbacks.forEach(function (cb) {
|
||||
window.setTimeout(cb, 0);
|
||||
});
|
||||
};
|
||||
|
||||
var wrapHistoryMethod = function (methodName) {
|
||||
var original = window.history[methodName];
|
||||
window.history[methodName] = function () {
|
||||
var result = original.apply(this, arguments);
|
||||
fireCallbacks();
|
||||
return result;
|
||||
};
|
||||
};
|
||||
|
||||
wrapHistoryMethod("pushState");
|
||||
wrapHistoryMethod("replaceState");
|
||||
window.addEventListener("popstate", fireCallbacks);
|
||||
window.addEventListener("hashchange", fireCallbacks);
|
||||
routeListenersInstalled = true;
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
function buildPageViewProperties() {
|
||||
return {
|
||||
url: window.location.href,
|
||||
title: document.title,
|
||||
referrer: document.referrer || null,
|
||||
path: normalizePathname(window.location.pathname),
|
||||
source: SOURCE,
|
||||
source_detail: SOURCE_DETAIL,
|
||||
surface: SURFACE,
|
||||
};
|
||||
}
|
||||
|
||||
function trackPageView() {
|
||||
var amplitude = getAmplitude();
|
||||
if (!amplitude || typeof amplitude.track !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
var url = window.location.href;
|
||||
if (url === lastTrackedUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
amplitude.track(PAGE_VIEW_EVENT, buildPageViewProperties());
|
||||
lastTrackedUrl = url;
|
||||
}
|
||||
|
||||
function parseUrl(href) {
|
||||
try {
|
||||
return new URL(href, window.location.origin);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isPrefectDestination(url) {
|
||||
return PREFECT_DESTINATION_HOSTNAMES.indexOf(url.hostname) !== -1;
|
||||
}
|
||||
|
||||
function addDeviceIdToLink(event) {
|
||||
var amplitude = getAmplitude();
|
||||
if (!amplitude || typeof amplitude.getDeviceId !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
var link = event.currentTarget;
|
||||
var href = link.getAttribute("href") || "";
|
||||
|
||||
var url = parseUrl(href);
|
||||
if (!url || !isPrefectDestination(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
url.searchParams.set(DEVICE_ID_PARAM, amplitude.getDeviceId());
|
||||
link.href = url.toString();
|
||||
}
|
||||
|
||||
function removeDeviceIdFromLink(event) {
|
||||
var link = event.currentTarget;
|
||||
var href = link.getAttribute("href") || "";
|
||||
|
||||
var url = parseUrl(href);
|
||||
if (!url || !isPrefectDestination(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
url.searchParams.delete(DEVICE_ID_PARAM);
|
||||
link.href = url.toString();
|
||||
}
|
||||
|
||||
function attachDeviceIdForwarding() {
|
||||
var elements = document.querySelectorAll("a[href]");
|
||||
elements.forEach(function (element) {
|
||||
if (element.dataset.fastmcpDeviceIdBound === "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
var url = parseUrl(element.getAttribute("href") || "");
|
||||
if (!url || !isPrefectDestination(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.addEventListener("mouseenter", addDeviceIdToLink);
|
||||
element.addEventListener("mouseleave", removeDeviceIdFromLink);
|
||||
element.addEventListener("focus", addDeviceIdToLink);
|
||||
element.addEventListener("blur", removeDeviceIdFromLink);
|
||||
element.addEventListener("touchstart", addDeviceIdToLink);
|
||||
element.addEventListener("touchcancel", removeDeviceIdFromLink);
|
||||
element.dataset.fastmcpDeviceIdBound = "true";
|
||||
});
|
||||
}
|
||||
|
||||
function trackOutboundClick(event) {
|
||||
var link = event.target && event.target.closest
|
||||
? event.target.closest("a[href]")
|
||||
: null;
|
||||
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
|
||||
var href = link.getAttribute("href");
|
||||
if (!href || href[0] === "#") {
|
||||
return;
|
||||
}
|
||||
|
||||
var destination;
|
||||
destination = parseUrl(href);
|
||||
if (!destination) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (destination.hostname === window.location.hostname) {
|
||||
return;
|
||||
}
|
||||
|
||||
var amplitude = getAmplitude();
|
||||
if (!amplitude || typeof amplitude.track !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
amplitude.track(OUTBOUND_CLICK_EVENT, {
|
||||
path: normalizePathname(window.location.pathname),
|
||||
url: window.location.href,
|
||||
title: document.title,
|
||||
source: SOURCE,
|
||||
source_detail: SOURCE_DETAIL,
|
||||
surface: SURFACE,
|
||||
destination: destination.href,
|
||||
destination_domain: destination.hostname,
|
||||
link_text: (link.textContent || "").trim().slice(0, 200),
|
||||
is_prefect_destination: isPrefectDestination(destination),
|
||||
});
|
||||
}
|
||||
|
||||
function initializeAmplitude() {
|
||||
var amplitude = getAmplitude();
|
||||
if (
|
||||
amplitudeInitialized ||
|
||||
!amplitude ||
|
||||
typeof amplitude.init !== "function"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
amplitude.init(AMPLITUDE_API_KEY, undefined, {
|
||||
useBatch: true,
|
||||
serverUrl: AMPLITUDE_SERVER_URL,
|
||||
attribution: {
|
||||
disabled: false,
|
||||
trackNewCampaigns: true,
|
||||
trackPageViews: true,
|
||||
resetSessionOnNewCampaign: true,
|
||||
},
|
||||
defaultTracking: {
|
||||
pageViews: false,
|
||||
sessions: false,
|
||||
formInteractions: true,
|
||||
fileDownloads: true,
|
||||
},
|
||||
});
|
||||
|
||||
amplitudeInitialized = true;
|
||||
observeRouteChanges(trackPageView);
|
||||
observeRouteChanges(attachDeviceIdForwarding);
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
document.addEventListener("click", trackOutboundClick, true);
|
||||
loadScript(AMPLITUDE_SCRIPT_URL, initializeAmplitude);
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initialize);
|
||||
} else {
|
||||
initialize();
|
||||
}
|
||||
})();
|
||||
|
|
@ -3,7 +3,7 @@ title: Quickstart
|
|||
icon: rocket-launch
|
||||
---
|
||||
|
||||
Welcome! This guide will help you quickly set up FastMCP, run your first MCP server, and deploy a server to Prefect Horizon.
|
||||
Welcome! This guide will help you quickly set up FastMCP, run your first MCP server, give it a visual UI, and deploy it to Prefect Horizon.
|
||||
|
||||
If you haven't already installed FastMCP, follow the [installation instructions](/getting-started/installation).
|
||||
|
||||
|
|
@ -117,6 +117,34 @@ Note that:
|
|||
- We must enter a client context (`async with client:`) before using the client
|
||||
- You can make multiple client calls within the same context
|
||||
|
||||
## Give Your Tool a UI
|
||||
|
||||
Tools normally return text, but any tool can return an interactive UI instead. Add `app=True` to your tool decorator and return a [Prefab](https://prefab.prefect.io) component — the host renders it as a chart, table, form, or any other visual element right in the conversation. This requires the `apps` extra (`pip install "fastmcp[apps]"`).
|
||||
|
||||
The `app=True` flag tells FastMCP to wire up the renderer and protocol metadata automatically. The tool still works like any other MCP tool — it receives arguments and returns a result — but the result is a component tree that the host displays visually instead of as plain text.
|
||||
|
||||
```python my_server.py
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading, Text, Badge, Row
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My MCP Server")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def greet(name: str) -> PrefabApp:
|
||||
"""Greet someone with a visual card."""
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading(f"Hello, {name}!")
|
||||
with Row(gap=2, align="center"):
|
||||
Text("Status")
|
||||
Badge("Greeted", variant="success")
|
||||
|
||||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
You can preview app tools locally with `fastmcp dev apps my_server.py` — no MCP host required. See the [Apps overview](/apps/overview) for the full guide, including state management, forms, charts, and server-connected interactivity.
|
||||
|
||||
## Deploy to Prefect Horizon
|
||||
|
||||
[Prefect Horizon](https://horizon.prefect.io) is the enterprise MCP platform built by the FastMCP team at [Prefect](https://www.prefect.io). It provides managed hosting, authentication, access control, and observability for MCP servers.
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ BREAKING CHANGES (will crash at import or runtime):
|
|||
10. DECORATORS: @mcp.tool, @mcp.resource, @mcp.prompt now return the original function, not a component object. Code that accesses .name, .description, or other component attributes on the decorated result will crash with AttributeError.
|
||||
Fix: set FASTMCP_DECORATOR_MODE=object for v2 compat (itself deprecated).
|
||||
|
||||
11. OAUTH STORAGE: Default OAuth client storage changed from DiskStore to FileTreeStore due to pickle deserialization vulnerability in diskcache (CVE-2025-69872). Clients using default storage will re-register automatically on first connection. If using DiskStore explicitly, switch to FileTreeStore or add pip install 'py-key-value-aio[disk]'.
|
||||
11. OAUTH STORAGE: Default OAuth client storage changed from DiskStore to FileTreeStore due to pickle deserialization vulnerability in diskcache (CVE-2025-69872). Clients using default storage will re-register automatically on first connection. If using DiskStore explicitly, switch to FileTreeStore (with key/collection sanitization strategies) or add pip install 'py-key-value-aio[disk]'.
|
||||
|
||||
12. REPO MOVE: GitHub repository moved from jlowin/fastmcp to PrefectHQ/fastmcp. Update git remotes and dependency URLs that reference the old location.
|
||||
|
||||
|
|
@ -126,7 +126,11 @@ The default OAuth client storage has moved from `DiskStore` to `FileTreeStore` t
|
|||
|
||||
If you were using the default storage (i.e., not passing an explicit `client_storage`), clients will need to re-register on their first connection after upgrading. This happens automatically — no user action required, and it's the same flow that already occurs whenever a server restarts with in-memory storage.
|
||||
|
||||
If you were passing a `DiskStore` explicitly, you can either [switch to `FileTreeStore`](/servers/storage-backends) (recommended) or keep using `DiskStore` by adding the dependency yourself:
|
||||
If you were passing a `DiskStore` explicitly, you can either [switch to `FileTreeStore`](/servers/storage-backends) (recommended) or keep using `DiskStore` by adding the dependency yourself.
|
||||
|
||||
<Warning>
|
||||
When switching to `FileTreeStore`, you **must** configure key and collection sanitization strategies. Without them, keys containing special characters (such as URL-based OAuth client IDs) will cause filesystem errors. See the [File Storage](/servers/storage-backends#file-storage) section for the recommended setup.
|
||||
</Warning>
|
||||
|
||||
<Warning>
|
||||
Keeping `DiskStore` requires `pip install 'py-key-value-aio[disk]'`, which re-introduces the vulnerable `diskcache` package into your dependency tree.
|
||||
|
|
|
|||
|
|
@ -80,10 +80,16 @@ FastMCP has three pillars:
|
|||
|
||||
**[Servers](/servers/server)** wrap your Python functions into MCP-compliant tools, resources, and prompts. **[Clients](/clients/client)** connect to any server with full protocol support. And **[Apps](/apps/overview)** give your tools interactive UIs rendered directly in the conversation.
|
||||
|
||||
Ready to build? Start with the [installation guide](/getting-started/installation) or jump straight to the [quickstart](/getting-started/quickstart). When you're ready to deploy, [Prefect Horizon](https://www.prefect.io/horizon) offers free hosting for FastMCP users.
|
||||
Ready to build? Start with the [installation guide](/getting-started/installation) or jump straight to the [quickstart](/getting-started/quickstart).
|
||||
|
||||
FastMCP is made with 💙 by [Prefect](https://www.prefect.io/).
|
||||
|
||||
## Run FastMCP in production with Horizon
|
||||
|
||||
FastMCP is how teams build MCP servers. **[Prefect Horizon](https://www.prefect.io/horizon)** is how enterprises run them in production. Register any MCP server behind a managed gateway with SSO, tool-level RBAC, audit logs, and observability. Deploy FastMCP servers and go from PR to preview in 60 seconds, then remix tools from across your registry into use-case-specific, permissioned endpoints. Horizon is everything we've learned about MCP at scale from building the world's most popular MCP framework. Free for individuals, built for teams.
|
||||
|
||||
[Deploy FastMCP with Horizon →](https://www.prefect.io/horizon)
|
||||
|
||||
<Tip>
|
||||
**This documentation reflects FastMCP's `main` branch**, meaning it always reflects the latest development version. Features are generally marked with version badges (e.g. `New in version: 3.0.0`) to indicate when they were introduced. Note that this may include features that are not yet released.
|
||||
</Tip>
|
||||
|
|
|
|||
96
docs/more/settings.mdx
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
title: Settings
|
||||
description: Configure FastMCP behavior through environment variables or a .env file.
|
||||
icon: gear
|
||||
---
|
||||
|
||||
FastMCP uses [pydantic-settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/) for configuration. Every setting is available as an environment variable with a `FASTMCP_` prefix. Settings are loaded from environment variables and from a `.env` file (see the [Tasks (Docket)](#tasks-docket) section for a caveat about nested settings in `.env` files).
|
||||
|
||||
```bash
|
||||
# Set via environment
|
||||
export FASTMCP_LOG_LEVEL=DEBUG
|
||||
export FASTMCP_PORT=3000
|
||||
|
||||
# Or use a .env file (loaded automatically)
|
||||
echo "FASTMCP_LOG_LEVEL=DEBUG" >> .env
|
||||
```
|
||||
|
||||
You can change which `.env` file is loaded by setting the `FASTMCP_ENV_FILE` environment variable (defaults to `.env`). Because this controls which file is loaded, it must be set as an environment variable — it cannot be set inside a `.env` file itself.
|
||||
|
||||
## Logging
|
||||
|
||||
| Environment Variable | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `FASTMCP_LOG_LEVEL` | `Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]` | `INFO` | Log level for FastMCP's own logging output. Case-insensitive. |
|
||||
| `FASTMCP_LOG_ENABLED` | `bool` | `true` | Enable or disable FastMCP logging entirely. |
|
||||
| `FASTMCP_CLIENT_LOG_LEVEL` | `Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"]` | None | Default minimum log level for messages sent to MCP clients via `context.log()`. When set, messages below this level are suppressed. Individual clients can override this per-session using the MCP `logging/setLevel` request. |
|
||||
| `FASTMCP_ENABLE_RICH_LOGGING` | `bool` | `true` | Use rich formatting for log output. Set to `false` for plain Python logging. |
|
||||
| `FASTMCP_ENABLE_RICH_TRACEBACKS` | `bool` | `true` | Use rich tracebacks for errors. |
|
||||
| `FASTMCP_DEPRECATION_WARNINGS` | `bool` | `true` | Show deprecation warnings. |
|
||||
|
||||
## Transport & HTTP
|
||||
|
||||
These control how the server listens when running with an HTTP transport.
|
||||
|
||||
| Environment Variable | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `FASTMCP_TRANSPORT` | `Literal["stdio", "http", "sse", "streamable-http"]` | `stdio` | Default transport. |
|
||||
| `FASTMCP_HOST` | `str` | `127.0.0.1` | Host to bind to. |
|
||||
| `FASTMCP_PORT` | `int` | `8000` | Port to bind to. |
|
||||
| `FASTMCP_SSE_PATH` | `str` | `/sse` | Path for SSE endpoint. |
|
||||
| `FASTMCP_MESSAGE_PATH` | `str` | `/messages/` | Path for SSE message endpoint. |
|
||||
| `FASTMCP_STREAMABLE_HTTP_PATH` | `str` | `/mcp` | Path for Streamable HTTP endpoint. |
|
||||
| `FASTMCP_STATELESS_HTTP` | `bool` | `false` | Enable stateless HTTP mode (new transport per request). Useful for multi-worker deployments. |
|
||||
| `FASTMCP_JSON_RESPONSE` | `bool` | `false` | Use JSON responses instead of SSE for Streamable HTTP. |
|
||||
| `FASTMCP_DEBUG` | `bool` | `false` | Enable debug mode. |
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Environment Variable | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `FASTMCP_MASK_ERROR_DETAILS` | `bool` | `false` | Mask error details before sending to clients. When enabled, only messages from explicitly raised `ToolError`, `ResourceError`, or `PromptError` are included in responses. |
|
||||
| `FASTMCP_STRICT_INPUT_VALIDATION` | `bool` | `false` | Strictly validate tool inputs against the JSON schema. When disabled, compatible inputs are coerced (e.g., the string `"10"` becomes the integer `10`). |
|
||||
| `FASTMCP_MOUNTED_COMPONENTS_RAISE_ON_LOAD_ERROR` | `bool` | `false` | Raise errors when loading mounted components instead of logging warnings. |
|
||||
|
||||
## Client
|
||||
|
||||
| Environment Variable | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `FASTMCP_CLIENT_INIT_TIMEOUT` | `float \| None` | None | Timeout in seconds for the client initialization handshake. Set to `0` or leave unset to disable. |
|
||||
| `FASTMCP_CLIENT_DISCONNECT_TIMEOUT` | `float` | `5` | Maximum time in seconds to wait for a clean disconnect before giving up. |
|
||||
| `FASTMCP_CLIENT_RAISE_FIRST_EXCEPTIONGROUP_ERROR` | `bool` | `true` | When an `ExceptionGroup` is raised, re-raise the first error directly instead of the group. Simplifies debugging but may mask secondary errors. |
|
||||
|
||||
## CLI & Display
|
||||
|
||||
| Environment Variable | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `FASTMCP_SHOW_SERVER_BANNER` | `bool` | `true` | Show the server banner on startup. Also controllable via `--no-banner` or `server.run(show_banner=False)`. |
|
||||
| `FASTMCP_CHECK_FOR_UPDATES` | `Literal["stable", "prerelease", "off"]` | `stable` | Update checking on CLI startup. `stable` checks stable releases only, `prerelease` includes pre-releases, `off` disables checking. |
|
||||
|
||||
## Tasks (Docket)
|
||||
|
||||
These configure the [Docket](https://github.com/prefecthq/docket) task queue used by [server tasks](/servers/tasks). All use the `FASTMCP_DOCKET_` prefix.
|
||||
|
||||
<Warning>
|
||||
When setting Docket values in a `.env` file, use a **double** underscore: `FASTMCP_DOCKET__URL` (not `FASTMCP_DOCKET_URL`). This is because `.env` values are resolved through the parent `Settings` class, which uses `__` as its nested delimiter. As regular environment variables (e.g., `export`), the single-underscore form `FASTMCP_DOCKET_URL` works fine.
|
||||
</Warning>
|
||||
|
||||
| Environment Variable | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `FASTMCP_DOCKET_NAME` | `str` | `fastmcp` | Queue name. Servers and workers sharing the same name and backend URL share a task queue. |
|
||||
| `FASTMCP_DOCKET_URL` | `str` | `memory://` | Backend URL. Use `memory://` for single-process or `redis://host:port/db` for distributed workers. |
|
||||
| `FASTMCP_DOCKET_WORKER_NAME` | `str \| None` | None | Worker name. Auto-generated if unset. |
|
||||
| `FASTMCP_DOCKET_CONCURRENCY` | `int` | `10` | Maximum concurrent tasks per worker. |
|
||||
| `FASTMCP_DOCKET_REDELIVERY_TIMEOUT` | `timedelta` | `300s` | If a worker doesn't complete a task within this time, it's redelivered to another worker. |
|
||||
| `FASTMCP_DOCKET_RECONNECTION_DELAY` | `timedelta` | `5s` | Delay between reconnection attempts when the worker loses its backend connection. |
|
||||
| `FASTMCP_DOCKET_MINIMUM_CHECK_INTERVAL` | `timedelta` | `50ms` | How frequently the worker polls for new tasks. Lower values reduce latency at the cost of more CPU usage. |
|
||||
|
||||
## Advanced
|
||||
|
||||
| Environment Variable | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `FASTMCP_HOME` | `Path` | Platform default | Data directory for FastMCP. Defaults to the platform-specific user data directory. |
|
||||
| `FASTMCP_ENV_FILE` | `str` | `.env` | Path to the `.env` file to load settings from. Must be set as an environment variable (see above). |
|
||||
| `FASTMCP_SERVER_DEPENDENCIES` | `list[str]` | `[]` | Additional dependencies to install in the server environment. |
|
||||
| `FASTMCP_DECORATOR_MODE` | `Literal["function", "object"]` | `function` | Controls what `@tool`, `@resource`, and `@prompt` decorators return. `function` returns the original function (default); `object` returns component objects (deprecated, will be removed). |
|
||||
| `FASTMCP_TEST_MODE` | `bool` | `false` | Enable test mode. |
|
||||
16
docs/python-sdk/fastmcp-apps-__init__.mdx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: __init__
|
||||
sidebarTitle: __init__
|
||||
---
|
||||
|
||||
# `fastmcp.apps`
|
||||
|
||||
|
||||
FastMCP Apps — interactive UIs for MCP tools.
|
||||
|
||||
This package contains the app-related components:
|
||||
|
||||
- ``FastMCPApp`` — composable provider for interactive apps with backend tools
|
||||
- ``AppConfig`` — configuration for MCP App tools and resources
|
||||
- ``ResourceCSP`` / ``ResourcePermissions`` — security configuration
|
||||
|
||||
146
docs/python-sdk/fastmcp-apps-app.mdx
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
---
|
||||
title: app
|
||||
sidebarTitle: app
|
||||
---
|
||||
|
||||
# `fastmcp.apps.app`
|
||||
|
||||
|
||||
FastMCPApp — a Provider that represents a composable MCP application.
|
||||
|
||||
FastMCPApp binds entry-point tools (model calls these) together with backend
|
||||
tools (the UI calls these via CallTool). Backend tools are tagged with
|
||||
``meta["fastmcp"]["app"]`` so they can be found through the provider chain
|
||||
even when transforms (namespace, visibility, etc.) have renamed or hidden
|
||||
them — the server sets a context var that tells ``Provider.get_tool`` to
|
||||
fall back to a direct lookup for app-visible tools.
|
||||
|
||||
Usage::
|
||||
|
||||
from fastmcp import FastMCP, FastMCPApp
|
||||
|
||||
app = FastMCPApp("Dashboard")
|
||||
|
||||
@app.ui()
|
||||
def show_dashboard() -> Component:
|
||||
return Column(...)
|
||||
|
||||
@app.tool()
|
||||
def save_contact(name: str, email: str) -> str:
|
||||
return name
|
||||
|
||||
server = FastMCP("Platform")
|
||||
server.add_provider(app)
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `FastMCPApp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/app.py#L131" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A Provider that represents an MCP application.
|
||||
|
||||
Binds together entry-point tools (``@app.ui``), backend tools
|
||||
(``@app.tool``), and the Prefab renderer resource. Backend tools
|
||||
are tagged with ``meta["fastmcp"]["app"]`` so ``Provider.get_tool``
|
||||
can find them by original name even when transforms have been applied.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `tool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/app.py#L158" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
tool(self, name_or_fn: F) -> F
|
||||
```
|
||||
|
||||
#### `tool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/app.py#L170" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
tool(self, name_or_fn: str | None = None) -> Callable[[F], F]
|
||||
```
|
||||
|
||||
#### `tool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/app.py#L181" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
tool(self, name_or_fn: str | AnyFunction | None = None) -> Any
|
||||
```
|
||||
|
||||
Register a backend tool that the UI calls via CallTool.
|
||||
|
||||
Backend tools default to ``visibility=["app"]``. Pass ``model=True``
|
||||
to also expose the tool to the model (``visibility=["app", "model"]``).
|
||||
|
||||
Supports multiple calling patterns::
|
||||
|
||||
@app.tool
|
||||
def save(name: str): ...
|
||||
|
||||
@app.tool()
|
||||
def save(name: str): ...
|
||||
|
||||
@app.tool("custom_name")
|
||||
def save(name: str): ...
|
||||
|
||||
|
||||
#### `ui` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/app.py#L242" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
ui(self, name_or_fn: F) -> F
|
||||
```
|
||||
|
||||
#### `ui` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/app.py#L257" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
ui(self, name_or_fn: str | None = None) -> Callable[[F], F]
|
||||
```
|
||||
|
||||
#### `ui` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/app.py#L271" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
ui(self, name_or_fn: str | AnyFunction | None = None) -> Any
|
||||
```
|
||||
|
||||
Register a UI entry-point tool that the model calls.
|
||||
|
||||
Entry-point tools default to ``visibility=["model"]`` and auto-wire
|
||||
the Prefab renderer resource and CSP. They are tagged with the app
|
||||
name so structured content includes ``_meta.fastmcp.app``.
|
||||
|
||||
Supports multiple calling patterns::
|
||||
|
||||
@app.ui
|
||||
def dashboard() -> Component: ...
|
||||
|
||||
@app.ui()
|
||||
def dashboard() -> Component: ...
|
||||
|
||||
@app.ui("my_dashboard")
|
||||
def dashboard() -> Component: ...
|
||||
|
||||
|
||||
#### `add_tool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/app.py#L360" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_tool(self, tool: Tool | Callable[..., Any]) -> Tool
|
||||
```
|
||||
|
||||
Add a tool to this app programmatically.
|
||||
|
||||
The tool is tagged with this app's name for routing.
|
||||
|
||||
|
||||
#### `lifespan` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/app.py#L410" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
lifespan(self) -> AsyncIterator[None]
|
||||
```
|
||||
|
||||
#### `run` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/app.py#L418" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run(self, transport: Literal['stdio', 'http', 'sse', 'streamable-http'] | None = None, **kwargs: Any) -> None
|
||||
```
|
||||
|
||||
Create a temporary FastMCP server and run this app standalone.
|
||||
|
||||
58
docs/python-sdk/fastmcp-apps-approval.mdx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
title: approval
|
||||
sidebarTitle: approval
|
||||
---
|
||||
|
||||
# `fastmcp.apps.approval`
|
||||
|
||||
|
||||
Approval — a Provider that adds human-in-the-loop approval to any server.
|
||||
|
||||
The LLM presents a summary of what it's about to do, and the user
|
||||
approves or rejects via buttons. The result is sent back into the
|
||||
conversation as a message, prompting the LLM's next turn.
|
||||
|
||||
Requires ``fastmcp[apps]`` (prefab-ui).
|
||||
|
||||
Usage::
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.approval import Approval
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(Approval())
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `Approval` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/approval.py#L49" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A Provider that adds human-in-the-loop approval to a server.
|
||||
|
||||
The LLM calls the ``request_approval`` tool with a summary and
|
||||
optional details. The user sees an approval card with Approve and
|
||||
Reject buttons. Clicking either sends a message back into the
|
||||
conversation (via ``SendMessage``), triggering the LLM's next turn.
|
||||
|
||||
The message appears as if the user sent it, so the LLM sees
|
||||
something like ``'"Deploy v3.2 to production" is APPROVED'``.
|
||||
|
||||
Example::
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.approval import Approval
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(Approval())
|
||||
|
||||
Customized::
|
||||
|
||||
Approval(
|
||||
title="Deploy Gate",
|
||||
approve_text="Ship it",
|
||||
approve_variant="default",
|
||||
reject_text="Abort",
|
||||
reject_variant="destructive",
|
||||
)
|
||||
|
||||
44
docs/python-sdk/fastmcp-apps-choice.mdx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
title: choice
|
||||
sidebarTitle: choice
|
||||
---
|
||||
|
||||
# `fastmcp.apps.choice`
|
||||
|
||||
|
||||
Choice — a Provider that lets the user pick from a set of options.
|
||||
|
||||
The LLM presents options, the user clicks one, and the selection
|
||||
flows back into the conversation as a message.
|
||||
|
||||
Requires ``fastmcp[apps]`` (prefab-ui).
|
||||
|
||||
Usage::
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.choice import Choice
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(Choice())
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `Choice` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/choice.py#L46" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A Provider that lets the user choose from a set of options.
|
||||
|
||||
The LLM calls ``choose`` with a prompt and a list of options.
|
||||
The user sees a card with one button per option. Clicking a button
|
||||
sends the selection back into the conversation via ``SendMessage``,
|
||||
triggering the LLM's next turn.
|
||||
|
||||
Example::
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.choice import Choice
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(Choice())
|
||||
|
||||
90
docs/python-sdk/fastmcp-apps-config.mdx
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
title: config
|
||||
sidebarTitle: config
|
||||
---
|
||||
|
||||
# `fastmcp.apps.config`
|
||||
|
||||
|
||||
MCP Apps support — extension negotiation and typed UI metadata models.
|
||||
|
||||
Provides constants and Pydantic models for the MCP Apps extension
|
||||
(io.modelcontextprotocol/ui), enabling tools and resources to carry
|
||||
UI metadata for clients that support interactive app rendering.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### `app_config_to_meta_dict` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/config.py#L173" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
app_config_to_meta_dict(app: AppConfig | dict[str, Any]) -> dict[str, Any]
|
||||
```
|
||||
|
||||
|
||||
Convert an AppConfig or dict to the wire-format dict for ``meta["ui"]``.
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `ResourceCSP` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/config.py#L20" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Content Security Policy for MCP App resources.
|
||||
|
||||
Declares which external origins the app is allowed to connect to or
|
||||
load resources from. Hosts use these declarations to build the
|
||||
``Content-Security-Policy`` header for the sandboxed iframe.
|
||||
|
||||
|
||||
### `ResourcePermissions` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/config.py#L52" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Iframe sandbox permissions for MCP App resources.
|
||||
|
||||
Each field, when set (typically to ``{}``), requests that the host
|
||||
grant the corresponding Permission Policy feature to the sandboxed
|
||||
iframe. Hosts MAY honour these; apps should use JS feature detection
|
||||
as a fallback.
|
||||
|
||||
|
||||
### `AppConfig` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/config.py#L79" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Configuration for MCP App tools and resources.
|
||||
|
||||
Controls how a tool or resource participates in the MCP Apps extension.
|
||||
On tools, ``resource_uri`` and ``visibility`` specify which UI resource
|
||||
to render and where the tool appears. On resources, those fields must
|
||||
be left unset (the resource itself is the UI).
|
||||
|
||||
All fields use ``exclude_none`` serialization so only explicitly-set
|
||||
values appear on the wire. Aliases match the MCP Apps wire format
|
||||
(camelCase).
|
||||
|
||||
|
||||
### `PrefabAppConfig` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/config.py#L117" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
App configuration for Prefab tools with sensible defaults.
|
||||
|
||||
Like ``app=True`` but customizable. Auto-wires the Prefab renderer
|
||||
URI and merges the renderer's CSP with any additional domains you
|
||||
specify. The renderer resource is registered automatically.
|
||||
|
||||
Example::
|
||||
|
||||
@mcp.tool(app=PrefabAppConfig()) # same as app=True
|
||||
|
||||
@mcp.tool(app=PrefabAppConfig(
|
||||
csp=ResourceCSP(frame_domains=["https://example.com"]),
|
||||
))
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `model_post_init` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/config.py#L133" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
model_post_init(self, __context: Any) -> None
|
||||
```
|
||||
144
docs/python-sdk/fastmcp-apps-file_upload.mdx
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
---
|
||||
title: file_upload
|
||||
sidebarTitle: file_upload
|
||||
---
|
||||
|
||||
# `fastmcp.apps.file_upload`
|
||||
|
||||
|
||||
FileUpload — a Provider that adds drag-and-drop file upload to any server.
|
||||
|
||||
Lets users upload files directly to the server through an interactive UI,
|
||||
bypassing the LLM context window entirely. The LLM can then read and work
|
||||
with uploaded files through model-visible tools.
|
||||
|
||||
Requires ``fastmcp[apps]`` (prefab-ui).
|
||||
|
||||
Usage::
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps import FileUpload
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(FileUpload())
|
||||
|
||||
For custom persistence, override the storage methods::
|
||||
|
||||
class S3Upload(FileUpload):
|
||||
def on_store(self, files, ctx):
|
||||
# write to S3, return summaries
|
||||
...
|
||||
|
||||
def on_list(self, ctx):
|
||||
# list from S3
|
||||
...
|
||||
|
||||
def on_read(self, name, ctx):
|
||||
# read from S3
|
||||
...
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `FileUpload` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/file_upload.py#L93" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A Provider that adds file upload capabilities to a server.
|
||||
|
||||
Registers a drag-and-drop UI tool, a backend storage tool, and
|
||||
model-visible tools for listing and reading uploaded files.
|
||||
|
||||
Files are scoped by MCP session and stored in memory by default.
|
||||
Override ``on_store``, ``on_list``, and ``on_read`` for custom
|
||||
persistence (filesystem, S3, database, etc.). Each method receives
|
||||
the current ``Context``, giving access to session ID, auth tokens,
|
||||
and request metadata for partitioning and authorization.
|
||||
|
||||
**Session scoping:** The default storage uses ``ctx.session_id`` to
|
||||
isolate files by session. This works with stdio, SSE, and stateful
|
||||
HTTP transports. In **stateless HTTP** mode, each request creates a
|
||||
new session, so files won't persist across requests. For stateless
|
||||
deployments, override the storage methods to partition by a stable
|
||||
identifier from the auth context::
|
||||
|
||||
class UserScopedUpload(FileUpload):
|
||||
def on_store(self, files, ctx):
|
||||
user_id = ctx.access_token["sub"]
|
||||
...
|
||||
|
||||
Example::
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.file_upload import FileUpload
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(FileUpload())
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `on_store` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/file_upload.py#L174" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_store(self, files: list[dict[str, Any]], ctx: Context) -> list[dict[str, Any]]
|
||||
```
|
||||
|
||||
Store uploaded files and return summaries.
|
||||
|
||||
**Args:**
|
||||
- `files`: List of file dicts, each with ``name``, ``size``,
|
||||
``type``, and ``data`` (base64-encoded content).
|
||||
- `ctx`: The current request context. Use for session ID,
|
||||
auth tokens, or any metadata needed for partitioning.
|
||||
|
||||
Override this method for custom persistence. The default
|
||||
implementation stores files in memory, scoped by
|
||||
``_get_scope_key(ctx)``.
|
||||
|
||||
**Returns:**
|
||||
- List of file summary dicts (``name``, ``type``, ``size``,
|
||||
- ``size_display``, ``uploaded_at``).
|
||||
|
||||
|
||||
#### `on_list` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/file_upload.py#L207" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_list(self, ctx: Context) -> list[dict[str, Any]]
|
||||
```
|
||||
|
||||
List all stored files.
|
||||
|
||||
**Args:**
|
||||
- `ctx`: The current request context.
|
||||
|
||||
Override this method for custom persistence. The default
|
||||
implementation returns files from the current scope.
|
||||
|
||||
**Returns:**
|
||||
- List of file summary dicts.
|
||||
|
||||
|
||||
#### `on_read` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/file_upload.py#L223" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_read(self, name: str, ctx: Context) -> dict[str, Any]
|
||||
```
|
||||
|
||||
Read a file's contents by name.
|
||||
|
||||
**Args:**
|
||||
- `name`: The filename to read.
|
||||
- `ctx`: The current request context.
|
||||
|
||||
Override this method for custom persistence. The default
|
||||
implementation reads from the current scope's in-memory store.
|
||||
Text files are decoded from base64; binary files return a
|
||||
truncated base64 preview.
|
||||
|
||||
**Returns:**
|
||||
- Dict with file metadata and ``content`` (text) or
|
||||
- ``content_base64`` (binary preview).
|
||||
|
||||
**Raises:**
|
||||
- `ValueError`: If the file is not found.
|
||||
|
||||
69
docs/python-sdk/fastmcp-apps-form.mdx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
title: form
|
||||
sidebarTitle: form
|
||||
---
|
||||
|
||||
# `fastmcp.apps.form`
|
||||
|
||||
|
||||
FormInput — a Provider that collects structured input from the user.
|
||||
|
||||
Define a Pydantic model for the data you need, and ``FormInput``
|
||||
generates a form UI. The user fills it out, the submission is
|
||||
validated, and an optional callback processes the result.
|
||||
|
||||
Requires ``fastmcp[apps]`` (prefab-ui).
|
||||
|
||||
Usage::
|
||||
|
||||
from pydantic import BaseModel
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.form import FormInput
|
||||
|
||||
class ShippingAddress(BaseModel):
|
||||
street: str
|
||||
city: str
|
||||
state: str
|
||||
zip_code: str
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(FormInput(model=ShippingAddress))
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `FormInput` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/form.py#L57" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A Provider that collects structured input via a Pydantic model.
|
||||
|
||||
Define a model for the data you need, and ``FormInput`` generates
|
||||
a form from it using ``Form.from_model()``. Field types, labels,
|
||||
descriptions, and validation are all derived from the model.
|
||||
|
||||
Optionally provide an ``on_submit`` callback to process the
|
||||
validated data. The callback receives a model instance and returns
|
||||
a string that goes back to the LLM. Without a callback, the
|
||||
validated JSON is sent directly.
|
||||
|
||||
Example::
|
||||
|
||||
from pydantic import BaseModel
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.form import FormInput
|
||||
|
||||
class Contact(BaseModel):
|
||||
name: str
|
||||
email: str
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(FormInput(model=Contact))
|
||||
|
||||
With a callback::
|
||||
|
||||
def save_contact(contact: Contact) -> str:
|
||||
db.insert(contact.model_dump())
|
||||
return f"Saved {contact.name}"
|
||||
|
||||
mcp.add_provider(FormInput(model=Contact, on_submit=save_contact))
|
||||
|
||||
56
docs/python-sdk/fastmcp-apps-generative.mdx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
title: generative
|
||||
sidebarTitle: generative
|
||||
---
|
||||
|
||||
# `fastmcp.apps.generative`
|
||||
|
||||
|
||||
GenerativeUI — a Provider that adds LLM-generated UI capabilities.
|
||||
|
||||
Registers tools and resources from ``prefab_ui.generative`` so that an
|
||||
LLM can write Prefab Python code, execute it in a sandbox, and render
|
||||
the result as a streaming interactive UI.
|
||||
|
||||
Requires ``fastmcp[apps]`` (prefab-ui).
|
||||
|
||||
Usage::
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.generative import GenerativeUI
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(GenerativeUI())
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `GenerativeUI` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/generative.py#L53" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A Provider that adds generative UI capabilities to a server.
|
||||
|
||||
Registers:
|
||||
|
||||
- A ``generate_ui`` tool that accepts Prefab Python code, executes
|
||||
it in a Pyodide sandbox, and returns the rendered PrefabApp.
|
||||
Supports streaming via ``ontoolinputpartial``.
|
||||
- A ``components`` tool that searches the Prefab component library.
|
||||
- The generative renderer resource with CSP for Pyodide CDN access.
|
||||
|
||||
Example::
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.generative import GenerativeUI
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(GenerativeUI())
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `lifespan` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/apps/generative.py#L196" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
lifespan(self) -> AsyncIterator[None]
|
||||
```
|
||||
47
docs/python-sdk/fastmcp-cli-apps_dev.mdx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: apps_dev
|
||||
sidebarTitle: apps_dev
|
||||
---
|
||||
|
||||
# `fastmcp.cli.apps_dev`
|
||||
|
||||
|
||||
Dev server for previewing FastMCPApp UIs locally.
|
||||
|
||||
Starts the user's MCP server on a configurable port, then starts a lightweight
|
||||
Starlette dev server that:
|
||||
|
||||
- Serves a Prefab-based tool picker at GET /
|
||||
- Proxies /mcp to the user's server (avoids browser CORS restrictions)
|
||||
- Serves the AppBridge host page at GET /launch
|
||||
|
||||
The host page uses @modelcontextprotocol/ext-apps to connect to the MCP server
|
||||
and render the selected UI tool inside an iframe.
|
||||
|
||||
Startup sequence
|
||||
----------------
|
||||
1. Download ext-apps app-bridge.js from npm and patch its bare
|
||||
``@modelcontextprotocol/sdk/…`` imports to use concrete esm.sh URLs.
|
||||
2. Detect the exact Zod v4 module URL that esm.sh serves for that SDK version
|
||||
and build an import-map entry that redirects the broken ``v4.mjs`` (which
|
||||
only re-exports ``{z, default}``) to ``v4/classic/index.mjs`` (which
|
||||
correctly exports every named Zod v4 function). Import maps apply to the
|
||||
full module graph in the document, including cross-origin esm.sh modules.
|
||||
3. Serve both the patched JS and the import-map JSON from the dev server.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### `run_dev_apps` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/apps_dev.py#L1682" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run_dev_apps(server_spec: str) -> None
|
||||
```
|
||||
|
||||
|
||||
Start the full dev environment for a FastMCPApp server.
|
||||
|
||||
Starts the user's MCP server on *mcp_port*, starts the Prefab dev UI
|
||||
on *dev_port* (with an /mcp proxy to the user's server), then opens
|
||||
the browser.
|
||||
|
||||
|
|
@ -50,7 +50,23 @@ Run an MCP server with the MCP Inspector for development.
|
|||
- `server_spec`: Python file to run, optionally with \:object suffix, or None to auto-detect fastmcp.json
|
||||
|
||||
|
||||
### `run` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/cli.py#L337" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `apps` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/cli.py#L337" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
apps(server_spec: str) -> None
|
||||
```
|
||||
|
||||
|
||||
Preview a FastMCPApp UI in the browser.
|
||||
|
||||
Starts the MCP server from SERVER_SPEC on --mcp-port, launches a local
|
||||
dev UI on --dev-port with a tool picker and AppBridge host, then opens
|
||||
the browser automatically.
|
||||
|
||||
Requires fastmcp[apps] to be installed (prefab-ui).
|
||||
|
||||
|
||||
### `run` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/cli.py#L385" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run(server_spec: str | None = None, *server_args: str) -> None
|
||||
|
|
@ -75,7 +91,7 @@ fastmcp run server.py -- --config config.json --debug
|
|||
- `server_spec`: Python file, object specification (file\:obj), config file, URL, or None to auto-detect
|
||||
|
||||
|
||||
### `inspect` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/cli.py#L712" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `inspect` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/cli.py#L760" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
inspect(server_spec: str | None = None) -> None
|
||||
|
|
@ -106,7 +122,7 @@ fastmcp inspect # auto-detect fastmcp.json
|
|||
- `server_spec`: Python file to inspect, optionally with \:object suffix, or fastmcp.json
|
||||
|
||||
|
||||
### `prepare` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/cli.py#L954" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `prepare` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/cli.py#L1002" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
prepare(config_path: Annotated[str | None, cyclopts.Parameter(help='Path to fastmcp.json configuration file')] = None, output_dir: Annotated[str | None, cyclopts.Parameter(help='Directory to create the persistent environment in')] = None, skip_source: Annotated[bool, cyclopts.Parameter(help='Skip source preparation (e.g., git clone)')] = False) -> None
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Client-side CLI commands for querying and invoking MCP servers.
|
|||
|
||||
## Functions
|
||||
|
||||
### `resolve_server_spec` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/client.py#L42" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `resolve_server_spec` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/client.py#L43" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
resolve_server_spec(server_spec: str | None) -> str | dict[str, Any] | ClientTransport
|
||||
|
|
@ -32,7 +32,7 @@ When ``command`` is provided, the string is shell-split into a
|
|||
``StdioTransport(command, args)``.
|
||||
|
||||
|
||||
### `coerce_value` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/client.py#L263" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `coerce_value` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/client.py#L264" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
coerce_value(raw: str, schema: dict[str, Any]) -> Any
|
||||
|
|
@ -42,7 +42,7 @@ coerce_value(raw: str, schema: dict[str, Any]) -> Any
|
|||
Coerce a string CLI value according to a JSON-Schema type hint.
|
||||
|
||||
|
||||
### `parse_tool_arguments` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/client.py#L297" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `parse_tool_arguments` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/client.py#L298" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
parse_tool_arguments(raw_args: tuple[str, ...], input_json: str | None, input_schema: dict[str, Any]) -> dict[str, Any]
|
||||
|
|
@ -56,7 +56,7 @@ A single JSON object argument is treated as the full argument dict.
|
|||
Values are coerced using the tool's ``inputSchema``.
|
||||
|
||||
|
||||
### `format_tool_signature` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/client.py#L369" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `format_tool_signature` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/client.py#L370" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
format_tool_signature(tool: mcp.types.Tool) -> str
|
||||
|
|
@ -66,7 +66,7 @@ format_tool_signature(tool: mcp.types.Tool) -> str
|
|||
Build ``name(param: type, ...) -> return_type`` from a tool's JSON schemas.
|
||||
|
||||
|
||||
### `list_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/client.py#L625" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `list_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/client.py#L641" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_command(server_spec: Annotated[str | None, cyclopts.Parameter(help='Server URL, Python file, MCPConfig JSON, or .js file')] = None) -> None
|
||||
|
|
@ -84,7 +84,7 @@ fastmcp list --command 'npx -y @mcp/server' --resources
|
|||
fastmcp list http://server/mcp --transport sse
|
||||
|
||||
|
||||
### `call_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/client.py#L774" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `call_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/client.py#L796" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
call_command(server_spec: Annotated[str | None, cyclopts.Parameter(help='Server URL, Python file, MCPConfig JSON, or .js file')] = None, target: Annotated[str, cyclopts.Parameter(help='Tool name, resource URI, or prompt name (with --prompt)')] = '', *arguments: str) -> None
|
||||
|
|
@ -110,7 +110,7 @@ fastmcp call http://server/mcp create --input-json '{"tags": ["a","b"]}'
|
|||
```
|
||||
|
||||
|
||||
### `discover_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/client.py#L875" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `discover_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/client.py#L897" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
discover_command() -> None
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ Install FastMCP server in Claude Code.
|
|||
- True if installation was successful, False otherwise
|
||||
|
||||
|
||||
### `claude_code_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/claude_code.py#L153" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `claude_code_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/claude_code.py#L155" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
claude_code_command(server_spec: str) -> None
|
||||
|
|
|
|||
|
|
@ -13,14 +13,17 @@ Claude Desktop integration for FastMCP install using Cyclopts.
|
|||
### `get_claude_config_path` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/claude_desktop.py#L20" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_claude_config_path() -> Path | None
|
||||
get_claude_config_path(config_path: Path | None = None) -> Path | None
|
||||
```
|
||||
|
||||
|
||||
Get the Claude config directory based on platform.
|
||||
|
||||
**Args:**
|
||||
- `config_path`: Optional custom path to the Claude Desktop config directory
|
||||
|
||||
### `install_claude_desktop` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/claude_desktop.py#L38" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
### `install_claude_desktop` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/claude_desktop.py#L49" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
install_claude_desktop(file: Path, server_object: str | None, name: str) -> bool
|
||||
|
|
@ -39,12 +42,13 @@ Install FastMCP server in Claude Desktop.
|
|||
- `python_version`: Optional Python version to use
|
||||
- `with_requirements`: Optional requirements file to install from
|
||||
- `project`: Optional project directory to run within
|
||||
- `config_path`: Optional custom path to Claude Desktop config directory
|
||||
|
||||
**Returns:**
|
||||
- True if installation was successful, False otherwise
|
||||
|
||||
|
||||
### `claude_desktop_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/claude_desktop.py#L125" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `claude_desktop_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/claude_desktop.py#L139" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
claude_desktop_command(server_spec: str) -> None
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ Install FastMCP server to workspace-specific Cursor configuration.
|
|||
- True if installation was successful, False otherwise
|
||||
|
||||
|
||||
### `install_cursor` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L140" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `install_cursor` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L143" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
install_cursor(file: Path, server_object: str | None, name: str) -> bool
|
||||
|
|
@ -93,7 +93,7 @@ Install FastMCP server in Cursor.
|
|||
- True if installation was successful, False otherwise
|
||||
|
||||
|
||||
### `cursor_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L225" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `cursor_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L228" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
cursor_command(server_spec: str) -> None
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ Install FastMCP server in Gemini CLI.
|
|||
- True if installation was successful, False otherwise
|
||||
|
||||
|
||||
### `gemini_cli_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/gemini_cli.py#L150" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `gemini_cli_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/gemini_cli.py#L152" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
gemini_cli_command(server_spec: str) -> None
|
||||
|
|
|
|||
|
|
@ -10,7 +10,19 @@ Shared utilities for install commands.
|
|||
|
||||
## Functions
|
||||
|
||||
### `parse_env_var` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/shared.py#L21" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `validate_server_name` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/shared.py#L28" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
validate_server_name(name: str) -> str
|
||||
```
|
||||
|
||||
|
||||
Validate that a server name is safe for use as a subprocess argument.
|
||||
|
||||
Raises SystemExit if the name contains shell metacharacters.
|
||||
|
||||
|
||||
### `parse_env_var` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/shared.py#L42" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
parse_env_var(env_var: str) -> tuple[str, str]
|
||||
|
|
@ -20,7 +32,7 @@ parse_env_var(env_var: str) -> tuple[str, str]
|
|||
Parse environment variable string in format KEY=VALUE.
|
||||
|
||||
|
||||
### `process_common_args` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/shared.py#L32" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `process_common_args` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/shared.py#L53" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
process_common_args(server_spec: str, server_name: str | None, with_packages: list[str] | None, env_vars: list[str] | None, env_file: Path | None) -> tuple[Path, str | None, str, list[str], dict[str, str] | None]
|
||||
|
|
@ -32,7 +44,7 @@ Process common arguments shared by all install commands.
|
|||
Handles both fastmcp.json config files and traditional file.py:object syntax.
|
||||
|
||||
|
||||
### `open_deeplink` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/shared.py#L148" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `open_deeplink` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/shared.py#L169" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
open_deeplink(url: str) -> bool
|
||||
|
|
|
|||
|
|
@ -32,37 +32,43 @@ Raised when OAuth client credentials are not found on the server.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `clear` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L99" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `clear` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L102" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
clear(self) -> None
|
||||
```
|
||||
|
||||
#### `get_tokens` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L104" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_tokens` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L111" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_tokens(self) -> OAuthToken | None
|
||||
```
|
||||
|
||||
#### `set_tokens` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L108" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_tokens` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L115" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_tokens(self, tokens: OAuthToken) -> None
|
||||
```
|
||||
|
||||
#### `get_client_info` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L119" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_token_expiry` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L135" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_token_expiry(self) -> float | None
|
||||
```
|
||||
|
||||
#### `get_client_info` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L145" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_client_info(self) -> OAuthClientInformationFull | None
|
||||
```
|
||||
|
||||
#### `set_client_info` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L125" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_client_info` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L151" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_client_info(self, client_info: OAuthClientInformationFull) -> None
|
||||
```
|
||||
|
||||
### `OAuth` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L138" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `OAuth` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L164" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
OAuth client provider for MCP servers with browser-based authentication.
|
||||
|
|
@ -73,7 +79,7 @@ a browser for user authorization and running a local callback server.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `redirect_handler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L290" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `redirect_handler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L320" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
redirect_handler(self, authorization_url: str) -> None
|
||||
|
|
@ -82,7 +88,7 @@ redirect_handler(self, authorization_url: str) -> None
|
|||
Open browser for authorization, with pre-flight check for invalid client.
|
||||
|
||||
|
||||
#### `callback_handler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L311" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `callback_handler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L341" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
callback_handler(self) -> tuple[str, str | None]
|
||||
|
|
@ -91,7 +97,7 @@ callback_handler(self) -> tuple[str, str | None]
|
|||
Handle OAuth callback and return (auth_code, state).
|
||||
|
||||
|
||||
#### `async_auth_flow` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L350" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `async_auth_flow` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L380" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.Request, httpx.Response]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: client
|
|||
|
||||
## Classes
|
||||
|
||||
### `ClientSessionState` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L95" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ClientSessionState` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L97" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Holds all session-related state for a Client instance.
|
||||
|
|
@ -16,13 +16,13 @@ This allows clean separation of configuration (which is copied) from
|
|||
session state (which should be fresh for each new client instance).
|
||||
|
||||
|
||||
### `CallToolResult` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L112" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CallToolResult` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L114" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Parsed result from a tool call.
|
||||
|
||||
|
||||
### `Client` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L122" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `Client` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L124" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
MCP client that delegates connection management to a Transport instance.
|
||||
|
|
@ -85,7 +85,7 @@ async with client:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `session` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L340" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `session` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L371" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
session(self) -> ClientSession
|
||||
|
|
@ -94,7 +94,7 @@ session(self) -> ClientSession
|
|||
Get the current active session. Raises RuntimeError if not connected.
|
||||
|
||||
|
||||
#### `initialize_result` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L350" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `initialize_result` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L381" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
initialize_result(self) -> mcp.types.InitializeResult | None
|
||||
|
|
@ -103,7 +103,7 @@ initialize_result(self) -> mcp.types.InitializeResult | None
|
|||
Get the result of the initialization request.
|
||||
|
||||
|
||||
#### `set_roots` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L354" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_roots` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L385" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_roots(self, roots: RootsList | RootsHandler) -> None
|
||||
|
|
@ -112,7 +112,7 @@ set_roots(self, roots: RootsList | RootsHandler) -> None
|
|||
Set the roots for the client. This does not automatically call `send_roots_list_changed`.
|
||||
|
||||
|
||||
#### `set_sampling_callback` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L358" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_sampling_callback` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L389" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_sampling_callback(self, sampling_callback: SamplingHandler, sampling_capabilities: mcp.types.SamplingCapability | None = None) -> None
|
||||
|
|
@ -121,7 +121,7 @@ set_sampling_callback(self, sampling_callback: SamplingHandler, sampling_capabil
|
|||
Set the sampling callback for the client.
|
||||
|
||||
|
||||
#### `set_elicitation_callback` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L373" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_elicitation_callback` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L404" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_elicitation_callback(self, elicitation_callback: ElicitationHandler) -> None
|
||||
|
|
@ -130,7 +130,7 @@ set_elicitation_callback(self, elicitation_callback: ElicitationHandler) -> None
|
|||
Set the elicitation callback for the client.
|
||||
|
||||
|
||||
#### `is_connected` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L381" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `is_connected` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L412" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
is_connected(self) -> bool
|
||||
|
|
@ -139,7 +139,7 @@ is_connected(self) -> bool
|
|||
Check if the client is currently connected.
|
||||
|
||||
|
||||
#### `new` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L385" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `new` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L416" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
new(self) -> Client[ClientTransportT]
|
||||
|
|
@ -155,7 +155,7 @@ share state with the original client.
|
|||
- A new Client instance with the same configuration but disconnected state.
|
||||
|
||||
|
||||
#### `initialize` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L430" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `initialize` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L461" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
initialize(self, timeout: datetime.timedelta | float | int | None = None) -> mcp.types.InitializeResult
|
||||
|
|
@ -183,13 +183,13 @@ capabilities, protocol version, and optional instructions.
|
|||
- `RuntimeError`: If the client is not connected or initialization times out.
|
||||
|
||||
|
||||
#### `close` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L731" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `close` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L762" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
close(self)
|
||||
```
|
||||
|
||||
#### `ping` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L737" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `ping` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L768" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
ping(self) -> bool
|
||||
|
|
@ -198,7 +198,7 @@ ping(self) -> bool
|
|||
Send a ping request.
|
||||
|
||||
|
||||
#### `cancel` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L742" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `cancel` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L773" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
cancel(self, request_id: str | int, reason: str | None = None) -> None
|
||||
|
|
@ -207,7 +207,7 @@ cancel(self, request_id: str | int, reason: str | None = None) -> None
|
|||
Send a cancellation notification for an in-progress request.
|
||||
|
||||
|
||||
#### `progress` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L759" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `progress` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L790" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
progress(self, progress_token: str | int, progress: float, total: float | None = None, message: str | None = None) -> None
|
||||
|
|
@ -216,7 +216,7 @@ progress(self, progress_token: str | int, progress: float, total: float | None =
|
|||
Send a progress notification.
|
||||
|
||||
|
||||
#### `set_logging_level` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L771" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_logging_level` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L802" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_logging_level(self, level: mcp.types.LoggingLevel) -> None
|
||||
|
|
@ -225,7 +225,7 @@ set_logging_level(self, level: mcp.types.LoggingLevel) -> None
|
|||
Send a logging/setLevel request.
|
||||
|
||||
|
||||
#### `send_roots_list_changed` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L775" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `send_roots_list_changed` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L806" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
send_roots_list_changed(self) -> None
|
||||
|
|
@ -234,7 +234,7 @@ send_roots_list_changed(self) -> None
|
|||
Send a roots/list_changed notification.
|
||||
|
||||
|
||||
#### `complete_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L781" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `complete_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L812" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
complete_mcp(self, ref: mcp.types.ResourceTemplateReference | mcp.types.PromptReference, argument: dict[str, str], context_arguments: dict[str, Any] | None = None) -> mcp.types.CompleteResult
|
||||
|
|
@ -257,7 +257,7 @@ containing the completion and any additional metadata.
|
|||
- `McpError`: If the request results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `complete` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L812" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `complete` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L843" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
complete(self, ref: mcp.types.ResourceTemplateReference | mcp.types.PromptReference, argument: dict[str, str], context_arguments: dict[str, Any] | None = None) -> mcp.types.Completion
|
||||
|
|
@ -279,7 +279,7 @@ include with the completion request. Defaults to None.
|
|||
- `McpError`: If the request results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `generate_name` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L839" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `generate_name` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L870" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
generate_name(cls, name: str | None = None) -> str
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Prompt-related methods for FastMCP Client.
|
|||
|
||||
## Classes
|
||||
|
||||
### `ClientPromptsMixin` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/prompts.py#L29" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ClientPromptsMixin` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/prompts.py#L31" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Mixin providing prompt-related methods for Client.
|
||||
|
|
@ -18,7 +18,7 @@ Mixin providing prompt-related methods for Client.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `list_prompts_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/prompts.py#L34" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_prompts_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/prompts.py#L36" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_prompts_mcp(self: Client) -> mcp.types.ListPromptsResult
|
||||
|
|
@ -38,10 +38,10 @@ containing the list of prompts and any additional metadata.
|
|||
- `McpError`: If the request results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `list_prompts` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/prompts.py#L57" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_prompts` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/prompts.py#L59" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_prompts(self: Client) -> list[mcp.types.Prompt]
|
||||
list_prompts(self: Client, max_pages: int = AUTO_PAGINATION_MAX_PAGES) -> list[mcp.types.Prompt]
|
||||
```
|
||||
|
||||
Retrieve all prompts available on the server.
|
||||
|
|
@ -50,15 +50,18 @@ This method automatically fetches all pages if the server paginates results,
|
|||
returning the complete list. For manual pagination control (e.g., to handle
|
||||
large result sets incrementally), use list_prompts_mcp() with the cursor parameter.
|
||||
|
||||
**Args:**
|
||||
- `max_pages`: Maximum number of pages to fetch before raising. Defaults to 250.
|
||||
|
||||
**Returns:**
|
||||
- list\[mcp.types.Prompt]: A list of all Prompt objects.
|
||||
|
||||
**Raises:**
|
||||
- `RuntimeError`: If called while the client is not connected.
|
||||
- `RuntimeError`: If the page limit is reached before pagination completes.
|
||||
- `McpError`: If the request results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `get_prompt_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/prompts.py#L92" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_prompt_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/prompts.py#L107" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_prompt_mcp(self: Client, name: str, arguments: dict[str, Any] | None = None, meta: dict[str, Any] | None = None) -> mcp.types.GetPromptResult
|
||||
|
|
@ -80,19 +83,19 @@ containing the prompt messages and any additional metadata.
|
|||
- `McpError`: If the request results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `get_prompt` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/prompts.py#L161" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_prompt` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/prompts.py#L176" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_prompt(self: Client, name: str, arguments: dict[str, Any] | None = None) -> mcp.types.GetPromptResult
|
||||
```
|
||||
|
||||
#### `get_prompt` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/prompts.py#L172" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_prompt` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/prompts.py#L187" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_prompt(self: Client, name: str, arguments: dict[str, Any] | None = None) -> PromptTask
|
||||
```
|
||||
|
||||
#### `get_prompt` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/prompts.py#L184" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_prompt` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/prompts.py#L199" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_prompt(self: Client, name: str, arguments: dict[str, Any] | None = None) -> mcp.types.GetPromptResult | PromptTask
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Resource-related methods for FastMCP Client.
|
|||
|
||||
## Classes
|
||||
|
||||
### `ClientResourcesMixin` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L28" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ClientResourcesMixin` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L30" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Mixin providing resource-related methods for Client.
|
||||
|
|
@ -18,7 +18,7 @@ Mixin providing resource-related methods for Client.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `list_resources_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L33" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_resources_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L35" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_resources_mcp(self: Client) -> mcp.types.ListResourcesResult
|
||||
|
|
@ -38,10 +38,10 @@ containing the list of resources and any additional metadata.
|
|||
- `McpError`: If the request results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `list_resources` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L56" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_resources` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L58" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_resources(self: Client) -> list[mcp.types.Resource]
|
||||
list_resources(self: Client, max_pages: int = AUTO_PAGINATION_MAX_PAGES) -> list[mcp.types.Resource]
|
||||
```
|
||||
|
||||
Retrieve all resources available on the server.
|
||||
|
|
@ -50,15 +50,18 @@ This method automatically fetches all pages if the server paginates results,
|
|||
returning the complete list. For manual pagination control (e.g., to handle
|
||||
large result sets incrementally), use list_resources_mcp() with the cursor parameter.
|
||||
|
||||
**Args:**
|
||||
- `max_pages`: Maximum number of pages to fetch before raising. Defaults to 250.
|
||||
|
||||
**Returns:**
|
||||
- list\[mcp.types.Resource]: A list of all Resource objects.
|
||||
|
||||
**Raises:**
|
||||
- `RuntimeError`: If called while the client is not connected.
|
||||
- `RuntimeError`: If the page limit is reached before pagination completes.
|
||||
- `McpError`: If the request results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `list_resource_templates_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L90" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_resource_templates_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L105" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_resource_templates_mcp(self: Client) -> mcp.types.ListResourceTemplatesResult
|
||||
|
|
@ -78,10 +81,10 @@ containing the list of resource templates and any additional metadata.
|
|||
- `McpError`: If the request results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `list_resource_templates` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L113" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_resource_templates` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L128" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_resource_templates(self: Client) -> list[mcp.types.ResourceTemplate]
|
||||
list_resource_templates(self: Client, max_pages: int = AUTO_PAGINATION_MAX_PAGES) -> list[mcp.types.ResourceTemplate]
|
||||
```
|
||||
|
||||
Retrieve all resource templates available on the server.
|
||||
|
|
@ -91,15 +94,18 @@ returning the complete list. For manual pagination control (e.g., to handle
|
|||
large result sets incrementally), use list_resource_templates_mcp() with the
|
||||
cursor parameter.
|
||||
|
||||
**Args:**
|
||||
- `max_pages`: Maximum number of pages to fetch before raising. Defaults to 250.
|
||||
|
||||
**Returns:**
|
||||
- list\[mcp.types.ResourceTemplate]: A list of all ResourceTemplate objects.
|
||||
|
||||
**Raises:**
|
||||
- `RuntimeError`: If called while the client is not connected.
|
||||
- `RuntimeError`: If the page limit is reached before pagination completes.
|
||||
- `McpError`: If the request results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `read_resource_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L149" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read_resource_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L177" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read_resource_mcp(self: Client, uri: AnyUrl | str, meta: dict[str, Any] | None = None) -> mcp.types.ReadResourceResult
|
||||
|
|
@ -120,19 +126,19 @@ containing the resource contents and any additional metadata.
|
|||
- `McpError`: If the request results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `read_resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L205" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read_resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L233" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read_resource(self: Client, uri: AnyUrl | str) -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]
|
||||
```
|
||||
|
||||
#### `read_resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L215" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read_resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L243" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read_resource(self: Client, uri: AnyUrl | str) -> ResourceTask
|
||||
```
|
||||
|
||||
#### `read_resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L226" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read_resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/resources.py#L254" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read_resource(self: Client, uri: AnyUrl | str) -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents] | ResourceTask
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Tool-related methods for FastMCP Client.
|
|||
|
||||
## Classes
|
||||
|
||||
### `ClientToolsMixin` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/tools.py#L32" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ClientToolsMixin` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/tools.py#L34" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Mixin providing tool-related methods for Client.
|
||||
|
|
@ -18,7 +18,7 @@ Mixin providing tool-related methods for Client.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `list_tools_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/tools.py#L37" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_tools_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/tools.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_tools_mcp(self: Client) -> mcp.types.ListToolsResult
|
||||
|
|
@ -38,10 +38,10 @@ containing the list of tools and any additional metadata.
|
|||
- `McpError`: If the request results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `list_tools` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/tools.py#L60" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_tools` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/tools.py#L62" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_tools(self: Client) -> list[mcp.types.Tool]
|
||||
list_tools(self: Client, max_pages: int = AUTO_PAGINATION_MAX_PAGES) -> list[mcp.types.Tool]
|
||||
```
|
||||
|
||||
Retrieve all tools available on the server.
|
||||
|
|
@ -50,15 +50,18 @@ This method automatically fetches all pages if the server paginates results,
|
|||
returning the complete list. For manual pagination control (e.g., to handle
|
||||
large result sets incrementally), use list_tools_mcp() with the cursor parameter.
|
||||
|
||||
**Args:**
|
||||
- `max_pages`: Maximum number of pages to fetch before raising. Defaults to 250.
|
||||
|
||||
**Returns:**
|
||||
- list\[mcp.types.Tool]: A list of all Tool objects.
|
||||
|
||||
**Raises:**
|
||||
- `RuntimeError`: If called while the client is not connected.
|
||||
- `RuntimeError`: If the page limit is reached before pagination completes.
|
||||
- `McpError`: If the request results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `call_tool_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/tools.py#L96" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `call_tool_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/tools.py#L111" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
call_tool_mcp(self: Client, name: str, arguments: dict[str, Any], progress_handler: ProgressHandler | None = None, timeout: datetime.timedelta | float | int | None = None, meta: dict[str, Any] | None = None) -> mcp.types.CallToolResult
|
||||
|
|
@ -88,19 +91,19 @@ containing the tool result and any additional metadata.
|
|||
- `McpError`: If the tool call requests results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `call_tool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/tools.py#L176" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `call_tool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/tools.py#L191" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
call_tool(self: Client, name: str, arguments: dict[str, Any] | None = None) -> CallToolResult
|
||||
```
|
||||
|
||||
#### `call_tool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/tools.py#L190" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `call_tool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/tools.py#L205" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
call_tool(self: Client, name: str, arguments: dict[str, Any] | None = None) -> ToolTask
|
||||
```
|
||||
|
||||
#### `call_tool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/tools.py#L205" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `call_tool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/mixins/tools.py#L220" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
call_tool(self: Client, name: str, arguments: dict[str, Any] | None = None) -> CallToolResult | ToolTask
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Anthropic sampling handler for FastMCP.
|
|||
|
||||
## Classes
|
||||
|
||||
### `AnthropicSamplingHandler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/sampling/handlers/anthropic.py#L46" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `AnthropicSamplingHandler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/sampling/handlers/anthropic.py#L72" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Sampling handler that uses the Anthropic API.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Google GenAI sampling handler with tool support for FastMCP 3.0.
|
|||
|
||||
## Classes
|
||||
|
||||
### `GoogleGenaiSamplingHandler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/sampling/handlers/google_genai.py#L54" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `GoogleGenaiSamplingHandler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/sampling/handlers/google_genai.py#L56" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Sampling handler that uses the Google GenAI API with tool support.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ OpenAI sampling handler for FastMCP.
|
|||
|
||||
## Classes
|
||||
|
||||
### `OpenAISamplingHandler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/sampling/handlers/openai.py#L45" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `OpenAISamplingHandler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/sampling/handlers/openai.py#L95" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Sampling handler that uses the OpenAI API.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: config
|
|||
|
||||
## Classes
|
||||
|
||||
### `MCPConfigTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/config.py#L22" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `MCPConfigTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/config.py#L25" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for connecting to one or more MCP servers defined in an MCPConfig.
|
||||
|
|
@ -59,13 +59,13 @@ async with client:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `connect_session` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/config.py#L85" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect_session` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/config.py#L88" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
|
||||
```
|
||||
|
||||
#### `close` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/config.py#L198" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `close` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/config.py#L205" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
close(self)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Streamable HTTP transport for FastMCP Client.
|
|||
|
||||
## Classes
|
||||
|
||||
### `StreamableHttpTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/http.py#L25" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `StreamableHttpTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/http.py#L27" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport implementation that connects to an MCP server via Streamable HTTP Requests.
|
||||
|
|
@ -18,19 +18,19 @@ Transport implementation that connects to an MCP server via Streamable HTTP Requ
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `connect_session` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/http.py#L92" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect_session` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/http.py#L148" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
|
||||
```
|
||||
|
||||
#### `get_session_id` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/http.py#L138" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_session_id` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/http.py#L201" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_session_id(self) -> str | None
|
||||
```
|
||||
|
||||
#### `close` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/http.py#L146" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `close` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/http.py#L209" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
close(self)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Server-Sent Events (SSE) transport for FastMCP Client.
|
|||
|
||||
## Classes
|
||||
|
||||
### `SSETransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/sse.py#L24" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `SSETransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/sse.py#L25" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport implementation that connects to an MCP server via Server-Sent Events.
|
||||
|
|
@ -18,7 +18,7 @@ Transport implementation that connects to an MCP server via Server-Sent Events.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `connect_session` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/sse.py#L64" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect_session` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/sse.py#L115" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
|
||||
|
|
|
|||
|
|
@ -30,49 +30,49 @@ connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[
|
|||
connect(self, **session_kwargs: Unpack[SessionKwargs]) -> ClientSession | None
|
||||
```
|
||||
|
||||
#### `disconnect` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L120" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `disconnect` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L127" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
disconnect(self)
|
||||
```
|
||||
|
||||
#### `close` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L135" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `close` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L162" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
close(self)
|
||||
```
|
||||
|
||||
### `PythonStdioTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L205" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `PythonStdioTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L232" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running Python scripts.
|
||||
|
||||
|
||||
### `FastMCPStdioTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L258" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCPStdioTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L285" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running FastMCP servers using the FastMCP CLI.
|
||||
|
||||
|
||||
### `NodeStdioTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L287" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `NodeStdioTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L314" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running Node.js scripts.
|
||||
|
||||
|
||||
### `UvStdioTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L340" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `UvStdioTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L367" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running commands via the uv tool.
|
||||
|
||||
|
||||
### `UvxStdioTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L419" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `UvxStdioTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L446" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running commands via the uvx tool.
|
||||
|
||||
|
||||
### `NpxStdioTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L484" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `NpxStdioTransport` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/transports/stdio.py#L511" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running commands via the npx tool.
|
||||
|
|
|
|||
|
|
@ -10,61 +10,71 @@ Custom exceptions for FastMCP.
|
|||
|
||||
## Classes
|
||||
|
||||
### `FastMCPError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L6" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCPDeprecationWarning` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L6" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Deprecation warning for FastMCP APIs.
|
||||
|
||||
Subclass of DeprecationWarning so that standard warning filters
|
||||
still apply, but FastMCP can selectively enable its own warnings
|
||||
without affecting other libraries in the process.
|
||||
|
||||
|
||||
### `FastMCPError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L15" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Base error for FastMCP.
|
||||
|
||||
|
||||
### `ValidationError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L10" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ValidationError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L19" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Error in validating parameters or return values.
|
||||
|
||||
|
||||
### `ResourceError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L14" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ResourceError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L23" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Error in resource operations.
|
||||
|
||||
|
||||
### `ToolError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L18" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ToolError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L27" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Error in tool operations.
|
||||
|
||||
|
||||
### `PromptError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L22" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `PromptError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L31" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Error in prompt operations.
|
||||
|
||||
|
||||
### `InvalidSignature` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L26" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `InvalidSignature` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L35" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Invalid signature for use with FastMCP.
|
||||
|
||||
|
||||
### `ClientError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L30" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ClientError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Error in client operations.
|
||||
|
||||
|
||||
### `NotFoundError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L34" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `NotFoundError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L43" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Object not found.
|
||||
|
||||
|
||||
### `DisabledError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L38" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `DisabledError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L47" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Object is disabled.
|
||||
|
||||
|
||||
### `AuthorizationError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L42" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `AuthorizationError` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/exceptions.py#L51" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Error when authorization check fails.
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ leave that limit uncapped.
|
|||
run(self, code: str) -> Any
|
||||
```
|
||||
|
||||
### `Search` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/experimental/transforms/code_mode.py#L180" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `Search` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/experimental/transforms/code_mode.py#L179" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Discovery tool factory that searches the catalog by query.
|
||||
|
|
@ -64,7 +64,7 @@ Defaults to BM25 ranking.
|
|||
The LLM can override this per call. ``None`` means no limit.
|
||||
|
||||
|
||||
### `GetSchemas` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/experimental/transforms/code_mode.py#L262" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `GetSchemas` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/experimental/transforms/code_mode.py#L261" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Discovery tool factory that returns schemas for tools by name.
|
||||
|
|
@ -78,7 +78,7 @@ types, and required markers.
|
|||
``"full"`` returns the complete JSON schema.
|
||||
|
||||
|
||||
### `GetTags` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/experimental/transforms/code_mode.py#L323" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `GetTags` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/experimental/transforms/code_mode.py#L322" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Discovery tool factory that lists tool tags from the catalog.
|
||||
|
|
@ -93,7 +93,7 @@ without tags appear under ``"untagged"``.
|
|||
``"full"`` lists all tools under each tag.
|
||||
|
||||
|
||||
### `ListTools` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/experimental/transforms/code_mode.py#L390" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ListTools` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/experimental/transforms/code_mode.py#L389" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Discovery tool factory that lists all tools in the catalog.
|
||||
|
|
@ -106,7 +106,7 @@ Discovery tool factory that lists all tools in the catalog.
|
|||
``"full"`` returns the complete JSON schema.
|
||||
|
||||
|
||||
### `CodeMode` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/experimental/transforms/code_mode.py#L439" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CodeMode` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/experimental/transforms/code_mode.py#L438" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transform that collapses all tools into discovery + execute meta-tools.
|
||||
|
|
@ -123,13 +123,13 @@ environment with ``call_tool(name, params)`` in scope.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `transform_tools` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/experimental/transforms/code_mode.py#L489" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `transform_tools` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/experimental/transforms/code_mode.py#L488" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
transform_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]
|
||||
```
|
||||
|
||||
#### `get_tool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/experimental/transforms/code_mode.py#L492" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_tool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/experimental/transforms/code_mode.py#L491" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_tool(self, name: str, call_next: GetToolNext) -> Tool | None
|
||||
|
|
|
|||