Super-MCP

Super-MCP

Dynamically generates MCP tools from any OData or OpenAPI spec, enabling natural-language queries via SAP AI Core.

Category
Visit Server

README

Super-MCP

OData + OpenAPI MCP Server

Dynamically generates tools from any OData $metadata or OpenAPI / Swagger spec and lets you query them with natural language via SAP AI Core (GPT-4o).

  • Load once via /generate — tools persist to disk and auto-load on every restart
  • spec_id is always optional — auto-detected when only one spec is loaded
  • Credentials live only in .env on the server — never passed through API requests or seen by the LLM
  • Full RBAC (read-only default, admin required for writes)

Two ways to use it:

Mode Entry point Best for
REST API api.py curl, Postman, programmatic access; docs at /docs
MCP server mcp_server.py Claude Desktop, Claude Code, any MCP client

Project Structure

mcp/
├── .env                    # Credentials — SAP AI Core + per-spec API keys (never commit)
├── .env.example            # Template — copy to .env and fill in values
├── requirements.txt
│
├── api.py                  # FastAPI REST server — primary entry point
├── mcp_server.py           # FastMCP server for Claude Desktop / Claude Code
│
├── odata_parser.py         # Parses OData v2/v4 $metadata XML
├── odata_executor.py       # Executes OData HTTP calls (GET/POST/PATCH/DELETE + CSRF)
├── openapi_parser.py       # Parses OpenAPI 3.x and Swagger 2.0 (JSON or YAML)
├── openapi_executor.py     # Executes HTTP calls for OpenAPI operations + 401-retry
├── auth_manager.py         # Reads credentials from env vars; fetches/refreshes tokens
├── rbac_manager.py         # Role-based access control (default = read-only, admin = writes)
├── tool_generator.py       # Writes tools/{spec_id}.py from a parsed spec
├── sap_ai_client.py        # SAP AI Core OAuth2 + GPT-4o client
├── config.py               # Loads SAP AI Core config from .env
├── state.py                # In-memory spec / tool registry (shared by api.py + mcp_server.py)
│
└── tools/                  # Auto-generated tool files (auto-loaded on startup)
    └── .gitkeep

Quick Setup

cd mcp
pip install -r requirements.txt
cp .env .env.example       # keep a credential-free template for the repo

# Start the REST API
python3 api.py
# → http://localhost:8080
# → Swagger UI: http://localhost:8080/docs

REST API Endpoints

OData

Method Endpoint Description
POST /api/v1/generate Fetch OData $metadata URL → parse → save tools/{id}.py
POST /api/v1/query Natural-language query against a loaded OData spec

OpenAPI / Swagger

Method Endpoint Description
POST /api/v1/generate/openapi Fetch OpenAPI/Swagger spec → parse → save tools/{id}.py
POST /api/v1/query/openapi Natural-language query against a loaded OpenAPI spec

Unified

Method Endpoint Description
POST /api/v1/ask Query anything — auto-detects spec type; spec_id always optional
POST /api/v1/call/{tool_name} Call a specific tool directly by name with explicit params

Auth

Method Endpoint Description
POST /api/v1/detect-auth Probe a URL without credentials — identify what auth is needed
GET /api/v1/auth-status/{spec_id} Show whether credentials are configured (values never returned)
POST /api/v1/auth/invalidate/{spec_id} Force token refresh on the next call

Management

Method Endpoint Description
GET /api/v1/specs List all loaded specs with entity/operation counts
GET /api/v1/files List all generated tool files on disk
DELETE /api/v1/files/{spec_id} Delete a tool file from disk
GET /health Health check — uptime and loaded spec count

OData Quick Start

1. Load a spec

# Northwind v2 (public demo)
curl -s -X POST http://localhost:8080/api/v1/generate \
  -H "Content-Type: application/json" \
  -d '{"source": "https://services.odata.org/V2/Northwind/Northwind.svc/$metadata",
       "spec_id": "nw"}' | jq

# TripPin v4 (public demo)
curl -s -X POST http://localhost:8080/api/v1/generate \
  -H "Content-Type: application/json" \
  -d '{"source": "https://services.odata.org/TripPinRESTierService/$metadata",
       "spec_id": "trip"}' | jq

