import os import requests from fastapi import FastAPI, HTTPException, Depends from fastapi.responses import JSONResponse from fastapi.security.api_key import APIKeyHeader from fastapi.middleware.cors import CORSMiddleware # ------------------------------------------------- # CONFIGURATION # ------------------------------------------------- HUGGINGFACE_BACKEND = os.getenv( "SAP_BACKEND_URL", "https://sandbox.api.sap.com/s4hanacloud/sap/opu/odata4/" "sap/api_purchaseorder_2/srvd_a2x/sap/purchaseorder/0001/PurchaseOrder?$top=10" ) AGENTKIT_API_KEY = os.getenv("AGENTKIT_API_KEY", None) BASE_URL = os.getenv("PUBLIC_URL", "https://pd03-agentkit.hf.space") # your Space URL app = FastAPI( title="SAP MCP Server", description="MCP-compatible FastAPI server exposing live SAP Purchase Orders for demo and AgentKit integration.", version="2.0.0", ) # ------------------------------------------------- # CORS MIDDLEWARE (needed for Google AI Studio / front-end calls) # ------------------------------------------------- app.add_middleware( CORSMiddleware, allow_origins=["*"], # for demo; restrict later if desired allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ------------------------------------------------- # AUTHENTICATION # ------------------------------------------------- api_key_header = APIKeyHeader(name="x-agentkit-api-key", auto_error=False) def verify_api_key(api_key: str = Depends(api_key_header)): """Verifies the x-agentkit-api-key header, if configured.""" if AGENTKIT_API_KEY is None: # open mode return True if api_key != AGENTKIT_API_KEY: raise HTTPException(status_code=401, detail="Invalid or missing API key") return True # ------------------------------------------------- # MCP MANIFEST (for AgentKit autodiscovery) # ------------------------------------------------- @app.get("/.well-known/mcp/manifest.json", include_in_schema=False) async def get_manifest(): """Manifest describing this MCP server and its tools.""" manifest = { "name": "sap_mcp_server", "description": "MCP server exposing a tool for retrieving SAP purchase orders from the SAP Sandbox API.", "version": "2.0.0", "auth": { "type": "api_key", "location": "header", "header_name": "x-agentkit-api-key", "description": "Custom header used to authenticate MCP requests." }, "tools": [ { "name": "get_purchase_orders", "description": "Fetches the top 10 purchase orders from the SAP Sandbox API.", "input_schema": {"type": "object", "properties": {}}, "output_schema": {"type": "object"}, "http": {"method": "GET", "url": f"{BASE_URL}/tools/get_purchase_orders"} } ] } return JSONResponse(content=manifest) # ------------------------------------------------- # TOOL: Fetch Purchase Orders # ------------------------------------------------- @app.get("/tools/get_purchase_orders", tags=["MCP Tools"]) async def get_purchase_orders(auth=Depends(verify_api_key)): """ Fetch the top purchase orders from SAP Sandbox API. Requires SAP_API_KEY secret and a valid SAP_BACKEND_URL. """ sap_api_key = os.getenv("SAP_API_KEY") if not sap_api_key: raise HTTPException(status_code=500, detail="SAP_API_KEY not set in environment") headers = {"APIKey": sap_api_key} try: print(f"📡 Calling SAP Sandbox: {HUGGINGFACE_BACKEND}") resp = requests.get(HUGGINGFACE_BACKEND, headers=headers, timeout=60) resp.raise_for_status() data = resp.json() records = data.get("value", []) print(f"✅ SAP API returned {len(records)} records") return { "source": "SAP Sandbox", "count": len(records), "data": records } except requests.exceptions.HTTPError as e: print(f"❌ SAP API HTTP error: {e}") raise HTTPException(status_code=resp.status_code, detail=f"SAP API error: {e.response.text}") except Exception as e: print(f"❌ SAP API general error: {e}") raise HTTPException(status_code=500, detail=f"Failed to call SAP API: {e}") # ------------------------------------------------- # HEALTH CHECK # ------------------------------------------------- @app.get("/health", tags=["System"]) async def health(): return {"status": "ok", "message": "SAP MCP Server is running"} # ------------------------------------------------- # ROOT (friendly landing page) # ------------------------------------------------- @app.get("/", tags=["Root"]) async def root(): return { "message": "👋 Welcome to the SAP MCP Server", "available_endpoints": { "health": "/health", "manifest": "/.well-known/mcp/manifest.json", "purchase_orders": "/tools/get_purchase_orders" }, "instructions": "Use /tools/get_purchase_orders with header x-agentkit-api-key to fetch live SAP sandbox data." }