diff --git a/.github/actions/setup-uv/action.yml b/.github/actions/setup-uv/action.yml index 0697b8cde..0becaffad 100644 --- a/.github/actions/setup-uv/action.yml +++ b/.github/actions/setup-uv/action.yml @@ -15,7 +15,7 @@ runs: using: "composite" steps: - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: "uv.lock" diff --git a/.github/workflows/auto-close-duplicates.yml b/.github/workflows/auto-close-duplicates.yml index a58115b46..a5606e5ff 100644 --- a/.github/workflows/auto-close-duplicates.yml +++ b/.github/workflows/auto-close-duplicates.yml @@ -26,7 +26,7 @@ jobs: private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }} - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@v7 - name: Auto-close duplicate issues run: uv run scripts/auto_close_duplicates.py diff --git a/.github/workflows/auto-close-needs-mre.yml b/.github/workflows/auto-close-needs-mre.yml index ef041abb1..08428ab0c 100644 --- a/.github/workflows/auto-close-needs-mre.yml +++ b/.github/workflows/auto-close-needs-mre.yml @@ -26,7 +26,7 @@ jobs: private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }} - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@v7 - name: Auto-close needs MRE issues run: uv run scripts/auto_close_needs_mre.py diff --git a/.github/workflows/marvin-comment-on-issue.yml b/.github/workflows/marvin-comment-on-issue.yml index d795719ca..72c38cdf7 100644 --- a/.github/workflows/marvin-comment-on-issue.yml +++ b/.github/workflows/marvin-comment-on-issue.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@v7 - name: Install UV - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: "uv.lock" diff --git a/.github/workflows/marvin-comment-on-pr.yml b/.github/workflows/marvin-comment-on-pr.yml index 276fab800..369a90c6b 100644 --- a/.github/workflows/marvin-comment-on-pr.yml +++ b/.github/workflows/marvin-comment-on-pr.yml @@ -30,7 +30,7 @@ jobs: fetch-depth: 0 - name: Install UV - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: "uv.lock" diff --git a/.github/workflows/marvin-dedupe-issues.yml b/.github/workflows/marvin-dedupe-issues.yml index f5a8c7b17..a9de7d0d3 100644 --- a/.github/workflows/marvin-dedupe-issues.yml +++ b/.github/workflows/marvin-dedupe-issues.yml @@ -36,12 +36,6 @@ jobs: with: app-id: ${{ secrets.MARVIN_APP_ID }} private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }} - # Match the job's `permissions:` block above. Unscoped, the token - # inherits the App installation's full set — which includes - # contents: write and actions: write, neither of which this job - # declares and both of which end up in the model's shell as GH_TOKEN. - permission-contents: read - permission-issues: write - name: Set dedupe prompt id: dedupe-prompt @@ -120,12 +114,8 @@ jobs: prompt: ${{ steps.dedupe-prompt.outputs.PROMPT }} anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY_FOR_CI }} allowed_non_write_users: "*" - # No `Bash(gh api:*)`: it reaches every endpoint the token can, which - # is the reason marvin-label-triage routes its one write through - # .github/scripts/triage-label.sh instead. Dedupe searches, reads and - # comments — the four verbs below cover that. claude_args: | - --allowedTools "Bash(gh issue view:*)","Bash(gh search:*)","Bash(gh issue list:*)","Bash(gh issue comment:*)",Task + --allowedTools "Bash(gh issue view:*)","Bash(gh search:*)","Bash(gh issue list:*)","Bash(gh api:*)","Bash(gh issue comment:*)",Task settings: | { "model": "claude-sonnet-5", diff --git a/.github/workflows/marvin-label-triage.yml b/.github/workflows/marvin-label-triage.yml index b01ad3844..2cd4fe7f5 100644 --- a/.github/workflows/marvin-label-triage.yml +++ b/.github/workflows/marvin-label-triage.yml @@ -56,14 +56,7 @@ jobs: with: app-id: ${{ secrets.MARVIN_APP_ID }} private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }} - # No `owner:` — with it set and `repositories:` empty the token is - # scoped to every repo in the PrefectHQ installation. Triage only - # ever touches this one. The permissions below match the job's - # `permissions:` block; unscoped the token would also carry - # contents: write and actions: write from the App installation. - permission-contents: read - permission-issues: write - permission-pull-requests: write + owner: PrefectHQ - name: Set triage prompt id: triage-prompt diff --git a/.github/workflows/marvin-test-failure.yml b/.github/workflows/marvin-test-failure.yml index 6b2a332af..c0c532b23 100644 --- a/.github/workflows/marvin-test-failure.yml +++ b/.github/workflows/marvin-test-failure.yml @@ -41,7 +41,7 @@ jobs: # Install UV package manager - name: Install UV - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@v7 # Install dependencies - name: Install dependencies diff --git a/.github/workflows/publish-fastmcp-remote.yml b/.github/workflows/publish-fastmcp-remote.yml index a2ee35966..9e2c67990 100644 --- a/.github/workflows/publish-fastmcp-remote.yml +++ b/.github/workflows/publish-fastmcp-remote.yml @@ -24,7 +24,7 @@ jobs: ref: ${{ github.event.workflow_run.head_sha || github.sha }} - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@v7 - name: Build fastmcp-remote run: uv build --package fastmcp-remote diff --git a/.github/workflows/publish-fastmcp-slim.yml b/.github/workflows/publish-fastmcp-slim.yml index b4d6ce0a3..9fdc69628 100644 --- a/.github/workflows/publish-fastmcp-slim.yml +++ b/.github/workflows/publish-fastmcp-slim.yml @@ -21,7 +21,7 @@ jobs: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@v7 - name: Build fastmcp-slim run: uv build --package fastmcp-slim diff --git a/.github/workflows/publish-fastmcp-tasks.yml b/.github/workflows/publish-fastmcp-tasks.yml index 1aec0c89f..29fc38554 100644 --- a/.github/workflows/publish-fastmcp-tasks.yml +++ b/.github/workflows/publish-fastmcp-tasks.yml @@ -38,7 +38,7 @@ jobs: fi - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@v7 - name: Build fastmcp-tasks if: steps.package_present.outputs.present == 'true' diff --git a/.github/workflows/publish-fastmcp.yml b/.github/workflows/publish-fastmcp.yml index 76e789291..8b2ce33b2 100644 --- a/.github/workflows/publish-fastmcp.yml +++ b/.github/workflows/publish-fastmcp.yml @@ -27,7 +27,7 @@ jobs: ref: ${{ github.event.workflow_run.head_sha || github.sha }} - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@v7 - name: Build fastmcp run: uv build --package fastmcp diff --git a/.github/workflows/require-issue-link.yml b/.github/workflows/require-issue-link.yml index fda27b36a..6d674d0e2 100644 --- a/.github/workflows/require-issue-link.yml +++ b/.github/workflows/require-issue-link.yml @@ -379,6 +379,9 @@ jobs: async function enforceFailure(kind) { await addLabel(); + const reason = kind === 'no-link' + ? "it doesn't reference a tracked issue assigned to you" + : "you aren't assigned to the issue it references"; const steps = kind === 'no-link' ? [ `1. Find or [open an issue](https://github.com/${owner}/${repo}/issues/new/choose) describing the change — if you open it, you have first claim on it.`, @@ -390,9 +393,9 @@ jobs: const commentBody = [ MARKER, - '**This pull request was closed because it must link to an issue assigned to you.** ' + - 'Once this PR links to an issue and a maintainer assigns that issue to you, it will ' + - 'reopen automatically. Please update this PR rather than opening a new one.', + "**Don't open a new pull request — this one reopens on its own.** It's closed for " + + `now because ${reason}, but the moment that's fixed it reopens automatically. Keep this ` + + 'PR and edit it; opening a fresh duplicate just starts you over and creates more to triage.', '', `Per [CONTRIBUTING.md](https://github.com/${owner}/${repo}/blob/main/CONTRIBUTING.md), an external PR must reference an issue that's assigned to its author. To get there:`, '', diff --git a/.github/workflows/run-schema-crash-test.yml b/.github/workflows/run-schema-crash-test.yml index 618db999e..6c9c766fb 100644 --- a/.github/workflows/run-schema-crash-test.yml +++ b/.github/workflows/run-schema-crash-test.yml @@ -37,7 +37,7 @@ jobs: - uses: actions/checkout@v7 - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@v7 - name: Set up Python run: uv python install 3.12 diff --git a/.github/workflows/run-static.yml b/.github/workflows/run-static.yml index 98e04ee9a..8297ef413 100644 --- a/.github/workflows/run-static.yml +++ b/.github/workflows/run-static.yml @@ -37,6 +37,6 @@ jobs: resolution: locked - name: Run prek - uses: j178/prek-action@v3.0.0 + uses: j178/prek-action@v2 env: SKIP: no-commit-to-branch diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index b94e062c7..7db2b5865 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -90,7 +90,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v7 with: - node-version: "24" + node-version: "22" - name: Run conformance tests uses: ./.github/actions/run-pytest diff --git a/.github/workflows/run-upgrade-checks.yml b/.github/workflows/run-upgrade-checks.yml index fb532f3ba..485cf1919 100644 --- a/.github/workflows/run-upgrade-checks.yml +++ b/.github/workflows/run-upgrade-checks.yml @@ -38,7 +38,7 @@ jobs: resolution: upgrade - name: Run prek - uses: j178/prek-action@v3.0.0 + uses: j178/prek-action@v2 env: SKIP: no-commit-to-branch diff --git a/.github/workflows/update-config-schema.yml b/.github/workflows/update-config-schema.yml index d17f9f857..6600981da 100644 --- a/.github/workflows/update-config-schema.yml +++ b/.github/workflows/update-config-schema.yml @@ -33,7 +33,7 @@ jobs: token: ${{ steps.marvin-token.outputs.token }} - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: "uv.lock" diff --git a/.github/workflows/update-sdk-docs.yml b/.github/workflows/update-sdk-docs.yml index aba01ac52..9d05684d4 100644 --- a/.github/workflows/update-sdk-docs.yml +++ b/.github/workflows/update-sdk-docs.yml @@ -33,7 +33,7 @@ jobs: token: ${{ steps.marvin-token.outputs.token }} - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: "uv.lock" diff --git a/dev-docs/v4-notes/index.md b/dev-docs/v4-notes/index.md index 2c89bd849..282c37af2 100644 --- a/dev-docs/v4-notes/index.md +++ b/dev-docs/v4-notes/index.md @@ -20,9 +20,9 @@ FastMCP v4.0 is an engine swap. Three forces drive the major version: ## Release strategy -The migration lives on `main`, which now depends on the stable MCP Python SDK 2.0 line. FastMCP continues cutting prereleases while the v4 APIs soak, then ships 4.0.0 from the same branch. +The migration merges to `main` and development continues there with subsequent PRs. Releases follow the SDK's own beta timeline: -- **`main` owns FastMCP 4.** It carries stable `mcp>=2.0.0` and `mcp-types>=2.0.0` dependencies. Beta 3 is the current prerelease target; the [Known Gaps](known-gaps.md) page tracks the remaining decisions before 4.0.0. +- **`main` carries the beta pins.** While the SDK is on `mcp==2.0.0b1` / `mcp-types==2.0.0b1`, `main` cuts **pre-releases** (`4.0.0b1`, `4.0.0b2`, …). No stable PyPI release goes out until `mcp 2.0.0` reaches GA — at which point the pins swap to the stable SDK and `4.0.0` ships. The pin-swap is a tracked checklist item on the [Known Gaps](known-gaps.md) page. - **`release/3.x` is the maintenance line.** A `release/3.x` branch is cut from pre-merge `main`. It stays on the SDK v1 line, receives upstream security patches, and serves users who cannot move to the SDK v2 beta yet. ### Release codenames @@ -34,9 +34,8 @@ Following the pun-title convention (`v: `), the v4 line runs a sin | `4.0.0a1` (alpha) | **Fourst Contact** | _first contact_ — the first, cautious look at the new engine | | `4.0.0a2` (alpha) | **Back and Fourth** | _back and forth_ — the second pass, where background tasks and stateless state land | | `4.0.0b1` (beta) | **Fourgone Conclusion** | _foregone conclusion_ — once the MCP SDK went v2, v4 was inevitable | -| `4.0.0b2` (beta) | **Four the Better** | _for the better_ — a hardening release focused on correctness, compatibility, and security | -| `4.0.0b3` (beta) | **Fast Fourward** | _fast forward_ — the final beta carries the accumulated v4 work into its GA soak | -| `4.0.0` (stable) | **Fourmidable** | _formidable_ — the stable release of the new protocol foundation | +| `4.0.0b2` (beta) | **Fourmidable** | _formidable_ — held in reserve for a second beta if one is needed | +| `4.0.0` (stable) | **Fast Fourward** | _fast forward_ — full speed onto the new foundation | ## How to read the register diff --git a/dev-docs/v4-notes/known-gaps.md b/dev-docs/v4-notes/known-gaps.md index 92c8f7cdf..0fb699029 100644 --- a/dev-docs/v4-notes/known-gaps.md +++ b/dev-docs/v4-notes/known-gaps.md @@ -2,17 +2,17 @@ title: Known Gaps and Upstream Dependencies --- -The migration ships with a small set of deliberate compatibility boundaries and expected test gaps. FastMCP now depends on the stable MCP Python SDK 2.0 line; this page tracks what remains for the beta-to-stable transition and the advisory relationship with the SDK team. +The migration ships with a set of deliberate gaps: temporary shims, xfailed tests, and pins that depend on the MCP Python SDK v2 reaching GA. Each is tracked here with its removal trigger. This page is the checklist for the beta-to-stable transition and the advisory relationship with the SDK team. ## The xfail register -The unit suite has three expected xfails. Two are strict SDK compatibility checks, so an upstream fix turns them into failures and prompts us to remove the markers. +Roughly forty `xfail` markers across the test tree name the SDK gaps and removed protocol surfaces they wait on. Re-running the suite against a new SDK beta surfaces which have closed (a strict xfail that starts passing fails the suite, prompting removal of the marker). They cluster in three areas — but the largest cluster is no longer a set of gaps to close. -**Stateless HTTP elicitation (`tests/client/test_streamable_http.py`).** One parametrized case exercises server-initiated elicitation over stateless HTTP. The sessionless protocol has no server-to-client back-channel, so the case is expected to xfail by construction. Guard-mode elicitation is the supported modern path. +**Task suite (`tests/server/tasks/`, `tests/client/tasks/`) — SEP-1686 wire layer being removed; engine rebuilt on SEP-2663.** The large majority. These cover the 2025 task protocol (SEP-1686), which left the core MCP spec and was reworked into the `io.modelcontextprotocol/tasks` extension (SEP-2663). FastMCP's SEP-1686 *wire* machinery (capability advertisement, the `tasks/get|result|list|cancel` handlers, the push notification/elicitation relay) is slated for removal, so the wire-protocol xfails disappear with the code they cover — they are not waiting on an SDK fix. The Docket/Redis *execution engine* underneath is not discarded: it is extracted into the planned `fastmcp-tasks` package and re-adapted to the SEP-2663 polling shape (see [Background Tasks (SEP-2663)](background-tasks.md)). The two SDK gaps these were originally filed against — **sdk-feedback #1** (SEP-1686 task result types omitted from the method registries) and **sdk-feedback #3** (no `task` field on `ReadResourceRequestParams` / `GetPromptRequestParams`) — are moot: they patched the SEP-1686 wire shape, which SEP-2663 replaces with a `CreateTaskResult` claimed on `tools/call`. The gap that matters for the rebuild is **sdk-feedback #2** (extensions capability stripped at pre-2026 negotiated versions) — it now gates a flagship feature and is escalated accordingly. -**MCP Apps (`tests/test_apps.py`).** Two strict xfails track **sdk-feedback #2**: the SDK strips `capabilities.extensions` at pre-2026 negotiated versions, so the UI extension cannot be advertised to legacy-era clients. Modern clients receive the extension normally. +**Protocol eras (`tests/server/test_protocol_eras.py`).** One remaining strict xfail, and it too is task-related: the v2 SDK high-level client exposes no `task=` parameter on `call_tool`, so a SEP-1686 task-augmented `tools/call` cannot be submitted through it. It resolves with the SEP-1686 wire-layer removal above; the SEP-2663 rebuild submits tasks by advertising the extension capability and claiming a `CreateTaskResult`, not through a `task=` params field. The earlier strict xfail for the `ctx.elicit` / `ctx.sample` "Method not found" degradation (sdk-feedback #10) is **gone** — the era-gating shipped in #4448 flipped it to a passing test. -Credential-gated GitHub integration suites also use conditional xfail markers when their environment variables are absent. Those are test-environment controls rather than product gaps and are not part of the GA decision. +**MCP Apps (`tests/test_apps.py`).** Two xfails tied to **sdk-feedback #2** — the `extensions` capability is stripped by the pre-2026 version sieve, so the UI extension can't be advertised to legacy-era clients. ## Shims and their removal triggers @@ -20,12 +20,15 @@ Every shim in the migration is temporary and carries a documented removal trigge | Shim | Location | Removal trigger | | --- | --- | --- | +| `_sdk_patches.py` — task registry widening | `fastmcp_slim/fastmcp/_sdk_patches.py` | Removed with FastMCP's SEP-1686 wire machinery (`server/tasks/`), which is slated for removal now that the 2025 task protocol left the spec. The SEP-2663 rebuild does not need it — `CreateTaskResult` is claimed on `tools/call` through the extensions mechanism, which the SDK registries already admit. | | `_compat.py` — camelCase field bridge | `fastmcp_slim/fastmcp/_compat.py` | User-migration aid; removed in a future release after users migrate reads to snake_case. Users can preview removal with `mcp_camelcase_compat = False`. | | `FastMCPRequestContext` ContextVar | `fastmcp_slim/fastmcp/server/dependencies.py` | The SDK deliberately passes context as an argument with no ContextVar; FastMCP's public `get_context()` needs ambient access, and the shim also lifts `_meta`, which the SDK's `TypedDict` drops. No planned removal — this is a permanent boundary, not a beta gap. | | `FastMCPServerMiddleware` | `fastmcp_slim/fastmcp/server/low_level.py` | Already the native SDK `ServerMiddleware` path; no cleaner hook exists. Permanent. | | Client `get_session_id` header sniff | `fastmcp_slim/fastmcp/client/transports/http.py` | SDK exposes session id (or an `on_session_created` callback) from `streamable_http_client`, at parity with `sse_client` (sdk-feedback #5). | | `_sdk_context_shim.py` — generic handler aliases | `fastmcp_slim/fastmcp/client/_sdk_context_shim.py` | The SDK's `ClientRequestContext` is not subscriptable, so FastMCP keeps the public generic `SamplingHandler`/`RootsHandler`/`ElicitationHandler` aliases. Permanent unless the SDK makes the context subscriptable (sdk-feedback #7). | +The `TaskNotificationHandler` binding (sdk-feedback #8) is the client-side equivalent: it registers a `NotificationBinding` for the SEP-1686 `notifications/tasks/status` because the SDK no longer tees custom server notifications to the message handler. It goes away with the SEP-1686 wire machinery it serves; the `fastmcp-tasks` client half registers its own binding for the SEP-2663 `notifications/tasks` shape when it ships (push notifications are deferred to a later `fastmcp-tasks` version — v1 is polling-only). + ## Statelessness on 2026-07-28 The `2026-07-28` era is stateless by protocol construction, and the recurring maintainer question is whether that statelessness has to be woven through FastMCP everywhere. It does not — but the honest accounting has three parts: features that are legacy-only because the protocol removed the mechanism, features that already work because they never relied on a session, and a short list of design holes where the current code *doesn't error* but also *doesn't work*. Everything below concerns `2026-07-28` connections only. Every client in the field today negotiates a handshake era, where all of this behaves exactly as it always has. @@ -77,8 +80,6 @@ Separately, the [SDK delegation round two](feature-program.md#sdk-delegation-rou The beta-to-stable transition is a small set of tracked steps: -- **Stable SDK dependencies — complete.** `fastmcp-slim` requires `mcp>=2.0.0,<3.0.0` and `mcp-types>=2.0.0,<3.0.0`; the lock resolves both to 2.0.0. -- **Re-run the full suite before GA.** Confirm the three expected xfails above remain the complete set. If either strict Apps xfail starts passing, remove the marker and the corresponding compatibility note. -- **Make the extension compatibility decision explicit.** GA can accept Apps and other extensions as modern-era capabilities, or wait for the SDK to preserve `capabilities.extensions` on legacy handshakes. Record that choice in the public protocol-support docs. -- **Prepare the stable docs.** Remove prerelease installation guidance, add the `4.0.0: Fourmidable` changelog and update entries, and merge those changes to `main` before tagging so the stable docs publication PR contains them. -- **Keep the 3.x maintenance line available — complete.** `release/3.x` is protected and continues receiving security and compatibility patches for SDK v1 users. +- **Swap the pins.** When `mcp 2.0.0` reaches GA, change `mcp-types==2.0.0b1` (core) and the `mcp` pin (the `[mcp]` extra) in `fastmcp_slim/pyproject.toml` from the beta to the stable release, and cut `4.0.0` instead of another pre-release. +- **Re-run the xfail suite against the GA SDK.** Any strict xfail that starts passing means a gap closed — remove the marker and, where applicable, the corresponding shim. +- **Confirm `release/3.x`** is cut from pre-merge `main` and receiving upstream security patches for users who stay on the SDK v1 line. diff --git a/docs/changelog.mdx b/docs/changelog.mdx index 813893a0a..0f7efeb17 100644 --- a/docs/changelog.mdx +++ b/docs/changelog.mdx @@ -5,60 +5,6 @@ rss: true tag: NEW --- - - -**[v4.0.0b3: Fast Fourward](https://github.com/PrefectHQ/fastmcp/releases/tag/v4.0.0b3)** - -FastMCP 4 beta 3 moves the v4 line toward general availability with Prefect Horizon authentication, `CallArgument` and `Depends` bindings for tools and background tasks, and a round of OAuth, proxy, OpenAPI, and Python 3.14 compatibility hardening. - -### Enhancements ✨ -* Add Prefect Horizon authentication client and local state by [@parkedwards](https://github.com/parkedwards) in [#4785](https://github.com/PrefectHQ/fastmcp/pull/4785) -* Clarify auto-closed PR message by [@jlowin](https://github.com/jlowin) in [#4820](https://github.com/PrefectHQ/fastmcp/pull/4820) -* Support CallArgument and Depends bindings from uncalled-for 0.4.0 by [@chrisguidry](https://github.com/chrisguidry) in [#4802](https://github.com/PrefectHQ/fastmcp/pull/4802) -* Fix static analysis under newer ty releases by [@zzstoatzz](https://github.com/zzstoatzz) in [#4831](https://github.com/PrefectHQ/fastmcp/pull/4831) -* Cover CallArgument resolution in background tasks by [@zzstoatzz](https://github.com/zzstoatzz) in [#4833](https://github.com/PrefectHQ/fastmcp/pull/4833) -* Scalekit issuer updates backward compatibility by [@AkshayParihar33](https://github.com/AkshayParihar33) in [#4798](https://github.com/PrefectHQ/fastmcp/pull/4798) - -### Security 🔒 -* Add audience pinning to GoogleTokenVerifier by [@zzstoatzz](https://github.com/zzstoatzz) in [#4827](https://github.com/PrefectHQ/fastmcp/pull/4827) -* Bump cryptography to 50.0.0 by [@zzstoatzz](https://github.com/zzstoatzz) in [#4836](https://github.com/PrefectHQ/fastmcp/pull/4836) - -### Fixes 🐞 -* Fix partial parameter hints on Python 3.14 by [@zzstoatzz](https://github.com/zzstoatzz) in [#4796](https://github.com/PrefectHQ/fastmcp/pull/4796) -* fix(openapi): extract parameter-level example and examples by [@doneman536](https://github.com/doneman536) in [#4793](https://github.com/PrefectHQ/fastmcp/pull/4793) -* Keep earlier consent CSRF tokens valid within a transaction by [@trevhud](https://github.com/trevhud) in [#4818](https://github.com/PrefectHQ/fastmcp/pull/4818) -* Fix StatefulProxyClient reconnection after session failure by [@jlowin](https://github.com/jlowin) in [#4829](https://github.com/PrefectHQ/fastmcp/pull/4829) - -### Docs 📚 -* Docs language dropdown by [@znicholasbrown](https://github.com/znicholasbrown) in [#4801](https://github.com/PrefectHQ/fastmcp/pull/4801) -* Docs: mirror v3.4.7 release notes by [@jlowin](https://github.com/jlowin) in [#4811](https://github.com/PrefectHQ/fastmcp/pull/4811) -* docs: prepare FastMCP 4 beta 3 by [@jlowin](https://github.com/jlowin) in [#4840](https://github.com/PrefectHQ/fastmcp/pull/4840) -* docs: add FastMCP 4 beta 3 release entries by [@jlowin](https://github.com/jlowin) in [#4841](https://github.com/PrefectHQ/fastmcp/pull/4841) - -## New Contributors -* @parkedwards made their first contribution in [#4785](https://github.com/PrefectHQ/fastmcp/pull/4785) -* @trevhud made their first contribution in [#4818](https://github.com/PrefectHQ/fastmcp/pull/4818) - -**Full Changelog**: [v4.0.0b2...v4.0.0b3](https://github.com/PrefectHQ/fastmcp/compare/v4.0.0b2...v4.0.0b3) - - - - - -**[v3.4.7: Know Your Audience](https://github.com/PrefectHQ/fastmcp/releases/tag/v3.4.7)** - -FastMCP 3.4.7 fixes CIMD `private_key_jwt` authentication on bare-origin OAuth proxy deployments by validating client assertions against the exact token endpoint advertised in OAuth metadata. - -### Security 🔒 -* Backport CIMD assertion audience fix to v3 by [@jlowin](https://github.com/jlowin) in [#4799](https://github.com/PrefectHQ/fastmcp/pull/4799) - -### Docs 📚 -* Docs: add v3.4.7 changelog entries by [@jlowin](https://github.com/jlowin) in [#4810](https://github.com/PrefectHQ/fastmcp/pull/4810) - -**Full Changelog**: [v3.4.6...v3.4.7](https://github.com/PrefectHQ/fastmcp/compare/v3.4.6...v3.4.7) - - - **[v3.4.6: Trust, but Proxy](https://github.com/PrefectHQ/fastmcp/releases/tag/v3.4.6)** diff --git a/docs/cli/overview.mdx b/docs/cli/overview.mdx index 675514ecc..9085daaa8 100644 --- a/docs/cli/overview.mdx +++ b/docs/cli/overview.mdx @@ -28,9 +28,6 @@ fastmcp --help | [`generate-cli`](/cli/generate-cli) | Scaffold a standalone typed CLI from a server's tool schemas | | [`project prepare`](/cli/running#pre-building-environments) | Pre-install dependencies into a reusable uv project | | [`auth cimd`](/cli/auth) | Create and validate CIMD documents for OAuth | -| `login` | Sign in to Prefect Horizon with a browser device flow | -| `whoami` | Show the current Horizon account | -| `logout` | Revoke the current Horizon key and remove the local credential | | `version` | Print version info (`--copy` to copy to clipboard) | ## Server Targets @@ -84,38 +81,6 @@ Run [`fastmcp discover`](/cli/client#discovering-configured-servers) to see what ## Authentication -### Prefect Horizon Account - -Use the top-level account commands to manage the credential for Prefect Horizon. - -```bash -fastmcp login -fastmcp whoami -fastmcp logout -``` - -`fastmcp login` first uses `HORIZON_API_KEY` or a valid stored key when one is available. -When login needs a new key, it shows a verification URL and code. -It opens a browser when the terminal supports it. -If the browser does not open, use the shown URL and code on another device. -To switch accounts, run `fastmcp logout` before you run `fastmcp login` again. - -Login stores only the personal Horizon API key. -It does not select or store a deployment organization. -`fastmcp whoami` gets the current user from Horizon. -`fastmcp logout` attempts to revoke the stored key and always removes its local credential. - -Set `HORIZON_API_KEY` to use an environment credential instead. -The CLI gives that value first precedence and never stores it. -When this variable controls the session, logout does not revoke or remove any credential. -Remove the variable from your environment to sign out. - -Use `--json` for stable command results. -During JSON login, the verification challenge goes to stderr and the final result goes to stdout. -JSON mode does not open a browser or ask a question. - -### MCP Server Authentication - When targeting an HTTP URL, the CLI enables OAuth authentication by default. If the server requires it, you'll be guided through the flow (typically opening a browser). If it doesn't, the setup is a silent no-op. To skip authentication entirely — useful for local development servers — pass `--auth none`: diff --git a/docs/clients/elicitation.mdx b/docs/clients/elicitation.mdx index 73cc6fdb7..302c908e4 100644 --- a/docs/clients/elicitation.mdx +++ b/docs/clients/elicitation.mdx @@ -164,3 +164,42 @@ client = Client( input_required_max_rounds=5, ) ``` + +### Answering the rounds yourself + +A handler answers a question the moment it is asked, which only works when the answer is available right then. Often it is not — a web app shows a form and the reply arrives on a different request, minutes later, possibly in another process. That is what the sessionless protocol is built for, and a callback cannot span it. + +So a client with **no** `elicitation_handler` is handed the request instead of being asked to answer it. `CallToolResult.input_required` carries what the server asked, and you answer by calling again with `input_responses` and the `request_state` it came with: + +```python +from fastmcp import Client +from mcp_types import ElicitResult + +async with Client("https://example.com/mcp") as client: + result = await client.call_tool("book_flight") + + while result.input_required: + answers = { + key: ElicitResult(action="accept", content={"value": show_form(request)}) + for key, request in result.input_required.input_requests.items() + } + result = await client.call_tool( + "book_flight", + input_responses=answers, + request_state=result.input_required.request_state, + ) + + print(result.data) +``` + +Every question the server grouped into one round arrives together, so a form can render them as one screen — which is what a server that batched its questions intended. A handler receives them one at a time and loses that grouping. + +`request_state` is opaque and sealed by the server; pass it back exactly as received. Nothing is stored between rounds, so the next call can be served by a different worker entirely. + + +Answering by hand uses `mcp_types.ElicitResult`, whose `content` is a plain dict matching the requested schema. That is a different class from the `fastmcp.client.elicitation.ElicitResult` an `elicitation_handler` returns, where `content` is an instance of the `response_type` FastMCP built for you. The names are the same and the `content` types are not, so import the one that matches the path you are on. + + + +This applies to `2026-07-28` connections, where the request is a result you can inspect. On handshake-era connections the server is blocked mid-call waiting on the back-channel, so there is nothing to hand back — a client without an `elicitation_handler` gets an "Elicitation not supported" error, which is accurate there. + diff --git a/docs/css/language-dropdown.css b/docs/css/language-dropdown.css index 8193274be..0eb810545 100644 --- a/docs/css/language-dropdown.css +++ b/docs/css/language-dropdown.css @@ -7,7 +7,6 @@ margin-left: auto; display: inline-flex; align-items: center; - position: relative; } #language-switch select { @@ -56,31 +55,3 @@ color: rgb(209 213 219); border-color: rgb(255 255 255 / 0.1); } - -/* Colored language mark on the visible trigger; the native