Update app.py
Browse files
app.py
CHANGED
|
@@ -3,21 +3,34 @@ import requests
|
|
| 3 |
from fastapi import FastAPI, HTTPException, Depends
|
| 4 |
from fastapi.responses import JSONResponse
|
| 5 |
from fastapi.security.api_key import APIKeyHeader
|
|
|
|
| 6 |
|
| 7 |
# -------------------------------------------------
|
| 8 |
# CONFIGURATION
|
| 9 |
# -------------------------------------------------
|
| 10 |
-
HUGGINGFACE_BACKEND = os.getenv(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
AGENTKIT_API_KEY = os.getenv("AGENTKIT_API_KEY", None)
|
| 12 |
-
BASE_URL = os.getenv("PUBLIC_URL", "https
|
| 13 |
|
| 14 |
app = FastAPI(
|
| 15 |
title="SAP MCP Server",
|
| 16 |
-
description=
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
)
|
| 22 |
|
| 23 |
# -------------------------------------------------
|
|
@@ -28,25 +41,23 @@ api_key_header = APIKeyHeader(name="x-agentkit-api-key", auto_error=False)
|
|
| 28 |
def verify_api_key(api_key: str = Depends(api_key_header)):
|
| 29 |
"""Verifies the x-agentkit-api-key header, if configured."""
|
| 30 |
if AGENTKIT_API_KEY is None:
|
| 31 |
-
#
|
| 32 |
return True
|
| 33 |
if api_key != AGENTKIT_API_KEY:
|
| 34 |
raise HTTPException(status_code=401, detail="Invalid or missing API key")
|
| 35 |
return True
|
| 36 |
|
|
|
|
| 37 |
# -------------------------------------------------
|
| 38 |
-
# MCP MANIFEST (AgentKit autodiscovery)
|
| 39 |
# -------------------------------------------------
|
| 40 |
@app.get("/.well-known/mcp/manifest.json", include_in_schema=False)
|
| 41 |
async def get_manifest():
|
| 42 |
-
"""
|
| 43 |
-
Returns the MCP manifest that AgentKit uses to discover tools.
|
| 44 |
-
The manifest defines auth and the tool's absolute URL.
|
| 45 |
-
"""
|
| 46 |
manifest = {
|
| 47 |
"name": "sap_mcp_server",
|
| 48 |
-
"description": "MCP server exposing a tool for retrieving SAP purchase orders.",
|
| 49 |
-
"version": "
|
| 50 |
"auth": {
|
| 51 |
"type": "api_key",
|
| 52 |
"location": "header",
|
|
@@ -56,36 +67,53 @@ async def get_manifest():
|
|
| 56 |
"tools": [
|
| 57 |
{
|
| 58 |
"name": "get_purchase_orders",
|
| 59 |
-
"description":
|
| 60 |
-
"Fetches the top 50 purchase orders from the SAP Sandbox API "
|
| 61 |
-
"via the Hugging Face backend."
|
| 62 |
-
),
|
| 63 |
"input_schema": {"type": "object", "properties": {}},
|
| 64 |
"output_schema": {"type": "object"},
|
| 65 |
-
"http": {
|
| 66 |
-
"method": "GET",
|
| 67 |
-
"url": f"{BASE_URL}/tools/get_purchase_orders"
|
| 68 |
-
}
|
| 69 |
}
|
| 70 |
]
|
| 71 |
}
|
| 72 |
return JSONResponse(content=manifest)
|
| 73 |
|
|
|
|
| 74 |
# -------------------------------------------------
|
| 75 |
-
# TOOL
|
| 76 |
# -------------------------------------------------
|
| 77 |
@app.get("/tools/get_purchase_orders", tags=["MCP Tools"])
|
| 78 |
async def get_purchase_orders(auth=Depends(verify_api_key)):
|
| 79 |
"""
|
| 80 |
-
|
| 81 |
-
Requires
|
| 82 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
try:
|
| 84 |
-
|
|
|
|
| 85 |
resp.raise_for_status()
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
# -------------------------------------------------
|
| 91 |
# HEALTH CHECK
|
|
@@ -93,3 +121,19 @@ async def get_purchase_orders(auth=Depends(verify_api_key)):
|
|
| 93 |
@app.get("/health", tags=["System"])
|
| 94 |
async def health():
|
| 95 |
return {"status": "ok", "message": "SAP MCP Server is running"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
from fastapi import FastAPI, HTTPException, Depends
|
| 4 |
from fastapi.responses import JSONResponse
|
| 5 |
from fastapi.security.api_key import APIKeyHeader
|
| 6 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 7 |
|
| 8 |
# -------------------------------------------------
|
| 9 |
# CONFIGURATION
|
| 10 |
# -------------------------------------------------
|
| 11 |
+
HUGGINGFACE_BACKEND = os.getenv(
|
| 12 |
+
"SAP_BACKEND_URL",
|
| 13 |
+
"https://sandbox.api.sap.com/s4hanacloud/sap/opu/odata4/"
|
| 14 |
+
"sap/api_purchaseorder_2/srvd_a2x/sap/purchaseorder/0001/PurchaseOrder?$top=10"
|
| 15 |
+
)
|
| 16 |
AGENTKIT_API_KEY = os.getenv("AGENTKIT_API_KEY", None)
|
| 17 |
+
BASE_URL = os.getenv("PUBLIC_URL", "https://pd03-agentkit.hf.space") # your Space URL
|
| 18 |
|
| 19 |
app = FastAPI(
|
| 20 |
title="SAP MCP Server",
|
| 21 |
+
description="MCP-compatible FastAPI server exposing live SAP Purchase Orders for demo and AgentKit integration.",
|
| 22 |
+
version="2.0.0",
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
# -------------------------------------------------
|
| 26 |
+
# CORS MIDDLEWARE (needed for Google AI Studio / front-end calls)
|
| 27 |
+
# -------------------------------------------------
|
| 28 |
+
app.add_middleware(
|
| 29 |
+
CORSMiddleware,
|
| 30 |
+
allow_origins=["*"], # for demo; restrict later if desired
|
| 31 |
+
allow_credentials=True,
|
| 32 |
+
allow_methods=["*"],
|
| 33 |
+
allow_headers=["*"],
|
| 34 |
)
|
| 35 |
|
| 36 |
# -------------------------------------------------
|
|
|
|
| 41 |
def verify_api_key(api_key: str = Depends(api_key_header)):
|
| 42 |
"""Verifies the x-agentkit-api-key header, if configured."""
|
| 43 |
if AGENTKIT_API_KEY is None:
|
| 44 |
+
# open mode
|
| 45 |
return True
|
| 46 |
if api_key != AGENTKIT_API_KEY:
|
| 47 |
raise HTTPException(status_code=401, detail="Invalid or missing API key")
|
| 48 |
return True
|
| 49 |
|
| 50 |
+
|
| 51 |
# -------------------------------------------------
|
| 52 |
+
# MCP MANIFEST (for AgentKit autodiscovery)
|
| 53 |
# -------------------------------------------------
|
| 54 |
@app.get("/.well-known/mcp/manifest.json", include_in_schema=False)
|
| 55 |
async def get_manifest():
|
| 56 |
+
"""Manifest describing this MCP server and its tools."""
|
|
|
|
|
|
|
|
|
|
| 57 |
manifest = {
|
| 58 |
"name": "sap_mcp_server",
|
| 59 |
+
"description": "MCP server exposing a tool for retrieving SAP purchase orders from the SAP Sandbox API.",
|
| 60 |
+
"version": "2.0.0",
|
| 61 |
"auth": {
|
| 62 |
"type": "api_key",
|
| 63 |
"location": "header",
|
|
|
|
| 67 |
"tools": [
|
| 68 |
{
|
| 69 |
"name": "get_purchase_orders",
|
| 70 |
+
"description": "Fetches the top 10 purchase orders from the SAP Sandbox API.",
|
|
|
|
|
|
|
|
|
|
| 71 |
"input_schema": {"type": "object", "properties": {}},
|
| 72 |
"output_schema": {"type": "object"},
|
| 73 |
+
"http": {"method": "GET", "url": f"{BASE_URL}/tools/get_purchase_orders"}
|
|
|
|
|
|
|
|
|
|
| 74 |
}
|
| 75 |
]
|
| 76 |
}
|
| 77 |
return JSONResponse(content=manifest)
|
| 78 |
|
| 79 |
+
|
| 80 |
# -------------------------------------------------
|
| 81 |
+
# TOOL: Fetch Purchase Orders
|
| 82 |
# -------------------------------------------------
|
| 83 |
@app.get("/tools/get_purchase_orders", tags=["MCP Tools"])
|
| 84 |
async def get_purchase_orders(auth=Depends(verify_api_key)):
|
| 85 |
"""
|
| 86 |
+
Fetch the top purchase orders from SAP Sandbox API.
|
| 87 |
+
Requires SAP_API_KEY secret and a valid SAP_BACKEND_URL.
|
| 88 |
"""
|
| 89 |
+
sap_api_key = os.getenv("SAP_API_KEY")
|
| 90 |
+
if not sap_api_key:
|
| 91 |
+
raise HTTPException(status_code=500, detail="SAP_API_KEY not set in environment")
|
| 92 |
+
|
| 93 |
+
headers = {"APIKey": sap_api_key}
|
| 94 |
+
|
| 95 |
try:
|
| 96 |
+
print(f"π‘ Calling SAP Sandbox: {HUGGINGFACE_BACKEND}")
|
| 97 |
+
resp = requests.get(HUGGINGFACE_BACKEND, headers=headers, timeout=60)
|
| 98 |
resp.raise_for_status()
|
| 99 |
+
data = resp.json()
|
| 100 |
+
|
| 101 |
+
records = data.get("value", [])
|
| 102 |
+
print(f"β
SAP API returned {len(records)} records")
|
| 103 |
+
|
| 104 |
+
return {
|
| 105 |
+
"source": "SAP Sandbox",
|
| 106 |
+
"count": len(records),
|
| 107 |
+
"data": records
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
except requests.exceptions.HTTPError as e:
|
| 111 |
+
print(f"β SAP API HTTP error: {e}")
|
| 112 |
+
raise HTTPException(status_code=resp.status_code, detail=f"SAP API error: {e.response.text}")
|
| 113 |
+
except Exception as e:
|
| 114 |
+
print(f"β SAP API general error: {e}")
|
| 115 |
+
raise HTTPException(status_code=500, detail=f"Failed to call SAP API: {e}")
|
| 116 |
+
|
| 117 |
|
| 118 |
# -------------------------------------------------
|
| 119 |
# HEALTH CHECK
|
|
|
|
| 121 |
@app.get("/health", tags=["System"])
|
| 122 |
async def health():
|
| 123 |
return {"status": "ok", "message": "SAP MCP Server is running"}
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
# -------------------------------------------------
|
| 127 |
+
# ROOT (friendly landing page)
|
| 128 |
+
# -------------------------------------------------
|
| 129 |
+
@app.get("/", tags=["Root"])
|
| 130 |
+
async def root():
|
| 131 |
+
return {
|
| 132 |
+
"message": "π Welcome to the SAP MCP Server",
|
| 133 |
+
"available_endpoints": {
|
| 134 |
+
"health": "/health",
|
| 135 |
+
"manifest": "/.well-known/mcp/manifest.json",
|
| 136 |
+
"purchase_orders": "/tools/get_purchase_orders"
|
| 137 |
+
},
|
| 138 |
+
"instructions": "Use /tools/get_purchase_orders with header x-agentkit-api-key to fetch live SAP sandbox data."
|
| 139 |
+
}
|