Spaces:
Running
Running
File size: 6,020 Bytes
ac3212f e97c2b8 ccc2857 e97c2b8 ac3212f 63bfcb4 ac3212f 8ea5e71 ac3212f 8ea5e71 ac3212f e97c2b8 ac3212f e97c2b8 ac3212f e97c2b8 ac3212f e97c2b8 ac3212f e97c2b8 ac3212f 54bdf2c e423e66 54bdf2c ac3212f e97c2b8 ac3212f e97c2b8 ac3212f e97c2b8 ccc2857 ac3212f e97c2b8 ac3212f e97c2b8 ac3212f 0907fff ac3212f 54bdf2c 0907fff 54bdf2c 0907fff 54bdf2c 0907fff 54bdf2c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | from __future__ import annotations
import logging
from collections.abc import Awaitable
from dataclasses import dataclass, field, replace
from datetime import datetime, timezone
from functools import partial
from typing import (
TYPE_CHECKING,
Any,
Generic,
Literal,
Protocol,
TypeVar,
runtime_checkable,
)
import mcp.types as mt
from fastmcp.prompts.prompt import Prompt
from fastmcp.resources.resource import Resource
from fastmcp.resources.template import ResourceTemplate
from fastmcp.tools.tool import Tool, ToolResult
if TYPE_CHECKING:
from fastmcp.server.context import Context
__all__ = [
"Middleware",
"MiddlewareContext",
"CallNext",
]
logger = logging.getLogger(__name__)
T = TypeVar("T")
R = TypeVar("R", covariant=True)
@runtime_checkable
class CallNext(Protocol[T, R]):
def __call__(self, context: MiddlewareContext[T]) -> Awaitable[R]: ...
@dataclass(kw_only=True, frozen=True)
class MiddlewareContext(Generic[T]):
"""
Unified context for all middleware operations.
"""
message: T
fastmcp_context: Context | None = None
# Common metadata
source: Literal["client", "server"] = "client"
type: Literal["request", "notification"] = "request"
method: str | None = None
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
def copy(self, **kwargs: Any) -> MiddlewareContext[T]:
return replace(self, **kwargs)
def make_middleware_wrapper(
middleware: Middleware, call_next: CallNext[T, R]
) -> CallNext[T, R]:
"""Create a wrapper that applies a single middleware to a context. The
closure bakes in the middleware and call_next function, so it can be
passed to other functions that expect a call_next function."""
async def wrapper(context: MiddlewareContext[T]) -> R:
return await middleware(context, call_next)
return wrapper
class Middleware:
"""Base class for FastMCP middleware with dispatching hooks."""
async def __call__(
self,
context: MiddlewareContext[T],
call_next: CallNext[T, Any],
) -> Any:
"""Main entry point that orchestrates the pipeline."""
handler_chain = await self._dispatch_handler(
context,
call_next=call_next,
)
return await handler_chain(context)
async def _dispatch_handler(
self, context: MiddlewareContext[Any], call_next: CallNext[Any, Any]
) -> CallNext[Any, Any]:
"""Builds a chain of handlers for a given message."""
handler = call_next
match context.method:
case "tools/call":
handler = partial(self.on_call_tool, call_next=handler)
case "resources/read":
handler = partial(self.on_read_resource, call_next=handler)
case "prompts/get":
handler = partial(self.on_get_prompt, call_next=handler)
case "tools/list":
handler = partial(self.on_list_tools, call_next=handler)
case "resources/list":
handler = partial(self.on_list_resources, call_next=handler)
case "resources/templates/list":
handler = partial(self.on_list_resource_templates, call_next=handler)
case "prompts/list":
handler = partial(self.on_list_prompts, call_next=handler)
match context.type:
case "request":
handler = partial(self.on_request, call_next=handler)
case "notification":
handler = partial(self.on_notification, call_next=handler)
handler = partial(self.on_message, call_next=handler)
return handler
async def on_message(
self,
context: MiddlewareContext[Any],
call_next: CallNext[Any, Any],
) -> Any:
return await call_next(context)
async def on_request(
self,
context: MiddlewareContext[mt.Request],
call_next: CallNext[mt.Request, Any],
) -> Any:
return await call_next(context)
async def on_notification(
self,
context: MiddlewareContext[mt.Notification],
call_next: CallNext[mt.Notification, Any],
) -> Any:
return await call_next(context)
async def on_call_tool(
self,
context: MiddlewareContext[mt.CallToolRequestParams],
call_next: CallNext[mt.CallToolRequestParams, ToolResult],
) -> ToolResult:
return await call_next(context)
async def on_read_resource(
self,
context: MiddlewareContext[mt.ReadResourceRequestParams],
call_next: CallNext[mt.ReadResourceRequestParams, mt.ReadResourceResult],
) -> mt.ReadResourceResult:
return await call_next(context)
async def on_get_prompt(
self,
context: MiddlewareContext[mt.GetPromptRequestParams],
call_next: CallNext[mt.GetPromptRequestParams, mt.GetPromptResult],
) -> mt.GetPromptResult:
return await call_next(context)
async def on_list_tools(
self,
context: MiddlewareContext[mt.ListToolsRequest],
call_next: CallNext[mt.ListToolsRequest, list[Tool]],
) -> list[Tool]:
return await call_next(context)
async def on_list_resources(
self,
context: MiddlewareContext[mt.ListResourcesRequest],
call_next: CallNext[mt.ListResourcesRequest, list[Resource]],
) -> list[Resource]:
return await call_next(context)
async def on_list_resource_templates(
self,
context: MiddlewareContext[mt.ListResourceTemplatesRequest],
call_next: CallNext[mt.ListResourceTemplatesRequest, list[ResourceTemplate]],
) -> list[ResourceTemplate]:
return await call_next(context)
async def on_list_prompts(
self,
context: MiddlewareContext[mt.ListPromptsRequest],
call_next: CallNext[mt.ListPromptsRequest, list[Prompt]],
) -> list[Prompt]:
return await call_next(context)
|