Commit graph

3,393 commits

Author SHA1 Message Date
Jeremiah Lowin
7d7607473d
Stop pydantic 2.13 from leaking _WrappedResult docstring into tool output schemas (#3918) v3.2.4 2026-04-13 21:38:13 -04:00
Jeremiah Lowin
b732a4a516
Overhaul apps docs (#3915) 2026-04-13 21:24:00 -04:00
marvin-context-protocol[bot]
5c2ff1bd7b
chore: Update SDK documentation (#3914)
Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
2026-04-13 21:23:43 -04:00
Jeremiah Lowin
f4f2ec07fb
Deprecate ctx.elicit() without response_type (#3916) 2026-04-13 21:23:31 -04:00
dependabot[bot]
338b80c3ae
chore(deps): bump the uv group across 2 directories with 1 update (#3913)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 20:12:30 -04:00
Jeremiah Lowin
110cd3adcb
Add response_title and response_description to ctx.elicit() (#3912) 2026-04-13 20:12:09 -04:00
marvin-context-protocol[bot]
3117846176
chore: Update SDK documentation (#3909)
Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
2026-04-13 20:08:29 -04:00
Bill Easton
031c7e03b4
Fix RetryMiddleware not retrying tool errors (#3858)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
2026-04-13 20:08:04 -04:00
Bill Easton
200d79e7d2
Enable PERF and T20 ruff rules (#3845)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:07:32 -04:00
Jeremiah Lowin
82f310fe61
AuthKit: auto-bind token audience to resource URL (RFC 8707) (#3905) 2026-04-13 17:11:17 -04:00
marvin-context-protocol[bot]
c2dafc1c88
chore: Update SDK documentation (#3901)
Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
2026-04-13 14:00:07 -04:00
Rishav Mitra
e66cce2538
fix: task.wait() hangs indefinitely when task enters input_required (#3798)
* fix: resolve OpenAPI 3.x server variables in _create_default_client

When an OpenAPI spec defines server variables (e.g. `https://{region}.api.example.com/v1`),
the default values are now substituted before constructing the httpx client base URL.
Previously, the URL was used as-is, causing all requests to fail for specs that use
server variable templating.

Fixes #1681

* fix: use str.replace instead of format_map for server variable substitution

format_map applies Python string formatting rules, so variable names
like {api.version} would be treated as attribute access and raise errors.
Literal token replacement handles all valid OpenAPI variable names safely.

* fix: task.wait() now returns on input_required instead of hanging

Previously, wait() used a terminal-state allowlist (completed, failed,
cancelled), so tasks entering input_required would hang until timeout.

Replaced with inverse logic: return whenever the task exits the 'working'
state. This handles input_required and any future blocking states without
needing to update the allowlist.

Fixes #3779

* fix: include submitted in in_progress_states to avoid premature return

* fix: revert submitted, update state docstring to match MCP spec

* fix: add _wait_terminal() so result() waits for completed/failed/cancelled

wait() correctly returns on input_required for human-in-the-loop use cases,
but result() needs to wait until the task fully resolves. Add a private
_wait_terminal() helper that loops through non-terminal states and use it
in all result() implementations.
2026-04-13 13:59:09 -04:00
Bill Easton
db6d7a8a61
fix: elicitation scalar return, resource auto-serialization, Client.new() state, prompt errors (#3859)
* fix: elicitation scalar return, resource auto-serialization, Client.new() state, prompt errors

- Auto-wrap scalar elicitation responses for ScalarElicitationType schemas
  so handlers can return T directly for ctx.elicit("msg", str/int/float)
- Auto-serialize dict/int/float/bool/None resource returns to JSON text
  instead of crashing with TypeError
- Reset _task_registry and _submitted_task_ids in Client.new() so cloned
  clients have independent task tracking state
- Include original error message in prompt render errors (matching tool
  error behavior)

Fixes #3856

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix misleading comment and add list/tuple auto-serialization for resources

The comment said "list/tuple of primitives" but the isinstance check
didn't include list or tuple. Now it does, and the comment matches.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix TaskNotificationHandler binding in Client.new() and meta forwarding for JSON resources

Two review-identified bugs:

1. Client.new() shallow-copies _session_kwargs, so the cloned client's
   TaskNotificationHandler still dispatches to the original client.
   Fix: create a fresh _session_kwargs dict with a new handler bound
   to the new client.

2. convert_result() for dict/int/float/bool/None fell through to
   ResourceResult(raw_value) which lost component meta (CSP, permissions).
   The str/bytes path correctly wrapped in ResourceContent with meta.
   Fix: explicitly serialize JSON-native types and wrap with meta,
   matching the str/bytes path. Other types still fall through for
   error handling.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Preserve custom message handlers in Client.new()

Only replace the message handler with a new TaskNotificationHandler
if the current handler IS a TaskNotificationHandler. If the user
provided a custom message_handler, preserve it in the clone.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix client.new and add regression tests

* Honor declared MIME type for auto-serialized JSON resources

* Fix static analysis: remove unused StdioTransport import, fix ty:ignore comment

* Exclude list[ResourceContent] from JSON auto-serialization path

A bare list[ResourceContent] would match the isinstance(list) check
and get JSON-serialized instead of passing through to ResourceResult
normalization. Check for ResourceContent items first.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 13:56:49 -04:00
Chris Guidry
9a063963f4
Scope tasks to authorization context, not session (#3800)
* Scope tasks to authorization context, not session

Tasks were keyed by the transport-layer Mcp-Session-Id, which is
server-assigned and changes on reconnect — so clients lost access to
their running tasks after any connection interruption.

The MCP spec says tasks should be bound to authorization context, not
session.  This replaces session_id with task_scope (derived from
AccessToken.client_id, URL-encoded) in all task data Redis keys and
Docket task keys.  When no auth is configured, a "_" sentinel is used
and security comes from UUID task ID entropy per the spec.

Session ID is still used for transport-level concerns (notification
queues, subscriber registration) and is now stored in the
TaskContextSnapshot payload so background workers can still deliver
notifications.

Also extracts all the task context infrastructure (TaskContextInfo,
TaskContextSnapshot, snapshot loading, session/server registries) from
server/dependencies.py into a new server/tasks/context.py to keep the
DI module from sprawling further.

Closes #3758

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Rename _redis_key to _snapshot_redis_key

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Document in-process session registry as an optimization

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Tidy imports and docstrings

Hoist imports where safe, keep subscriptions/notifications deferred in
handlers.py since they pull in docket at module level. Sharpen docstrings
on keys.py and context.py so each module owns its lane. Clean up the
re-export block in dependencies.py.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix misleading comment on re-export block

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Tighten task scope: include sub claim, partition keyspaces

Addresses review feedback on #3800:

- Compose task scope from client_id and the JWT sub claim (when present)
  so fixed-OAuth deployments isolate per user, not just per client.
- Replace the "_" anonymous sentinel with a tagged keyspace partition.
  Docket keys are now auth:{enc_scope}:... or anon:..., and Redis keys
  use fastmcp:task:auth:{enc_scope}:... or fastmcp:task:anon:...,
  routed through a single task_redis_prefix() helper.
- get_task_scope() returns the raw scope (or None); encoding happens
  once at the keys.py boundary, collapsing the previous double-quote
  invariant.
- Drop the dormant fallback in notifications.py that routed
  input_required relays into the anon keyspace when task_scope was
  missing -- log and skip instead.
- Add comprehensive parser/encoder tests in test_task_keys.py covering
  round-trips, malformed keys, and adversarial scopes ("anon", "_", and
  scopes containing : / | %).
- Add cross-scope rejection tests: distinct client_ids, distinct sub
  claims under a shared client_id, and authenticated vs anonymous.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 13:50:43 -04:00
Stephan Eberle
a6bd66aca8
docs: link fastmcp-keycloak-local companion project from Keycloak integration page (#3904) 2026-04-13 13:43:19 -04:00
Adam Azzam
f21e51794c
Allow auth providers to override protected resource base URLs (#3900)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
2026-04-13 13:36:55 -04:00
Stephan Eberle
99bf81c64f
Add Keycloak OAuth Provider for Enterprise Authentication and local dev (#1937) 2026-04-13 12:23:10 -04:00
Vonbai
279b601f80
Harden forced client disconnect cleanup (#3885)
* Harden forced client disconnect cleanup

* Handle cancelled force-close waits

Generated with Codex.

---------

Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
2026-04-13 12:11:11 -04:00
Jeremiah Lowin
74d09ee0f5
Fix wildcard resource template params in mounted servers (#3899) 2026-04-13 11:25:51 -04:00
Jeremiah Lowin
0a921f5372
refactor: unify object-schema conversion through _object_schema_to_type (#3884) 2026-04-12 21:33:22 -04:00
marvin-context-protocol[bot]
e8f2fefb06
chore: Update SDK documentation (#3876)
Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
2026-04-12 21:23:37 -04:00
Jeremiah Lowin
fa9ad6cb81
fix: strip titles from bare-metadata nodes (Gemini 2.5 Flash) (#3881) 2026-04-12 21:06:27 -04:00
Ke Wang
f23599283c
fix: resolve list[dict] return type producing Root() instead of dicts (#3880)
When a tool returns `list[dict]`, the client deserializes each dict as a
`Root()` dataclass with no fields instead of preserving the original dict
data.

The root cause is in `_get_from_type_handler`: its `"object"` branch
always fell through to `_create_dataclass` for schemas without
`properties`, creating an empty dataclass named `Root`. The top-level
`json_schema_to_type` already handled this case correctly (returning
`dict[str, Any]`), but that logic was not shared with `_schema_to_type`
which is used when converting nested schemas (e.g., array items).

Extract `_object_schema_to_type` to unify the four object-schema cases
(dict, typed dict, BaseModel with extra, dataclass) so both top-level
and nested paths produce the correct type.

Fixes #3867

Co-authored-by: Ke Wang <ke@pika.art>
2026-04-12 17:54:00 -04:00
Jeremiah Lowin
9a447cb08d
test: bump OpenAPI init perf threshold to 200ms for Windows CI (#3879) 2026-04-12 17:17:14 -04:00
vincent
673e6bb0f7
docs: add uv-managed dependencies and uvx examples to mcp-json configuration (#3843)
Closes #1707

- Add example for configuring mcp.json with uv-managed projects (pyproject.toml)
- Add examples for running published pip packages via uvx
- Update both main and v2 docs

Co-authored-by: Emily Chen <emilychen.techwriter@gmail.com>
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
2026-04-12 17:09:15 -04:00
Jeremiah Lowin
57f1b1bced
fix: drop configurable dedupe from AggregateProvider, always warn (#3877) 2026-04-12 17:03:02 -04:00
Jeremiah Lowin
dfe9b307bd
Rename filesystem-provider example dir to avoid mcp/ collision (#3878) 2026-04-12 17:01:58 -04:00
Jeremiah Lowin
1d39e26025
Fix CIMD clients getting required_scopes instead of valid_scopes (#3836) 2026-04-12 16:34:56 -04:00
Jeremiah Lowin
7f80f78906
ci: speed up schema crash test (CSafeLoader + xdist-safe aggregation) (#3873) 2026-04-12 16:34:13 -04:00
dependabot[bot]
82253ad0f5
chore(deps-dev): bump pydantic-monty from 0.0.9 to 0.0.10 (#3809)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
2026-04-12 16:13:20 -04:00
dependabot[bot]
6a82eb3ead
chore(deps): bump actions/checkout from 4 to 6 (#3864)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
2026-04-12 15:39:34 -04:00
dependabot[bot]
9264728330
chore(deps): bump astral-sh/setup-uv from 6 to 7 (#3865)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
2026-04-12 15:39:24 -04:00
Lars Kiesow
06f58eb617
Fix MCP tool on docs welcome page (#3874) 2026-04-12 15:39:09 -04:00
dependabot[bot]
683ce919e3
chore(deps): bump extractions/setup-just from 3 to 4 (#3863)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
2026-04-12 13:53:48 -04:00
marvin-context-protocol[bot]
56456cb188
chore: Update SDK documentation (#3808)
Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
2026-04-12 13:53:38 -04:00
Jeremiah Lowin
f3c00ba1b7
Extract parameter descriptions from docstrings (#3872) 2026-04-12 13:43:48 -04:00
Bill Easton
fb03e85592
Fix high-severity test quality issues (#3854)
- Delete entirely commented-out test_run_server.py (99 lines dead code)
- Fix test_pydantic_model_with_stringified_json_no_strict: replace
  try/except-both-branches-pass with clear pytest.raises assertion
- Fix test_path_traversal_blocked: remove dead assertions after
  pytest.raises (lines after raise never execute)

Error handling middleware test fixes are in a separate PR (#3858)
which also fixes the underlying RetryMiddleware bug.

🤖 Generated with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:14:52 -04:00
Bill Easton
cae9333f4f
fix: cap consecutive final_response validation retries (#3851)
* Cap consecutive final_response validation retries to 3

Previously, when the LLM repeatedly called final_response with data that
failed validation, the retry loop would continue up to 100 times (the
shared max_iterations limit), wasting tokens on a model that cannot
satisfy the schema.

Add _MAX_VALIDATION_RETRIES (default 3) that caps consecutive validation
failures. The counter resets when the LLM calls other tools (not
final_response), so the cap only applies to consecutive failures.

Fixes #3848

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add tests for consecutive validation retry cap

Tests cover:
- Validation failures within cap followed by success
- Consecutive validation failures exceeding cap (raises RuntimeError)
- Counter reset when LLM calls other tools between validation failures

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Slim down validation retry cap tests

Reduce boilerplate with helper functions.
Simplify counter-reset test from 5 calls to 4.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix static analysis: move imports to module level and format

Move CreateMessageResultWithTools and ToolUseContent imports to the
top of the test file so ty can resolve the names used in return-type
annotations of the helper functions. Also fix ruff import sorting
and formatting issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Align validation retry semantics with text-response retries

Change `>=` to `>` so _MAX_VALIDATION_RETRIES means "number of
retries after the initial attempt" (total = N+1), matching the
convention used by _MAX_TEXT_RESPONSE_RETRIES in the text-response
retry path.

Before: _MAX=3 meant 3 total attempts (>= comparison)
After:  _MAX=3 means 1 initial + 3 retries = 4 total (> comparison)

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:14:06 -04:00
Bill Easton
d5a3d54662
fix: GoogleGenaiSamplingHandler leaks thought parts and gives unhelpful errors on empty responses (#3849)
* Fix GoogleGenaiSamplingHandler thought part leaking and unhelpful errors

- Filter thought parts (part.thought=True) from response content instead
  of leaking them as TextContent in _response_to_result_with_tools
- Include finish_reason in error messages when no content is found, so
  safety-filtered responses (SAFETY, RECITATION, etc.) are distinguishable
- Add specific error message for thinking-only responses in
  _response_to_create_message_result

Fixes #3846

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add tests for thought part filtering and error message improvements

Tests cover:
- Thought parts filtered from tool-path responses
- Thought-only responses produce descriptive errors
- Safety-filtered responses include finish_reason in error
- Normal responses (text + function calls) unaffected

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix ruff format and ty check issues

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix ty check errors in tests

Remove unused ty: ignore comments from lines where isinstance() narrows
the type, and add correct ty: ignore[invalid-argument-type] and
ty: ignore[not-subscriptable] comments on lines in newly added test
functions where ty cannot infer the union type is a list. Also apply
ruff format fix in test_task_return_types.py.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix: use all() not any() for thinking-only detection

Addresses review feedback: any() would misclassify mixed responses
(thought + function_call) as thinking-only, hiding the real error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:10:55 -04:00
Jeremiah Lowin
f71680c427
docs: require agents to respect DNM markers on PRs (#3871) 2026-04-12 12:58:17 -04:00
Bill Easton
73deedf3a4
Enable 4 ruff rules (DTZ, ERA, ISC, INP) and fix 9 violations (#3842)
- DTZ: Use timezone-aware datetime.now(timezone.utc)
- ERA: Remove commented-out code, clarify ambiguous comment
- ISC: Convert explicit string concatenation to implicit
- INP: Add missing __init__.py in auth/handlers/

🤖 Generated with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:54:52 -04:00
Bill Easton
901453902f
Fix broken code examples in docs (#3869)
* Fix broken code examples in docs

- Tag error output blocks as ```text instead of ```python (anthropic,
  openai integration docs + v2 mirrors)
- Quote unquoted URL in Descope config example (+ v2 mirror)
- Fix GoogleGenAISamplingHandler → GoogleGenaiSamplingHandler casing
  in sampling docs
- Fix import path: handlers.GoogleGenaiSamplingHandler →
  handlers.google_genai.GoogleGenaiSamplingHandler in v3-features

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix remaining broken doc examples and add skip tags for false positives

- BearerTokenAuth → StaticTokenVerifier in deployment/http.mdx
- providers.oauth → server.auth import in authentication.mdx
- ListToolsNext → updated list_tools API in v3-features.mdx
- OAuthClientProvider → OAuth in v2/storage-backends.mdx
- Add test="skip" for upgrade guides, contrib placeholders, f-string backticks

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Ratchet doc example baselines to zero

All 1444 examples now pass syntax and import checks.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add pytest-examples dev dep, fix client_id in StaticTokenVerifier example, commit missed openapi fixes

- Add pytest-examples to dev dependencies (fixes CI ModuleNotFoundError)
- Include required client_id in StaticTokenVerifier token payload
- Commit previously unstaged HTTPRoute import fixes in openapi.mdx

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Update deprecated import paths across docs

- fastmcp.server.openapi → fastmcp.server.providers.openapi
- fastmcp.server.proxy → fastmcp.server.providers.proxy
- fastmcp.server.apps → fastmcp.apps
- Tag upgrade guide "Before" examples with test="skip"

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:53:04 -04:00
Bill Easton
d6b55c0b2f
Raise on unhandled content types in sampling handler dispatch chains (#3857)
* Raise on unhandled content types in sampling handler dispatch chains

The Anthropic and OpenAI sampling handlers have isinstance chains that
dispatch on MCP content types but silently drop unhandled variants like
EmbeddedResource and ResourceLink. This adds explicit else-raise guards
to match the Gemini handler's behavior and the single-content dispatch
paths that already raise.

Raising is the right choice over warn-and-skip: a partial conversion
produces a plausible-but-wrong LLM response (the model confidently
answers based on incomplete input), which is worse than a clear error
that tells the user exactly what isn't supported.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add tests for unsupported content type raises in sampling handlers

Tests the new ValueError raises for unsupported content types
(e.g. EmbeddedResource) in the Anthropic and OpenAI message
conversion loops. Uses model_construct to bypass Pydantic's
union validation since the raise is a defensive guard for
future SDK content types.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:52:31 -04:00
Bill Easton
f26c8fad3e
fix: retry when LLM returns text instead of calling final_response (#3850)
* Retry when LLM returns text instead of calling final_response tool

Instead of raising RuntimeError immediately when the LLM returns a text
response instead of calling the `final_response` tool for structured
output, retry up to 3 times with an explicit nudge message asking the
model to use the tool. This mirrors the existing retry behavior for
validation errors but with a separate, smaller cap.

Fixes #3847

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add tests for text response retry logic

Tests cover:
- Text response followed by successful final_response (retry works)
- Text response exceeding max retries (raises RuntimeError)
- Nudge message appended to history on retry
- No retry when result_type is None (text is valid)

Addresses review feedback from PR review tool (v1 flagged missing tests as high severity).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Slim down text response retry tests

Remove test_nudge_message_in_history (implementation detail).
Reduce boilerplate in remaining 3 tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix ruff format

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:52:16 -04:00
Jeremiah Lowin
af957e773f
Replace ___ with hash-based backend tool routing and per-tool prefab resources (#3824)
Replaces the ___ separator for FastMCPApp backend tool routing with a
deterministic hash(app_name, tool_name) prefix, and replaces the shared
singleton prefab renderer resource with per-tool resources synthesized
on demand.

Backend tools are now callable via <hash>_<local_name> instead of
<app_name>___<local_name>. The dispatcher walks the provider tree
recursively via get_tool_by_hash (same pattern as get_app_tool).

Each prefab tool gets its own renderer resource at
ui://prefab/tool/<hash>/renderer.html with per-tool CSP — fixing the
bug where PrefabAppConfig(csp=...) never actually applied.

Closes #3735, closes #3805
2026-04-12 12:52:07 -04:00
Bill Easton
671eaf0f03
fix: strip title fields from tool schemas for Gemini 2.5 Flash compatibility (#3861)
* fix: strip title fields from tool schemas for Gemini compatibility

Gemini 2.5 Flash produces MALFORMED_FUNCTION_CALL when a function
declaration's parameters_json_schema contains 'title' fields (which
Pydantic adds by default). Strip them in _convert_tool_to_google_genai
before passing to the API.

Fixes #3860

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix ty errors and lowest-deps test failure

- Add assertions for non-None before subscripting FunctionDeclaration
  fields (ty check)
- Test compress_schema directly instead of constructing FunctionDeclaration
  which may not support parameters_json_schema in google-genai==1.18.0

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:51:49 -04:00
Bill Easton
74797c8cac
fix: don't pass HTTP kwargs to run_async when transport is unspecified (#3838)
When transport is None (the default), run_async resolves it to
settings.transport which defaults to "stdio". The previous guard
`transport != "stdio"` passed HTTP kwargs through for None transport,
causing TypeError in run_stdio_async.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:51:34 -04:00
Bill Easton
9e2e602038
fix: cross-provider duplicate detection, error visibility, mask propagation (#3827)
- AggregateProvider._collect_list_results detects duplicate component
  names across providers, respecting the server's on_duplicate setting
- Provider errors logged at WARNING instead of DEBUG
- Parent server re-masks ToolErrors from mounted children at the
  FastMCPError catch boundary instead of mutating the child server

Fixes #3825

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:51:21 -04:00
Bill Easton
7825355b98
Promote 7 ty rules from ignore to warn, fix 9 violations (#3852)
Enables stricter type checking by promoting rules that default to
ignore: division-by-zero, possibly-missing-attribute,
possibly-missing-import, possibly-unresolved-reference,
unsupported-dynamic-base, unsupported-operator, unused-ignore-comment.

6 of the 7 rules had zero violations. possibly-unresolved-reference
had 9 (5 in src/, 3 in tests/, 1 walrus-operator false positive
suppressed with ty: ignore).

🤖 Generated with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:50:56 -04:00
Bill Easton
b99b050e80
Enable 7 zero-violation ruff rules: PLE, LOG, RSE, FA, FLY, SLOT, TID (#3841)
🤖 Generated with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:50:14 -04:00