From ead41caa0df7fb8329bfc0ace8d8af9da4343b75 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:59:44 -0500 Subject: [PATCH] Rename server --- examples/desktop.py | 6 +++--- examples/weather.py | 6 +++--- src/fastmcp/__init__.py | 1 + src/fastmcp/server.py | 21 ++++++++++---------- src/fastmcp/utilities/__init__.py | 4 ++++ src/fastmcp/utilities/logging.py | 33 +++++++++++++++++++++++++++++++ tests/test_server.py | 12 +++++------ 7 files changed, 60 insertions(+), 23 deletions(-) create mode 100644 src/fastmcp/__init__.py create mode 100644 src/fastmcp/utilities/__init__.py create mode 100644 src/fastmcp/utilities/logging.py diff --git a/examples/desktop.py b/examples/desktop.py index b8953e2e4..245cba5d7 100644 --- a/examples/desktop.py +++ b/examples/desktop.py @@ -7,10 +7,10 @@ A simple example that exposes the desktop directory as a resource. import asyncio from pathlib import Path -from fastmcp.server import FastMCPServer +from fastmcp.server import FastMCP # Create server -app = FastMCPServer("desktop") +app = FastMCP("desktop") # Add desktop as a directory resource desktop = Path.home() / "Desktop" @@ -24,7 +24,7 @@ app.add_dir_resource( def main123(): # Run the server - asyncio.run(FastMCPServer.run_stdio(app)) + asyncio.run(FastMCP.run_stdio(app)) if __name__ == "__main__": diff --git a/examples/weather.py b/examples/weather.py index 10593e31f..0a53a152d 100644 --- a/examples/weather.py +++ b/examples/weather.py @@ -5,7 +5,7 @@ FastMCP Weather Server Example import os import httpx from pydantic import BaseModel, Field -from fastmcp.server import FastMCPServer +from fastmcp.server import FastMCP # Load env vars API_KEY = os.getenv("OPENWEATHER_API_KEY") @@ -32,7 +32,7 @@ class AlertParams(BaseModel): # Create server -app = FastMCPServer("weather-service") +app = FastMCP("weather-service") # Tools using Pydantic models @@ -126,7 +126,7 @@ def main(): ) # Run the server - asyncio.run(FastMCPServer.run_stdio(app)) + asyncio.run(FastMCP.run_stdio(app)) if __name__ == "__main__": diff --git a/src/fastmcp/__init__.py b/src/fastmcp/__init__.py new file mode 100644 index 000000000..d6507801f --- /dev/null +++ b/src/fastmcp/__init__.py @@ -0,0 +1 @@ +from .server import FastMCP \ No newline at end of file diff --git a/src/fastmcp/server.py b/src/fastmcp/server.py index 125e766a0..06e2bc7dd 100644 --- a/src/fastmcp/server.py +++ b/src/fastmcp/server.py @@ -1,13 +1,16 @@ """FastMCP - A more ergonomic interface for MCP servers.""" +import asyncio import base64 import functools import json import logging +from dataclasses import dataclass from typing import Any, Callable, Dict, Optional, Sequence, Union, Literal from mcp.server import Server as MCPServer from mcp.server.stdio import stdio_server +from mcp.server.sse import SseServerTransport from mcp.types import Resource as MCPResource from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource from pydantic import BaseModel @@ -16,9 +19,9 @@ from pydantic_settings import BaseSettings from .exceptions import ResourceError from .resources import Resource, FunctionResource, ResourceManager from .tools import ToolManager +from .utilities import get_logger, configure_logging - -logger = logging.getLogger("fastmcp") +logger = get_logger(__name__) class Settings(BaseSettings): @@ -45,10 +48,10 @@ class Settings(BaseSettings): warn_on_duplicate_tools: bool = True -class FastMCPServer: +class FastMCP: def __init__(self, name=None, **settings: Optional[Settings]): self.settings = Settings(**settings) - self._mcp_server = MCPServer(name=name or "FastMCPServer") + self._mcp_server = MCPServer(name=name or "FastMCP") self._tool_manager = ToolManager( warn_on_duplicate_tools=self.settings.warn_on_duplicate_tools ) @@ -57,11 +60,7 @@ class FastMCPServer: ) # Configure logging - logging.basicConfig( - level=getattr(logging, self.settings.log_level.upper()), - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - ) - logger.setLevel(getattr(logging, self.settings.log_level.upper())) + configure_logging(self.settings.log_level) self._setup_handlers() @@ -297,7 +296,7 @@ class FastMCPServer: await self._mcp_server.run(*args, **kwargs) @classmethod - async def run_stdio(cls, app: "FastMCPServer") -> None: + async def run_stdio(cls, app: "FastMCP") -> None: """Run the server using stdio transport.""" async with stdio_server() as (read_stream, write_stream): await app.run( @@ -309,7 +308,7 @@ class FastMCPServer: @classmethod async def run_sse( cls, - app: "FastMCPServer", + app: "FastMCP", ) -> None: """Run the server using SSE transport.""" from mcp.server.sse import SseServerTransport diff --git a/src/fastmcp/utilities/__init__.py b/src/fastmcp/utilities/__init__.py new file mode 100644 index 000000000..a943cff70 --- /dev/null +++ b/src/fastmcp/utilities/__init__.py @@ -0,0 +1,4 @@ +"""Utility functions for FastMCP.""" +from .logging import get_logger, configure_logging + +__all__ = ["get_logger", "configure_logging"] diff --git a/src/fastmcp/utilities/logging.py b/src/fastmcp/utilities/logging.py new file mode 100644 index 000000000..5a3656ccb --- /dev/null +++ b/src/fastmcp/utilities/logging.py @@ -0,0 +1,33 @@ +"""Logging utilities for FastMCP.""" +import logging +from typing import Optional + + +def get_logger(name: Optional[str] = None) -> logging.Logger: + """Get a logger instance nested under the FastMCP namespace. + + Args: + name: Optional name to append to the FastMCP namespace. + If provided, the logger will be named 'FastMCP.[name]'. + If not provided, returns the root FastMCP logger. + + Returns: + A configured logger instance + """ + logger_name = "FastMCP" + if name: + logger_name = f"{logger_name}.{name}" + return logging.getLogger(logger_name) + + +def configure_logging(level: str = "INFO") -> None: + """Configure the root FastMCP logger. + + Args: + level: The log level to use. Defaults to INFO. + """ + logging.basicConfig( + level=getattr(logging, level.upper()), + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + ) + get_logger().setLevel(getattr(logging, level.upper())) diff --git a/tests/test_server.py b/tests/test_server.py index 5ab03b989..cd049d5d8 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -1,13 +1,13 @@ from mcp.shared.memory import ( create_connected_server_and_client_session as client_session, ) -from fastmcp.server import FastMCPServer +from fastmcp.server import FastMCP class TestServer: async def test_create_server(self): - server = FastMCPServer() - assert server.name == "FastMCPServer" + server = FastMCP() + assert server.name == "FastMCP" def tool_fn(x: int, y: int) -> int: @@ -16,20 +16,20 @@ def tool_fn(x: int, y: int) -> int: class TestServerTools: async def test_add_tool(self): - server = FastMCPServer() + server = FastMCP() server.add_tool(tool_fn) server.add_tool(tool_fn) assert len(server._tool_manager.list_tools()) == 1 async def test_list_tools(self): - server = FastMCPServer() + server = FastMCP() server.add_tool(tool_fn) async with client_session(server._mcp_server) as client: tools = await client.list_tools() assert len(tools.tools) == 1 async def test_call_tool(self): - server = FastMCPServer() + server = FastMCP() server.add_tool(tool_fn) async with client_session(server._mcp_server) as client: result = await client.call_tool("my_tool", {"arg1": "value"})