fastmcp 2.2.9__py3-none-any.whl → 2.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fastmcp/tools/tool.py CHANGED
@@ -10,7 +10,9 @@ from mcp.types import EmbeddedResource, ImageContent, TextContent, ToolAnnotatio
10
10
  from mcp.types import Tool as MCPTool
11
11
  from pydantic import BaseModel, BeforeValidator, Field
12
12
 
13
+ import fastmcp
13
14
  from fastmcp.exceptions import ToolError
15
+ from fastmcp.server.dependencies import get_context
14
16
  from fastmcp.utilities.json_schema import prune_params
15
17
  from fastmcp.utilities.logging import get_logger
16
18
  from fastmcp.utilities.types import (
@@ -21,10 +23,7 @@ from fastmcp.utilities.types import (
21
23
  )
22
24
 
23
25
  if TYPE_CHECKING:
24
- from mcp.server.session import ServerSessionT
25
- from mcp.shared.context import LifespanContextT
26
-
27
- from fastmcp.server import Context
26
+ pass
28
27
 
29
28
  logger = get_logger(__name__)
30
29
 
@@ -40,9 +39,6 @@ class Tool(BaseModel):
40
39
  name: str = Field(description="Name of the tool")
41
40
  description: str = Field(description="Description of what the tool does")
42
41
  parameters: dict[str, Any] = Field(description="JSON schema for tool parameters")
43
- context_kwarg: str | None = Field(
44
- None, description="Name of the kwarg that should receive context"
45
- )
46
42
  tags: Annotated[set[str], BeforeValidator(_convert_set_defaults)] = Field(
47
43
  default_factory=set, description="Tags for the tool"
48
44
  )
@@ -59,13 +55,12 @@ class Tool(BaseModel):
59
55
  fn: Callable[..., Any],
60
56
  name: str | None = None,
61
57
  description: str | None = None,
62
- context_kwarg: str | None = None,
63
58
  tags: set[str] | None = None,
64
59
  annotations: ToolAnnotations | None = None,
65
60
  serializer: Callable[[Any], str] | None = None,
66
61
  ) -> Tool:
67
62
  """Create a Tool from a function."""
68
- from fastmcp import Context
63
+ from fastmcp.server.context import Context
69
64
 
70
65
  # Reject functions with *args or **kwargs
71
66
  sig = inspect.signature(fn)
@@ -85,8 +80,7 @@ class Tool(BaseModel):
85
80
  type_adapter = get_cached_typeadapter(fn)
86
81
  schema = type_adapter.json_schema()
87
82
 
88
- if context_kwarg is None:
89
- context_kwarg = find_kwarg_by_type(fn, kwarg_type=Context)
83
+ context_kwarg = find_kwarg_by_type(fn, kwarg_type=Context)
90
84
  if context_kwarg:
91
85
  schema = prune_params(schema, params=[context_kwarg])
92
86
 
@@ -95,42 +89,55 @@ class Tool(BaseModel):
95
89
  name=func_name,
96
90
  description=func_doc,
97
91
  parameters=schema,
98
- context_kwarg=context_kwarg,
99
92
  tags=tags or set(),
100
93
  annotations=annotations,
101
94
  serializer=serializer,
102
95
  )
103
96
 
104
97
  async def run(
105
- self,
106
- arguments: dict[str, Any],
107
- context: Context[ServerSessionT, LifespanContextT] | None = None,
98
+ self, arguments: dict[str, Any]
108
99
  ) -> list[TextContent | ImageContent | EmbeddedResource]:
109
100
  """Run the tool with arguments."""
110
- try:
111
- injected_args = (
112
- {self.context_kwarg: context} if self.context_kwarg is not None else {}
113
- )
101
+ from fastmcp.server.context import Context
114
102
 
115
- parsed_args = arguments.copy()
103
+ arguments = arguments.copy()
116
104
 
