Jeremiah Lowin commited on
Commit
5338a1c
·
1 Parent(s): 1534a61

Update server.py

Browse files
Files changed (1) hide show
  1. src/fastmcp/server/server.py +49 -61
src/fastmcp/server/server.py CHANGED
@@ -25,7 +25,6 @@ from mcp.server.auth.provider import OAuthAuthorizationServerProvider
25
  from mcp.server.lowlevel.helper_types import ReadResourceContents
26
  from mcp.server.lowlevel.server import LifespanResultT
27
  from mcp.server.lowlevel.server import Server as MCPServer
28
- from mcp.server.session import ServerSession
29
  from mcp.server.sse import SseServerTransport
30
  from mcp.server.stdio import stdio_server
31
  from mcp.types import (
@@ -49,14 +48,14 @@ from starlette.responses import Response
49
  from starlette.routing import Mount, Route
50
  from starlette.types import Receive, Scope, Send
51
 
52
- import fastmcp
53
  import fastmcp.settings
54
  from fastmcp.exceptions import NotFoundError, ResourceError
55
  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.server.http import RequestMiddleware
60
  from fastmcp.tools import ToolManager
61
  from fastmcp.tools.tool import Tool
62
  from fastmcp.utilities.cache import TimedCache
@@ -65,7 +64,6 @@ from fastmcp.utilities.logging import configure_logging, get_logger
65
 
66
  if TYPE_CHECKING:
67
  from fastmcp.client import Client
68
- from fastmcp.server.context import Context
69
  from fastmcp.server.openapi import FastMCPOpenAPI
70
  from fastmcp.server.proxy import FastMCPProxy
71
 
@@ -217,20 +215,6 @@ class FastMCP(Generic[LifespanResultT]):
217
  self._mcp_server.get_prompt()(self._mcp_get_prompt)
218
  self._mcp_server.list_resource_templates()(self._mcp_list_resource_templates)
219
 
220
- def get_context(self) -> Context[ServerSession, LifespanResultT]:
221
- """
222
- Returns a Context object. Note that the context will only be valid
223
- during a request; outside a request, most methods will error.
224
- """
225
-
226
- try:
227
- request_context = self._mcp_server.request_context
228
- except LookupError:
229
- request_context = None
230
- from fastmcp.server.context import Context
231
-
232
- return Context(request_context=request_context, fastmcp=self)
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:
@@ -368,43 +352,46 @@ class FastMCP(Generic[LifespanResultT]):
368
  self, key: str, arguments: dict[str, Any]
369
  ) -> list[TextContent | ImageContent | EmbeddedResource]:
370
  """Call a tool by name with arguments."""
371
- if self._tool_manager.has_tool(key):
372
- context = self.get_context()
373
- result = await self._tool_manager.call_tool(key, arguments, context=context)
374
 
375
- else:
376
- for server in self._mounted_servers.values():
377
- if server.match_tool(key):
378
- new_key = server.strip_tool_prefix(key)
379
- result = await server.server._mcp_call_tool(new_key, arguments)
380
- break
381
  else:
382
- raise NotFoundError(f"Unknown tool: {key}")
383
- return result
 
 
 
 
 
 
384
 
385
  async def _mcp_read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
386
  """
387
  Read a resource by URI, in the format expected by the low-level MCP
388
  server.
389
  """
390
- if self._resource_manager.has_resource(uri):
391
- context = self.get_context()
392
- resource = await self._resource_manager.get_resource(uri, context=context)
393
- try:
394
- content = await resource.read(context=context)
395
- return [
396
- ReadResourceContents(content=content, mime_type=resource.mime_type)
397
- ]
398
- except Exception as e:
399
- logger.error(f"Error reading resource {uri}: {e}")
400
- raise ResourceError(str(e))
401
- else:
402
- for server in self._mounted_servers.values():
403
- if server.match_resource(str(uri)):
404
- new_uri = server.strip_resource_prefix(str(uri))
405
- return await server.server._mcp_read_resource(new_uri)
406
  else:
407
- raise NotFoundError(f"Unknown resource: {uri}")
 
 
 
 
 
408
 
