Jeremiah Lowin commited on
Commit
4af3969
·
1 Parent(s): 748718c

refactor utilities

Browse files
src/fastmcp/server/context.py CHANGED
@@ -17,8 +17,8 @@ from pydantic import BaseModel, ConfigDict
17
  from pydantic.networks import AnyUrl
18
  from starlette.requests import Request
19
 
 
20
  from fastmcp.server.server import FastMCP
21
- from fastmcp.utilities.http import get_current_starlette_request
22
  from fastmcp.utilities.logging import get_logger
23
 
24
  logger = get_logger(__name__)
 
17
  from pydantic.networks import AnyUrl
18
  from starlette.requests import Request
19
 
20
+ from fastmcp.server.http import get_current_starlette_request
21
  from fastmcp.server.server import FastMCP
 
22
  from fastmcp.utilities.logging import get_logger
23
 
24
  logger = get_logger(__name__)
src/fastmcp/{utilities → server}/http.py RENAMED
File without changes
src/fastmcp/server/server.py CHANGED
@@ -56,10 +56,11 @@ from fastmcp.prompts import Prompt, PromptManager
56
  from fastmcp.prompts.prompt import PromptResult
57
  from fastmcp.resources import Resource, ResourceManager
58
  from fastmcp.resources.template import ResourceTemplate
 
59
  from fastmcp.tools import ToolManager
60
  from fastmcp.tools.tool import Tool
 
61
  from fastmcp.utilities.decorators import DecoratedFunction
62
- from fastmcp.utilities.http import RequestMiddleware
63
  from fastmcp.utilities.logging import configure_logging, get_logger
64
 
65
  if TYPE_CHECKING:
@@ -70,99 +71,6 @@ if TYPE_CHECKING:
70
 
71
  logger = get_logger(__name__)
72
 
73
- NOT_FOUND = object()
74
-
75
-
76
- class MountedServer:
77
- def __init__(
78
- self,
79
- prefix: str,
80
- server: FastMCP,
81
- tool_separator: str | None = None,
82
- resource_separator: str | None = None,
83
- prompt_separator: str | None = None,
84
- ):
85
- if tool_separator is None:
86
- tool_separator = "_"
87
- if resource_separator is None:
88
- resource_separator = "+"
89
- if prompt_separator is None:
90
- prompt_separator = "_"
91
-
92
- _validate_resource_prefix(f"{prefix}{resource_separator}")
93
-
94
- self.server = server
95
- self.prefix = prefix
96
- self.tool_separator = tool_separator
97
- self.resource_separator = resource_separator
98
- self.prompt_separator = prompt_separator
99
-
100
- async def get_tools(self) -> dict[str, Tool]:
101
- tools = await self.server.get_tools()
102
- return {
103
- f"{self.prefix}{self.tool_separator}{key}": tool
104
- for key, tool in tools.items()
105
- }
106
-
107
- async def get_resources(self) -> dict[str, Resource]:
108
- resources = await self.server.get_resources()
109
- return {
110
- f"{self.prefix}{self.resource_separator}{key}": resource
111
- for key, resource in resources.items()
112
- }
113
-
114
- async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
115
- templates = await self.server.get_resource_templates()
116
- return {
117
- f"{self.prefix}{self.resource_separator}{key}": template
118
- for key, template in templates.items()
119
- }
120
-
121
- async def get_prompts(self) -> dict[str, Prompt]:
122
- prompts = await self.server.get_prompts()
123
- return {
124
- f"{self.prefix}{self.prompt_separator}{key}": prompt
125
- for key, prompt in prompts.items()
126
- }
127
-
128
- def match_tool(self, key: str) -> bool:
129
- return key.startswith(f"{self.prefix}{self.tool_separator}")
130
-
131
- def strip_tool_prefix(self, key: str) -> str:
132
- return key.removeprefix(f"{self.prefix}{self.tool_separator}")
133
-
134
- def match_resource(self, key: str) -> bool:
135
- return key.startswith(f"{self.prefix}{self.resource_separator}")
136
-
137
- def strip_resource_prefix(self, key: str) -> str:
138
- return key.removeprefix(f"{self.prefix}{self.resource_separator}")
139
-
140
- def match_prompt(self, key: str) -> bool:
141
- return key.startswith(f"{self.prefix}{self.prompt_separator}")
142
-
143
- def strip_prompt_prefix(self, key: str) -> str:
144
- return key.removeprefix(f"{self.prefix}{self.prompt_separator}")
145
-
146
-
147
- class TimedCache:
148
- def __init__(self, expiration: datetime.timedelta):
149
- self.expiration = expiration
150
- self.cache: dict[Any, tuple[Any, datetime.datetime]] = {}
151
-
152
- def set(self, key: Any, value: Any) -> None:
153
- expires = datetime.datetime.now() + self.expiration
154
- self.cache[key] = (value, expires)
155
-
156
- def get(self, key: Any) -> Any:
157
- value = self.cache.get(key)
158
- if value is not None and value[1] > datetime.datetime.now():
159
- return value[0]
160
- else:
161
- return NOT_FOUND
162
-
163
- def clear(self) -> None:
164
- self.cache.clear()
165
-
166
 
