Jeremiah Lowin commited on
Commit
8ea5e71
·
1 Parent(s): 09cabfa

Minor updates

Browse files
docs/docs.json CHANGED
@@ -78,6 +78,7 @@
78
  "servers/auth/bearer"
79
  ]
80
  },
 
81
  "servers/openapi",
82
  "servers/proxy",
83
  "servers/composition",
 
78
  "servers/auth/bearer"
79
  ]
80
  },
81
+ "servers/middleware",
82
  "servers/openapi",
83
  "servers/proxy",
84
  "servers/composition",
docs/servers/fastmcp.mdx CHANGED
@@ -193,7 +193,7 @@ def hello():
193
  return "hi"
194
 
195
  # Mount directly
196
- main.mount("sub", sub)
197
  ```
198
 
199
  ## Proxying Servers
 
193
  return "hi"
194
 
195
  # Mount directly
196
+ main.mount(sub, prefix="sub")
197
  ```
198
 
199
  ## Proxying Servers
docs/servers/middleware.mdx CHANGED
@@ -1,18 +1,22 @@
1
  ---
2
  title: MCP Middleware
3
  sidebarTitle: Middleware
4
- description: Add cross-cutting functionality to your MCP server with middleware that can inspect, modify, and respond to all MCP requests and responses.
5
  icon: layers
6
  ---
7
 
8
  import { VersionBadge } from "/snippets/version-badge.mdx"
9
 
10
- <VersionBadge version="2.8.0" />
11
 
12
  MCP middleware is a powerful concept that allows you to add cross-cutting functionality to your FastMCP server. Unlike traditional web middleware, MCP middleware is designed specifically for the Model Context Protocol, providing hooks for different types of MCP operations like tool calls, resource reads, and prompt requests.
13
 
14
- <Warning>
15
  MCP middleware is a FastMCP-specific concept and is not part of the official MCP protocol specification. This middleware system is designed to work with FastMCP servers and may not be compatible with other MCP implementations.
 
 
 
 
16
  </Warning>
17
 
18
  ## What is MCP Middleware?
@@ -58,13 +62,13 @@ The middleware hook system is designed to be extensible. As FastMCP evolves and
58
 
59
  ### Basic Middleware Structure
60
 
61
- MCP middleware is implemented by subclassing the `MCPMiddleware` base class and overriding the hooks you need:
62
 
63
  ```python
64
  from fastmcp import FastMCP
65
- from fastmcp.server.middleware import MCPMiddleware, MiddlewareContext
66
 
67
- class LoggingMiddleware(MCPMiddleware):
68
  """Middleware that logs all MCP operations."""
69
 
70
  async def on_message(self, context: MiddlewareContext, call_next):
@@ -97,7 +101,7 @@ mcp.add_middleware(LoggingMiddleware())
97
  The `MiddlewareContext` object provides access to information about the current request:
98
 
99
  ```python
100
- class InspectionMiddleware(MCPMiddleware):
101
  async def on_request(self, context: MiddlewareContext, call_next):
102
  # Access request information
103
  method = context.method # e.g., "tools/call"
@@ -120,7 +124,7 @@ Each middleware hook receives a `MiddlewareContext` and a `call_next` function.
120
  3. **Operation-specific hooks**: Called for specific MCP operations
121
 
122
  ```python
123
- class ComprehensiveMiddleware(MCPMiddleware):
124
  async def on_message(self, context: MiddlewareContext, call_next):
125
  """Called for ALL messages (requests and notifications)."""
126
  print(f"Message: {context.method}")
@@ -143,10 +147,10 @@ class ComprehensiveMiddleware(MCPMiddleware):
143
  ### Authentication Middleware
144
 
145
  ```python
146
- from fastmcp.server.middleware import MCPMiddleware, MiddlewareContext
147
  from fastmcp.exceptions import ToolError
148
 
149
- class AuthenticationMiddleware(MCPMiddleware):
150
  def __init__(self, required_token: str):