# With GPT-4o enriched docstrings (~30s)
curl -s -X POST http://localhost:8080/api/v1/generate \
  -H "Content-Type: application/json" \
  -d '{"source": "https://services.odata.org/TripPinRESTierService/$metadata",
       "spec_id": "trip",
       "use_ai_descriptions": true}' | jq

2. Query in natural language

# spec_id auto-detected when only one spec is loaded
curl -s -X POST http://localhost:8080/api/v1/query \
  -H "Content-Type: application/json" \
  -d '{"query": "Get top 5 most expensive products"}' | jq

# explicit spec_id (required when multiple specs are loaded)
curl -s -X POST http://localhost:8080/api/v1/query \
  -H "Content-Type: application/json" \
  -d '{"spec_id": "trip", "query": "Get person with username russellwhyte"}' | jq

# cap the number of records
curl -s -X POST http://localhost:8080/api/v1/query \
  -H "Content-Type: application/json" \
  -d '{"query": "List airports", "max_records": 5}' | jq

3. Call tools directly

Tool names follow the pattern {spec_id}__{EntitySet}__{operation}:

# filter + sort + field selection
curl -s -X POST http://localhost:8080/api/v1/call/nw__Products__list \
  -H "Content-Type: application/json" \
  -d '{"top": 5, "orderby": "UnitPrice desc",
       "select": "ProductID,ProductName,UnitPrice"}' | jq

# single record by key
curl -s -X POST http://localhost:8080/api/v1/call/nw__Products__get \
  -H "Content-Type: application/json" \
  -d '{"ProductID": 1}' | jq

OpenAPI / Swagger Quick Start

1. Load a spec

# Swagger 2.0
curl -s -X POST http://localhost:8080/api/v1/generate/openapi \
  -H "Content-Type: application/json" \
  -d '{"source": "https://petstore.swagger.io/v2/swagger.json",
       "spec_id": "petstore"}' | jq

# OpenAPI 3.x
curl -s -X POST http://localhost:8080/api/v1/generate/openapi \
  -H "Content-Type: application/json" \
  -d '{"source": "https://petstore3.swagger.io/api/v3/openapi.json",
       "spec_id": "ps3",
       "base_url": "https://petstore3.swagger.io/api/v3"}' | jq

For authenticated services — see the Auth section below. Credentials go in .env, not in the request body.

2. Query

# auto-detected spec
curl -s -X POST http://localhost:8080/api/v1/query/openapi \
  -H "Content-Type: application/json" \
  -d '{"query": "Get all available pets"}' | jq

# unified endpoint (works for both OData and OpenAPI)
curl -s -X POST http://localhost:8080/api/v1/ask \
  -H "Content-Type: application/json" \
  -d '{"query": "Find pets with status sold"}' | jq

3. Call tools directly

Tool names follow {spec_id}__{operationId}:

curl -s -X POST http://localhost:8080/api/v1/call/petstore__findPetsByStatus \
  -H "Content-Type: application/json" \
  -d '{"status": "available"}' | jq

curl -s -X POST http://localhost:8080/api/v1/call/petstore__getPetById \
  -H "Content-Type: application/json" \
  -d '{"petId": 1}' | jq

Auth — Secure Credential Handling

Credentials are stored only in .env on the server. The LLM and API callers never provide or see credentials. Tokens are fetched and refreshed automatically at runtime.

Supported auth types

Type .env value Description
SAP XSUAA / OAuth2 xsuaa or oauth2 Client-credentials flow; tokens auto-refreshed
Static bearer token bearer_static Fixed JWT/token — no refresh
HTTP Basic basic Username + password → Base64 Authorization header
API key (header) apikey Key injected into a request header
API key (query) apikey Key appended as a query parameter
API key (cookie) apikey Key sent as a cookie
None none Public APIs — no auth

Step 1 — Detect what auth is required

curl -s -X POST http://localhost:8080/api/v1/detect-auth \
  -H "Content-Type: application/json" \
  -d '{"url": "https://yourapp.cfapps.eu10.hana.ondemand.com",
       "spec_id": "myservice"}' | jq

