Skip to main content
Glama

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

Related MCP server: SAP OData to MCP Server

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.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JyotishKumarr/Super-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server