| 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 |
|
|
| |
| |
| |
| 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") |
|
|
| 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", |
| ) |
|
|
| |
| |
| |
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_credentials=True, |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| |
| |
| |
| 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: |
| |
| return True |
| if api_key != AGENTKIT_API_KEY: |
| raise HTTPException(status_code=401, detail="Invalid or missing API key") |
| return True |
|
|
|
|
| |
| |
| |
| @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) |
|
|
|
|
| |
| |
| |
| @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}") |
|
|
|
|
| |
| |
| |
| @app.get("/health", tags=["System"]) |
| async def health(): |
| return {"status": "ok", "message": "SAP MCP Server is running"} |
|
|
|
|
| |
| |
| |
| @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." |
| } |
|
|