The response shows the detected auth type and the exact env-var names to set:

{
  "auth_required": true,
  "auth_type": "Bearer (SAP XSUAA)",
  "is_xsuaa": true,
  "configured": false,
  "env_vars_to_set": {
    "MYSERVICE_AUTH_TYPE":   "xsuaa",
    "MYSERVICE_AUTH_URL":    "<url from XSUAA service credentials>",
    "MYSERVICE_CLIENT_ID":   "<clientid>",
    "MYSERVICE_CLIENT_SECRET": "<clientsecret>"
  },
  "how_to_get_creds": "BTP Cockpit → Space → Service Instances → XSUAA → View Credentials"
}

Step 2 — Add credentials to .env

The naming convention is {SPEC_ID_UPPERCASE}_{VAR}. Examples:

# ── SAP BTP / XSUAA (OAuth2 client_credentials) ────────────────────────────
MYSERVICE_AUTH_TYPE=xsuaa
MYSERVICE_AUTH_URL=https://<subaccount>.authentication.eu10.hana.ondemand.com
MYSERVICE_CLIENT_ID=sb-app!t1234
MYSERVICE_CLIENT_SECRET=xxxxxxxxxxxxxxxx

# Optionally override the token endpoint (default: AUTH_URL/oauth/token)
MYSERVICE_TOKEN_URL=https://<subaccount>.authentication.eu10.hana.ondemand.com/oauth/token

# ── Static bearer token ─────────────────────────────────────────────────────
REPORTAPI_AUTH_TYPE=bearer_static
REPORTAPI_BEARER_TOKEN=eyJhbGciOiJSUzI1NiJ9...

# ── HTTP Basic auth ─────────────────────────────────────────────────────────
LEGACYAPI_AUTH_TYPE=basic
LEGACYAPI_USERNAME=admin
LEGACYAPI_PASSWORD=secret

# ── API key in a header ─────────────────────────────────────────────────────
WEATHERAPI_AUTH_TYPE=apikey
WEATHERAPI_API_KEY=abc123xyz
WEATHERAPI_API_KEY_NAME=X-API-Key
WEATHERAPI_API_KEY_IN=header       # header | query | cookie

# ── API key as query param ──────────────────────────────────────────────────
MAPSAPI_AUTH_TYPE=apikey
MAPSAPI_API_KEY=mymapskey
MAPSAPI_API_KEY_NAME=key
MAPSAPI_API_KEY_IN=query

Restart api.py — it loads .env on startup. For the MCP server, set these in .env the same way.

Step 3 — Load the spec (no credentials needed in the request)

curl -s -X POST http://localhost:8080/api/v1/generate/openapi \
  -H "Content-Type: application/json" \
  -d '{"source": "https://yourapp.cfapps.eu10.hana.ondemand.com/api/openapi.json",
       "spec_id": "myservice"}' | jq

The server reads MYSERVICE_AUTH_TYPE, fetches an XSUAA token, and stores it internally. No credentials in the request.

Step 4 — Query normally

curl -s -X POST http://localhost:8080/api/v1/ask \
  -H "Content-Type: application/json" \
  -d '{"query": "Get all open orders"}' | jq

Before every outgoing API call the executor checks whether the cached token is still valid (60-second buffer). If expired it silently fetches a new one and retries. On an unexpected 401 it invalidates the cache and retries once.

Auth management

# See configuration status — credential values are never returned
curl -s http://localhost:8080/api/v1/auth-status/myservice | jq
# → {"configured": true, "auth_type": "xsuaa",
#    "token_status": {"cached": true, "expires_in_seconds": 43140}}

# Force token refresh before the next call
curl -s -X POST http://localhost:8080/api/v1/auth/invalidate/myservice | jq

RBAC — Role-Based Access Control

Every operation is gated by role. The default role is read-only — it can only call list and get (OData) and GET/HEAD (HTTP). Write operations (create, update, delete / POST, PUT, PATCH, DELETE) require the admin role.

Configuring roles (environment variables)

# ── Option 1: single-role for the whole server instance ────────────────────
MCP_USER_ROLE=admin           # everyone on this instance is admin

