Spaces:
Running
Running
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 (
|
|
|
|
|
|
|
| 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()
|