151
  self.required_token = required_token
152
 
@@ -184,7 +188,7 @@ mcp.add_middleware(AuthenticationMiddleware("secret-token-123"))
184
  import time
185
  import logging
186
 
187
- class PerformanceMiddleware(MCPMiddleware):
188
  def __init__(self):
189
  self.logger = logging.getLogger("performance")
190
 
@@ -214,7 +218,7 @@ class PerformanceMiddleware(MCPMiddleware):
214
  ### Request/Response Transformation Middleware
215
 
216
  ```python
217
- class TransformationMiddleware(MCPMiddleware):
218
  async def on_call_tool(self, context: MiddlewareContext, call_next):
219
  """Transform tool arguments and results."""
220
 
@@ -253,7 +257,7 @@ import asyncio
253
  from collections import defaultdict
254
  from datetime import datetime, timedelta
255
 
256
- class RateLimitMiddleware(MCPMiddleware):
257
  def __init__(self, max_requests: int = 100, window_minutes: int = 1):
258
  self.max_requests = max_requests
259
  self.window = timedelta(minutes=window_minutes)
@@ -327,7 +331,7 @@ mcp.add_middleware(LoggingMiddleware())
327
  ### Conditional Middleware
328
 
329
  ```python
330
- class ConditionalMiddleware(MCPMiddleware):
331
  def __init__(self, condition_func):
332
  self.should_process = condition_func
333
 
@@ -352,7 +356,7 @@ mcp.add_middleware(ConditionalMiddleware(only_expensive_tools))
352
  ### Middleware with State
353
 
354
  ```python
355
- class StatefulMiddleware(MCPMiddleware):
356
  def __init__(self):
357
  self.call_count = 0
358
  self.tools_used = set()
@@ -377,7 +381,7 @@ class StatefulMiddleware(MCPMiddleware):
377
  ### Error Handling Middleware
378
 
379
  ```python
380
- class ErrorHandlingMiddleware(MCPMiddleware):
381
  async def on_call_tool(self, context: MiddlewareContext, call_next):
382
  """Provide consistent error handling."""
383
  try:
@@ -400,7 +404,7 @@ class ErrorHandlingMiddleware(MCPMiddleware):
400
  3. **Cache when possible**: Store frequently accessed data to avoid repeated lookups
401
 
402
  ```python
403
- class EfficientMiddleware(MCPMiddleware):
404
  def __init__(self):
405
  self._cache = {}
406
 
@@ -428,7 +432,7 @@ class EfficientMiddleware(MCPMiddleware):
428
  3. **Use `ToolError` for client-facing errors**: Keep internal errors internal
429
 
430
  ```python
431
- class RobustMiddleware(MCPMiddleware):
432
  async def on_request(self, context: MiddlewareContext, call_next):
433
  """Robust error handling example."""
434
  try:
 
1
  ---
2
  title: MCP Middleware
3
  sidebarTitle: Middleware
4
+ description: Add custom functionality to your MCP server with middleware that can inspect, modify, and respond to all MCP requests and responses.
5
  icon: layers
6
  ---
7
 
8
  import { VersionBadge } from "/snippets/version-badge.mdx"
9
 
10
+ <VersionBadge version="2.9.0" />
11
 
12
  MCP middleware is a powerful concept that allows you to add cross-cutting functionality to your FastMCP server. Unlike traditional web middleware, MCP middleware is designed specifically for the Model Context Protocol, providing hooks for different types of MCP operations like tool calls, resource reads, and prompt requests.
13
 
14
+ <Tip>
15
  MCP middleware is a FastMCP-specific concept and is not part of the official MCP protocol specification. This middleware system is designed to work with FastMCP servers and may not be compatible with other MCP implementations.
16
+ </Tip>
17
+
18
+ <Warning>
19
+ MCP middleware is a brand new concept and may be subject to breaking changes in future versions.
20
  </Warning>