409
  async def _mcp_get_prompt(
410
  self, name: str, arguments: dict[str, Any] | None = None
@@ -414,19 +401,19 @@ class FastMCP(Generic[LifespanResultT]):
414
  MCP server.
415
 
416
  """
417
- if self._prompt_manager.has_prompt(name):
418
- context = self.get_context()
419
- prompt_result = await self._prompt_manager.render_prompt(
420
- name, arguments=arguments or {}, context=context
421
- )
422
- return prompt_result
423
- else:
424
- for server in self._mounted_servers.values():
425
- if server.match_prompt(name):
426
- new_key = server.strip_prompt_prefix(name)
427
- return await server.server._mcp_get_prompt(new_key, arguments)
428
  else:
429
- raise NotFoundError(f"Unknown prompt: {name}")
 
 
 
 
 
430
 
431
  def add_tool(
432
  self,
@@ -737,10 +724,11 @@ class FastMCP(Generic[LifespanResultT]):
737
  ) -> None:
738
  """Run the server using SSE transport."""
739
  uvicorn_config = uvicorn_config or {}
740
- # the SSE app hangs even when a signal is sent, so we disable the timeout to make it possible to close immediately.
741
- # see https://github.com/jlowin/fastmcp/issues/296
 
742
  uvicorn_config.setdefault("timeout_graceful_shutdown", 0)
743
- app = RequestMiddleware(self.sse_app())
744
 
745
  config = uvicorn.Config(
746
  app,
 
25
  from mcp.server.lowlevel.helper_types import ReadResourceContents
26
  from mcp.server.lowlevel.server import LifespanResultT
27
  from mcp.server.lowlevel.server import Server as MCPServer
 
28
  from mcp.server.sse import SseServerTransport
29
  from mcp.server.stdio import stdio_server
30
  from mcp.types import (
 
48
  from starlette.routing import Mount, Route
49
  from starlette.types import Receive, Scope, Send
50
 
51
+ import fastmcp.server
52
  import fastmcp.settings
53
  from fastmcp.exceptions import NotFoundError, ResourceError
54
  from fastmcp.prompts import Prompt, PromptManager
55
  from fastmcp.prompts.prompt import PromptResult
56
  from fastmcp.resources import Resource, ResourceManager
57
  from fastmcp.resources.template import ResourceTemplate
58
+ from fastmcp.server.http import RequestContextMiddleware
59
  from fastmcp.tools import ToolManager
60
  from fastmcp.tools.tool import Tool
61
  from fastmcp.utilities.cache import TimedCache
 
64
 
65
  if TYPE_CHECKING:
66
  from fastmcp.client import Client
 
67
  from fastmcp.server.openapi import FastMCPOpenAPI
68
  from fastmcp.server.proxy import FastMCPProxy
69
 
 
215
  self._mcp_server.get_prompt()(self._mcp_get_prompt)
216
  self._mcp_server.list_resource_templates()(self._mcp_list_resource_templates)
217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  async def get_tools(self) -> dict[str, Tool]:
219
  """Get all registered tools, indexed by registered key."""
220
  if (tools := self._cache.get("tools")) is self._cache.NOT_FOUND:
 
352
  self, key: str, arguments: dict[str, Any]
353
  ) -> list[TextContent | ImageContent | EmbeddedResource]:
354
  """Call a tool by name with arguments."""
 
 
 
355
 
356
+ with fastmcp.server.context.Context(fastmcp=self):
357
+ if self._tool_manager.has_tool(key):
358
+ result = await self._tool_manager.call_tool(key, arguments)
359
+
 
 
360
  else:
361
+ for server in self._mounted_servers.values():
362
+ if server.match_tool(key):
363
+ new_key = server.strip_tool_prefix(key)
364
+ result = await server.server._mcp_call_tool(new_key, arguments)
365
+ break
366
+ else:
367
+ raise NotFoundError(f"Unknown tool: {key}")
368
+ return result
369
 
370
  async def _mcp_read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
371
  """
372
  Read a resource by URI, in the format expected by the low-level MCP
373
  server.
374
  """
375
+ with fastmcp.server.context.Context(fastmcp=self):
376
+ if self._resource_manager.has_resource(uri):
377
+ resource = await self._resource_manager.get_resource(uri)
378
+ try:
379
+ content = await resource.read()
380
+ return [
381
+ ReadResourceContents(
382
+ content=content, mime_type=resource.mime_type
383
+ )
384
+ ]
385
+ except Exception as e:
386
+ logger.error(f"Error reading resource {uri}: {e}")
387
+ raise ResourceError(str(e))
 
 
 
388
  else:
389
+ for server in self._mounted_servers.values():
390
+ if server.match_resource(str(uri)):
391
+ new_uri = server.strip_resource_prefix(str(uri))
392
+ return await server.server._mcp_read_resource(new_uri)
393
+ else:
394
+ raise NotFoundError(f"Unknown resource: {uri}")
395
 
396
  async def _mcp_get_prompt(
397
  self, name: str, arguments: dict[str, Any] | None = None
 
401
  MCP server.
402
 
403
  """
404
+ with fastmcp.server.context.Context(fastmcp=self):
405
+ if self._prompt_manager.has_prompt(name):
406
+ prompt_result = await self._prompt_manager.render_prompt(
407
+ name, arguments=arguments or {}
408
+ )
409
+ return prompt_result
 
 
 
 
 
410
  else:
411
+ for server in self._mounted_servers.values():
412
+ if server.match_prompt(name):
413
+ new_key = server.strip_prompt_prefix(name)
414
+ return await server.server._mcp_get_prompt(new_key, arguments)
415
+ else:
416
+ raise NotFoundError(f"Unknown prompt: {name}")
417
 
418
  def add_tool(
419
  self,
 
724
  ) -> None:
725
  """Run the server using SSE transport."""
726
  uvicorn_config = uvicorn_config or {}
727
+ # the SSE app hangs even when a signal is sent, so we disable the
728
+ # timeout to make it possible to close immediately. see
729
+ # https://github.com/jlowin/fastmcp/issues/296
730
  uvicorn_config.setdefault("timeout_graceful_shutdown", 0)
731
+ app = RequestContextMiddleware(self.sse_app())
732
 
733
  config = uvicorn.Config(
734
  app,