167
  @asynccontextmanager
168
  async def default_lifespan(server: FastMCP) -> AsyncIterator[Any]:
@@ -325,7 +233,7 @@ class FastMCP(Generic[LifespanResultT]):
325
 
326
  async def get_tools(self) -> dict[str, Tool]:
327
  """Get all registered tools, indexed by registered key."""
328
- if (tools := self._cache.get("tools")) is NOT_FOUND:
329
  tools = {}
330
  for server in self._mounted_servers.values():
331
  server_tools = await server.get_tools()
@@ -336,7 +244,7 @@ class FastMCP(Generic[LifespanResultT]):
336
 
337
  async def get_resources(self) -> dict[str, Resource]:
338
  """Get all registered resources, indexed by registered key."""
339
- if (resources := self._cache.get("resources")) is NOT_FOUND:
340
  resources = {}
341
  for server in self._mounted_servers.values():
342
  server_resources = await server.get_resources()
@@ -347,7 +255,9 @@ class FastMCP(Generic[LifespanResultT]):
347
 
348
  async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
349
  """Get all registered resource templates, indexed by registered key."""
350
- if (templates := self._cache.get("resource_templates")) is NOT_FOUND:
 
 
351
  templates = {}
352
  for server in self._mounted_servers.values():
353
  server_templates = await server.get_resource_templates()
@@ -360,7 +270,7 @@ class FastMCP(Generic[LifespanResultT]):
360
  """
361
  List all available prompts.
362
  """
363
- if (prompts := self._cache.get("prompts")) is NOT_FOUND:
364
  prompts = {}
365
  for server in self._mounted_servers.values():
366
  server_prompts = await server.get_prompts()