21
 
22
  ## What is MCP Middleware?
 
62
 
63
  ### Basic Middleware Structure
64
 
65
+ MCP middleware is implemented by subclassing the `Middleware` base class and overriding the hooks you need:
66
 
67
  ```python
68
  from fastmcp import FastMCP
69
+ from fastmcp.server.middleware import Middleware, MiddlewareContext
70
 
71
+ class LoggingMiddleware(Middleware):
72
  """Middleware that logs all MCP operations."""
73
 
74
  async def on_message(self, context: MiddlewareContext, call_next):
 
101
  The `MiddlewareContext` object provides access to information about the current request:
102
 
103
  ```python
104
+ class InspectionMiddleware(Middleware):
105
  async def on_request(self, context: MiddlewareContext, call_next):
106
  # Access request information
107
  method = context.method # e.g., "tools/call"
 
124
  3. **Operation-specific hooks**: Called for specific MCP operations
125
 
126
  ```python
127
+ class ComprehensiveMiddleware(Middleware):
128
  async def on_message(self, context: MiddlewareContext, call_next):
129
  """Called for ALL messages (requests and notifications)."""
130
  print(f"Message: {context.method}")
 
147
  ### Authentication Middleware
148
 
149
  ```python
150
+ from fastmcp.server.middleware import Middleware, MiddlewareContext
151
  from fastmcp.exceptions import ToolError
152
 
153
+ class AuthenticationMiddleware(Middleware):
154
  def __init__(self, required_token: str):
155
  self.required_token = required_token
156
 
 
188
  import time
189
  import logging
190
 
191
+ class PerformanceMiddleware(Middleware):
192
  def __init__(self):
193
  self.logger = logging.getLogger("performance")
194
 
 
218
  ### Request/Response Transformation Middleware
219
 
220
  ```python
221
+ class TransformationMiddleware(Middleware):
222
  async def on_call_tool(self, context: MiddlewareContext, call_next):
223
  """Transform tool arguments and results."""
224
 
 
257
  from collections import defaultdict
258
  from datetime import datetime, timedelta
259
 
260
+ class RateLimitMiddleware(Middleware):
261
  def __init__(self, max_requests: int = 100, window_minutes: int = 1):
262
  self.max_requests = max_requests
263
  self.window = timedelta(minutes=window_minutes)
 
331
  ### Conditional Middleware
332
 
333
  ```python
334
+ class ConditionalMiddleware(Middleware):
335
  def __init__(self, condition_func):
336
  self.should_process = condition_func
337
 
 
356
  ### Middleware with State
357
 
358
  ```python
359
+ class StatefulMiddleware(Middleware):
360
  def __init__(self):
361
  self.call_count = 0
362
  self.tools_used = set()
 
381
  ### Error Handling Middleware
382
 
383
  ```python
384
+ class ErrorHandlingMiddleware(Middleware):
385
  async def on_call_tool(self, context: MiddlewareContext, call_next):
386
  """Provide consistent error handling."""
387
  try:
 
404
  3. **Cache when possible**: Store frequently accessed data to avoid repeated lookups
405
 
406
  ```python
407
+ class EfficientMiddleware(Middleware):
408
  def __init__(self):
409
  self._cache = {}
410
 
 
432
  3. **Use `ToolError` for client-facing errors**: Keep internal errors internal
433
 
434
  ```python
435
+ class RobustMiddleware(Middleware):
436
  async def on_request(self, context: MiddlewareContext, call_next):
437
  """Robust error handling example."""
438
  try:
src/fastmcp/server/middleware.py CHANGED
@@ -104,7 +104,7 @@ class MiddlewareContext(Generic[T]):
104
 
105
 
106
  def make_middleware_wrapper(
107
- middleware: MCPMiddleware, call_next: CallNext[T, R]
108
  ) -> CallNext[T, R]:
