Skip to content

Commit f5d5d32

Browse files
committed
dockerize-and-troubleshoot-n8n-connection
1 parent 82058da commit f5d5d32

4 files changed

Lines changed: 121 additions & 30 deletions

File tree

Dockerfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# syntax=docker/dockerfile:1
2+
FROM python:3.11-slim
3+
4+
# example call to run the container:
5+
# docker run --rm -it -p 8000:8000 spendee-mcp
6+
7+
WORKDIR /app
8+
9+
# Copy requirements and install
10+
COPY requirements.txt ./
11+
RUN pip install --no-cache-dir -r requirements.txt
12+
13+
# Copy source code
14+
COPY . .
15+
16+
# Entrypoint for MCP server (mock get_wallets, ready for spendee_firestore)
17+
CMD ["python", "/app/spendee/spendee_mcp.py"]

build.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
IMAGE_NAME="spendee-mcp"
5+
REGISTRY="127.0.0.1:5555"
6+
TAG="latest"
7+
8+
NO_PUSH=0
9+
10+
while [[ $# -gt 0 ]]; do
11+
case "$1" in
12+
-t)
13+
TAG="$2"
14+
shift 2
15+
;;
16+
--no-push)
17+
NO_PUSH=1
18+
shift
19+
;;
20+
*)
21+
echo "Usage: $0 [-t tag] [--no-push]"
22+
exit 1
23+
;;
24+
esac
25+
done
26+
27+
REGISTRY_TAG="$REGISTRY/$IMAGE_NAME:$TAG"
28+
29+
cd "$(dirname "$0")"
30+
31+
echo "Building Docker image..."
32+
docker build -t "$IMAGE_NAME:$TAG" -t "$REGISTRY_TAG" .
33+
34+
if [[ "$NO_PUSH" -eq 0 ]]; then
35+
echo "Pushing Docker image to local registry..."
36+
docker push "$REGISTRY_TAG"
37+
else
38+
echo "Skipping push to local registry (--no-push specified)."
39+
fi
40+
41+
echo "Done."

inspect.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
cd "$(dirname "$0")"
4+
source .venv/bin/activate
5+
DANGEROUSLY_OMIT_AUTH=true mcp dev spendee/spendee_mcp.py

spendee/spendee_mcp.py

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,23 @@
88
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
99

1010
# to start (after .venv setup):
11-
# python spendee/spendee_mcp.py
11+
# python spendee/spendee_mcp.py
1212

1313
# to test:
14-
# DANGEROUSLY_OMIT_AUTH=true mcp dev spendee/spendee_mcp.py
14+
# mcp dev spendee/spendee_mcp.py
1515
# Then on the url: http://localhost:6274/
1616
# setup "Transport Type" to "Streamable HTTP"
1717
# and "Server URL" to "http://localhost:8000/mcp"
1818

1919
ACCEPTED_TOKEN = os.environ.get("MCP_TOKEN", "spendee-token")
20+
PORT = int(os.environ.get("MCP_PORT", 8000))
21+
DEBUG_MODE = os.environ.get("DEBUG_MODE", "") != ""
22+
DISABLE_AUTH = os.environ.get("DISABLE_AUTH", "") != ""
2023

2124
logging.basicConfig(level=logging.DEBUG)
2225
logger = logging.getLogger(__name__)
2326

24-
mcp = FastMCP("spendee")
27+
mcp = FastMCP("spendee", host="0.0.0.0", port=PORT)
2528

2629
@mcp.tool()
2730
def get_wallets():
@@ -31,41 +34,65 @@ def get_wallets():
3134
{"id": 2, "name": "Savings", "currency": "EUR", "balance": 7890.12},
3235
]
3336

34-
# --- Extra authentication layer ---
35-
session_manager = StreamableHTTPSessionManager(
36-
app=mcp._mcp_server, # Use the underlying MCPServer instance
37-
)
3837

39-
async def auth_middleware(scope, receive, send):
40-
request = Request(scope, receive)
41-
auth_header = request.headers.get("authorization")
38+
# def server_with_authentication():
39+
# session_manager = StreamableHTTPSessionManager(
40+
# app=mcp._mcp_server, # Use the underlying MCPServer instance
41+
# )
4242

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.")
43+
# async def auth_middleware(scope, receive, send):
44+
# request = Request(scope, receive)
45+
# # Log request method, url, and headers for troubleshooting
46+
# if DEBUG_MODE:
47+
# logger.debug(f"Incoming request: method={request.method}, url={request.url}")
48+
# logger.debug(f"Request headers: {dict(request.headers)}")
4649

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.")
50+
# auth_header = request.headers.get("authorization")
5151

52-
await session_manager.handle_request(scope, receive, send)
52+
# if not auth_header or not auth_header.lower().startswith("bearer "):
53+
# logger.warning("Missing or invalid Authorization header.")
54+
# raise HTTPException(401, "Missing or invalid Authorization header.")
5355

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.")
56+
# token = auth_header.split(" ", 1)[1]
57+
# if token != ACCEPTED_TOKEN:
58+
# logger.warning("Invalid or expired token.")
59+
# raise HTTPException(401, "Invalid or expired token.")
6060

61-
app = FastAPI(lifespan=lifespan)
62-
app.mount("/mcp", auth_middleware)
61+
# await session_manager.handle_request(scope, receive, send)
62+
63+
# @contextlib.asynccontextmanager
64+
# async def lifespan(app: FastAPI):
65+
# async with session_manager.run():
66+
# logger.info("StreamableHTTPSessionManager started.")
67+
# yield
68+
# logger.info("StreamableHTTPSessionManager stopped.")
69+
70+
# app = FastAPI(lifespan=lifespan)
71+
# app.mount("/mcp", auth_middleware)
72+
73+
# logger.info(f"Starting Spendee MCP Server with Bearer Token Authentication on port {PORT}")
74+
# logger.info(f"Access the MCP endpoint at http://0.0.0.0:{PORT}/mcp")
75+
# uvicorn.run(app, host="0.0.0.0", port=PORT)
76+
77+
def server_with_sse():
78+
app = FastAPI()
79+
@app.get("/")
80+
def read_root():
81+
return {"Hello": "World"}
82+
#from app.sse import create_sse_server
83+
app.mount("/", app.sse.create_sse_server(mcp))
6384

64-
# --- Server Execution ---
6585
if __name__ == "__main__":
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)
86+
logger.info("Starting Spendee MCP Server as SSE without authentication")
87+
mcp.run(transport="sse") # for n8n compatibility, authentication implemented on cloudflare level
88+
#server_with_sse()
89+
90+
# if DISABLE_AUTH:
91+
# logger.warning("Running without authentication! This is insecure and should only be used for local testing.")
92+
# #mcp.run(transport="streamable-http")
93+
# mcp.run(transport="sse")
94+
# else:
95+
# server_with_authentication()
6996

7097

7198
# relevant URLs for learning:
@@ -76,3 +103,4 @@ async def lifespan(app: FastAPI):
76103
# - https://modelcontextprotocol.io/docs/tools/debugging
77104
# - https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#session-management
78105
# - https://github.com/modelcontextprotocol/python-sdk?tab=readme-ov-file#authentication
106+
# - auth inspiration: https://github.com/zahere-dev/mcp-labs/tree/main

0 commit comments

Comments
 (0)