@@ -1145,3 +1055,74 @@ def _validate_resource_prefix(prefix: str) -> None:
1145
  raise ValueError(
1146
  f"Resource prefix or separator would result in an invalid resource URI: {e}"
1147
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  from fastmcp.prompts.prompt import PromptResult
57
  from fastmcp.resources import Resource, ResourceManager
58
  from fastmcp.resources.template import ResourceTemplate
59
+ from fastmcp.server.http import RequestMiddleware
60
  from fastmcp.tools import ToolManager
61
  from fastmcp.tools.tool import Tool
62
+ from fastmcp.utilities.cache import TimedCache
63
  from fastmcp.utilities.decorators import DecoratedFunction
 
64
  from fastmcp.utilities.logging import configure_logging, get_logger
65
 
66
  if TYPE_CHECKING:
 
71
 
72
  logger = get_logger(__name__)
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
  @asynccontextmanager
76
  async def default_lifespan(server: FastMCP) -> AsyncIterator[Any]:
 
233
 
234
  async def get_tools(self) -> dict[str, Tool]:
235
  """Get all registered tools, indexed by registered key."""
236
+ if (tools := self._cache.get("tools")) is self._cache.NOT_FOUND:
237
  tools = {}
238
  for server in self._mounted_servers.values():
239
  server_tools = await server.get_tools()
 
244
 
245
  async def get_resources(self) -> dict[str, Resource]:
246
  """Get all registered resources, indexed by registered key."""
247
+ if (resources := self._cache.get("resources")) is self._cache.NOT_FOUND:
248
  resources = {}
249
  for server in self._mounted_servers.values():
250
  server_resources = await server.get_resources()
 
255
 
256
  async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
257
  """Get all registered resource templates, indexed by registered key."""
258
+ if (
259
+ templates := self._cache.get("resource_templates")
260
+ ) is self._cache.NOT_FOUND:
261
  templates = {}
262
  for server in self._mounted_servers.values():
263
  server_templates = await server.get_resource_templates()
 
270
  """
271
  List all available prompts.
272
  """
273
+ if (prompts := self._cache.get("prompts")) is self._cache.NOT_FOUND:
274
  prompts = {}
275
  for server in self._mounted_servers.values():
276
  server_prompts = await server.get_prompts()
 
1055
  raise ValueError(
1056
  f"Resource prefix or separator would result in an invalid resource URI: {e}"
1057
  )
1058
+
1059
+
1060
+ class MountedServer:
1061
+ def __init__(
1062
+ self,
1063
+ prefix: str,
1064
+ server: FastMCP,
1065
+ tool_separator: str | None = None,
1066
+ resource_separator: str | None = None,
1067
+ prompt_separator: str | None = None,
1068
+ ):
1069
+ if tool_separator is None:
1070
+ tool_separator = "_"
1071
+ if resource_separator is None:
1072
+ resource_separator = "+"
1073
+ if prompt_separator is None:
1074
+ prompt_separator = "_"
1075
+
1076
+ _validate_resource_prefix(f"{prefix}{resource_separator}")
1077
+
1078
+ self.server = server
1079
+ self.prefix = prefix
1080
+ self.tool_separator = tool_separator
1081
+ self.resource_separator = resource_separator
1082
+ self.prompt_separator = prompt_separator
1083
+
1084
+ async def get_tools(self) -> dict[str, Tool]:
1085
+ tools = await self.server.get_tools()
1086
+ return {
1087
+ f"{self.prefix}{self.tool_separator}{key}": tool
1088
+ for key, tool in tools.items()
1089
+ }
1090
+
1091
+ async def get_resources(self) -> dict[str, Resource]:
1092
+ resources = await self.server.get_resources()
1093
+ return {
1094
+ f"{self.prefix}{self.resource_separator}{key}": resource
1095
+ for key, resource in resources.items()
1096
+ }
1097
+
1098
+ async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
1099
+ templates = await self.server.get_resource_templates()
1100
+ return {
1101
+ f"{self.prefix}{self.resource_separator}{key}": template
1102
+ for key, template in templates.items()
1103
+ }
1104
+
1105
+ async def get_prompts(self) -> dict[str, Prompt]:
1106
+ prompts = await self.server.get_prompts()
1107
+ return {
1108
+ f"{self.prefix}{self.prompt_separator}{key}": prompt
1109
+ for key, prompt in prompts.items()
1110
+ }
1111
+
1112
+ def match_tool(self, key: str) -> bool:
1113
+ return key.startswith(f"{self.prefix}{self.tool_separator}")
1114
+
1115
+ def strip_tool_prefix(self, key: str) -> str:
1116
+ return key.removeprefix(f"{self.prefix}{self.tool_separator}")
1117
+
1118
+ def match_resource(self, key: str) -> bool:
1119
+ return key.startswith(f"{self.prefix}{self.resource_separator}")
1120
+
1121
+ def strip_resource_prefix(self, key: str) -> str:
1122
+ return key.removeprefix(f"{self.prefix}{self.resource_separator}")
1123
+
1124
+ def match_prompt(self, key: str) -> bool:
1125
+ return key.startswith(f"{self.prefix}{self.prompt_separator}")
1126
+
1127
+ def strip_prompt_prefix(self, key: str) -> str:
1128
+ return key.removeprefix(f"{self.prefix}{self.prompt_separator}")
src/fastmcp/utilities/cache.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+ from typing import Any
3
+
4
+
5
+ class TimedCache:
6
+ NOT_FOUND = object()
7
+
8
+ def __init__(self, expiration: datetime.timedelta):
9
+ self.expiration = expiration
10
+ self.cache: dict[Any, tuple[Any, datetime.datetime]] = {}
11
+
12
+ def set(self, key: Any, value: Any) -> None:
13
+ expires = datetime.datetime.now() + self.expiration
14
+ self.cache[key] = (value, expires)
15
+
16
+ def get(self, key: Any) -> Any:
17
+ value = self.cache.get(key)
18
+ if value is not None and value[1] > datetime.datetime.now():
19
+ return value[0]
20
+ else:
21
+ return self.NOT_FOUND
22
+
23
+ def clear(self) -> None:
24
+ self.cache.clear()