PD03 commited on
Commit
6b64b9c
Β·
verified Β·
1 Parent(s): 1575c56

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +276 -0
app.py ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import os
3
+ import json
4
+ import requests
5
+ import gradio as gr
6
+ from dotenv import load_dotenv
7
+ from fastapi import FastAPI
8
+ from mcp.server import Server
9
+ from mcp.server.http import make_http_app
10
+ from mcp.types import Tool, TextContent
11
+
12
+ # ── 1) CONFIG ───────────────────────────────────────────────────────────────
13
+
14
+ load_dotenv()
15
+ SAP_KEY = os.getenv("SAP_API_KEY")
16
+ SAP_BASE = os.getenv("SAP_API_BASE_URL", "").rstrip("/")
17
+ if not SAP_KEY or not SAP_BASE:
18
+ raise RuntimeError("Set SAP_API_KEY and SAP_API_BASE_URL in your env vars")
19
+
20
+ # ── 2) MCP SERVER SETUP ─────────────────────────────────────────────────────
21
+
22
+ mcp = Server("sap-mcp")
23
+
24
+ @mcp.list_tools()
25
+ async def list_tools():
26
+ return [
27
+ # your existing generic GET tool
28
+ Tool(
29
+ name="sap_api_get",
30
+ description="GET any SAP endpoint under your base URL",
31
+ inputSchema={
32
+ "type": "object",
33
+ "properties": {
34
+ "endpoint": {"type": "string"},
35
+ "params": {"type": "object", "additionalProperties": {"type": "string"}}
36
+ },
37
+ "required": ["endpoint"]
38
+ }
39
+ ),
40
+ # OData query–style tool
41
+ Tool(
42
+ name="sap_odata_query",
43
+ description="Generic OData query ($filter, $select, $top, …)",
44
+ inputSchema={
45
+ "type":"object",
46
+ "properties":{
47
+ "service": {"type":"string"},
48
+ "select": {"type":"string"},
49
+ "filter": {"type":"string"},
50
+ "top": {"type":"integer"},
51
+ "skip": {"type":"integer"},
52
+ "orderby": {"type":"string"}
53
+ },
54
+ "required":["service"]
55
+ }
56
+ ),
57
+ # Business partner demo tool
58
+ Tool(
59
+ name="sap_business_partner",
60
+ description="Query Business Partner data",
61
+ inputSchema={
62
+ "type":"object",
63
+ "properties":{
64
+ "partner_id": {"type":"string"},
65
+ "company_name": {"type":"string"},
66
+ "limit": {"type":"integer"}
67
+ }
68
+ }
69
+ ),
70
+ # Metadata tool
71
+ Tool(
72
+ name="sap_metadata",
73
+ description="Fetch $metadata for a given service",
74
+ inputSchema={
75
+ "type":"object",
76
+ "properties":{
77
+ "service": {"type":"string"}
78
+ }
79
+ }
80
+ ),
81
+ # ── NEW: Purchase Requisition tool
82
+ Tool(
83
+ name="purchase_requisition",
84
+ description="List or filter Purchase Requisitions (CE_PURCHASEREQUISITION_0001)",
85
+ inputSchema={
86
+ "type":"object",
87
+ "properties":{
88
+ "filter": {"type":"string"},
89
+ "select": {"type":"string"},
90
+ "top": {"type":"integer"},
91
+ "skip": {"type":"integer"},
92
+ "orderby": {"type":"string"}
93
+ }
94
+ }
95
+ ),
96
+ # ── NEW: Purchase Order tool
97
+ Tool(
98
+ name="purchase_order",
99
+ description="List or filter Purchase Orders (CE_PURCHASEORDER_0001)",
100
+ inputSchema={
101
+ "type":"object",
102
+ "properties":{
103
+ "filter": {"type":"string"},
104
+ "select": {"type":"string"},
105
+ "top": {"type":"integer"},
106
+ "skip": {"type":"integer"},
107
+ "orderby": {"type":"string"}
108
+ }
109
+ }
110
+ ),
111
+ ]
112
+
113
+ @mcp.call_tool()
114
+ async def call_tool(name: str, arguments: dict):
115
+ """
116
+ Routes the six tools above into HTTP GETs against your SAP_BASE.
117
+ """
118
+ # helper to build OData params
119
+ def odata_params(args):
120
+ p = {}
121
+ for k in ("select","filter","top","skip","orderby"):
122
+ v = args.get(k)
123
+ if v is not None:
124
+ p[f"${k}"] = str(v)
125
+ p["$format"] = "json"
126
+ return p
127
+
128
+ # 1) Generic GET
129
+ if name == "sap_api_get":
130
+ url = f"{SAP_BASE}/{arguments['endpoint']}"
131
+ resp = requests.get(url, headers={"APIKey":SAP_KEY}, params=arguments.get("params",{}))
132
+ resp.raise_for_status()
133
+ return [TextContent(type="text", text=json.dumps(resp.json(), indent=2))]
134
+
135
+ # 2) Generic OData
136
+ if name == "sap_odata_query":
137
+ url = f"{SAP_BASE}/{arguments['service']}"
138
+ resp = requests.get(
139
+ url,
140
+ headers={"APIKey":SAP_KEY},
141
+ params=odata_params(arguments)
142
+ )
143
+ resp.raise_for_status()
144
+ data = resp.json()
145
+ # unwrap OData v2/v4
146
+ if isinstance(data, dict) and "d" in data:
147
+ data = data["d"].get("results", data["d"])
148
+ return [TextContent(type="text", text=json.dumps(data, indent=2))]
149
+
150
+ # 3) Business Partner (mock/demo)
151
+ if name == "sap_business_partner":
152
+ # …your existing logic…
153
+ return [TextContent(type="text", text="(business partner demo)…")]
154
+
155
+ # 4) Metadata
156
+ if name == "sap_metadata":
157
+ svc = arguments.get("service")
158
+ url = f"{SAP_BASE}/{svc}/$metadata" if svc else f"{SAP_BASE}/$metadata"
159
+ resp = requests.get(url, headers={"APIKey":SAP_KEY})
160
+ resp.raise_for_status()
161
+ return [TextContent(type="text", text=resp.text)]
162
+
163
+ # ── NEW TOOL: Purchase Requisition
164
+ if name == "purchase_requisition":
165
+ url = f"{SAP_BASE}/PurchaseRequisition"
166
+ resp = requests.get(url, headers={"APIKey":SAP_KEY}, params=odata_params(arguments))
167
+ resp.raise_for_status()
168
+ data = resp.json().get("d", {}).get("results", resp.json())
169
+ return [TextContent(type="text", text=json.dumps(data, indent=2))]
170
+
171
+ # ── NEW TOOL: Purchase Order
172
+ if name == "purchase_order":
173
+ url = f"{SAP_BASE}/PurchaseOrder"
174
+ resp = requests.get(url, headers={"APIKey":SAP_KEY}, params=odata_params(arguments))
175
+ resp.raise_for_status()
176
+ data = resp.json().get("d", {}).get("results", resp.json())
177
+ return [TextContent(type="text", text=json.dumps(data, indent=2))]
178
+
179
+ raise ValueError(f"Unknown tool: {name}")
180
+
181
+ # wrap MCP in FastAPI under /mcp
182
+ mcp_app = make_http_app(mcp, prefix="/mcp")
183
+
184
+ # ── 3) GRADIO UI ─────────────────────────────────────────────────────────────
185
+
186
+ # list all six tools in the dropdown
187
+ TOOLS = [
188
+ "sap_api_get",
189
+ "sap_odata_query",
190
+ "sap_business_partner",
191
+ "sap_metadata",
192
+ "purchase_requisition",
193
+ "purchase_order",
194
+ ]
195
+
196
+ def execute_tool(tool, endpoint, params_json,
197
+ service, select, filter_, top, skip, orderby,
198
+ partner_id, company_name, limit):
199
+ # build args exactly as the MCP server expects
200
+ args = {}
201
+ if tool == "sap_api_get":
202
+ args["endpoint"] = endpoint
203
+ try:
204
+ args["params"] = json.loads(params_json or "{}")
205
+ except:
206
+ return "❗ Invalid JSON in params"
207
+ elif tool == "sap_odata_query":
208
+ args = {k:v for k,v in {
209
+ "service": service, "select": select, "filter": filter_,
210
+ "top": top, "skip": skip, "orderby": orderby
211
+ }.items() if v not in (None,"")}
212
+ elif tool == "sap_business_partner":
213
+ args = {k:v for k,v in {
214
+ "partner_id":partner_id, "company_name":company_name, "limit":limit
215
+ }.items() if v not in (None,"")}
216
+ elif tool == "sap_metadata":
217
+ if service: args["service"] = service
218
+ elif tool in ("purchase_requisition","purchase_order"):
219
+ args = {k:v for k,v in {
220
+ "select": select, "filter": filter_, "top": top, "skip": skip, "orderby": orderby
221
+ }.items() if v not in (None,"")}
222
+ # call your MCP HTTP endpoint
223
+ payload = {
224
+ "jsonrpc":"2.0",
225
+ "method":"call_tool",
226
+ "params":{"name":tool,"arguments":args},
227
+ "id":1
228
+ }
229
+ r = requests.post("http://localhost:7860/mcp", json=payload)
230
+ r.raise_for_status()
231
+ texts = [c["text"] for c in r.json().get("result",[]) if c.get("type")=="text"]
232
+ return "\n\n".join(texts) or "(no result)"
233
+
234
+ def toggle_visibility(tool):
235
+ # 11 widgets: endpoint,params, service,select,filter,top,skip,orderby, partner_id,company_name,limit
236
+ if tool=="sap_api_get": return [True,True] + [False]*9
237
+ if tool=="sap_odata_query": return [False,False,True] + [True]*5 + [False]*3
238
+ if tool=="sap_business_partner": return [False]*8 + [True]*3
239
+ if tool=="sap_metadata": return [False,False,True] + [False]*8
240
+ if tool in ("purchase_requisition","purchase_order"):
241
+ return [False,False,False] + [True]*5 + [False]*3
242
+ return [False]*11
243
+
244
+ with gr.Blocks() as demo:
245
+ gr.Markdown("## πŸš€ SAP MCP + CE_PR & CE_PO Demo")
246
+ tool = gr.Dropdown(TOOLS, label="Tool")
247
+ # inputs row by row
248
+ endpoint = gr.Textbox(label="Endpoint")
249
+ params_json = gr.Textbox(label="Params (JSON)")
250
+ service = gr.Textbox(label="Service")
251
+ select = gr.Textbox(label="$select")
252
+ filter_ = gr.Textbox(label="$filter")
253
+ top = gr.Number(label="$top")
254
+ skip = gr.Number(label="$skip")
255
+ orderby = gr.Textbox(label="$orderby")
256
+ partner_id = gr.Textbox(label="Business Partner ID")
257
+ company_name = gr.Textbox(label="Company Name")
258
+ limit = gr.Number(label="Limit")
259
+ run = gr.Button("πŸš€ Execute")
260
+ output = gr.Textbox(label="Result", lines=12)
261
+
262
+ tool.change(toggle_visibility, tool,
263
+ [endpoint,params_json,service,select,filter_,top,skip,orderby,
264
+ partner_id,company_name,limit])
265
+ run.click(execute_tool,
266
+ [tool,endpoint,params_json,service,select,filter_,top,skip,orderby,
267
+ partner_id,company_name,limit],
268
+ output)
269
+
270
+ # mount MCP under the same FastAPI app
271
+ demo.app.mount("/mcp", mcp_app)
272
+
273
+ # start on HF Spaces port
274
+ if __name__=="__main__":
275
+ demo.launch(server_name="0.0.0.0",
276
+ server_port=int(os.environ.get("PORT",7860)))