From 9a4eb1a1c92ff2b39feea10040c1de3891a6774c Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Sat, 5 Apr 2025 17:40:28 -0400 Subject: [PATCH] add server and context --- src/fastmcp/__init__.py | 9 +++-- src/fastmcp/server.py | 11 ------- src/fastmcp/server/__init__.py | 5 +++ src/fastmcp/server/context.py | 60 ++++++++++++++++++++++++++++++++++ src/fastmcp/server/server.py | 24 ++++++++++++++ src/fastmcp/settings.py | 28 ++++------------ 6 files changed, 101 insertions(+), 36 deletions(-) delete mode 100644 src/fastmcp/server.py create mode 100644 src/fastmcp/server/__init__.py create mode 100644 src/fastmcp/server/context.py create mode 100644 src/fastmcp/server/server.py diff --git a/src/fastmcp/__init__.py b/src/fastmcp/__init__.py index 955391205..03ed6204b 100644 --- a/src/fastmcp/__init__.py +++ b/src/fastmcp/__init__.py @@ -1,7 +1,10 @@ -"""FastMCP - A more ergonomic interface for MCP servers.""" +"""FastMCP - An ergonomic MCP interface.""" from importlib.metadata import version -from mcp.server.fastmcp import FastMCP, Context, Image +from fastmcp.server import FastMCP, Context __version__ = version("fastmcp") -__all__ = ["FastMCP", "Context", "Image"] +__all__ = [ + "FastMCP", + "Context", +] diff --git a/src/fastmcp/server.py b/src/fastmcp/server.py deleted file mode 100644 index 53286c782..000000000 --- a/src/fastmcp/server.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import Any - -import mcp.server.fastmcp -from fastmcp.utilities.logging import get_logger - -logger = get_logger(__name__) - - -class FastMCP(mcp.server.fastmcp.FastMCP): - def __init__(self, name: str | None = None, **settings: Any): - super().__init__(name=name or "FastMCP", **settings) diff --git a/src/fastmcp/server/__init__.py b/src/fastmcp/server/__init__.py new file mode 100644 index 000000000..e1d937863 --- /dev/null +++ b/src/fastmcp/server/__init__.py @@ -0,0 +1,5 @@ +from .server import FastMCP +from .context import Context + + +__all__ = ["FastMCP", "Context"] diff --git a/src/fastmcp/server/context.py b/src/fastmcp/server/context.py new file mode 100644 index 000000000..f0a1355af --- /dev/null +++ b/src/fastmcp/server/context.py @@ -0,0 +1,60 @@ +from typing import Any + +import mcp.server.fastmcp +from mcp.server.fastmcp.utilities.logging import get_logger +from mcp.server.session import ServerSessionT +from mcp.shared.context import LifespanContextT, RequestContext +from mcp.types import ( + CreateMessageResult, + ImageContent, + SamplingMessage, + TextContent, +) + +logger = get_logger(__name__) + + +class Context(mcp.server.fastmcp.Context[ServerSessionT, LifespanContextT]): + def __init__( + self, + *, + request_context: RequestContext[ServerSessionT, LifespanContextT] | None = None, + fastmcp: mcp.server.fastmcp.FastMCP | None = None, + **kwargs: Any, + ): + super().__init__(request_context=request_context, fastmcp=fastmcp, **kwargs) + + async def sample( + self, + message: str, + system_prompt: str | None = None, + temperature: float | None = None, + max_tokens: int | None = None, + ) -> TextContent | ImageContent: + """ + Send a sampling request to the client and await the response. + + Call this method at any time to have the server request an LLM + completion from the client. The client must be appropriately configured, + or the request will error. + """ + + if max_tokens is None: + max_tokens = 512 + + assert self._request_context is not None + assert self._request_context.session is not None + + sampling_message = SamplingMessage( + content=TextContent(text=message, type="text"), + role="user", + ) + + result: CreateMessageResult = await self.request_context.session.create_message( + messages=[sampling_message], + system_prompt=system_prompt, + temperature=temperature, + max_tokens=max_tokens, + ) + + return result.content diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py new file mode 100644 index 000000000..79532341d --- /dev/null +++ b/src/fastmcp/server/server.py @@ -0,0 +1,24 @@ +from typing import Any + +import mcp.server.fastmcp + +from fastmcp.server.context import Context +from fastmcp.utilities.logging import get_logger + +logger = get_logger(__name__) + + +class FastMCP(mcp.server.fastmcp.FastMCP): + def __init__(self, name: str | None = None, **settings: Any): + super().__init__(name=name or "FastMCP", **settings) + + def get_context(self) -> Context: + """ + Returns a Context object. Note that the context will only be valid + during a request; outside a request, most methods will error. + """ + try: + request_context = self._mcp_server.request_context + except LookupError: + request_context = None + return Context(request_context=request_context, fastmcp=self) diff --git a/src/fastmcp/settings.py b/src/fastmcp/settings.py index fda707dd3..c89bde116 100644 --- a/src/fastmcp/settings.py +++ b/src/fastmcp/settings.py @@ -1,8 +1,8 @@ -from pydantic import Field +from typing import Literal + from pydantic_settings import BaseSettings, SettingsConfigDict - -from typing import Literal +LOG_LEVEL = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] class Settings(BaseSettings): @@ -18,24 +18,8 @@ class Settings(BaseSettings): extra="ignore", ) - # Server settings debug: bool = False - log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO" + log_level: LOG_LEVEL = "INFO" - # HTTP settings - host: str = "0.0.0.0" - port: int = 8000 - - # resource settings - warn_on_duplicate_resources: bool = True - - # tool settings - warn_on_duplicate_tools: bool = True - - # prompt settings - warn_on_duplicate_prompts: bool = True - - dependencies: list[str] = Field( - default_factory=list, - description="List of dependencies to install in the server environment", - ) + # Client settings + client_log_level: LOG_LEVEL | None = None