Commit graph

32 commits

Author SHA1 Message Date
Jeremiah Lowin
87b4a6fb99 Refactor Client class into mixins for better organization
Split the monolithic Client class into focused mixin classes:
- ClientToolsMixin (tools_client.py)
- ClientPromptsMixin (prompts.py)
- ClientResourcesMixin (resources.py)
- ClientTaskManagementMixin (task_management.py)

Also:
- Extract timeout normalization helpers to utilities/timeout.py
- Split SSETransport into its own module (transports/sse.py)
- Clean up unused imports across client modules
- Fix bare except clauses to catch specific McpError

Note: ty reports unresolved-attribute errors on mixin classes because
it doesn't understand that mixins will be composed with Client. This is
a known limitation of mixin typing in Python. The code is functionally
correct and all tests pass.
2026-01-19 09:21:38 -05:00
Jeremiah Lowin
a6cd764b5f
Add component versioning and VersionFilter transform (#2894) 2026-01-16 20:53:18 -05:00
Jeremiah Lowin
b61c76b19b Fix task capabilities location (issue #2870)
Tasks belong in capabilities.tasks (first-class field) per SEP-1686,
not capabilities.experimental.tasks. This fixes VS Code Copilot 1.107+
integration which checks capabilities.tasks?.requests?.tools?.call.

Changes:
- Update get_task_capabilities() to return ServerTasksCapability types
- Override get_capabilities() in LowLevelServer to set tasks field
- Remove experimental_capabilities parameter usage
- Update test to verify correct location

Fixes #2870
2026-01-13 22:26:13 -05:00
Jeremiah Lowin
2b6a0faf1c
Add loq file size limits and clean up type ignores (#2859) 2026-01-13 07:29:12 -05:00
Jeremiah Lowin
1b723f302d
Decorators return functions instead of component objects (#2856) 2026-01-12 21:58:07 -05:00
Jeremiah Lowin
7b0de84ba7
Add task_meta to prompts and centralize fn_key enrichment (#2751) 2025-12-26 15:04:25 -05:00
Jeremiah Lowin
fc19f1f8de
Add task_meta parameter to read_resource() for explicit task control (#2750) 2025-12-26 12:59:01 -05:00
Jeremiah Lowin
bf1c222e20
Add explicit task_meta parameter to FastMCP.call_tool() (#2749) 2025-12-26 11:38:35 -05:00
Jeremiah Lowin
648684d2bb
Introduce ResourceResult as canonical resource return type (#2734) 2025-12-25 21:21:55 -05:00
Jeremiah Lowin
aef1ccc026
Add type-prefixed keys for globally unique component identification (#2704) 2025-12-24 08:39:43 -05:00
Jeremiah Lowin
f36b147f60
Unify component storage in LocalProvider (#2680) 2025-12-23 19:24:09 -05:00
Chris Guidry
7aae52d18d Bump pydocket to 0.16.2 for task cancellation support
Adds a test that verifies task cancellation actually interrupts running
coroutines (they receive CancelledError) rather than just marking the task
as cancelled in Redis while the coroutine continues to completion.

This requires pydocket >= 0.16.2 which added best-effort cancellation via
Redis pub/sub signaling to workers.

Closes #2679

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 14:43:09 -05:00
Jeremiah Lowin
4177d8358d
Fix ty 0.0.5 type errors (#2676) 2025-12-22 19:47:20 -05:00
Jeremiah Lowin
748d25eede
Refactor FastMCPProxy into ProxyProvider (#2669)
* Refactor FastMCPProxy into ProxyProvider

Move proxy functionality from custom manager classes to the Provider pattern:

- Create ProxyProvider that implements the Provider interface
- Move all proxy code to src/fastmcp/server/providers/proxy.py
- Keep FastMCPProxy as a convenience wrapper using ProxyProvider
- Add deprecation warning when importing from old location
- Convert handler classmethods to module-level functions
- Remove redundant get_* methods (base class defaults work)

* Remove unused Components class, simplify TaskComponents()
2025-12-21 21:30:03 -05:00
Jeremiah Lowin
7ff23a06f4
Add poll_interval to TaskConfig (#2666)
* Add poll_interval to TaskConfig

Allow users to configure polling interval per component via
TaskConfig(poll_interval=timedelta(...)). Default is 5 seconds.

* Update snapshots for poll_interval field

* Add version badge to poll_interval docs

* Add defensive handling for Redis data and align default poll intervals
2025-12-21 16:59:43 -05:00
Jeremiah Lowin
049bd22827
Replace type: ignore[attr-defined] with isinstance assertions in tests (#2665)
* Replace type: ignore[attr-defined] with isinstance assertions in tests

* Fix isinstance assertions in failing tests

- Fix enum test to check for ResponseEnum instead of str
- Fix binary resource test to check for BlobResourceContents instead of TextResourceContents
- Fix Root type tests to check attributes directly instead of isinstance checks

* Fix type errors without using type: ignore

- Remove execution methods from TransformingProvider (only handles transformations)
- Add execution methods to base Provider class with default implementations
- Fix type narrowing in tests using cast() instead of type: ignore
- Fix PromptResult type handling in prompt render tests
- Fix type narrowing in middleware test for arguments and structured_content
2025-12-21 16:37:20 -05:00
Jeremiah Lowin
d6654a2379
Add supports_tasks() method to replace string mode checks (#2664)
* Add supports_tasks() method to replace string mode checks

Consolidates task config mode checks into a readable method on TaskConfig.
Instead of `task_config.mode == "forbidden"` or `task_config.mode != "forbidden"`,
code now uses `task_config.supports_tasks()` for clearer intent.

Updated 20 instances across the codebase and added type assertions in tests
to resolve type checker warnings.

* Update test to match new error message
2025-12-21 15:41:46 -05:00
Jeremiah Lowin
19fdac7b02
Refactor provider execution: components own their execution (#2663)
* Add test_custom_subclass_tasks.py

* Refactor provider execution: delegate to middleware via wrapper components

- Remove execution methods (call_tool, read_resource, etc.) from Provider base
- Add FastMCPProvider* wrapper classes that delegate to child server middleware
- Move task routing to Tool._run() using contextvars (_task_metadata, _tool_call_key)
- Add convert_to_tool_result(result, output_schema) utility for Docket results
- Add convert_to_prompt_result() utility for prompt task results
- Pass namespaced key via add_to_docket(name=) for mounted tool lookup

* Standardize add_to_docket() with fn_key/task_key parameters

All components now use explicit fn_key (function lookup) and task_key
(result storage) parameters instead of relying on implicit key handling.
This fixes mounted component task execution where the MCP-visible key
differs from the Docket-registered function name.

* Add middleware chain tests for three-level mount hierarchy

Tests verify middleware runs at parent, child, and grandchild levels
for tools, resources, prompts, and resource templates.

* WIP: Provider refactor - unified submit_to_docket, template _read() in progress

Work in progress on refactoring execution to use component _read()/_run()/_render() methods.
Template background tasks not yet working - needs fix for Docket key lookup.

* Fix conversion functions to take full component for attribute access

Pass Tool/Prompt/Resource/Template to conversion functions instead of
individual attributes, ensuring access to serializer, output_schema,
mime_type, etc. Also fixes mixed-content output schema validation.

* Refactor: unified convert_result() methods and check_background_task helper

- Add convert_result() instance methods to all component types (Tool, Prompt, Resource, ResourceTemplate)
- Extract duplicated task routing logic into check_background_task() helper
- Fix type annotations on FastMCPProviderResource.read() and FastMCPProviderPrompt.render()
- Update protocol.py to use component.convert_result() uniformly

* Update tests to use namespace= instead of deprecated prefix= parameter
2025-12-21 15:03:24 -05:00
Jeremiah Lowin
fc8ba728a8
Use CreateTaskResult for background task creation (#2660)
* Use CreateTaskResult for background task creation

Move result conversion logic to components (convert_result methods) and
return proper CreateTaskResult SDK type from task handlers. Consolidates
MCP protocol handler overrides into server.py with documentation.

* Address CodeRabbit nitpicks

- Add type annotation for resource parameter in handle_resource_as_task
- Move RootModel import to module level in client.py

* Document intentionally unused task_meta parameters

Prefix with underscore to suppress lint warnings. Client TTL will be
configurable via TaskConfig in the future; keeping parameter for API stability.
2025-12-20 20:19:11 -05:00
Jeremiah Lowin
caeaa86394
Simplify .key as computed property (#2648)
* Simplify .key as computed property

Keep .key as the standard lookup interface for all components but
implement it as a computed property instead of a stored field.

- Remove _key private attribute and custom model_copy(key=...)
- .key returns .name for tools/prompts, str(.uri) for resources,
  .uri_template for templates
- Use .key universally for component lookups in managers
- MountedProvider: prefix URIs only for resources/templates, not names
- Docket registration: tools/prompts use .key, resources use .name
  (matches fn.__name__ for function lookup)

* Simplify .key as computed property

Keep .key as the standard lookup interface for all components but
implement it as a computed property instead of a stored field.

- Remove _key private attribute and custom model_copy(key=...)
- .key returns .name for tools/prompts, str(.uri) for resources,
  .uri_template for templates
- Use .key universally for component lookups and Docket registration
- MountedProvider: prefix URIs only for resources/templates, not names
- Add _backend_* fields to proxy classes to preserve original identifiers
  for backend calls when prefixed via import_server

* Standardize .key as computed property

- .key is now a read-only computed property:
  - Tools/Prompts: returns .name
  - Resources: returns str(.uri)
  - Templates: returns .uri_template
- Prefixing uses model_copy(update={...}) to change underlying field
- Resource/template names are NOT prefixed, only URIs
- Move import_server tests to tests/deprecated/
2025-12-18 14:48:47 -05:00
Chris Guidry
a5064cc007 Add end-to-end tests for custom-named resources and templates
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 11:37:06 -05:00
Chris Guidry
22e135fa5a Fix task execution for tools with custom names
When a tool was registered with a custom name different from its function
name, task execution would fail because Docket registered the function by
its `__name__` but the handler looked it up by the tool's configured name.

This switches to using Docket's new `names=` parameter (pydocket 0.16.0)
to register functions with their proper lookup keys, and removes the
`_create_named_fn_wrapper` hack that was used for mounted servers.

Closes #2642

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 11:36:34 -05:00
Jeremiah Lowin
b20de51512
Remove TaskConfig and client from root exports (#2580) 2025-12-09 12:28:01 -05:00
Chris Guidry
dc00f5c2bb Centralize task capabilities, add component filtering
Addresses code review feedback:
- Extract `get_task_capabilities()` to avoid duplicating the SEP-1686
  capability structure across transports
- Add `_should_enable_component()` check before task routing for tools,
  resources, and prompts to respect enable/tag filtering
- Simplify tasks/__init__.py to avoid circular import issues

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-09 10:02:38 -05:00
Chris Guidry
350f723592 Remove enable_tasks setting, enable task protocol by default
The task protocol (SEP-1686) is now always enabled - server always
registers task handlers and advertises task capabilities. Users still
opt into background execution at the server level (tasks=True) or
component level (task=True on tools, prompts, resources).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-09 09:34:42 -05:00
Chris Guidry
4a6f8730f5 Prefix Docket function names to avoid collisions in multi-mount setups
When multiple servers with task-enabled tools are mounted into a parent,
their functions were all registered with Docket using `fn.__name__`. This
meant two mounted servers each having a function named `add` would both
register under `"add"`, with the second overwriting the first.

Now mounted functions use prefixed names matching their client-facing tool
names (e.g., `c1_add`, `c2_add`). Root server functions still use their
original names with no prefix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 15:12:40 -05:00
Chris Guidry
82301773cf Proxy tasks forbidden, mount tasks working
Simplifies the task support story for proxies and mounts:
- Mounts get full SEP-1686 task support (unchanged)
- Proxies explicitly forbid task execution

The cross-session task forwarding for proxies turned out to be complex
since each client connection creates a new server lifespan with a new
Docket context, and task keys include session_id. Rather than introduce
that complexity, proxies now explicitly refuse task-augmented execution.

Key changes:
- All proxy components (ProxyTool, ProxyPrompt, ProxyResource,
  ProxyTemplate) now have task_config.mode="forbidden"
- Proxy tests verify forbidden behavior (sync execution works,
  task=True returns error/raises McpError)
- Fixed prompt task handler to check hasattr(prompt, "task_config")
  instead of isinstance(prompt, FunctionPrompt) so it applies to
  ProxyPrompt too
- Added test suites for both proxy and mount task behavior

Also includes minor fixes:
- Fixed result.meta_ -> result.meta in ProxyTool.run()
- Fixed client handling of returned_immediately without taskId
- Bumped pydocket>=0.15.2

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 11:44:28 -05:00
Jeremiah Lowin
a231ea4c3c Add TaskConfig for SEP-1686 execution modes
Expose the full MCP task execution modes (forbidden/optional/required)
via TaskConfig instead of just boolean task=True/False.
2025-12-06 21:01:11 -05:00
Jeremiah Lowin
1ed976b1d9
refactor: move task attribute to function-based variants only [SEP-1686] (#2560)
* refactor: move task attribute to function-based variants only

* fix: update snapshot test for ResourceTemplate without task field
2025-12-05 21:05:42 -05:00
Chris Guidry
47d3044b06 Remove enable_docket setting; Docket is now always on
Docket provides background task execution and is now always available
for all FastMCP servers. Only `enable_tasks` remains to control the
SEP-1686 task protocol support.

Changes:
- Remove `enable_docket` setting and related validation
- Docket/Worker lifecycle is always active in server lifespan
- CurrentDocket and CurrentWorker dependencies work without config
- Add server readiness signaling via `_started` event
- Fix test timing issues with proper port probing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 11:40:04 -05:00
Jeremiah Lowin
e47abb4bd7
[SEP-1686] Raise ValueError when sync functions have task=True (#2554)
* Raise ValueError when sync functions have task=True

Move validation from server.py decorators to the from_function() class
methods on FunctionTool, FunctionPrompt, FunctionResource, and
FunctionResourceTemplate. This ensures the check runs regardless of how
the objects are created.

* Fix async check for callable classes and staticmethods

Move the task=True async validation to run AFTER callable classes and
staticmethods are unwrapped, preventing false positives for async
callable classes with sync-looking signatures.
2025-12-05 11:31:28 -05:00
Chris Guidry
66aaf420c9
[2.14] SEP-1686 tasks (#2378)
* Implement MCP background tasks (SEP-1686) using Docket

Adds support for background task execution via the MCP task protocol,
powered by Docket for task queue management.

- Tools, resources, and prompts can be marked with `task=True` to run async
- Progress dependency for tracking task progress
- CurrentDocket and CurrentWorker dependencies for advanced use cases
- Client API with `.call_tool(..., task=True)` returns task handles
- Task status notifications via subscriptions
- CLI worker command for distributed task processing

Configuration via environment:
- FASTMCP_ENABLE_DOCKET=true
- FASTMCP_ENABLE_TASKS=true
- FASTMCP_DOCKET_URL=redis://... (or memory:// for single-process)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix tasks example import (TaskStatusResponse → GetTaskResult)

The example was using a non-existent TaskStatusResponse type.
Updated to use mcp.types.GetTaskResult which is what the
on_status_change callback actually receives.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix env var name in Docket error messages

The error messages referenced FASTMCP_EXPERIMENTAL_ENABLE_DOCKET but the
actual setting is FASTMCP_ENABLE_DOCKET.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Remove deprecated code re-added from pre-#2329 branch

- Remove ExtendedEnvSettingsSource (FASTMCP_SERVER_ prefix support)
- Remove dependencies parameter from FastMCP.__init__

* Replace fakeredis git pin with PyPI release

* Remove redundant fakeredis dev dep (pulled via pydocket)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
2025-12-04 20:10:35 -05:00