117
- # Pre-parse data from JSON in order to handle cases like `["a", "b", "c"]`
118
- # being passed in as JSON inside a string rather than an actual list.
119
- #
120
- # Claude desktop is prone to this - in fact it seems incapable of NOT doing
121
- # this. For sub-models, it tends to pass dicts (JSON objects) as JSON strings,
122
- # which can be pre-parsed here.
123
- for param_name in self.parameters["properties"]:
124
- if isinstance(parsed_args.get(param_name, None), str):
105
+ try:
106
+ context_kwarg = find_kwarg_by_type(self.fn, kwarg_type=Context)
107
+ if context_kwarg and context_kwarg not in arguments:
108
+ arguments[context_kwarg] = get_context()
109
+
110
+ if fastmcp.settings.settings.tool_attempt_parse_json_args:
111
+ # Pre-parse data from JSON in order to handle cases like `["a", "b", "c"]`
112
+ # being passed in as JSON inside a string rather than an actual list.
113
+ #
114
+ # Claude desktop is prone to this - in fact it seems incapable of NOT doing
115
+ # this. For sub-models, it tends to pass dicts (JSON objects) as JSON strings,
116
+ # which can be pre-parsed here.
117
+ signature = inspect.signature(self.fn)
118
+ for param_name in self.parameters["properties"]:
119
+ arg = arguments.get(param_name, None)
120
+ # if not in signature, we won't have annotations, so skip logic
121
+ if param_name not in signature.parameters:
122
+ continue
123
+ # if not a string, we won't have a JSON to parse, so skip logic
124
+ if not isinstance(arg, str):
125
+ continue
126
+ # skip if the type is a simple type (int, float, bool)
127
+ if signature.parameters[param_name].annotation in (
128
+ int,
129
+ float,
130
+ bool,
131
+ ):
132
+ continue
125
133
  try:
126
- parsed_args[param_name] = json.loads(parsed_args[param_name])
134
+ arguments[param_name] = json.loads(arg)
135
+
127
136
  except json.JSONDecodeError:
128
137
  pass
129
138
 
130
- type_adapter = get_cached_typeadapter(
131
- self.fn, config=frozenset([("coerce_numbers_to_str", True)])
132
- )
133
- result = type_adapter.validate_python(parsed_args | injected_args)
139
+ type_adapter = get_cached_typeadapter(self.fn)
140
+ result = type_adapter.validate_python(arguments)
134
141
  if inspect.isawaitable(result):
135
142
  result = await result
136
143
 
@@ -3,7 +3,6 @@ from __future__ import annotations as _annotations
3
3
  from collections.abc import Callable
4
4
  from typing import TYPE_CHECKING, Any
5
5
 
6
- from mcp.shared.context import LifespanContextT
7
6
  from mcp.types import EmbeddedResource, ImageContent, TextContent, ToolAnnotations
8
7
 
9
8
  from fastmcp.exceptions import NotFoundError
@@ -12,9 +11,7 @@ from fastmcp.tools.tool import Tool
12
11
  from fastmcp.utilities.logging import get_logger
13
12
 
14
13
  if TYPE_CHECKING:
15
- from mcp.server.session import ServerSessionT
16
-
17
- from fastmcp.server import Context
14
+ pass
18
15
 
19
16
  logger = get_logger(__name__)
20
17
 
@@ -98,14 +95,11 @@ class ToolManager:
98
95
  return tool
99
96
 
100
97
  async def call_tool(
101
- self,
102
- key: str,
103
- arguments: dict[str, Any],
104
- context: Context[ServerSessionT, LifespanContextT] | None = None,
98
+ self, key: str, arguments: dict[str, Any]
105
99
  ) -> list[TextContent | ImageContent | EmbeddedResource]:
106
100
  """Call a tool by name with arguments."""
107
101
  tool = self.get_tool(key)
108
102
  if not tool:
109
103
  raise NotFoundError(f"Unknown tool: {key}")
110
104
 
