|
1 | | -"""Flask application factory using GopherAuth reusable module. |
| 1 | +"""MCP Server using FastMCP with Streamable HTTP transport. |
2 | 2 |
|
3 | | -Creates and configures the Flask application for the Auth MCP Server. |
4 | | -Replaces manual auth setup with the GopherAuth module from |
5 | | -gopher_mcp_python.auth. |
| 3 | +Mirrors the JS auth example pattern using the official Python MCP SDK. |
| 4 | +OAuth authentication handled by GopherAuth from gopher_mcp_python.auth. |
6 | 5 | """ |
7 | 6 |
|
8 | 7 | from __future__ import annotations |
9 | 8 |
|
| 9 | +import signal |
| 10 | +import sys |
10 | 11 | from pathlib import Path |
11 | 12 |
|
12 | | -from flask import Flask |
13 | | -from flask_cors import CORS |
| 13 | +from mcp.server.fastmcp import FastMCP |
14 | 14 |
|
15 | 15 | from gopher_mcp_python.auth import GopherAuth |
16 | 16 |
|
17 | | -from .routes.mcp_handler import McpHandler |
18 | | -from .routes import register_mcp_routes |
19 | | -from .tools import register_weather_tools |
20 | 17 |
|
21 | | - |
22 | | -def create_app( |
23 | | - config_path: str | Path | None = None, |
24 | | - auth_disabled: bool = False, |
25 | | -) -> Flask: |
26 | | - """Create and configure the Flask application. |
| 18 | +def create_server(config_path: str | None = None) -> tuple[FastMCP, GopherAuth]: |
| 19 | + """Create MCP server and GopherAuth instance. |
27 | 20 |
|
28 | 21 | Args: |
29 | 22 | config_path: Path to server.config file. |
30 | | - auth_disabled: If True, disable authentication entirely. |
31 | 23 |
|
32 | 24 | Returns: |
33 | | - Configured Flask application. |
| 25 | + Tuple of (FastMCP server, GopherAuth instance). |
34 | 26 | """ |
35 | 27 | # Initialize GopherAuth from config file |
36 | | - auth = GopherAuth( |
37 | | - config_path=str(config_path) if config_path else None, |
38 | | - auth_disabled=auth_disabled, |
39 | | - ) |
| 28 | + auth = GopherAuth(config_path=config_path) |
40 | 29 | auth.initialize() |
41 | 30 |
|
42 | | - # Create Flask app |
43 | | - app = Flask(__name__) |
44 | | - app.config["JSON_SORT_KEYS"] = False |
45 | | - app.config["GOPHER_AUTH"] = auth |
46 | | - |
47 | | - # Enable CORS |
48 | | - CORS(app, resources={r"/*": {"origins": "*"}}) |
| 31 | + # Create MCP server with FastMCP |
| 32 | + mcp = FastMCP( |
| 33 | + "py-auth-mcp-server", |
| 34 | + json_response=True, |
| 35 | + ) |
49 | 36 |
|
50 | | - # Register health endpoint |
51 | | - @app.route("/health") |
52 | | - def health(): |
53 | | - return {"status": "healthy", "service": "py-auth-mcp-server"} |
| 37 | + # Register weather tools |
| 38 | + @mcp.tool() |
| 39 | + def get_weather(city: str) -> str: |
| 40 | + """Get current weather for a city. No auth required.""" |
| 41 | + h = sum(ord(c) for c in city) |
| 42 | + conditions = ["Sunny", "Cloudy", "Rainy", "Partly Cloudy", "Windy"] |
| 43 | + temp = 10 + (h % 26) |
| 44 | + cond = conditions[h % len(conditions)] |
| 45 | + return f"Weather in {city}: {temp}C, {cond}, Humidity: {40 + h % 40}%" |
| 46 | + |
| 47 | + @mcp.tool() |
| 48 | + def get_forecast(city: str) -> str: |
| 49 | + """Get 5-day forecast. Requires mcp:read scope.""" |
| 50 | + h = sum(ord(c) for c in city) |
| 51 | + conditions = ["Sunny", "Cloudy", "Rainy", "Partly Cloudy", "Windy"] |
| 52 | + days = ["Today", "Tomorrow", "Day 3", "Day 4", "Day 5"] |
| 53 | + forecast = [] |
| 54 | + for i, day in enumerate(days): |
| 55 | + hi = 10 + ((h + i * 7) % 26) + 5 |
| 56 | + lo = hi - 10 |
| 57 | + forecast.append(f"{day}: {hi}C/{lo}C {conditions[(h + i) % len(conditions)]}") |
| 58 | + return f"5-Day Forecast for {city}:\n" + "\n".join(forecast) |
| 59 | + |
| 60 | + @mcp.tool() |
| 61 | + def get_weather_alerts(region: str) -> str: |
| 62 | + """Get weather alerts. Requires mcp:admin scope.""" |
| 63 | + h = sum(ord(c) for c in region) |
| 64 | + if h % 3 == 0: |
| 65 | + return f"Alert for {region}: Heat Warning - High temperatures expected" |
| 66 | + elif h % 3 == 1: |
| 67 | + return f"Alerts for {region}: Storm Watch, Wind Advisory" |
| 68 | + return f"No active alerts for {region}" |
| 69 | + |
| 70 | + return mcp, auth |
| 71 | + |
| 72 | + |
| 73 | +def main() -> None: |
| 74 | + """Run the MCP server with Streamable HTTP transport.""" |
| 75 | + # Determine config path |
| 76 | + config_path = sys.argv[1] if len(sys.argv) > 1 else str( |
| 77 | + Path(__file__).parent.parent / "server.config" |
| 78 | + ) |
54 | 79 |
|
55 | | - # Create and register MCP handler |
56 | | - mcp_handler = McpHandler() |
57 | | - app.config["MCP_HANDLER"] = mcp_handler |
58 | | - register_mcp_routes(app, mcp_handler) |
| 80 | + print("========================================") |
| 81 | + print(" Python Auth MCP Server") |
| 82 | + print("========================================") |
59 | 83 |
|
60 | | - # Register weather tools (scope checking can be added via middleware) |
61 | | - register_weather_tools(mcp_handler, auth) |
| 84 | + mcp, auth = create_server(config_path) |
62 | 85 |
|
63 | | - return app |
| 86 | + port = auth.native_config.get_int("port") if auth.native_config else 3001 |
| 87 | + host = auth.native_config.get_string("host") if auth.native_config else "0.0.0.0" |
64 | 88 |
|
| 89 | + print(f"Server: http://{host}:{port}") |
| 90 | + print(f"MCP: http://{host}:{port}/mcp") |
| 91 | + print(f"Config: {config_path}") |
| 92 | + print(f"Auth: {'DISABLED' if auth.is_disabled else 'ENABLED'}") |
| 93 | + print() |
65 | 94 |
|
66 | | -def cleanup_app(app: Flask) -> None: |
67 | | - """Clean up application resources.""" |
68 | | - auth = app.config.get("GOPHER_AUTH") |
69 | | - if auth is not None: |
| 95 | + # Graceful shutdown |
| 96 | + def shutdown_handler(sig, frame): |
| 97 | + print("\nShutting down...") |
70 | 98 | auth.shutdown() |
| 99 | + sys.exit(0) |
| 100 | + |
| 101 | + signal.signal(signal.SIGINT, shutdown_handler) |
| 102 | + signal.signal(signal.SIGTERM, shutdown_handler) |
| 103 | + |
| 104 | + # Run with Streamable HTTP transport |
| 105 | + mcp.run( |
| 106 | + transport="streamable-http", |
| 107 | + host=host, |
| 108 | + port=port, |
| 109 | + ) |
| 110 | + |
| 111 | + |
| 112 | +if __name__ == "__main__": |
| 113 | + main() |
0 commit comments