Support CallArgument and Depends bindings from uncalled-for 0.4.0 (#4802)

* Support CallArgument and Depends bindings from uncalled-for 0.4.0

uncalled-for 0.4.0 adds explicit argument references: CallArgument()
lets a dependency factory read an argument of the function it serves,
and Depends(factory, **bindings) supplies factory arguments at the
declaration site (https://github.com/chrisguidry/uncalled-for/pull/12).
FastMCP's resolver now opens a frame_scope() around dependency
resolution, with the sanitized user arguments as the frame's provided
values. A CallArgument can reference a tool call's public parameters,
but a caller-supplied value for a dependency parameter name is still
stripped before resolution. CallArgument and CycleError are re-exported
from fastmcp.dependencies, and the dependency-injection docs cover both
features.

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

* Raise the pydocket floor to 0.24.0 outside Windows

pydocket 0.24.0 resolves TaskArgument and CallArgument through
uncalled-for 0.4.0's call-scoped frames. Windows keeps the 0.20.0
floor: the burner-redis<0.1.7 pin there transitively caps pydocket to
<0.20.2, and burner-redis has shipped no fixed release yet.

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

* Bump the pydocket floor to 0.24.1 for reliable worker shutdown

docket 0.24.1 fixes a lost cancellation in worker shutdown on Python
3.10 and 3.11 (chrisguidry/docket#456): asyncio.wait_for swallowed a
cancellation delivered in the same event-loop tick that its inner future
completed, so cancelling run_forever during our lifespan teardown left
the worker running and hung the test session. That is what timed out the
Python 3.10 and lowest-direct jobs here. The floor stays platform-split;
Windows keeps >=0.20.0 under the burner-redis pin.

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

* Drop the Windows burner-redis pin and unify the pydocket floor at 0.24.1

The pin blamed the wrong package. The Windows "interpreter crash" that
motivated it (#4618) was pydocket 0.23.1 losing an external cancellation
during worker teardown; pytest-timeout's hard kill of the hung xdist
worker discarded its stdout and looked like a native fault. Capping
burner-redis also dragged pydocket below 0.20.2, so the two variables
were never separated. The repro matrix on prefectlabs/burner-redis#7
shows the July environment failing as resolved, passing with only
pydocket rolled back, and passing with pydocket 0.24.1 alongside
burner-redis 0.1.7 on Windows. pydocket 0.24.1 carries the fix
(chrisguidry/docket#456), so every platform now shares one floor.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: nate nowack <thrast36@gmail.com>
This commit is contained in:
Chris Guidry 2026-08-13 23:19:17 -04:00 committed by GitHub
commit ba283ddb4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 385 additions and 116 deletions

View file

@ -430,4 +430,57 @@ async def call_api(endpoint: str, client: dict = Depends(get_api_client)) -> str
return f"Calling {client['base_url']}/{client['version']}/{endpoint}"
```
### Call Arguments
<VersionBadge version="4.0.0" />
A dependency factory can read the arguments of the function it serves. Declare the reference with `CallArgument()`:
```python
from fastmcp import FastMCP
from fastmcp.dependencies import CallArgument, Depends
mcp = FastMCP("Call Arguments Demo")
def get_account(user_id: str = CallArgument()) -> dict:
return {"id": user_id, "plan": "pro"}
@mcp.tool
async def show_account(user_id: str, account: dict = Depends(get_account)) -> str:
return f"{account['id']} is on {account['plan']}"
```
When a client calls `show_account`, the factory receives the same `user_id` value the tool receives. The bare form takes the name of the parameter it is declared on. `CallArgument("user_id")` names the parameter explicitly. The reference also sees a value that another dependency on the tool's signature produced. `CallArgument("tenant", optional=True)` yields `None` when the function has no such parameter. References that form a cycle raise `CycleError`, importable from `fastmcp.dependencies`.
Clients still cannot override dependencies this way: an argument whose name collides with a dependency parameter is stripped before resolution, so a `CallArgument` reference to that parameter resolves the dependency itself.
### Bindings
<VersionBadge version="4.0.0" />
`Depends()` accepts keyword bindings, so you can wire up a factory without changing it:
```python
from fastmcp import FastMCP
from fastmcp.dependencies import CallArgument, Depends
mcp = FastMCP("Bindings Demo")
def get_account(user_id: str) -> dict:
return {"id": user_id, "plan": "pro"}
@mcp.tool
async def show_account(
owner: str,
account: dict = Depends(get_account, user_id=CallArgument("owner")),
) -> str:
return f"{account['id']} is on {account['plan']}"
```
A binding that is a `Dependency`, such as `CallArgument(...)` or another `Depends(...)`, resolves first and the factory receives its value. Any other value passes through as it is. A binding replaces the default of the factory's own parameter, which is then never resolved. Two dependencies on the same factory share one cached result only when their bindings match. See the [Docket dependency documentation](https://docket.lol/en/latest/dependency-injection/) for more detail on call arguments and bindings.
For advanced dependency patterns—like `TaskArgument()` for accessing task parameters, or custom `Dependency` subclasses—see the [Docket dependency documentation](https://chrisguidry.github.io/docket/dependencies/).

View file

@ -11,7 +11,7 @@ using the uncalled-for DI engine. The docket-specific dependencies
from typing import Any
from uncalled_for import Dependency, Depends, Shared
from uncalled_for import CallArgument, CycleError, Dependency, Depends, Shared
from fastmcp.server.dependencies import (
CurrentAccessToken,
@ -25,11 +25,13 @@ from fastmcp.server.dependencies import (
)
__all__ = [
"CallArgument",
"CurrentAccessToken",
"CurrentContext",
"CurrentFastMCP",
"CurrentHeaders",
"CurrentRequest",
"CycleError",
"Dependency",
"Depends",
"Progress",

View file

@ -30,7 +30,12 @@ from mcp.server.context import ServerRequestContext
from mcp.server.session import ServerSession
from packaging.version import Version
from starlette.requests import Request
from uncalled_for import Dependency, get_dependency_parameters
from uncalled_for import (
CycleError,
Dependency,
frame_scope,
get_dependency_parameters,
)
from uncalled_for.resolution import _Depends
from fastmcp.exceptions import FastMCPError
@ -751,11 +756,12 @@ def without_injected_parameters(
async def _resolve_fastmcp_dependencies(
fn: Callable[..., Any], arguments: dict[str, Any]
) -> AsyncGenerator[dict[str, Any], None]:
"""Resolve Docket dependencies for a FastMCP function.
"""Resolve uncalled-for dependencies for a FastMCP function.
Sets up the minimal context needed for Docket's Depends() to work:
Sets up the context that uncalled-for's Depends() needs:
- A cache for resolved dependencies
- An AsyncExitStack for managing context manager lifetimes
- A resolution frame, so CallArgument() can read the call's arguments
The Docket instance (for CurrentDocket dependency) is managed separately
by the server's lifespan and made available via ContextVar.
@ -783,33 +789,35 @@ async def _resolve_fastmcp_dependencies(
async with AsyncExitStack() as stack:
stack_token = _Depends.stack.set(stack)
try:
resolved: dict[str, Any] = {}
# The frame memoizes each parameter per call, so a
# CallArgument() that references a sibling dependency gets
# the same value the function receives for it.
with frame_scope(fn, arguments) as frame:
resolved: dict[str, Any] = {}
for parameter, dependency in dependency_params.items():
# If argument was explicitly provided, use that instead
if parameter in arguments:
resolved[parameter] = arguments[parameter]
continue
for parameter in dependency_params:
# Resolve the dependency. The frame returns an
# explicitly provided argument as-is.
try:
resolved[parameter] = await frame.resolve(parameter)
except (FastMCPError, CycleError):
# Let FastMCPError subclasses (ToolError,
# ResourceError, etc.) propagate unchanged so they
# can be handled appropriately. CycleError already
# names the cyclic reference path, so wrapping it
# would only hide that.
raise
except Exception as error:
fn_name = getattr(fn, "__name__", repr(fn))
raise RuntimeError(
f"Failed to resolve dependency '{parameter}' "
f"for {fn_name}"
) from error
# Resolve the dependency
try:
resolved[parameter] = await stack.enter_async_context(
dependency
)
except FastMCPError:
# Let FastMCPError subclasses (ToolError, ResourceError, etc.)
# propagate unchanged so they can be handled appropriately
raise
except Exception as error:
fn_name = getattr(fn, "__name__", repr(fn))
raise RuntimeError(
f"Failed to resolve dependency '{parameter}' for {fn_name}"
) from error
# Merge resolved dependencies with provided arguments
final_arguments = {**arguments, **resolved}
# Merge resolved dependencies with provided arguments
final_arguments = {**arguments, **resolved}
yield final_arguments
yield final_arguments
finally:
_Depends.stack.reset(stack_token)
finally:
@ -828,6 +836,9 @@ async def resolve_dependencies(
The filtering prevents external callers from overriding injected parameters by
providing values for dependency parameter names. This is a security feature.
The filtered arguments also feed the resolution frame, so a CallArgument()
reference to a dependency parameter resolves the dependency and never a
caller-supplied value.
Note: Context injection is handled via transform_context_annotations() which
converts `ctx: Context` to `ctx: Context = Depends(get_context)` at registration

View file

@ -103,7 +103,7 @@ server = [
"pyperclip>=1.9.0",
"python-multipart>=0.0.26",
"pyyaml>=6.0,<7.0",
"uncalled-for>=0.2.0",
"uncalled-for>=0.4.0",
"uvicorn>=0.35",
"watchfiles>=1.0.0",
"websockets>=15.0.1",

View file

@ -56,16 +56,13 @@ dependencies = [
# Fernet and the PBKDF2 key derivation behind FASTMCP_TASKS_ENCRYPTION_KEY,
# which encrypts task context snapshots at rest.
"cryptography>=43.0.0",
"pydocket>=0.20.0",
# burner-redis 0.1.7's Windows build crashes the interpreter (native fault,
# no Python traceback) running the memory:// backend under pytest-xdist —
# reproduced on GitHub Actions windows-latest, confirmed absent on
# macOS/Linux with the same versions (full suite green there under the
# identical upgraded dependencies). pydocket only floors it at >=0.1.6, so
# capping pydocket alone is not enough: a resolver is free to pick the
# newest burner-redis satisfying that floor regardless. Pin burner-redis
# directly on Windows only (which in turn caps pydocket to <0.20.2 there,
# the last release that doesn't itself require burner-redis>=0.1.7) until
# upstream ships a fix — other platforms are unaffected and stay unpinned.
"burner-redis<0.1.7; sys_platform == 'win32'",
# pydocket 0.24.1 resolves CallArgument references through uncalled-for's
# call-scoped frames and shuts its worker down reliably when run_forever
# is cancelled on Python 3.10 and 3.11, which our lifespan does on every
# server shutdown. Without that fix a worker cancelled during teardown
# hangs; on Windows, pytest-timeout's hard kill of the hung xdist worker
# was misread as a burner-redis 0.1.7 interpreter crash, which is why a
# burner-redis pin and a platform-split floor used to live here
# (prefectlabs/burner-redis#7 has the exoneration).
"pydocket>=0.24.1",
]

View file

@ -73,10 +73,11 @@ members = ["fastmcp_slim", "fastmcp_remote", "fastmcp_tasks"]
default-groups = ["dev"]
exclude-newer = "1 week"
# The cooldown above refuses anything published in the last week. Exempt the
# first-party packages, whose fresh releases we install deliberately, and the
# first-party packages, whose fresh releases we install deliberately, the
# MCP SDK, where a new major is the only version satisfying our floor and so
# has nothing older to fall back to.
exclude-newer-package = { fastmcp = false, fastmcp-slim = false, fastmcp-remote = false, prefab-ui = false, mcp = false, mcp-types = false }
# has nothing older to fall back to, and uncalled-for, the DI engine whose
# releases we adopt deliberately.
exclude-newer-package = { fastmcp = false, fastmcp-slim = false, fastmcp-remote = false, prefab-ui = false, mcp = false, mcp-types = false, uncalled-for = false, pydocket = false }
[dependency-groups]
dev = [

View file

@ -0,0 +1,189 @@
"""Tests for CallArgument and Depends bindings through FastMCP's resolution."""
import pytest
from mcp_types import TextContent
from fastmcp import FastMCP
from fastmcp.dependencies import CallArgument, CycleError, Depends
from fastmcp.server.dependencies import resolve_dependencies
@pytest.fixture
def mcp():
"""Create a FastMCP server for testing."""
return FastMCP("test-server")
async def test_bare_call_argument_reads_tool_parameter(mcp: FastMCP):
"""A bare CallArgument takes the value of the same-named tool parameter."""
def get_greeting(name: str = CallArgument()) -> str:
return f"Hello, {name}!"
@mcp.tool()
async def greet(name: str, greeting: str = Depends(get_greeting)) -> str:
return greeting
result = await mcp.call_tool("greet", {"name": "Alice"})
assert result.structured_content is not None
assert result.structured_content["result"] == "Hello, Alice!"
async def test_named_call_argument_reads_tool_parameter(mcp: FastMCP):
"""CallArgument("name") reads a tool parameter with a different name."""
def get_greeting(who: str = CallArgument("name")) -> str:
return f"Hello, {who}!"
@mcp.tool()
async def greet(name: str, greeting: str = Depends(get_greeting)) -> str:
return greeting
result = await mcp.call_tool("greet", {"name": "Bob"})
assert result.structured_content is not None
assert result.structured_content["result"] == "Hello, Bob!"
async def test_call_argument_in_binding(mcp: FastMCP):
"""A CallArgument binding wires a tool parameter to a factory parameter."""
def get_account(user_id: str) -> dict[str, str]:
return {"id": user_id, "plan": "pro"}
@mcp.tool()
async def show_account(
owner: str,
account: dict[str, str] = Depends(get_account, user_id=CallArgument("owner")),
) -> str:
return f"{account['id']}:{account['plan']}"
result = await mcp.call_tool("show_account", {"owner": "alice"})
assert result.structured_content is not None
assert result.structured_content["result"] == "alice:pro"
async def test_plain_value_binding(mcp: FastMCP):
"""A binding that is not a Dependency passes through to the factory as-is."""
def get_url(scheme: str) -> str:
return f"{scheme}://example.com"
@mcp.tool()
async def fetch(path: str, url: str = Depends(get_url, scheme="https")) -> str:
return f"{url}/{path}"
result = await mcp.call_tool("fetch", {"path": "docs"})
assert result.structured_content is not None
assert result.structured_content["result"] == "https://example.com/docs"
async def test_binding_replaces_factory_depends_default(mcp: FastMCP):
"""A binding replaces the factory's own Depends default, which never runs."""
default_calls = 0
def get_default_region() -> str:
nonlocal default_calls
default_calls += 1
return "us-east-1"
def get_bucket(region: str = Depends(get_default_region)) -> str:
return f"bucket-{region}"
@mcp.tool()
async def store(
data: str, bucket: str = Depends(get_bucket, region="eu-west-1")
) -> str:
return bucket
result = await mcp.call_tool("store", {"data": "payload"})
assert result.structured_content is not None
assert result.structured_content["result"] == "bucket-eu-west-1"
assert default_calls == 0
async def test_optional_call_argument_yields_none(mcp: FastMCP):
"""CallArgument(optional=True) yields None for a name the tool lacks."""
def get_note(tenant: str | None = CallArgument("tenant", optional=True)) -> str:
return f"tenant={tenant}"
@mcp.tool()
async def report(topic: str, note: str = Depends(get_note)) -> str:
return note
result = await mcp.call_tool("report", {"topic": "sales"})
assert result.structured_content is not None
assert result.structured_content["result"] == "tenant=None"
async def test_sibling_dependency_resolves_once(mcp: FastMCP):
"""A CallArgument reference to a dependency-backed sibling shares one value."""
session_calls = 0
def get_session() -> str:
nonlocal session_calls
session_calls += 1
return "session-1"
def audit(session: str = CallArgument()) -> str:
return f"audit:{session}"
@mcp.tool()
async def act(
step: str,
session: str = Depends(get_session),
log: str = Depends(audit),
) -> str:
return f"{log}|{session}"
result = await mcp.call_tool("act", {"step": "one"})
assert result.structured_content is not None
assert result.structured_content["result"] == "audit:session-1|session-1"
assert session_calls == 1
async def test_call_argument_cycle_raises_cycle_error():
"""CallArgument references that form a cycle raise CycleError with the path."""
def get_a(b: str = CallArgument()) -> str:
return b
def get_b(a: str = CallArgument()) -> str:
return a
async def entangled(a: str = Depends(get_a), b: str = Depends(get_b)) -> str:
return f"{a}{b}"
with pytest.raises(CycleError, match="a -> b -> a"):
async with resolve_dependencies(entangled, {}):
pass
async def test_colliding_argument_never_reaches_call_argument(mcp: FastMCP):
"""A caller-supplied value for a dependency parameter name is stripped.
A CallArgument that references the dependency parameter resolves the
dependency itself, never the caller's value.
"""
def get_role() -> str:
return "user"
def describe(role: str = CallArgument()) -> str:
return f"role={role}"
@mcp.prompt()
async def status(
topic: str,
role: str = Depends(get_role),
summary: str = Depends(describe),
) -> str:
return f"{topic}: {summary}"
result = await mcp.render_prompt("status", {"topic": "audit", "role": "admin"})
content = result.messages[0].content
assert isinstance(content, TextContent)
assert "role=user" in content.text
assert "admin" not in content.text

150
uv.lock generated
View file

@ -2,20 +2,26 @@ version = 1
revision = 3
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.14'",
"python_full_version == '3.13.*'",
"python_full_version >= '3.11' and python_full_version < '3.13'",
"python_full_version < '3.11'",
"python_full_version >= '3.14' and sys_platform == 'win32'",
"python_full_version >= '3.14' and sys_platform != 'win32'",
"python_full_version == '3.13.*' and sys_platform == 'win32'",
"python_full_version == '3.13.*' and sys_platform != 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'win32'",
"python_full_version < '3.11' and sys_platform == 'win32'",
"python_full_version < '3.11' and sys_platform != 'win32'",
]
[options]
exclude-newer = "2026-07-21T14:39:05.08339Z"
exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.
exclude-newer-span = "P1W"
[options.exclude-newer-package]
mcp-types = false
fastmcp = false
prefab-ui = false
pydocket = false
uncalled-for = false
fastmcp = false
mcp = false
fastmcp-remote = false
fastmcp-slim = false
@ -33,10 +39,11 @@ name = "aiofile"
version = "3.9.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.11'",
"python_full_version < '3.11' and sys_platform == 'win32'",
"python_full_version < '3.11' and sys_platform != 'win32'",
]
dependencies = [
{ name = "caio", marker = "python_full_version < '3.11'" },
{ name = "caio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/67/e2/d7cb819de8df6b5c1968a2756c3cb4122d4fa2b8fc768b53b7c9e5edb646/aiofile-3.9.0.tar.gz", hash = "sha256:e5ad718bb148b265b6df1b3752c4d1d83024b93da9bd599df74b9d9ffcf7919b", size = 17943, upload-time = "2024-10-08T10:39:35.846Z" }
wheels = [
@ -48,12 +55,15 @@ name = "aiofile"
version = "3.11.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.14'",
"python_full_version == '3.13.*'",
"python_full_version >= '3.11' and python_full_version < '3.13'",
"python_full_version >= '3.14' and sys_platform == 'win32'",
"python_full_version >= '3.14' and sys_platform != 'win32'",
"python_full_version == '3.13.*' and sys_platform == 'win32'",
"python_full_version == '3.13.*' and sys_platform != 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'win32'",
]
dependencies = [
{ name = "caio", marker = "python_full_version >= '3.11'" },
{ name = "caio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/48/41/2fea7e193e061ce54eacc3b7bc0e6a99e4fcff43c78cf0a76dd781ed8334/aiofile-3.11.1.tar.gz", hash = "sha256:1f91912c6643d2a4e49ca4ae3514f0bf3867ce948a36d99a6411b8f4755f4cf9", size = 19342, upload-time = "2026-05-16T08:18:33.538Z" }
wheels = [
@ -253,18 +263,18 @@ wheels = [
[[package]]
name = "burner-redis"
version = "0.1.6"
version = "0.1.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c8/6f/ec3eeb9e3e9d7fedc51fcb56dd09da0f164495ab6fdf4caaa3754ceed659/burner_redis-0.1.6.tar.gz", hash = "sha256:362091d98c09953ef99be8bd026d75fad42599a0f153211e1a22d3e3029c7cfb", size = 843118, upload-time = "2026-04-27T17:11:41.879Z" }
sdist = { url = "https://files.pythonhosted.org/packages/52/89/54706febafc135095b2a9d797cfbd4eed2ab1ad7819808b99b587020471b/burner_redis-0.1.7.tar.gz", hash = "sha256:7474ff092669fd11ef765411572cdafcc3d89b8054aef4ca0617be6d6be4c680", size = 638644, upload-time = "2026-05-08T15:01:42.961Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d6/cc/061897380b88c637e4bea1f6715ffba851d10b16d6610f2832ab61fa15b5/burner_redis-0.1.6-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:5dc9c170b9994b8d57958041857f240d1b0b9ac1559d0d35473f03fb62386dea", size = 1275400, upload-time = "2026-04-27T17:11:27.07Z" },
{ url = "https://files.pythonhosted.org/packages/db/24/e4c6fb37d059b268c2a26b173d3f84b49547e837340983d3d018c08191c6/burner_redis-0.1.6-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:f29caae7f80fea2e47350df24264a049aeaa934454210cddcadc2336ee8b423a", size = 1223570, upload-time = "2026-04-27T17:11:28.782Z" },
{ url = "https://files.pythonhosted.org/packages/2e/be/718af7f42bbebbfbfd771ba43697526c922dff54d1b0d62654e21418b25e/burner_redis-0.1.6-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ce82edea4ed1ec34448a8610c62665517b3d2030254f31af32828b070a92a8", size = 1325624, upload-time = "2026-04-27T17:11:30.648Z" },
{ url = "https://files.pythonhosted.org/packages/06/8a/4f72de7f967532d3739caa461625dc9122f0ca0d46faa883c153a10d0117/burner_redis-0.1.6-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e3dff7d691ab0035468c17f51632e452b075065a30e026278ed1b297441ce93", size = 1356531, upload-time = "2026-04-27T17:11:32.201Z" },
{ url = "https://files.pythonhosted.org/packages/bd/22/369338d6372abd12dee51965566428c06f3badd95f668ff1c11680b99b30/burner_redis-0.1.6-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3b43f983b6e8fbc208734f04b0bae8cf95323fc43105f977d20f0993fc28c1b3", size = 1526049, upload-time = "2026-04-27T17:11:33.93Z" },
{ url = "https://files.pythonhosted.org/packages/b3/23/0651cf86bc5ed390fef09e30ff4a4664cc3c5b88ddf4c6b8d905acc0d60e/burner_redis-0.1.6-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5b6ba097d910effff00a4610160a4a759503ad590e2c47a580bdcdac9a325823", size = 1579068, upload-time = "2026-04-27T17:11:35.964Z" },
{ url = "https://files.pythonhosted.org/packages/a0/8c/302638fdad4476d4760d477b0f3c6b96c0f88d3f278e5f14d06eb048f788/burner_redis-0.1.6-cp310-abi3-win_amd64.whl", hash = "sha256:98c6b6fc397617cd5a6778ac020e4ed9985c393ad204f9f5cf524b68ae16070b", size = 1103735, upload-time = "2026-04-27T17:11:38.38Z" },
{ url = "https://files.pythonhosted.org/packages/c0/ba/18668d92e18210150f7f93e2930264ad77a82e4d3e5f74ca1aecc002f78f/burner_redis-0.1.6-cp310-abi3-win_arm64.whl", hash = "sha256:c2583e98f9a3836ac2c6243ea0c8d56b40e7017b46617991e329bc807c544bad", size = 1029386, upload-time = "2026-04-27T17:11:40.266Z" },
{ url = "https://files.pythonhosted.org/packages/6c/5d/198bd1d22e504b3034353430703afbdb3efe6e25cb90bf52d896e1d266a7/burner_redis-0.1.7-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f80c866996e0455d584eb3c0f3b067e411c632fb0519eab454e0968edf01e62c", size = 1288888, upload-time = "2026-05-08T15:01:26.103Z" },
{ url = "https://files.pythonhosted.org/packages/2f/4e/ce5c91b884ac37fcd380756402536f8810964014097950900517ce8bd30c/burner_redis-0.1.7-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a3d9569a376b690fb5876d454e4904443332dc3ad5c0057e149fc2ad220bf599", size = 1234282, upload-time = "2026-05-08T15:01:28.286Z" },
{ url = "https://files.pythonhosted.org/packages/6f/c0/31c25cc88143eac2dddcc394151a0db627923d44c94376a83768552c9f13/burner_redis-0.1.7-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20eba1917e3bca9eea5957d5700ff8defcb5a209e57a7841d005549aa0151f44", size = 1337341, upload-time = "2026-05-08T15:01:30.397Z" },
{ url = "https://files.pythonhosted.org/packages/e1/32/95cfa1833316ca2b6b2e58150a4900bc1ad256043cdd36198f1887618ccc/burner_redis-0.1.7-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39111467059b8a28f15ea061d2414ec25c3e57c65759983f90f4d358e7d6a72d", size = 1366800, upload-time = "2026-05-08T15:01:32.891Z" },
{ url = "https://files.pythonhosted.org/packages/34/ad/93c3916f053f89b7b5760da5bf855cd78b7885d480f9cfcc64f3732c1dc2/burner_redis-0.1.7-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9b5adfe99aeb8407f468078f3769b2a63e9168fea12f7709df5d2a3b152706e4", size = 1538160, upload-time = "2026-05-08T15:01:34.667Z" },
{ url = "https://files.pythonhosted.org/packages/5c/b9/19bae42cb124932d71168bc8e5bcb1da33aa62b908e5e632b3d298d7cb15/burner_redis-0.1.7-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:591a9d20685f9d6d22bf0c863b50b12dfcf328b06111b3f62c33cd3185d48ce0", size = 1591491, upload-time = "2026-05-08T15:01:36.708Z" },
{ url = "https://files.pythonhosted.org/packages/f5/30/207f47f406619a5b564355d2946c3171f84231a28b800709b5645b06a5ae/burner_redis-0.1.7-cp310-abi3-win_amd64.whl", hash = "sha256:f6cf4ac666766b32fd63940aad0c120847905fd3102c17e5b6b305f91a21d079", size = 1117564, upload-time = "2026-05-08T15:01:39.221Z" },
{ url = "https://files.pythonhosted.org/packages/76/6f/e9beaf46c5e9fd10dfcdb889ebf7d3aa85142c650c0ab17ab284194f58e1/burner_redis-0.1.7-cp310-abi3-win_arm64.whl", hash = "sha256:458f88feeddfb40a586cc3fcbd8e9384bbdfd2a4512a695af4900e06052570d4", size = 1040407, upload-time = "2026-05-08T15:01:41.235Z" },
]
[[package]]
@ -1077,7 +1087,7 @@ requires-dist = [
{ name = "starlette", marker = "extra == 'mcp'", specifier = ">=1.0.1" },
{ name = "starlette", marker = "extra == 'server'", specifier = ">=1.0.1" },
{ name = "typing-extensions", specifier = ">=4.0.0" },
{ name = "uncalled-for", marker = "extra == 'server'", specifier = ">=0.2.0" },
{ name = "uncalled-for", marker = "extra == 'server'", specifier = ">=0.4.0" },
{ name = "uvicorn", marker = "extra == 'server'", specifier = ">=0.35" },
{ name = "watchfiles", marker = "extra == 'server'", specifier = ">=1.0.0" },
{ name = "websockets", marker = "extra == 'server'", specifier = ">=15.0.1" },
@ -1088,7 +1098,6 @@ provides-extras = ["anthropic", "apps", "azure", "client", "code-mode", "gemini"
name = "fastmcp-tasks"
source = { editable = "fastmcp_tasks" }
dependencies = [
{ name = "burner-redis", marker = "sys_platform == 'win32'" },
{ name = "cryptography" },
{ name = "fastmcp-slim", extra = ["server"] },
{ name = "pydocket" },
@ -1096,10 +1105,9 @@ dependencies = [
[package.metadata]
requires-dist = [
{ name = "burner-redis", marker = "sys_platform == 'win32'", specifier = "<0.1.7" },
{ name = "cryptography", specifier = ">=43.0.0" },
{ name = "fastmcp-slim", extras = ["server"], editable = "fastmcp_slim" },
{ name = "pydocket", specifier = ">=0.20.0" },
{ name = "pydocket", specifier = ">=0.24.1" },
]
[[package]]
@ -1303,7 +1311,7 @@ name = "importlib-metadata"
version = "9.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "zipp", marker = "python_full_version < '3.13'" },
{ name = "zipp" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" }
wheels = [
@ -1346,20 +1354,21 @@ name = "ipython"
version = "8.39.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.11'",
"python_full_version < '3.11' and sys_platform == 'win32'",
"python_full_version < '3.11' and sys_platform != 'win32'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" },
{ name = "decorator", marker = "python_full_version < '3.11'" },
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
{ name = "jedi", marker = "python_full_version < '3.11'" },
{ name = "matplotlib-inline", marker = "python_full_version < '3.11'" },
{ name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "prompt-toolkit", marker = "python_full_version < '3.11'" },
{ name = "pygments", marker = "python_full_version < '3.11'" },
{ name = "stack-data", marker = "python_full_version < '3.11'" },
{ name = "traitlets", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "decorator" },
{ name = "exceptiongroup" },
{ name = "jedi" },
{ name = "matplotlib-inline" },
{ name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "prompt-toolkit" },
{ name = "pygments" },
{ name = "stack-data" },
{ name = "traitlets" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" }
wheels = [
@ -1371,23 +1380,26 @@ name = "ipython"
version = "9.15.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.14'",
"python_full_version == '3.13.*'",
"python_full_version >= '3.11' and python_full_version < '3.13'",
"python_full_version >= '3.14' and sys_platform == 'win32'",
"python_full_version >= '3.14' and sys_platform != 'win32'",
"python_full_version == '3.13.*' and sys_platform == 'win32'",
"python_full_version == '3.13.*' and sys_platform != 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'win32'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" },
{ name = "decorator", marker = "python_full_version >= '3.11'" },
{ name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" },
{ name = "jedi", marker = "python_full_version >= '3.11'" },
{ name = "matplotlib-inline", marker = "python_full_version >= '3.11'" },
{ name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "prompt-toolkit", marker = "python_full_version >= '3.11'" },
{ name = "psutil", marker = "python_full_version >= '3.11' and sys_platform != 'cygwin' and sys_platform != 'emscripten'" },
{ name = "pygments", marker = "python_full_version >= '3.11'" },
{ name = "stack-data", marker = "python_full_version >= '3.11'" },
{ name = "traitlets", marker = "python_full_version >= '3.11'" },
{ name = "typing-extensions", marker = "python_full_version == '3.11.*'" },
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "decorator" },
{ name = "ipython-pygments-lexers" },
{ name = "jedi" },
{ name = "matplotlib-inline" },
{ name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "prompt-toolkit" },
{ name = "psutil", marker = "sys_platform != 'cygwin' and sys_platform != 'emscripten'" },
{ name = "pygments" },
{ name = "stack-data" },
{ name = "traitlets" },
{ name = "typing-extensions", marker = "python_full_version < '3.12'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/53/59/165d3b4d75cc34add3122c4417ecb229085140ac573103c223cd01dde96f/ipython-9.15.0.tar.gz", hash = "sha256:da2819ce2aa83135257df830660b1176d986c3d2876db24df01974fa955b2756", size = 4442580, upload-time = "2026-06-26T11:03:35.913Z" }
wheels = [
@ -1399,7 +1411,7 @@ name = "ipython-pygments-lexers"
version = "1.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pygments", marker = "python_full_version >= '3.11'" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" }
wheels = [
@ -2367,7 +2379,7 @@ wheels = [
[[package]]
name = "pydocket"
version = "0.20.1"
version = "0.24.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "burner-redis" },
@ -2386,9 +2398,9 @@ dependencies = [
{ name = "tzdata", marker = "sys_platform == 'win32'" },
{ name = "uncalled-for" },
]
sdist = { url = "https://files.pythonhosted.org/packages/72/bf/7f1134e990855f373e5ee6ba316db8fe654a2d7dd852b41ab890fcfb91e3/pydocket-0.20.1.tar.gz", hash = "sha256:d72b3784e4b5069b39e5f49f599d54a891e1b6222c27a8bcfbd4dee0f57d4895", size = 361993, upload-time = "2026-05-06T14:06:25.956Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b7/6b/a87c6e3fd197807f630af4270aa2ce8f4c1fca4e43bba783f273d298d646/pydocket-0.24.1.tar.gz", hash = "sha256:477d77be1fcfd10ee0c2d0b8aa8c6e97851b9c7f39bb6f2b4e6d42e9b4d6e95a", size = 430759, upload-time = "2026-08-10T19:50:03.368Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9d/9d/1bd873a0ea480dec388c40ac1a7500c129efbb9d61e2fef6b97236703458/pydocket-0.20.1-py3-none-any.whl", hash = "sha256:c886ece90ac93018f069d1eef9443f888404081d7258955e16847752575c95ae", size = 102774, upload-time = "2026-05-06T14:06:24.548Z" },
{ url = "https://files.pythonhosted.org/packages/a7/05/4e3b902bc0ca407188aa5fe38af49be634487aec242a77287103242e11b2/pydocket-0.24.1-py3-none-any.whl", hash = "sha256:1faa6c3d566f1f0431e35dfa12db4ccee515d945b913b516ee3e6279afb9e789", size = 130249, upload-time = "2026-08-10T19:50:01.627Z" },
]
[[package]]
@ -2869,7 +2881,8 @@ name = "rpds-py"
version = "0.30.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.11'",
"python_full_version < '3.11' and sys_platform == 'win32'",
"python_full_version < '3.11' and sys_platform != 'win32'",
]
sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" }
wheels = [
@ -2994,9 +3007,12 @@ name = "rpds-py"
version = "2026.6.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.14'",
"python_full_version == '3.13.*'",
"python_full_version >= '3.11' and python_full_version < '3.13'",
"python_full_version >= '3.14' and sys_platform == 'win32'",
"python_full_version >= '3.14' and sys_platform != 'win32'",
"python_full_version == '3.13.*' and sys_platform == 'win32'",
"python_full_version == '3.13.*' and sys_platform != 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform == 'win32'",
"python_full_version >= '3.11' and python_full_version < '3.13' and sys_platform != 'win32'",
]
sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" }
wheels = [
@ -3218,8 +3234,8 @@ name = "taskgroup"
version = "0.2.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
{ name = "exceptiongroup" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/8d/e218e0160cc1b692e6e0e5ba34e8865dbb171efeb5fc9a704544b3020605/taskgroup-0.2.2.tar.gz", hash = "sha256:078483ac3e78f2e3f973e2edbf6941374fbea81b9c5d0a96f51d297717f4752d", size = 11504, upload-time = "2025-01-03T09:24:13.761Z" }
wheels = [
@ -3391,11 +3407,11 @@ wheels = [
[[package]]
name = "uncalled-for"
version = "0.3.2"
version = "0.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b5/82/345cc927f7fbdae6065e7768759932fcc827fc20b29b45dfbafa2f1f7da4/uncalled_for-0.3.2.tar.gz", hash = "sha256:89f5dbcd71e2b8f47c030b1fa302e6cce2ec795d1ac565eeb6525c5fe55cb8a2", size = 50032, upload-time = "2026-05-06T13:38:25.204Z" }
sdist = { url = "https://files.pythonhosted.org/packages/6b/5a/92ce0b3ea5481915f55da994c2c2c5f7a3c09949afde196ee89f8ab961aa/uncalled_for-0.4.0.tar.gz", hash = "sha256:335b95bd2422332ec210d518f314a16e4c640921c39fc8bf2ad095bd3538f4af", size = 56979, upload-time = "2026-08-10T14:51:46.247Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/25/2c87754f3a9e692315f7b811244090e68f362979fc8886b3fbd2985a1d8c/uncalled_for-0.3.2-py3-none-any.whl", hash = "sha256:0ff60b142c7d1f8070bde9d42afaa70aedc77dcc10998c227687e9c15713418e", size = 11444, upload-time = "2026-05-06T13:38:24.025Z" },
{ url = "https://files.pythonhosted.org/packages/a2/40/97cec87c077eb3291fc7905e6633e08b7ca593c57d30238444bcb6bb3d53/uncalled_for-0.4.0-py3-none-any.whl", hash = "sha256:16c4bb3337532e4bd5569adc192285976f3ad5305402256d34c67a12b5c968bd", size = 15502, upload-time = "2026-08-10T14:51:45.068Z" },
]
[[package]]