111
- return await tool.run(arguments, context=context)
105
+ return await tool.run(arguments)
@@ -0,0 +1,26 @@
1
+ import datetime
2
+ from typing import Any
3
+
4
+ UTC = datetime.timezone.utc
5
+
6
+
7
+ class TimedCache:
8
+ NOT_FOUND = object()
9
+
10
+ def __init__(self, expiration: datetime.timedelta):
11
+ self.expiration = expiration
12
+ self.cache: dict[Any, tuple[Any, datetime.datetime]] = {}
13
+
14
+ def set(self, key: Any, value: Any) -> None:
15
+ expires = datetime.datetime.now(UTC) + self.expiration
16
+ self.cache[key] = (value, expires)
17
+
18
+ def get(self, key: Any) -> Any:
19
+ value = self.cache.get(key)
20
+ if value is not None and value[1] > datetime.datetime.now(UTC):
21
+ return value[0]
22
+ else:
23
+ return self.NOT_FOUND
24
+
25
+ def clear(self) -> None:
26
+ self.cache.clear()
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ import multiprocessing
5
+ import socket
6
+ import time
7
+ from collections.abc import Callable, Generator
8
+ from contextlib import contextmanager
9
+ from typing import TYPE_CHECKING, Any, Literal
10
+
11
+ import uvicorn
12
+
13
+ from fastmcp.settings import settings
14
+
15
+ if TYPE_CHECKING:
16
+ from fastmcp.server.server import FastMCP
17
+
18
+
19
+ @contextmanager
20
+ def temporary_settings(**kwargs: Any):
21
+ """
22
+ Temporarily override ControlFlow setting values.
23
+
24
+ Args:
25
+ **kwargs: The settings to override, including nested settings.
26
+
27
+ Example:
28
+ Temporarily override a setting:
29
+ ```python
30
+ import fastmcp
31
+ from fastmcp.utilities.tests import temporary_settings
32
+
33
+ with temporary_settings(log_level='DEBUG'):
34
+ assert fastmcp.settings.settings.log_level == 'DEBUG'
35
+ assert fastmcp.settings.settings.log_level == 'INFO'
36
+ ```
37
+ """
38
+ old_settings = copy.deepcopy(settings.model_dump())
39
+
40
+ try:
41
+ # apply the new settings
42
+ for attr, value in kwargs.items():
43
+ if not hasattr(settings, attr):
44
+ raise AttributeError(f"Setting {attr} does not exist.")
45
+ setattr(settings, attr, value)
46
+ yield
47
+
48
+ finally:
49
+ # restore the old settings
50
+ for attr in kwargs:
51
+ if hasattr(settings, attr):
52
+ setattr(settings, attr, old_settings[attr])
53
+
54
+
55
+ def _run_server(mcp_server: FastMCP, transport: Literal["sse"], port: int) -> None:
56
+ # Some Starlette apps are not pickleable, so we need to create them here based on the indicated transport
57
+ if transport == "sse":
58
+ app = mcp_server.sse_app()
59
+ else:
60
+ raise ValueError(f"Invalid transport: {transport}")
61
+ uvicorn_server = uvicorn.Server(
62
+ config=uvicorn.Config(
63
+ app=app,
64
+ host="127.0.0.1",
65
+ port=port,
66
+ log_level="error",
67
+ )
68
+ )
69
+ uvicorn_server.run()
70
+
71
+
72
+ @contextmanager
73
+ def run_server_in_process(
74
+ server_fn: Callable[[str, int], None],
75
+ ) -> Generator[str, None, None]:
76
+ """
77
+ Context manager that runs a Starlette app in a separate process and returns the
78
+ server URL. When the context manager is exited, the server process is killed.
79
+
80
+ Args:
81
+ app: The Starlette app to run.
82
+
83
+ Returns:
84
+ The server URL.
85
+ """
86
+ host = "127.0.0.1"
87
+ with socket.socket() as s:
88
+ s.bind((host, 0))
89
+ port = s.getsockname()[1]
90
+
91
+ proc = multiprocessing.Process(target=server_fn, args=(host, port), daemon=True)
92
+ proc.start()
93
+
94
+ # Wait for server to be running
95
+ max_attempts = 100
96
+ attempt = 0
97
+ while attempt < max_attempts and proc.is_alive():
98
+ try:
99
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
100
+ s.connect((host, port))
101
+ break
102
+ except ConnectionRefusedError:
103
+ time.sleep(0.01)
104
+ attempt += 1
105
+ else:
106
+ raise RuntimeError(f"Server failed to start after {max_attempts} attempts")
107
+
108
+ yield f"http://{host}:{port}"
109
+
110
+ proc.kill()
111
+ proc.join(timeout=2)
112
+ if proc.is_alive():
113
+ raise RuntimeError("Server process failed to terminate")
@@ -6,26 +6,23 @@ from collections.abc import Callable
6
6
  from functools import lru_cache
