|
1 | | -from mcp.server.fastmcp import FastMCP |
| 1 | + |
| 2 | +import os |
2 | 3 | import logging |
| 4 | +import contextlib |
| 5 | +import uvicorn |
| 6 | +from fastapi import FastAPI, HTTPException, Request |
| 7 | +from mcp.server.fastmcp import FastMCP |
| 8 | +from mcp.server.streamable_http_manager import StreamableHTTPSessionManager |
3 | 9 |
|
4 | 10 | # to start (after .venv setup): |
5 | 11 | # python spendee/spendee_mcp.py |
|
10 | 16 | # setup "Transport Type" to "Streamable HTTP" |
11 | 17 | # and "Server URL" to "http://localhost:8000/mcp" |
12 | 18 |
|
| 19 | +ACCEPTED_TOKEN = os.environ.get("MCP_TOKEN", "spendee-token") |
| 20 | + |
13 | 21 | logging.basicConfig(level=logging.DEBUG) |
14 | | -mcp = FastMCP("spendee", host="0.0.0.0", port=8000) |
| 22 | +logger = logging.getLogger(__name__) |
| 23 | + |
| 24 | +mcp = FastMCP("spendee") |
15 | 25 |
|
16 | 26 | @mcp.tool() |
17 | 27 | def get_wallets(): |
18 | 28 | # Hardcoded example wallets |
19 | | - example_wallets = [ |
| 29 | + return [ |
20 | 30 | {"id": 1, "name": "Main Account", "currency": "USD", "balance": 1234.56}, |
21 | 31 | {"id": 2, "name": "Savings", "currency": "EUR", "balance": 7890.12}, |
22 | 32 | ] |
23 | | - return example_wallets |
24 | 33 |
|
| 34 | +# --- Extra authentication layer --- |
| 35 | +session_manager = StreamableHTTPSessionManager( |
| 36 | + app=mcp._mcp_server, # Use the underlying MCPServer instance |
| 37 | +) |
| 38 | + |
| 39 | +async def auth_middleware(scope, receive, send): |
| 40 | + request = Request(scope, receive) |
| 41 | + auth_header = request.headers.get("authorization") |
| 42 | + |
| 43 | + if not auth_header or not auth_header.lower().startswith("bearer "): |
| 44 | + logger.warning("Missing or invalid Authorization header.") |
| 45 | + raise HTTPException(401, "Missing or invalid Authorization header.") |
| 46 | + |
| 47 | + token = auth_header.split(" ", 1)[1] |
| 48 | + if token != ACCEPTED_TOKEN: |
| 49 | + logger.warning("Invalid or expired token.") |
| 50 | + raise HTTPException(401, "Invalid or expired token.") |
| 51 | + |
| 52 | + await session_manager.handle_request(scope, receive, send) |
| 53 | + |
| 54 | +@contextlib.asynccontextmanager |
| 55 | +async def lifespan(app: FastAPI): |
| 56 | + async with session_manager.run(): |
| 57 | + logger.info("StreamableHTTPSessionManager started.") |
| 58 | + yield |
| 59 | + logger.info("StreamableHTTPSessionManager stopped.") |
| 60 | + |
| 61 | +app = FastAPI(lifespan=lifespan) |
| 62 | +app.mount("/mcp", auth_middleware) |
| 63 | + |
| 64 | +# --- Server Execution --- |
25 | 65 | if __name__ == "__main__": |
26 | | - mcp.run(transport="streamable-http") |
| 66 | + logger.info("Starting Spendee MCP Server with Bearer Token Authentication") |
| 67 | + logger.info("Access the MCP endpoint at http://0.0.0.0:8000/mcp") |
| 68 | + uvicorn.run(app, host="0.0.0.0", port=8000) |
| 69 | + |
27 | 70 |
|
28 | 71 | # relevant URLs for learning: |
29 | 72 | # - https://modelcontextprotocol.io/specification/2025-06-18/server/tools |
|
0 commit comments