Commit before I will try another thing... that might then (maybe) really really work)

This commit is contained in:
Moritz Graf 2025-10-22 21:42:00 +02:00
parent fdba242f48
commit 5251eb6185
2 changed files with 125 additions and 19 deletions

View File

@ -12,12 +12,11 @@
# # This key MUST be GARTH_TOKEN to match the application's environment variable # # This key MUST be GARTH_TOKEN to match the application's environment variable
# GARTH_TOKEN: your_base64_encoded_token_here # GARTH_TOKEN: your_base64_encoded_token_here
--- ---
# --- 2. Deployment for the Garth MCP Server --- # deployment.yaml
# This will create a Pod running the server application.
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: garth-mcp-server-deployment name: garth-mcp-server
namespace: n8n namespace: n8n
labels: labels:
app: garth-mcp-server app: garth-mcp-server
@ -33,28 +32,50 @@ spec:
spec: spec:
containers: containers:
- name: garth-mcp-server - name: garth-mcp-server
# Use a standard Python slim image as a base # Use a Python image version >= 3.13 as requested.
image: python:3.11-slim image: python:3.13-slim
# The command first installs the 'uv' tool, then uses 'uvx' to run the server. workingDir: /app
# It's configured to listen on all interfaces (0.0.0.0) inside the container. # This command now installs dependencies and directly executes the mounted script.
command: ["/bin/sh", "-c"] command: ["/bin/sh", "-c"]
args: args:
- "pip install -U garth-mcp-server && garth-mcp-server --host 0.0.0.0 --port 8080" - |
set -e
echo "----> Installing Python dependencies..."
pip install flask garth garth-mcp-server
echo "----> Dependencies installed."
echo "----> Starting Flask server from mounted script."
exec python /app/wrapper.py
ports: ports:
- name: http - containerPort: 5000
containerPort: 8080 name: http
protocol: TCP # Mount the wrapper.py script from the ConfigMap into the container.
volumeMounts:
- name: wrapper-script-volume
mountPath: /app/wrapper.py
subPath: wrapper.py
# Inject the Garmin token securely from the Kubernetes Secret.
envFrom: envFrom:
# Load environment variables from the Secret created above
- secretRef: - secretRef:
name: garth-token-secret name: garth-token-secret
resources: # Health probes for Kubernetes to manage the pod's lifecycle.
requests: livenessProbe:
memory: "64Mi" httpGet:
cpu: "100m" path: /healthz
limits: port: 5000
memory: "128Mi" initialDelaySeconds: 15
cpu: "250m" periodSeconds: 20
readinessProbe:
httpGet:
path: /healthz
port: 5000
# Increased delay to allow for dependency installation.
initialDelaySeconds: 60
periodSeconds: 10
# Define the volume that will be populated by the ConfigMap.
volumes:
- name: wrapper-script-volume
configMap:
name: garth-wrapper-script
--- ---
# --- 3. Service to expose the Deployment --- # --- 3. Service to expose the Deployment ---
# This creates a stable internal endpoint for the server. # This creates a stable internal endpoint for the server.

View File

@ -0,0 +1,85 @@
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: garth-wrapper-script
namespace: n8n
data:
wrapper.py: |
# wrapper.py
import subprocess
import logging
from flask import Flask, request, Response
# Configure basic logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# Initialize the Flask application
app = Flask(__name__)
# Define the main endpoint that n8n will call. It only accepts POST requests.
@app.route('/mcp', methods=['POST'])
def mcp_wrapper():
"""
This function receives a POST request from n8n, executes the garth-mcp-server
as a subprocess, pipes the request body to the subprocess's stdin,
captures its stdout, and returns it as the HTTP response.
"""
# Retrieve the raw binary data from the incoming request body.
request_data = request.get_data()
logging.info(f"Received request with {len(request_data)} bytes of data.")
# Define the command to execute the garth-mcp-server.
# 'uvx' is the recommended runner for this package.
command = ['uvx', 'garth-mcp-server']
try:
# Execute the command as a subprocess.
# - input: The data to be sent to the subprocess's stdin.
# - capture_output=True: Captures stdout and stderr.
# - check=True: Raises a CalledProcessError if the command returns a non-zero exit code.
# - timeout: Sets a 60-second timeout to prevent hanging processes.
result = subprocess.run(
command,
input=request_data,
capture_output=True,
check=True,
timeout=60
)
# If the command was successful, log the success and return the captured stdout.
# The content type is set to text/plain to ensure proper handling by clients.
logging.info(f"Subprocess executed successfully. Returning {len(result.stdout)} bytes of stdout.")
return Response(result.stdout, mimetype='text/plain', status=200)
except subprocess.CalledProcessError as e:
# If the subprocess returns a non-zero exit code, it indicates an error.
# Log the error, including the captured stderr for debugging.
error_message = e.stderr.decode('utf-8', errors='ignore')
logging.error(f"Subprocess failed with exit code {e.returncode}. Stderr: {error_message}")
return Response(f"Error executing garth-mcp-server: {error_message}", mimetype='text/plain', status=500)
except subprocess.TimeoutExpired:
# If the subprocess takes longer than the specified timeout.
logging.error("Subprocess timed out after 60 seconds.")
return Response("Error: garth-mcp-server process timed out.", mimetype='text/plain', status=504)
except Exception as e:
# Catch any other unexpected exceptions.
logging.error(f"An unexpected error occurred: {str(e)}")
return Response(f"An unexpected server error occurred: {str(e)}", mimetype='text/plain', status=500)
# Define a simple health check endpoint for Kubernetes liveness and readiness probes.
@app.route('/healthz', methods=['GET'])
def health_check():
"""
A simple endpoint that returns a 200 OK response, indicating the
Flask server is running and responsive.
"""
return Response("OK", status=200)
# Main execution block to run the Flask development server.
# host='0.0.0.0' makes the server accessible from outside the container.
# port=5000 is the port the server will listen on.
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)