Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,48 @@ logging.basicConfig(level=logging.DEBUG) # or INFO, WARNING, etc.

This allows you to control the verbosity and destination of log messages from both your code and the framework.

## Middleware Configuration

You can now configure middleware for your MCP server using a dedicated `config.py` file and the `MiddlewareConfig` class. This allows you to easily customize CORS, logging, compression, and add your own middleware.

### Example: config.py

```python
from pymcp.middleware import MiddlewareConfig

middleware_config = MiddlewareConfig(
cors={
"allow_origins": ["https://myapp.com"],
"allow_methods": ["GET", "POST"],
"allow_headers": ["*"],
"allow_credentials": True,
},
logging={
"level": "DEBUG",
"format": "%(asctime)s %(levelname)s %(message)s",
},
compression={
"enabled": True
},
custom=[
# Add custom middleware classes here if needed
]
)
```

### Example: run_server.py

```python
from config import middleware_config
from pymcp.applications import create_app

app = create_app(middleware_config=middleware_config)
```

This approach keeps your configuration clean and separated from your application logic.

For more details and advanced configuration options, see [guide.md](./guide.md).




18 changes: 18 additions & 0 deletions example/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from pymcp.middleware import MiddlewareConfig

middleware_config = MiddlewareConfig(
cors={
"allow_origins": ["https://myapp.com"],
"allow_methods": ["GET", "POST"],
"allow_headers": ["*"],
"allow_credentials": True,
},
logging={
"level": "DEBUG",
"format": "%(asctime)s %(levelname)s %(message)s",
},
compression={"enabled": True},
custom=[
# Add custom middleware classes here if needed
],
)
7 changes: 5 additions & 2 deletions example/run_server.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import logging

from config import middleware_config
from pymcp.applications import create_app
from pymcp.registry import tool_registry
from pymcp.server import app

logging.basicConfig(level=logging.DEBUG)


@tool_registry.register
def addNumbersTool(a: float, b: float) -> str:
"""Adds two numbers 'a' and 'b' and returns their sum."""
Expand Down Expand Up @@ -38,7 +40,8 @@ def promptEchoTool(prompt: str) -> str:
return f"You said: {prompt}"



if __name__ == "__main__":
import uvicorn

app = create_app(middleware_config=middleware_config)
uvicorn.run(app, host="0.0.0.0", port=8088)
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import requests

from pymcp.applications import create_app
from pymcp.registry import tool_registry
from pymcp.server import app


@tool_registry.register
Expand All @@ -22,4 +22,4 @@ def callFlaskHelloTool() -> str:
print(
"[INFO] Make sure to start the Flask API server (flask_api_server.py) before running this example."
)
uvicorn.run(app, host="0.0.0.0", port=8088)
uvicorn.run(create_app(middleware_config=None), host="0.0.0.0", port=8088)
171 changes: 171 additions & 0 deletions guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Framework Middleware Configuration Guide

This guide explains how to configure middleware in your framework using the provided `MiddlewareConfig` class or via keyword arguments.

## Default Middleware

The framework includes the following middleware by default:

- **CORS**: Allows cross-origin requests (default: all origins, all methods, all headers).
- **Logging**: Basic request/response logging (default: INFO level).
- **Error Handling**: Basic error handling to prevent leaking internal details.

Optional middleware you can enable/configure:
- **Compression**: GZip compression for responses.
- **Custom Middleware**: Add your own FastAPI-compatible middleware.

---

## Configuration Methods

You can configure middleware in two ways:

### 1. Using `MiddlewareConfig`

```python
from pymcp.server import create_app, MiddlewareConfig

config = MiddlewareConfig(
cors={
"allow_origins": ["https://myapp.com"],
"allow_methods": ["GET", "POST"],
"allow_headers": ["*"],
"allow_credentials": True,
},
logging={
"level": "DEBUG",
"format": "%(asctime)s %(levelname)s %(message)s",
},
error_handling={
# Add custom error handlers if needed
},
compression={
"enabled": True
},
custom=[
# List of custom middleware classes
]
)

app = create_app(middleware_config=config)
```

### 2. Using Keyword Arguments

```python
from pymcp.server import create_app

app = create_app(
cors={
"allow_origins": ["https://myapp.com"],
"allow_methods": ["GET", "POST"],
"allow_headers": ["*"],
"allow_credentials": True,
},
logging={
"level": "DEBUG",
"format": "%(asctime)s %(levelname)s %(message)s",
},
compression={
"enabled": True
},
custom=[
# List of custom middleware classes
]
)
```

