mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-22 13:34:17 +02:00
Add support for starlette request
This commit is contained in:
parent
f15405aae5
commit
b672468d4f
4 changed files with 77 additions and 37 deletions
|
|
@ -269,11 +269,31 @@ async def advanced_tool(ctx: Context) -> str:
|
|||
return f"Server: {server_name}"
|
||||
```
|
||||
|
||||
For web applications, you can access the underlying HTTP request:
|
||||
|
||||
```python
|
||||
@mcp.tool()
|
||||
async def handle_web_request(ctx: Context) -> dict:
|
||||
"""Access HTTP request information from the Starlette request."""
|
||||
request = ctx.get_starlette_request()
|
||||
|
||||
# Access HTTP headers, query parameters, etc.
|
||||
user_agent = request.headers.get("user-agent", "Unknown")
|
||||
client_ip = request.client.host if request.client else "Unknown"
|
||||
|
||||
return {
|
||||
"user_agent": user_agent,
|
||||
"client_ip": client_ip,
|
||||
"path": request.url.path,
|
||||
}
|
||||
```
|
||||
|
||||
**Advanced Properties:**
|
||||
|
||||
- **`ctx.fastmcp -> FastMCP`**: Access the server instance the context belongs to
|
||||
- **`ctx.session`**: Access the raw `mcp.server.session.ServerSession` object
|
||||
- **`ctx.request_context`**: Access the raw `mcp.shared.context.RequestContext` object
|
||||
- **`ctx.get_starlette_request() -> Request`**: Access the active Starlette request object (when running with a web server)
|
||||
|
||||
<Warning>
|
||||
Direct use of `session` or `request_context` requires understanding the low-level MCP Python SDK and may be less stable than using the methods provided directly on the `Context` object.
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from pydantic.networks import AnyUrl
|
|||
from starlette.requests import Request
|
||||
|
||||
from fastmcp.server.server import FastMCP
|
||||
from fastmcp.utilities.http import get_current_starlette_request
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
|
@ -59,7 +60,6 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
|
|||
|
||||
_request_context: RequestContext[ServerSessionT, LifespanContextT] | None
|
||||
_fastmcp: FastMCP | None
|
||||
_request: Request | None
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
|
|
@ -68,13 +68,11 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
|
|||
*,
|
||||
request_context: RequestContext[ServerSessionT, LifespanContextT] | None = None,
|
||||
fastmcp: FastMCP | None = None,
|
||||
request: Request | None = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self._request_context = request_context
|
||||
self._fastmcp = fastmcp
|
||||
self._request = request
|
||||
|
||||
@property
|
||||
def fastmcp(self) -> FastMCP:
|
||||
|
|
@ -90,13 +88,6 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
|
|||
raise ValueError("Context is not available outside of a request")
|
||||
return self._request_context
|
||||
|
||||
@property
|
||||
def request(self) -> Request:
|
||||
"""Access to the underlying request."""
|
||||
if self._request is None:
|
||||
raise ValueError("Context is not available outside of a request")
|
||||
return self._request
|
||||
|
||||
async def report_progress(
|
||||
self, progress: float, total: float | None = None
|
||||
) -> None:
|
||||
|
|
@ -235,3 +226,10 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
|
|||
)
|
||||
|
||||
return result.content
|
||||
|
||||
def get_starlette_request(self) -> Request:
|
||||
"""Get the active starlette request."""
|
||||
request = get_current_starlette_request()
|
||||
if request is None:
|
||||
raise ValueError("Request is not available outside a Starlette request")
|
||||
return request
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from contextlib import (
|
|||
AsyncExitStack,
|
||||
asynccontextmanager,
|
||||
)
|
||||
from contextvars import ContextVar
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Any, Generic, Literal
|
||||
|
||||
|
|
@ -60,6 +59,7 @@ from fastmcp.resources.template import ResourceTemplate
|
|||
from fastmcp.tools import ToolManager
|
||||
from fastmcp.tools.tool import Tool
|
||||
from fastmcp.utilities.decorators import DecoratedFunction
|
||||
from fastmcp.utilities.http import RequestMiddleware
|
||||
from fastmcp.utilities.logging import configure_logging, get_logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -73,25 +73,6 @@ logger = get_logger(__name__)
|
|||
NOT_FOUND = object()
|
||||
|
||||
|
||||
_current_starlette_request: ContextVar[Request | None] = ContextVar(
|
||||
"starlette_request",
|
||||
default=None,
|
||||
)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def starlette_request_context(request: Request):
|
||||
token = _current_starlette_request.set(request)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_current_starlette_request.reset(token)
|
||||
|
||||
|
||||
def get_current_starlette_request() -> Request | None:
|
||||
return _current_starlette_request.get()
|
||||
|
||||
|
||||
class MountedServer:
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -333,11 +314,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
request_context = None
|
||||
from fastmcp.server.context import Context
|
||||
|
||||
return Context(
|
||||
request_context=request_context,
|
||||
fastmcp=self,
|
||||
request=get_current_starlette_request(),
|
||||
)
|
||||
return Context(request_context=request_context, fastmcp=self)
|
||||
|
||||
async def get_tools(self) -> dict[str, Tool]:
|
||||
"""Get all registered tools, indexed by registered key."""
|
||||
|
|
@ -846,10 +823,11 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
log_level: str | None = None,
|
||||
) -> None:
|
||||
"""Run the server using SSE transport."""
|
||||
starlette_app = self.sse_app()
|
||||
app = self.sse_app()
|
||||
app = RequestMiddleware(app)
|
||||
|
||||
config = uvicorn.Config(
|
||||
starlette_app,
|
||||
app,
|
||||
host=host or self.settings.host,
|
||||
port=port or self.settings.port,
|
||||
log_level=log_level or self.settings.log_level.lower(),
|
||||
|
|
|
|||
44
src/fastmcp/utilities/http.py
Normal file
44
src/fastmcp/utilities/http.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from contextlib import (
|
||||
asynccontextmanager,
|
||||
)
|
||||
from contextvars import ContextVar
|
||||
|
||||
from starlette.requests import Request
|
||||
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
_current_starlette_request: ContextVar[Request | None] = ContextVar(
|
||||
"starlette_request",
|
||||
default=None,
|
||||
)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def starlette_request_context(request: Request):
|
||||
token = _current_starlette_request.set(request)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_current_starlette_request.reset(token)
|
||||
|
||||
|
||||
def get_current_starlette_request() -> Request | None:
|
||||
return _current_starlette_request.get()
|
||||
|
||||
|
||||
class RequestMiddleware:
|
||||
"""
|
||||
Middleware that stores each request in a ContextVar
|
||||
"""
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
async def __call__(self, scope, receive, send):
|
||||
async with starlette_request_context(Request(scope)):
|
||||
await self.app(scope, receive, send)
|
||||
Loading…
Add table
Add a link
Reference in a new issue