7
7
  from pathlib import Path
8
8
  from types import UnionType
9
- from typing import Annotated, Any, TypeVar, Union, get_args, get_origin
9
+ from typing import Annotated, TypeVar, Union, get_args, get_origin
10
10
 
11
11
  from mcp.types import ImageContent
12
- from pydantic import ConfigDict, TypeAdapter
12
+ from pydantic import TypeAdapter
13
13
 
14
14
  T = TypeVar("T")
15
15
 
16
16
 
17
17
  @lru_cache(maxsize=5000)
18
- def get_cached_typeadapter(
19
- cls: T, config: frozenset[tuple[str, Any]] | None = None
20
- ) -> TypeAdapter[T]:
18
+ def get_cached_typeadapter(cls: T) -> TypeAdapter[T]:
21
19
  """
22
20
  TypeAdapters are heavy objects, and in an application context we'd typically
23
21
  create them once in a global scope and reuse them as often as possible.
24
22
  However, this isn't feasible for user-generated functions. Instead, we use a
25
23
  cache to minimize the cost of creating them as much as possible.
26
24
  """
27
- config_dict = dict(config or {})
28
- return TypeAdapter(cls, config=ConfigDict(**config_dict))
25
+ return TypeAdapter(cls)
29
26
 
30
27
 
31
28
  def issubclass_safe(cls: type, base: type) -> bool:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastmcp
3
- Version: 2.2.9
3
+ Version: 2.3.0
4
4
  Summary: The fast, Pythonic way to build MCP servers.
5
5
  Project-URL: Homepage, https://gofastmcp.com
6
6
  Project-URL: Repository, https://github.com/jlowin/fastmcp
@@ -19,7 +19,7 @@ Classifier: Typing :: Typed
19
19
  Requires-Python: >=3.10
20
20
  Requires-Dist: exceptiongroup>=1.2.2
21
21
  Requires-Dist: httpx>=0.28.1
22
- Requires-Dist: mcp<2.0.0,>=1.7.1
22
+ Requires-Dist: mcp<2.0.0,>=1.8.0
23
23
  Requires-Dist: openapi-pydantic>=0.5.1
24
24
  Requires-Dist: python-dotenv>=1.1.0
25
25
  Requires-Dist: rich>=13.9.4
@@ -379,6 +379,10 @@ Run tests using pytest:
379
379
  ```bash
380
380
  pytest
381
381
  ```
382
+ or if you want an overview of the code coverage
383
+ ```bash
384
+ uv run pytest --cov=src --cov=examples --cov-report=html
385
+ ```
382
386
 
383
387
  ### Static Checks
384
388
 
@@ -1,17 +1,17 @@
1
- fastmcp/__init__.py,sha256=e-wu5UQpgduOauB-H8lzbnxv_H9K90fCJVnc1qgaAhM,413
1
+ fastmcp/__init__.py,sha256=yTAqLZORsPqbr7AE0ayw6zIYBeMlxQlI-3HE2WqbvHk,435
2
2
  fastmcp/exceptions.py,sha256=QKVHbftoZp4YZQ2NxA-t1SjztqspFdX95YTFOAmr5EE,640
3
3
  fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- fastmcp/settings.py,sha256=Ix_VrAegM27wpPViCJvC04dmPm2QsBgtbxcZXHGQOhM,2213
