Commit graph

1,316 commits

Author SHA1 Message Date
William Easton
4cfeb63b95
Fix input validation for ports, Optional[bool] form backfill, async error callbacks
🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 23:11:11 -05:00
Jeremiah Lowin
f4f2ec07fb
Deprecate ctx.elicit() without response_type (#3916) 2026-04-13 21:23:31 -04:00
Jeremiah Lowin
110cd3adcb
Add response_title and response_description to ctx.elicit() (#3912) 2026-04-13 20:12:09 -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
Jeremiah Lowin
82f310fe61
AuthKit: auto-bind token audience to resource URL (RFC 8707) (#3905) 2026-04-13 17:11:17 -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
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
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
Jeremiah Lowin
57f1b1bced
fix: drop configurable dedupe from AggregateProvider, always warn (#3877) 2026-04-12 17:03:02 -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
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
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
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
d0bcec979c
fix: TransformedTool sync fn crash and schema mutation (#3823)
* fix: TransformedTool sync fn crash, schema mutation, output_schema=False

- Handle sync transform_fn in run() using is_coroutine_function check
  instead of unconditionally awaiting (fixes TypeError crash)
- Deep copy parent property schemas to prevent mutation corruption
- Accept output_schema=False via BeforeValidator (converts to None)
- Remove inaccurate docstring claiming str/None shorthand for transform_args

Fixes #3821

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

* Add regression tests for sync transform_fn and schema mutation

🤖 Generated with Claude Code

Co-authored-by: Jeremiah Lowin <jeremiah@lowin.dev>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Co-authored-by: Jeremiah Lowin <jeremiah@lowin.dev>
2026-04-11 11:50:23 -04:00
Bill Easton
4b59e0d94b
Fix json_schema_to_type crashes on keywords, boolean schemas, empty enums, and name collisions (#3818)
* Fix crash bugs in json_schema_to_type

- Handle boolean schemas (True/False) at the public entry point
- Append trailing underscore to Python keyword property names (PEP 8)
- Return Any for empty enum values instead of crashing Pydantic
- Deduplicate field names after sanitization to prevent collisions
  (e.g. "foo-bar" and "foo_bar" both sanitizing to "foo_bar")

🤖 Generated with Claude Code

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

* Move local imports to module level in test_json_schema_type

---------

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-11 11:29:26 -04:00
Bill Easton
468559978a
Add real-world schema crash test against APIs.guru directory (#3826)
Integration test that runs json_schema_to_type against 232K schemas
from 4,120 real-world OpenAPI specs (APIs.guru openapi-directory).
Snapshots crash counts as regression baselines so future changes
can't silently increase the crash rate.

Current baseline (openapi-directory@f7207cf0):
  TypeErrors:   2,342 (datetime serialization)
  SchemaErrors:   273 (invalid regexes in specs)
  Timeouts:         0
  Other:            0

Skipped unless openapi-directory is cloned locally.
Run with: pytest -m integration tests/.../test_real_world_schemas.py

🤖 Generated with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 11:23:55 -04:00
Bill Easton
eec52b1f02
fix: materialize generators before result conversion, handle bytes gracefully (#3830)
- Detect async/sync generators after tool execution and materialize
  into lists before the result conversion pipeline processes them
- Generator materialization runs inside timeout scope so slow generators
  respect the configured timeout
- Handle bytes return types: UTF-8 bytes as text, non-UTF-8 as base64
- Suppress output_schema for bytes return types (can't be structured JSON)
- Catch UnicodeDecodeError alongside PydanticSerializationError in
  convert_result for robustness

Fixes #3829

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 11:23:51 -04:00
Bill Easton
c946664a16
Validate actual base64 data size in FileUpload, not client-reported size (#3816)
The store_files tool checked the client-provided `size` field to enforce
max_file_size, but this field is untrusted input. A client could set
size=1 while sending a multi-megabyte payload, bypassing the limit.

Now computes actual size from the base64 data length instead.

🤖 Generated with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 11:23:49 -04:00
Chris Guidry
db9e2685fb
Bump pydocket>=0.19.0, drop fakeredis pin (#3822)
pydocket 0.19.0 fixes the fakeredis 2.35.0 FakeConnection rename
internally, so we no longer need to carry the fakeredis ceiling
ourselves. Removes the direct fakeredis[lua]<2.35.0 dependency from the
tasks extra entirely — it's just a transitive of pydocket now.

🤖 Generated with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 13:41:18 -04:00
Jeremiah Lowin
faf5f86e09
Version-check is_docket_available() to avoid transitive pydocket crash (#3807) 2026-04-09 21:52:21 -04:00
Jeremiah Lowin
6592aaa70e
fix: accept both client_id and identifier_uri as Azure audience (#3797) 2026-04-08 20:25:42 -04:00
aimable100
556fd8fa42
Harden client tool result error handling (#3778) 2026-04-08 13:57:10 -04:00
Jeremiah Lowin
f5be772403
fix: bump ty to >=0.0.29 and suppress new false positives (#3790) 2026-04-07 19:49:09 -04:00
Jeremiah Lowin
2b9d3eee0f
fix: use identifier_uri as audience for Azure token validation (#3787)
* fix: use identifier_uri as audience for Azure token validation

* fix test expectations for identifier_uri audience
2026-04-07 18:37:52 -04:00
Jeremiah Lowin
e1ea133fb7
fix: Cognito token verification checks client_id instead of aud (#3786)
* fix: Cognito token verification checks client_id instead of aud

* fix: Cognito token verification checks client_id instead of aud

* handle list audience values in Cognito client_id check
2026-04-07 18:09:43 -04:00
Kakarlamudi Venkata Dhanush
042db1d0e3
Fix OpenAPI 3.0 nullable fields in tool input schemas (#3768)
* Fix OpenAPI 3.0 nullable fields leaking into tool input schemas

* fix: convert nullable fields in input schemas and fix recursion

* Fix unused loop variable in OpenAPI converter

* Refactor OpenAPI nullable conversion and add tests

* Clean up and add integration tests for nullable input schemas

---------

Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
2026-04-07 18:09:26 -04:00
Jeremiah Lowin
e5b96343d1
Fix boolean property schemas in JSON Schema parsing (#3785) 2026-04-07 17:55:51 -04:00
Jeremiah Lowin
8ee81b3037
fix: boolean false values dropped in form submissions (#3776)
* fix: boolean false values dropped in form submissions

* revert apps_dev.py boolean coercion
2026-04-07 11:23:34 -04:00
Fatia Kusuma Dewi
5587cb7c43
changeable allowed_client_redirect_uris (#3772) 2026-04-06 20:58:09 -04:00
Kakarlamudi Venkata Dhanush
c642d6a5cf
fix: propagate upstream_claims in load_access_token (#3750) 2026-04-06 19:55:26 -04:00
Kakarlamudi Venkata Dhanush
07e950ec09
fix: FastAPI TestClient compatibility and lifespan re-initialization (#3736) 2026-04-06 19:53:27 -04:00
Rishav Mitra
99eaeb8af4
fix: substitute server variable defaults when building base URL from OpenAPI spec (#3770)
* 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.
2026-04-06 19:48:30 -04:00
Jeremiah Lowin
4bbc4eec3b
fix: ResponseLimitingMiddleware no longer breaks outputSchema tools (#3756) 2026-04-05 10:36:38 -04:00
Chris Guidry
d41bcb2c9e
Unify background task context forwarding, fix concurrent dependency bugs (#3710)
* Unify background task context forwarding and fix concurrent dependency bugs

We've been getting a steady trickle of edge-case reports around background tasks
and contextual dependencies over the last few months (#3654, #3656, #3569). Each
one pointed at a different symptom, but they all traced back to the same area:
the way context is negotiated between the "frontend" server and Docket workers
was grown piecemeal, with each new piece of context (access tokens, HTTP headers,
origin request IDs) getting its own Redis key, its own restore function, and its
own ContextVar. This made it hard to reason about what state was available where,
and the shared-instance Dependency pattern made concurrent tasks stomp on each
other's cleanup state.

This takes a step back and reworks the whole thing as a single unified system:

- Dependency subclasses (_CurrentContext, Progress, _CurrentAccessToken, etc.)
  are now stateless factories — __aenter__ returns a fresh per-invocation
  object, so concurrent tasks never share mutable state. Fixes #3654, #3656.

- The three individual context-snapshot Redis keys (access_token, http_headers,
  origin_request_id) are collapsed into a single TaskContextSnapshot stored as
  one JSON key per task. The three _restore_task_* functions and two ContextVars
  they populated are gone.

- Sync functions like get_http_request() and get_access_token() now find the
  snapshot transparently in background tasks via a 3-tier sync fallback:
  ContextVar (set by _CurrentContext for functions with deps) → in-memory dict
  (same-process workers) → sync Redis GET (out-of-process workers). No function
  wrapping needed.

- The _wrap_for_task_http_headers hack is deleted. FunctionTool registers its
  raw function with Docket so Docket sees and resolves ALL dependencies,
  including Docket-native ones like Retry and Timeout.

- ProxyTool.from_mcp_tool() now propagates execution.taskSupport metadata from
  remote tools. Fixes #3569.

- Removed redundant _current_docket/_current_worker ContextVar management from
  Context.__aenter__/__aexit__ (they're only set in the lifespan now).

Closes #3654
Closes #3656
Closes #3569

🤖 Generated with Claude Code

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

* Address code review feedback

- _OptionalCurrentContext: guard __aexit__ against cleaning up contexts it
  didn't create (check is_background_task before delegating)
- Narrow except clauses in snapshot loading (OSError, JSONDecodeError, etc.
  instead of bare Exception)
- Fix docstrings on register_with_docket for resources/prompts/templates
- Simplify Progress: read ExecutionProgress directly from current_execution
  instead of creating and manually entering a DocketProgress wrapper

🤖 Generated with Claude Code

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

* Use pop-on-access transfer buffer instead of bounded LRU cache for snapshots

The in-memory snapshot dict is a transfer mechanism, not a cache. Entries go
in at submission and come out at the worker's first access. Using pop instead
of get means the dict only holds entries during the brief submission-to-execution
window, bounded by task concurrency (~10) rather than a 10,000-entry LRU limit.

🤖 Generated with Claude Code

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

* Drop in-memory transfer buffer, use sync Redis for all backends

Instead of maintaining an in-memory dict to bridge the async/sync gap, use
a sync Redis client directly. For memory:// backends (fakeredis), shares the
same FakeServer instance via docket._redis.get_memory_server() so data written
by the async Docket client is visible to sync reads. For real Redis, creates a
standard sync connection. No in-process state to manage at all.

🤖 Generated with Claude Code

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

* Move snapshot operations to TaskContextSnapshot methods

capture(), from_json(), to_json(), save() are now classmethod/instance methods
on the dataclass instead of free functions. Deduplicates JSON parsing that was
copy-pasted between the async and sync load paths.

🤖 Generated with Claude Code

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

* Trim implementation details from register_with_docket docstrings

🤖 Generated with Claude Code

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

* Clarify docket lookup comment in submit_to_docket

🤖 Generated with Claude Code

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

* Restore docket/worker ContextVar bridge in Context.__aenter__

Servers that own the Docket (the parent) re-set _current_docket/_current_worker
from their instance attributes when entering a Context. Mounted children skip
this (their _docket is None), so they inherit the parent's value. This is needed
for ASGI deployments where ContextVars set during the lifespan don't propagate
to request handlers.

🤖 Generated with Claude Code

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

* Key snapshot cache by task_id to prevent cross-task context leakage

Docket workers may reuse the same asyncio context for sequential tasks.
The ContextVar cache now stores (task_id, snapshot) tuples so stale entries
from previous tasks are automatically ignored.

🤖 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-03 10:48:27 -04:00