109
  """Create a wrapper that applies a single middleware to a context. The
110
  closure bakes in the middleware and call_next function, so it can be
@@ -116,7 +116,7 @@ def make_middleware_wrapper(
116
  return wrapper
117
 
118
 
119
- class MCPMiddleware:
120
  """Base class for FastMCP middleware with dispatching hooks."""
121
 
122
  async def __call__(
 
104
 
105
 
106
  def make_middleware_wrapper(
107
+ middleware: Middleware, call_next: CallNext[T, R]
108
  ) -> CallNext[T, R]:
109
  """Create a wrapper that applies a single middleware to a context. The
110
  closure bakes in the middleware and call_next function, so it can be
 
116
  return wrapper
117
 
118
 
119
+ class Middleware:
120
  """Base class for FastMCP middleware with dispatching hooks."""
121
 
122
  async def __call__(
src/fastmcp/server/server.py CHANGED
@@ -35,7 +35,7 @@ from mcp.types import Resource as MCPResource
35
  from mcp.types import ResourceTemplate as MCPResourceTemplate
36
  from mcp.types import Tool as MCPTool
37
  from pydantic import AnyUrl
38
- from starlette.middleware import Middleware
39
  from starlette.requests import Request
40
  from starlette.responses import Response
41
  from starlette.routing import BaseRoute, Route
@@ -55,7 +55,7 @@ from fastmcp.server.http import (
55
  create_sse_app,
56
  create_streamable_http_app,
57
  )
58
- from fastmcp.server.middleware import MCPMiddleware, MiddlewareContext
59
  from fastmcp.settings import Settings
60
  from fastmcp.tools import ToolManager
61
  from fastmcp.tools.tool import FunctionTool, Tool
@@ -118,7 +118,7 @@ class FastMCP(Generic[LifespanResultT]):
118
  *,
119
  version: str | None = None,
120
  auth: OAuthProvider | None = None,
121
- middleware: list[MCPMiddleware] | None = None,
122
  lifespan: (
123
  Callable[
124
  [FastMCP[LifespanResultT]],
@@ -335,7 +335,7 @@ class FastMCP(Generic[LifespanResultT]):
335
  chain = partial(mw, call_next=chain)
336
  return await chain(context)
337
 
338
- def add_middleware(self, middleware: MCPMiddleware) -> None:
339
  self.middleware.append(middleware)
340
 
341
  async def get_tools(self) -> dict[str, Tool]:
@@ -917,7 +917,7 @@ class FastMCP(Generic[LifespanResultT]):
917
  Args:
918
  template: A ResourceTemplate instance to add
919
  """
920
- self._resource_manager.add_template(template)
921
 
922
  def add_resource_fn(
923
  self,
@@ -1260,7 +1260,7 @@ class FastMCP(Generic[LifespanResultT]):
1260
  log_level: str | None = None,
1261
  path: str | None = None,
1262
  uvicorn_config: dict[str, Any] | None = None,
1263
- middleware: list[Middleware] | None = None,
1264
  ) -> None:
1265
  """Run the server using HTTP transport.
1266
 
@@ -1331,7 +1331,7 @@ class FastMCP(Generic[LifespanResultT]):
1331
  self,
1332
  path: str | None = None,
1333
  message_path: str | None = None,
1334
- middleware: list[Middleware] | None = None,
1335
  ) -> StarletteWithLifespan:
1336
  """
1337
  Create a Starlette app for the SSE server.
@@ -1361,7 +1361,7 @@ class FastMCP(Generic[LifespanResultT]):
1361
  def streamable_http_app(
1362
  self,
1363
  path: str | None = None,
1364
- middleware: list[Middleware] | None = None,
1365
  ) -> StarletteWithLifespan:
1366
  """
1367
  Create a Starlette app for the StreamableHTTP server.
@@ -1382,7 +1382,7 @@ class FastMCP(Generic[LifespanResultT]):
1382
  def http_app(
1383
  self,
1384
  path: str | None = None,
1385
- middleware: list[Middleware] | None = None,
1386
  json_response: bool | None = None,
1387
  stateless_http: bool | None = None,
1388
  transport: Literal["streamable-http", "sse"] = "streamable-http",
 
35
  from mcp.types import ResourceTemplate as MCPResourceTemplate
36
  from mcp.types import Tool as MCPTool
37
  from pydantic import AnyUrl
38
+ from starlette.middleware import Middleware as ASGIMiddleware
39
  from starlette.requests import Request
40
  from starlette.responses import Response
41
  from starlette.routing import BaseRoute, Route
 
55
  create_sse_app,
56
  create_streamable_http_app,
57
  )
58
+ from fastmcp.server.middleware import Middleware, MiddlewareContext
59
  from fastmcp.settings import Settings
60
  from fastmcp.tools import ToolManager
61
  from fastmcp.tools.tool import FunctionTool, Tool
 
118
  *,
119
  version: str | None = None,
120
  auth: OAuthProvider | None = None,
121
+ middleware: list[Middleware] | None = None,
122
  lifespan: (
123
  Callable[
124
  [FastMCP[LifespanResultT]],
 
335
  chain = partial(mw, call_next=chain)
336
  return await chain(context)
337
 
338
+ def add_middleware(self, middleware: Middleware) -> None:
339
  self.middleware.append(middleware)
340
 
341
  async def get_tools(self) -> dict[str, Tool]:
 
917
  Args:
918
  template: A ResourceTemplate instance to add
919
  """
920
+ self._resource_manager.add_template(template, key=key)
921
 
922
  def add_resource_fn(
923
  self,
 
1260
  log_level: str | None = None,
1261
  path: str | None = None,
1262
  uvicorn_config: dict[str, Any] | None = None,
1263
+ middleware: list[ASGIMiddleware] | None = None,
1264
  ) -> None:
1265
  """Run the server using HTTP transport.
1266
 
 
1331
  self,
1332
  path: str | None = None,
1333
  message_path: str | None = None,
1334
+ middleware: list[ASGIMiddleware] | None = None,
1335
  ) -> StarletteWithLifespan:
1336
  """
1337
  Create a Starlette app for the SSE server.
 
1361
  def streamable_http_app(
1362
  self,
1363
  path: str | None = None,
1364
+ middleware: list[ASGIMiddleware] | None = None,
1365
  ) -> StarletteWithLifespan:
1366
  """
1367
  Create a Starlette app for the StreamableHTTP server.
 
1382
  def http_app(
1383
  self,
1384
  path: str | None = None,
1385
+ middleware: list[ASGIMiddleware] | None = None,
1386
  json_response: bool | None = None,
1387
  stateless_http: bool | None = None,
1388
  transport: Literal["streamable-http", "sse"] = "streamable-http",
tests/server/middleware/test_middleware.py CHANGED
@@ -7,7 +7,7 @@ import pytest
7
 
8
  from fastmcp import Client, FastMCP
9
  from fastmcp.server.context import Context
10
- from fastmcp.server.middleware import MCPMiddleware, MiddlewareContext
11
 
12
 
13
  @dataclass
@@ -18,7 +18,7 @@ class Recording:
18
  result: mcp.types.ServerResult | None
19
 
20
 
21
- class RecordingMiddleware(MCPMiddleware):
22
  """A middleware that automatically records all method calls."""
23
 
24
  def __init__(self, name: str | None = None):
 
7
 
8
  from fastmcp import Client, FastMCP
9
  from fastmcp.server.context import Context
10
+ from fastmcp.server.middleware import Middleware, MiddlewareContext
11
 
12
 
13
  @dataclass
 
18
  result: mcp.types.ServerResult | None
19
 
20
 
21
+ class RecordingMiddleware(Middleware):
22
  """A middleware that automatically records all method calls."""
23
 
24
  def __init__(self, name: str | None = None):