4
+ fastmcp/settings.py,sha256=rDClnYEpYjEl8VsvvVrKp9oaE4YLfNQcMoZ41H_bDL0,2968
5
5
  fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
6
6
  fastmcp/cli/claude.py,sha256=IAlcZ4qZKBBj09jZUMEx7EANZE_IR3vcu7zOBJmMOuU,4567
7
- fastmcp/cli/cli.py,sha256=wsFIYTv48_nr0mcW8vjI1l7_thr4cOrRxzo9g-qr2l8,15726
7
+ fastmcp/cli/cli.py,sha256=7s5RsV8D_tPs20EJcrCvyO-i69DmV60OiRGD21oEJSI,15728
8
8
  fastmcp/client/__init__.py,sha256=BXO9NUhntZ5GnUACfaRCzDJ5IzxqFJs8qKG-CRMSco4,490
9
9
  fastmcp/client/base.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- fastmcp/client/client.py,sha256=Uvcqh1Ww5bkyPaxeM31-l6SAFdTnO6-rqCLhQo-0jzk,15413
10
+ fastmcp/client/client.py,sha256=zfSLWSGqiBoADveKehsAL66CdyGGqmzVHR1q46zfdQY,15479
11
11
  fastmcp/client/logging.py,sha256=Q8jYcZj4KA15Yiz3RP8tBXj8sd9IxL3VThF_Y0O4Upc,356
12
12
  fastmcp/client/roots.py,sha256=IxI_bHwHTmg6c2H-s1av1ZgrRnNDieHtYwdGFbzXT5c,2471
13
13
  fastmcp/client/sampling.py,sha256=UlDHxnd6k_HoU8RA3ob0g8-e6haJBc9u27N_v291QoI,1698
14
- fastmcp/client/transports.py,sha256=FMMniyxnaaHyWeFHXXP_ayOpb4jtCoyjaiqLobgYb_M,16885
14
+ fastmcp/client/transports.py,sha256=rIOodm2_7tuwy2oMgobiSlZsU08Z2Iy0XJs0dhvprWE,18705
15
15
  fastmcp/contrib/README.md,sha256=rKknYSI1T192UvSszqwwDlQ2eYQpxywrNTLoj177SYU,878
16
16
  fastmcp/contrib/bulk_tool_caller/README.md,sha256=5aUUY1TSFKtz1pvTLSDqkUCkGkuqMfMZNsLeaNqEgAc,1960
17
17
  fastmcp/contrib/bulk_tool_caller/__init__.py,sha256=xvGSSaUXTQrc31erBoi1Gh7BikgOliETDiYVTP3rLxY,75
@@ -22,30 +22,33 @@ fastmcp/contrib/mcp_mixin/__init__.py,sha256=aw9IQ1ssNjCgws4ZNt8bkdpossAAGVAwwjB
22
22
  fastmcp/contrib/mcp_mixin/example.py,sha256=GnunkXmtG5hLLTUsM8aW5ZURU52Z8vI4tNLl-fK7Dg0,1228
23
23
  fastmcp/contrib/mcp_mixin/mcp_mixin.py,sha256=cfIRbnSxsVzglTD-auyTE0izVQeHP7Oz18qzYoBZJgg,7899
24
24
  fastmcp/prompts/__init__.py,sha256=An8uMBUh9Hrb7qqcn_5_Hent7IOeSh7EA2IUVsIrtHc,179
25
- fastmcp/prompts/prompt.py,sha256=2akOGp7THYJRpaqbWHsrrA6TI6czn2Dx-FdnyBpfhzY,8036
26
- fastmcp/prompts/prompt_manager.py,sha256=YbOCpwwwWGdB4K7HqqjtpIwXd85xSOi7rRPt4eFh4dY,3385
25
+ fastmcp/prompts/prompt.py,sha256=psc-YiBRttbjETINaP9P9QV328yk96mDBsZgjOHVyKM,7777
26
+ fastmcp/prompts/prompt_manager.py,sha256=9VcioLE-AoUKe1e9SynNQME9SvWy0q1QAvO1ewIWVmI,3126
27
27
  fastmcp/resources/__init__.py,sha256=t0x1j8lc74rjUKtXe9H5Gs4fpQt82K4NgBK6Y7A0xTg,467
28
- fastmcp/resources/resource.py,sha256=GGG0XugoIMbgAJRMVxsBcZLbt19W3COy8PyTh_uoWjs,2705
29
- fastmcp/resources/resource_manager.py,sha256=yfQNCEUooZiQ8LNslnAzjQp4Vh-y1YOlFyGEQZ0BtAg,9586
30
- fastmcp/resources/template.py,sha256=EgZzjYtOpQaVBxsyPys1ncUJnMpBOaGt6HiyqVSNeik,7689
31
- fastmcp/resources/types.py,sha256=ht0Jzo-CQwmmJ1XIz2VaCDETsUcGbAiN81wm1uxDSmk,7559
32
- fastmcp/server/__init__.py,sha256=pdkghG11VLMZiluQ-4_rl2JK1LMWmV003m9dDRUN8W4,92
33
- fastmcp/server/context.py,sha256=X8mBdF7SPwH_x8qG4oDoZBPZO9ibYFLjZu_pZoQvafg,7858
34
- fastmcp/server/openapi.py,sha256=1Yhep4zwHhZPlfNIo2Y3sIxfwHNJRdzw3Kc4DZrEW3Q,23950
35
- fastmcp/server/proxy.py,sha256=f7V1X9lDUanHS_iTzEf7Nt0WwkDOXDiJ3-XrMWdq3tw,9969
36
- fastmcp/server/server.py,sha256=kXcnO_TzJuoColFjSBER8M5IaA4k9Cvwkrd5LUYKgRQ,43588
28
+ fastmcp/resources/resource.py,sha256=Rx1My_fi1f-oqnQ9R_v7ejopAk4BJDfbB75-s4d31dM,2492
29
+ fastmcp/resources/resource_manager.py,sha256=_kJQMjb7U0NYPhWZEayDCgpMeYOnvtoCzi2sMZc71NA,9442
30
+ fastmcp/resources/template.py,sha256=X_x1wnmIWGykoKwhwGLZmvGysXIubVLHDrvSmbsu4kg,7306
31
+ fastmcp/resources/types.py,sha256=QPDeka_cM1hmvwW4FeFhqy6BEEi4MlwtpvhWUVWh5Fc,6459
32
+ fastmcp/server/__init__.py,sha256=bMD4aQD4yJqLz7-mudoNsyeV8UgQfRAg3PRwPvwTEds,119
33
+ fastmcp/server/context.py,sha256=ykitQygA7zT5prbFTLCuYlnAzuljf_9ErUT0FYBPv3E,8135
34
+ fastmcp/server/dependencies.py,sha256=1utkxFsV37HZcWBwI69JyngVN2ppGO_PEgxUlUHHy_Q,742
35
+ fastmcp/server/http.py,sha256=yFazRx3hKLKfthcGGkeL-8gQxV0r2oGpugsuOvgjBzw,10013
36
+ fastmcp/server/openapi.py,sha256=0nANnwHJ5VZInNyo2f9ErmO0K3igMv6bwyxf3G-BSls,23473
37
+ fastmcp/server/proxy.py,sha256=qcBD2wWMcXA4dhqppStVH4UhsyWm0cpPUuItLpO6H6A,9621
38
+ fastmcp/server/server.py,sha256=usocXySZvmrp6GOJzQQrh1lUiAyVMkRnJhk4NBhp0FQ,40827
37
39
  fastmcp/tools/__init__.py,sha256=ocw-SFTtN6vQ8fgnlF8iNAOflRmh79xS1xdO0Bc3QPE,96
