# 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)