sap-mcp-bridge: new app — five SAP MCP servers behind one nginx-proxied port

This commit is contained in:
2026-05-19 09:48:55 -07:00
parent bcc9060aab
commit 92ed300f97
8 changed files with 641 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
# SAP MCP Bridge
Five SAP / UI5 / Fiori Model Context Protocol servers, packaged in a
single container and routed through an internal **nginx reverse
proxy** so the app exposes only **one external port** (Runtipi /
Traefik friendly).
## Bundled MCP servers
| Path prefix | MCP server | Use case |
| -------------- | ----------------------------------------- | ------------------------------------------ |
| `/cap/mcp` | `@iflow-mcp/cap-js-mcp-server` | SAP CAP (Cloud Application Programming) |
| `/abap/mcp` | `@iflow-mcp/mcp-abap-adt` | SAP ABAP via ADT (S/4HANA Cloud, on-prem) |
| `/odata/mcp` | `@iflow-mcp/btp-sap-odata-to-mcp-server` | SAP OData via BTP Destination service |
| `/ui5/mcp` | `@ui5/mcp-server` | SAPUI5 framework guidance + linting |
| `/fiori/mcp` | `@sap-ux/fiori-mcp-server` | SAP Fiori tools / app generation |
Each stdio MCP server is wrapped by `supergateway` and speaks
**Streamable HTTP** on its own internal port (90019005). An internal
nginx (port 8080 — the only EXPOSEd port) routes external requests by
URL path. Long timeouts and disabled buffering make MCP streaming
responses work correctly.
## Health endpoints
- `/healthz` — aggregate (probes the CAP gateway)
- `/cap/healthz`, `/abap/healthz`, `/odata/healthz`, `/ui5/healthz`, `/fiori/healthz`
## Using from Claude Code
Once the app is installed (and either exposed via a domain or
reachable on the LAN), wire the servers you want into your MCP
client. With the Claude Code CLI:
```bash
claude mcp add --transport http sap-cap https://sap-mcp.example.com/cap/mcp
claude mcp add --transport http sap-abap https://sap-mcp.example.com/abap/mcp
claude mcp add --transport http sap-odata https://sap-mcp.example.com/odata/mcp
claude mcp add --transport http ui5 https://sap-mcp.example.com/ui5/mcp
claude mcp add --transport http fiori https://sap-mcp.example.com/fiori/mcp
```
…or by adding the equivalent block to your project's `.mcp.json` /
`~/.claude.json`:
```json
{
"mcpServers": {
"sap-cap": { "type": "http", "url": "https://sap-mcp.example.com/cap/mcp" },
"sap-abap": { "type": "http", "url": "https://sap-mcp.example.com/abap/mcp" },
"sap-odata": { "type": "http", "url": "https://sap-mcp.example.com/odata/mcp" },
"ui5": { "type": "http", "url": "https://sap-mcp.example.com/ui5/mcp" },
"fiori": { "type": "http", "url": "https://sap-mcp.example.com/fiori/mcp" }
}
}
```
## ⚠️ Security warning — read before exposing externally
This app intentionally enables **wildcard CORS** (`Access-Control-Allow-Origin: *`)
inside the supergateway wrappers, because MCP clients may call the
endpoint from a variety of origins. Combined with `exposable: true`
and **no built-in authentication**, this means: if you expose the app
on a public URL via Traefik or a Cloudflare Tunnel, *anyone who knows
the URL* can drive your configured SAP credentials.
Recommended deployments:
- **Local LAN only**: leave `exposable: false` (default) and reach it
via `http://<runtipi-host>:<port>` from inside the LAN.
- **Exposed to the internet**: put it behind a Traefik
**ForwardAuth** middleware (e.g. Authentik / Authelia), a
Cloudflare Access policy, or basic-auth in nginx-proxy-manager —
i.e. authenticate at the proxy, not the app.
Do NOT expose this publicly without an auth layer in front of it.
## Configuration
All credential form fields are **optional**. Empty values are fine —
servers that don't have their required env vars simply won't be
usable, but the others (and the container as a whole) will still run.
- **`sap-abap-adt` needs:** `SAP_URL`, `SAP_USERNAME`, `SAP_PASSWORD`,
`SAP_CLIENT`
- **`sap-odata` needs:** `SAP_DESTINATION_NAME`,
`DESTINATION_SERVICE_URL`, `DESTINATION_CLIENT_ID`,
`DESTINATION_CLIENT_SECRET`, `DESTINATION_AUTH_URL`
- **`sap-cap`, `ui5-mcp-server`, `fiori-mcp-server`** require no
credentials — they read your **project workspace** at request time.
## Workspace mount
The container's `/workspace` is bound to:
```
/etc/runtipi/user-config/runtipi/sap-mcp-bridge/workspace
```
This lives under `user-config/`, which **survives Runtipi restarts
and `app update`**. Drop your CAP / UI5 / Fiori projects in there for
the `cap`, `ui5`, and `fiori` MCP servers to operate on.
The entrypoint chowns this directory to the in-container `mcp` user
(UID 10001) on every start, so a freshly auto-created mount works.
If you want `/workspace` to point at a different host path, create
`/etc/runtipi/user-config/runtipi/sap-mcp-bridge/docker-compose.yml`
with an overlay, e.g.:
```yaml
services:
sap-mcp-bridge:
volumes:
- /your/other/path:/workspace
```
## Image
Custom image:
`git.alexzaw.dev/alexz/sap-mcp-bridge:latest` — built from the
sources in `build/` (Dockerfile + entrypoint.sh + nginx.conf). See
`build/README.md` for the build & push procedure.