38
- fastmcp/tools/tool.py,sha256=qglTiW2K5EVpn-0Ctl29VPfubuwfKIt9_NVX9HzMSqg,7390
39
- fastmcp/tools/tool_manager.py,sha256=whi3oYfTqUXC0vXWvSvGBaUqwo1YKZxSI-clRAKKnWQ,3606
40
+ fastmcp/tools/tool.py,sha256=lr9F90-A36Z6DT4LScJBW88uFq8xrSv00ivFxiERJe8,7786
41
+ fastmcp/tools/tool_manager.py,sha256=p2nHyLFgz28tbsLpWOurkbWRU2Z34_HcDohjrvwjI0E,3369
40
42
  fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
43
+ fastmcp/utilities/cache.py,sha256=aV3oZ-ZhMgLSM9iAotlUlEy5jFvGXrVo0Y5Bj4PBtqY,707
41
44
  fastmcp/utilities/decorators.py,sha256=AjhjsetQZF4YOPV5MTZmIxO21iFp_4fDIS3O2_KNCEg,2990
42
- fastmcp/utilities/http.py,sha256=EzOe38e2oC0SjAnLmcf__1AjEtF19px8ZIVxPLt3Cr0,991
43
45
  fastmcp/utilities/json_schema.py,sha256=mSakhP8bENxhLFMwHJSxJAFllNeByIBDjVohwlpac6w,2026
44
46
  fastmcp/utilities/logging.py,sha256=zav8pnFxG_fvGJHUV2XpobmT9WVrmv1mlQBSCz-CPx4,1159
45
47
  fastmcp/utilities/openapi.py,sha256=Er3G1MyFwiWVxZXicXtD2j-BvttHEDTi1dgkq1KiBQc,51073
46
- fastmcp/utilities/types.py,sha256=LVNfzDGRNglNo5BUpFVWJhfK672saznxxju0tLrwy8Q,4544
47
- fastmcp-2.2.9.dist-info/METADATA,sha256=UE27Qphn0YxHdfX9Jz4q8YZmImSvxouxt8mDWdeouVc,16279
48
- fastmcp-2.2.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
49
- fastmcp-2.2.9.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
50
- fastmcp-2.2.9.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
51
- fastmcp-2.2.9.dist-info/RECORD,,
48
+ fastmcp/utilities/tests.py,sha256=uUV-8CkhCe5zZJkxhgJXnxrjJ3Yq7cCMZN8xWKGuqdY,3181
49
+ fastmcp/utilities/types.py,sha256=6CcqAQ1QqCO2HGSFlPS6FO5JRWnacjCcO2-EhyEnZV0,4400
50
+ fastmcp-2.3.0.dist-info/METADATA,sha256=v9jZ7vkBNKfVq-L4hoGpjOMrJfi-FUs4q3Cxiqxh-zU,16396
51
+ fastmcp-2.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
52
+ fastmcp-2.3.0.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
53
+ fastmcp-2.3.0.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
54
+ fastmcp-2.3.0.dist-info/RECORD,,
fastmcp/utilities/http.py DELETED
@@ -1,44 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from contextlib import (
4
- asynccontextmanager,
5
- )
6
- from contextvars import ContextVar
7
-
8
- from starlette.requests import Request
9
-
10
- from fastmcp.utilities.logging import get_logger
11
-
12
- logger = get_logger(__name__)
13
-
14
-
15
- _current_starlette_request: ContextVar[Request | None] = ContextVar(
16
- "starlette_request",
17
- default=None,
18
- )
19
-
20
-
21
- @asynccontextmanager
22
- async def starlette_request_context(request: Request):
23
- token = _current_starlette_request.set(request)
24
- try:
25
- yield
26
- finally:
27
- _current_starlette_request.reset(token)
28
-
29
-
30
- def get_current_starlette_request() -> Request | None:
31
- return _current_starlette_request.get()
32
-
33
-
34
- class RequestMiddleware:
35
- """
36
- Middleware that stores each request in a ContextVar
37
- """
38
-
39
- def __init__(self, app):
40
- self.app = app
41
-
42
- async def __call__(self, scope, receive, send):
43
- async with starlette_request_context(Request(scope)):
44
- await self.app(scope, receive, send)