---

## Middleware Options

### CORS
- `allow_origins`: List of allowed origins (default: `["*"]`)
- `allow_methods`: List of allowed HTTP methods (default: `["*"]`)
- `allow_headers`: List of allowed headers (default: `["*"]`)
- `allow_credentials`: Allow credentials (default: `True`)

### Logging
- `level`: Log level (default: `"INFO"`)
- `format`: Log format string

### Error Handling
- `custom_handler`: (Optional) Your custom error handler function

### Compression
- `enabled`: Enable GZip compression (default: `False`)

### Custom Middleware
- `custom`: List of FastAPI-compatible middleware classes to add

---

## Example: Adding Custom Middleware

```python
from starlette.middleware.base import BaseHTTPMiddleware

class MyCustomMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
# Custom logic here
response = await call_next(request)
return response

config = MiddlewareConfig(custom=[MyCustomMiddleware])
app = create_app(middleware_config=config)
```

---

## Best Practices
- Restrict CORS origins in production.
- Use appropriate log levels for your environment.
- Add custom error handlers for user-friendly error messages.
- Enable compression for large responses or slow networks.
- Add custom middleware for authentication, metrics, etc.

---

## Recommended Usage: config.py

For best practice, create a `config.py` file in your project root to define your middleware configuration. Then, import this config in your server entry point (e.g., `run_server.py`).

### Example: config.py

```python

from pymcp.applications import create_app
from pymcp.middleware import MiddlewareConfig

middleware_config = MiddlewareConfig(
cors={
"allow_origins": ["https://myapp.com"],
"allow_methods": ["GET", "POST"],
"allow_headers": ["*"],
"allow_credentials": True,
},
logging={
"level": "DEBUG",
"format": "%(asctime)s %(levelname)s %(message)s",
},
compression={
"enabled": True
},
custom=[
# Add custom middleware classes here if needed
]
)
```

### Example: run_server.py

```python
from config import middleware_config
from pymcp.server import create_app

app = create_app(middleware_config=middleware_config)
```

This approach keeps your configuration clean and separated from your application logic.

For more details, see the framework documentation or contact the maintainers.
25 changes: 25 additions & 0 deletions pymcp/applications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
App factory for the MCP framework.
Provides the create_app function to instantiate and configure the FastAPI app.
"""

from typing import Optional

from fastapi import FastAPI

from .middleware import MiddlewareConfig, setup_middleware
from .server import router


def create_app(middleware_config: Optional[MiddlewareConfig] = None, **kwargs):
app = FastAPI()
config = middleware_config or MiddlewareConfig(
cors=kwargs.get("cors"),
logging=kwargs.get("logging"),
error_handling=kwargs.get("error_handling"),
compression=kwargs.get("compression"),
custom=kwargs.get("custom"),
)
setup_middleware(app, config)
app.include_router(router)
return app
60 changes: 60 additions & 0 deletions pymcp/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""
Middleware configuration and setup for the MCP framework.
"""

from typing import Any, Dict, Optional

from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware


class MiddlewareConfig:
"""
Configuration class for setting up middleware in the MCP server.
"""

def __init__(
self,
cors: Optional[Dict[str, Any]] = None,
logging: Optional[Dict[str, Any]] = None,
error_handling: Optional[Dict[str, Any]] = None,
compression: Optional[Dict[str, Any]] = None,
custom: Optional[list] = None,
):
self.cors = cors or {
"allow_origins": ["*"],
"allow_credentials": True,
"allow_methods": ["*"],
"allow_headers": ["*"],
}
self.logging = logging or {
"level": "INFO",
"format": "%(asctime)s %(levelname)s %(message)s",
}
self.error_handling = error_handling or {}
self.compression = compression or {"enabled": False}
self.custom = custom or []


def setup_middleware(app, config: MiddlewareConfig):
"""
Apply middleware to the FastAPI app based on the provided MiddlewareConfig.
"""
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=config.cors["allow_origins"],
allow_credentials=config.cors["allow_credentials"],
allow_methods=config.cors["allow_methods"],
allow_headers=config.cors["allow_headers"],
)

# Compression
if config.compression.get("enabled", False):
app.add_middleware(GZipMiddleware)
# Custom middleware
for mw in config.custom:
if not hasattr(mw, "__call__"):
raise ValueError(f"Custom middleware {mw} is not callable")
app.add_middleware(mw)
# Error handling can be added here as needed
Loading