# ── Option 2: tie role to the server's email identity ──────────────────────
MCP_USER_EMAIL=alice@acme.com
RBAC_EMAIL_ROLES=alice@acme.com:admin,bob@acme.com:default

# ── Option 3: grant a role to an entire domain ─────────────────────────────
RBAC_DOMAIN_ROLES=acme.com:admin,partner.com:default

# ── Option 4: opaque API keys (HTTP / SSE multi-user transport) ─────────────
RBAC_USERS=secret_adminkey123:admin,readonly_xyz:default

Resolution order (first match wins): MCP_USER_ROLE → email exact match → email domain → X-API-Key header → fallback default.

Checking your role (MCP tool)

whoami           → shows role, permitted operations, and RBAC config

Error response when role is insufficient

{
  "error": "This operation requires the 'admin' role. Current role: 'default'. Set MCP_USER_ROLE=admin or add your email to RBAC_EMAIL_ROLES.",
  "required_role": "admin"
}

Persistence — Auto-load on Restart

Every /generate call writes tools/{spec_id}.py that embeds the full spec as base64. On the next startup:

server starts
 └─ scans tools/*.py
 └─ decodes embedded spec (no network call needed)
 └─ restores all in-memory state (specs, tools)
 └─ server is query-ready immediately

Auth credentials are not stored in tool files. They are re-read from .env on every execute call.


MCP Server — Claude Desktop / Claude Code

Start

# stdio (Claude Desktop / Claude Code)
python3 mcp_server.py

# SSE transport on a custom port
python3 mcp_server.py --sse 8001
# → http://localhost:8001/sse

# Streamable-HTTP transport
python3 mcp_server.py --http 8002
# → http://localhost:8002/mcp

Claude Desktop config (~/.claude/claude_desktop_config.json)

{
  "mcpServers": {
    "odata-mcp": {
      "command": "python3",
      "args": ["/home/user/projects/mcp/mcp_server.py"]
    }
  }
}

Static MCP tools (always available)

Tool Description
load_odata_spec Load an OData $metadata URL and register CRUD tools
load_openapi_spec Load an OpenAPI/Swagger spec and register operation tools
smart_query Natural-language query over any loaded spec
generate_tool_file Persist the current spec to tools/{spec_id}.py
list_generated_tools List all dynamically registered tools
list_tool_files List tool files on disk
get_ai_insights Ask GPT-4o a question about a loaded spec
generate_odata_query Convert natural language to an OData REST call
test_ai_connection Ping SAP AI Core / GPT-4o
whoami Show current role and permitted operations

How It Works

POST /api/v1/generate  (or load_odata_spec in MCP)
  └─ fetch spec from URL (follows redirects)
  └─ parse → EntitySets / operations, parameters, keys, security schemes
  └─ register tools in memory
  └─ write tools/{spec_id}.py (embedded spec as base64)

POST /api/v1/ask  (or smart_query in MCP)
  └─ resolve spec_id
  │   ├─ single spec loaded → use it automatically
  │   └─ multiple specs loaded → GPT-4o picks the right one
  └─ build compact spec summary (entity names, keys, fields)
  └─ GPT-4o returns JSON: { entity_set, operation, args, explanation }
  └─ RBAC check (write ops require admin role)
  └─ executor called with resolved args

OpenAPI executor
  └─ auth_manager.get_auth_headers(spec_id)
  │    └─ reads {SPEC_ID}_AUTH_TYPE from env
  │    └─ for xsuaa/oauth2: fetch token (client_credentials)
  │    └─ cache token with 60-second expiry buffer
  │    └─ return { Authorization: "Bearer <token>" }
  └─ route args by location: path / query / header / body / cookie
  └─ build URL: base_url + path (with path-param substitution)
  └─ HTTP call
  └─ on 401: invalidate cache → refresh token → retry once

OData executor
  └─ build OData URL: base_url/EntitySet(key)?$filter=...&$top=...
  └─ for write ops: fetch X-CSRF-Token first (SAP OData v2 requirement)
  └─ POST/PATCH/DELETE with CSRF token + cookies
  └─ return parsed JSON

Tool naming convention

OData    → {spec_id}__{EntitySet}__{operation}
           nw__Products__list     nw__Products__get
           nw__Products__create   nw__Products__update   nw__Products__delete

OpenAPI  → {spec_id}__{operationId}
           petstore__findPetsByStatus   petstore__getPetById   petstore__addPet

SAP AI Core Configuration

SAP_AI_API_URL=https://api.ai.prod.eu-central-1.aws.ml.hana.ondemand.com
SAP_AI_CLIENT_ID=<client_id from AI Core service binding>
SAP_AI_CLIENT_SECRET=<client_secret>
SAP_AI_AUTH_URL=https://<subaccount>.authentication.eu10.hana.ondemand.com
SAP_AI_RESOURCE_GROUP=default
SAP_AI_MODEL_NAME=gpt-4o

# Pin to a specific deployment (skips auto-discovery at startup)
SAP_AI_DEPLOYMENT_ID=d5c7fe212eec831c

The client auto-discovers the right deployment ID from /v2/lm/deployments if SAP_AI_DEPLOYMENT_ID is not set. It prefers gpt-4ogpt-4.1gpt-4 → first RUNNING deployment.


Environment Variables — Full Reference

SAP AI Core

Variable Description
SAP_AI_API_URL AI Core API base URL
SAP_AI_CLIENT_ID OAuth2 client ID
SAP_AI_CLIENT_SECRET OAuth2 client secret
SAP_AI_AUTH_URL XSUAA auth URL (without /oauth/token)
SAP_AI_RESOURCE_GROUP AI Core resource group (default: default)
SAP_AI_MODEL_NAME Model name (default: gpt-4o)
SAP_AI_DEPLOYMENT_ID Pin to a specific deployment ID

RBAC

Variable Description
MCP_USER_ROLE admin or default — server-wide role override
MCP_USER_EMAIL Email to look up in RBAC_EMAIL_ROLES
RBAC_EMAIL_ROLES email:role,email:role,... — exact email → role
RBAC_DOMAIN_ROLES domain:role,... — domain-level catch-all
RBAC_USERS apikey:role,... — opaque API keys for HTTP transport

Per-spec auth ({PREFIX} = spec_id uppercased, hyphens → underscores)

Variable Description
{PREFIX}_AUTH_TYPE xsuaa / oauth2 / basic / apikey / bearer_static / none
{PREFIX}_AUTH_URL XSUAA / OAuth2 auth URL
{PREFIX}_CLIENT_ID OAuth2 client ID
{PREFIX}_CLIENT_SECRET OAuth2 client secret
{PREFIX}_GRANT_TYPE OAuth2 grant type (default: client_credentials)
{PREFIX}_TOKEN_URL Override the token endpoint
{PREFIX}_BEARER_TOKEN Static bearer token
{PREFIX}_USERNAME Basic auth username
{PREFIX}_PASSWORD Basic auth password
{PREFIX}_API_KEY API key value
{PREFIX}_API_KEY_NAME API key header/param name (default: X-API-Key)
{PREFIX}_API_KEY_IN Where to send the key: header / query / cookie

Troubleshooting

Symptom Fix
port already in use lsof -ti:8080 | xargs kill -9
No specs loaded First run: call /generate. Subsequent runs: tools auto-load from tools/*.py.
AI Core 404 on inference Set SAP_AI_DEPLOYMENT_ID to the hex deployment ID shown in BTP cockpit.
401 on API call Run POST /api/v1/detect-auth → set env vars → restart server.
401 persists after setting env vars Token may be stale: POST /api/v1/auth/invalidate/{spec_id}
XSUAA token fetch fails Verify AUTH_URL has no trailing slash; GRANT_TYPE=client_credentials.
CSRF error on OData write Handled automatically — executor fetches X-CSRF-Token before write ops.
TripPin returns 0 results Session URL changes on each access — /generate follows redirects automatically.
Write op denied (role error) Set MCP_USER_ROLE=admin in .env or add your email to RBAC_EMAIL_ROLES.
permission denied on MCP Call the whoami MCP tool to see your current role and how to upgrade it.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured