{
  "markdown": "# GuniWeb SAP S/4HANA & ECC MCP Server\n\nDesigned for n8n, usable from any MCP host. Free to use — built and maintained by [GuniWeb](https://guniweb.de).\n\nConnect n8n workflows to SAP S/4HANA and ECC via OData V2, V4, IDocs, and RFC/BAPI using the Model Context Protocol (MCP). Made for **AI-assisted, human-governed workflows**: the workflow author decides which SAP APIs are called and in what order; the LLM extracts, maps and fills in parameters. Writes are off by default and switched on explicitly, per server and per token.\n\n```mermaid\ngraph LR\n    n8n[\"n8n workflow<br/>AI-assisted, human-governed\"] -->|MCP Protocol| MCP[\"guniweb-sap-mcp<br/>22 Tools &bull; 8 Auth Types\"]\n    MCP -->|\"OData V2/V4<br/>IDoc XML<br/>RFC/BAPI\"| SAP[\"SAP S/4HANA & ECC\"]\n    MCP -->|\"OAuth2 / JWT\"| BTP[\"SAP BTP\"]\n    BTP --> SAP\n\n    style n8n fill:#4a90d9,color:#fff,stroke:#3a7bc8\n    style MCP fill:#2d2d2d,color:#fff,stroke:#555\n    style SAP fill:#1a6fb5,color:#fff,stroke:#0f5a9d\n    style BTP fill:#e8a317,color:#fff,stroke:#c88d14\n```\n\n## Features\n\n### OData & IDoc\n- **OData V2 + V4 CRUD** -- Read, query, create, update, delete with auto version detection\n- **Deep Insert** -- Create parent + child entities in one request\n- **Function Imports (V2) & Actions/Functions (V4)** -- Trigger SAP business logic\n- **Batch Operations** -- Multiple operations in a single `$batch` request\n- **NL-to-OData Query** -- Natural language queries converted to structured OData filters\n- **IDoc Send & Receive** -- HTTP/XML based, no RFC dependency\n\n### Discovery & Intelligence\n- **Service Discovery** -- Auto-discover OData services with 16 business domain categories\n- **Progressive Metadata** -- Service catalog -> entity sets -> properties -> query\n- **Smart Query Routing** -- Domain-based routing resources for LLM tool selection\n- **Decision-Tree Tool Descriptions** -- \"When to use / When NOT to use\" blocks guide LLMs\n\n### Governed tool visibility\n- **4-Tier Tool Registry** -- Core / OData / IDoc / RFC tiers with on-demand activation\n- **`sap_enable_tools`** -- LLMs activate additional tool tiers at runtime, within the limits you set\n- **Read-only by default (since 0.2.2)** -- write tools require `--allow-write`\n- **Per-token policy (0.3.0)** -- `readOnly` and tool-tier ceilings per Bearer token, only ever more restrictive\n- **IDoc / RFC auto-disable** -- tools hidden when the corresponding configuration is missing\n- **MCP Annotations** -- readOnlyHint, destructiveHint, idempotentHint on all 22 tools\n\n### Authentication (8 Types)\n\n```mermaid\ngraph TB\n    subgraph Technical[\"Technical User Auth\"]\n        basic[\"basic<br/><i>Direct S/4HANA</i>\"]\n        userbasic[\"user-basic<br/><i>Personal SAP login per request</i>\"]\n        oauth2[\"oauth2<br/><i>BTP Client Credentials</i>\"]\n        apikey[\"apikey<br/><i>Sandbox / Testing</i>\"]\n    end\n    subgraph Enterprise[\"Enterprise Auth (OIDC)\"]\n        ias[\"ias<br/><i>SAP IAS Token</i>\"]\n        xsuaa[\"xsuaa<br/><i>SAP XSUAA Token</i>\"]\n    end\n    subgraph Principal[\"Principal Propagation<br/><i>End-user identity forwarded to SAP</i>\"]\n        btp[\"btp-principal<br/><i>JWT Bearer Exchange<br/>via Destination Service</i>\"]\n        saml[\"saml-bearer<br/><i>SAML 2.0 Assertion<br/>+ RSA-SHA256 Signing</i>\"]\n    end\n\n    style Technical fill:#2a5f8f,color:#fff,stroke:#1a4f7f\n    style Enterprise fill:#5b8c5a,color:#fff,stroke:#4b7c4a\n    style Principal fill:#8b5e3c,color:#fff,stroke:#7b4e2c\n```\n\n### Infrastructure\n- **Three Transport Modes** -- stdio, Streamable HTTP, SSE\n- **Named destinations (0.3.0)** -- one server, several SAP systems; a Bearer token per n8n credential selects the system and its permissions. Static API key still supported\n- **Docker Ready** -- Docker Compose for n8n + SAP MCP sidecar\n- **Zero telemetry** -- the server talks to the SAP/BTP endpoints you configure and to nothing else. No usage statistics, no update checks, no crash reports\n- **1267 Tests** -- Unit, integration, E2E with CI/CD via GitHub Actions\n\n## Quick Start\n\n### Try it without an SAP system: `--demo`\n\nNo SAP system at hand? Start the server against a built-in mock S/4HANA and try every tool from n8n or any MCP client:\n\n```bash\nnpx guniweb-sap-mcp --demo                                   # stdio — n8n MCP Client in command mode\nnpx guniweb-sap-mcp --demo --transport http --port 8808      # HTTP — n8n MCP Client with URL http://localhost:8808/mcp\nnpx guniweb-sap-mcp --demo --allow-write                     # also try sap_create / sap_update / sap_delete / sap_batch\n```\n\nThe demo starts an in-memory OData V2 gateway on localhost (three services with real SAP field names — `API_BUSINESS_PARTNER`, `API_SALES_ORDER_SRV`, `API_PRODUCT_SRV`; ~20 business partners, 15 sales orders with items, 10 products) and points the server at it. It behaves like a Gateway where it matters: catalog discovery, `$metadata`, `$filter`/`$expand`/paging, CSRF tokens, ETags, deep insert, function imports, `$batch` with changesets, SAP-style error payloads. Sample data only — nothing is real, nothing leaves your machine, and changes are gone when the process ends. Also `SAP_MCP_DEMO=true` (e.g. in Docker). A workflow you build against the demo runs against a real system after you replace `--demo` with your `SAP_*` configuration.\n\n### 1. Install\n\n```bash\nnpm install -g guniweb-sap-mcp\n```\n\nOr [run it as a container](#run-as-a-container): `docker run --rm -p 8808:8808 -e SAP_MCP_DEMO=true ghcr.io/guniweb/guniweb-sap-mcp:latest`\n\n### 2. Configure\n\n```bash\nexport SAP_BASE_URL=https://your-sap-system.example.com\nexport SAP_AUTH_TYPE=basic\nexport SAP_USERNAME=your-user\nexport SAP_PASSWORD=your-password\nexport SAP_CLIENT=100   # do not omit: without it a client-dependent Gateway\n                        # answers 401 \"Anmeldung fehlgeschlagen\", which looks\n                        # like a wrong password but is not one\n```\n\n### 3. Run\n\n```bash\n# stdio (default) -- for n8n MCP Client in command mode\nguniweb-sap-mcp\n\n# HTTP -- for n8n MCP Client in URL mode or Docker\nguniweb-sap-mcp --transport http --port 8808\n\n# With tool tier control\nguniweb-sap-mcp --transport http --port 8808 --tiers core,odata\nguniweb-sap-mcp --transport http --port 8808 --allow-write   # writes are OFF by default\n\n# Production: authenticate the MCP endpoint AND the inbound IDoc webhook\nguniweb-sap-mcp --transport http --port 8808 --api-key <mcp-key> --webhook-secret <idoc-secret>\n```\n\n### Run as a container\n\n```bash\n# Look around first — mock S/4HANA inside the container, no SAP system needed\ndocker run --rm -p 8808:8808 -e SAP_MCP_DEMO=true ghcr.io/guniweb/guniweb-sap-mcp:latest\n\n# Against a real system (pin the version in production)\ndocker run -d --name sap-mcp -p 8808:8808 --env-file .env \\\n  ghcr.io/guniweb/guniweb-sap-mcp:<version>\n\n# Flags go straight to the server — writes stay off unless you say so\ndocker run -d -p 8808:8808 --env-file .env \\\n  ghcr.io/guniweb/guniweb-sap-mcp:<version> --allow-write\n```\n\nThe image is built **from the published npm package**, not from a second build path — what runs in the container is the artifact you would have installed with `npm install`, so the two cannot drift apart. HTTP transport on port 8808 is the default inside the image (`SAP_MCP_TRANSPORT` / `SAP_MCP_PORT`); it runs as a non-root user, answers a health check on `/healthz`, and is published for `linux/amd64` and `linux/arm64` with build provenance.\n\nNot included: **RFC/BAPI**. That path needs the SAP NW RFC SDK, which SAP licenses to customers only and which therefore cannot ship in a public image. Everything else — OData V2/V4, IDoc over HTTP/XML — works. For RFC, install the SDK on the host and run the server from npm ([Setup for SAP ECC](#setup-for-sap-ecc)).\n\nA `docker-compose.yml` that puts n8n next to the server is in the repository.\n\n#### Production hardening\n\n| Variable / Flag               | Default        | Description                                                                                     |\n|-------------------------------|----------------|-------------------------------------------------------------------------------------------------|\n| `--tool-timeout` / `SAP_MCP_TOOL_TIMEOUT` | `30` (seconds) | Time budget for a **whole tool call**, however many SAP requests it makes. On expiry the client gets a valid MCP response with `truncated: true` and a plain-language reason — never a hanging connection. Paginated reads return the pages fetched so far. `0` disables the budget (logged as a warning at startup) |\n| `SAP_HTTP_TIMEOUT_MS`         | `30000`        | Timeout for a **single** outbound SAP request (ms). Hanging gateways fail fast with a structured `TIMEOUT` error instead of stalling the n8n agent. Capped by whatever is left of the tool budget above |\n| `--cache-dir` / `SAP_CACHE_DIR` | _(unset = in-memory only)_ | Persist the catalog and metadata caches to disk so a restart does not refetch them. Without it every restart re-reads the full catalog — on a system with 1222 services that is the cold start that breaks client timeouts. n8n evicts its own client entry on a transport error, so restarts are not rare |\n| `SAP_CA_CERT` | _(unset)_ | Path to the root CA that signs your SAP certificate — the documented way past a corporate PKI. Mirrored into `NODE_EXTRA_CA_CERTS`. Prefer this over `SAP_TLS_VERIFY=false`, which disables the check rather than solving it |\n| `--concurrency` / `SAP_CONCURRENCY` | `5` | Upper bound on simultaneous SAP requests where several are unavoidable |\n| `--allow-write` / `SAP_MCP_ALLOW_WRITE` | _(unset = read-only)_ | **Since 0.2.2 the server is read-only by default.** `sap_create`, `sap_update`, `sap_delete`, `sap_function` and `sap_idoc_send` are not registered at all until you set this — a tool an agent cannot see is a tool it cannot call by accident. `--read-only` is still accepted and now simply describes the default |\n| `SAP_JWT_ISSUER` | _(unset = no verification)_ | Issuer whose JWKS is used to verify incoming user tokens. Without it tokens are only decoded, and the resulting identity is marked `verified: false` — safe to forward to SAP (SAP verifies itself), but not safe to base a local decision on. With it, a token failing verification is rejected with `401` rather than silently downgraded to the technical user |\n| `SAP_WEBHOOK_SECRET` / `--webhook-secret` | _(unset = open + startup warning)_ | Shared secret for the inbound IDoc webhook. Accepts `Authorization: Bearer <secret>` or HTTP Basic auth whose **password** equals the secret (matches SAP SM59 HTTP destinations) |\n| `SAP_WEBHOOK_HOST` / `--webhook-host` | all interfaces | Bind address of the standalone IDoc webhook (stdio mode). Set `127.0.0.1` behind a reverse proxy |\n| `SAP_WEBHOOK_MAX_BODY_BYTES`  | `10485760` (10 MB) | Reject larger IDoc payloads with `413` before they are buffered in memory                    |\n\nIn HTTP transport mode the `/idoc` route is mounted on the MCP port: with `--webhook-secret` it authenticates independently of `--api-key` (so SAP's Basic auth works while n8n uses the Bearer key); without it, it falls under `--api-key` if set — and is **open** if neither is configured (the server logs a warning at startup).\n\nBoth timeouts must stay **below the smallest client limit** in your setup. Measured against a production S/4HANA behind Cloudflare: Cloudflare aborts at 100 s (`524`), n8n's MCP client transport at 300 s. A budget above the smallest limit never takes effect, because the client gives up first.\n\n#### Tracing a slow tool call\n\nEvery tool call logs one line on completion, carrying a correlation id that also appears on each SAP request it triggered:\n\n```json\n{\"correlationId\":\"a3f19c8b2d04\",\"tool\":\"sap_discover_services\",\"outcome\":\"ok\",\"durationMs\":842,\"sapCalls\":1,\"resultBytes\":18422}\n```\n\n`grep a3f19c8b2d04` then shows the full story of that one call — including how many SAP requests it produced. `outcome` is one of `ok`, `error`, `timeout` (budget expired) or `aborted` (client disconnected).\n\n**`level: 50` means something is actually wrong.** Expected non-2xx responses — the `404`s from the catalog path probing and from the V4 catalog attempt on systems that have none — are logged at `info` with `\"expected\": true`, not at `error`. Measured on a production system: over 96 hours of runtime that false alarm was the *only* error-level entry in the log. Alerting on `level: 50` is therefore meaningful; before, it produced nothing but noise.\n\n#### When something breaks\n\n`test-connection` walks the whole chain and reports the **first** failing stage — DNS, TLS (including the certificate chain), authentication, client (Mandant), catalog. Stages after the failure are reported as `skipped`, never as passed. The same five stages are logged once at startup, so the container log shows the state of the connection without calling anything.\n\nEvery error response carries a classification, the SAP error code where one exists, a plain-language message and the next action — no tool answers with `\"Unknown error\"`. Three cases that used to cost a separate diagnosis each:\n\n| What SAP returns | What the server tells you |\n|---|---|\n| `403` with `/IWFND/MED/170` | The service is not registered — activate it in `/IWFND/MAINT_SERVICE`. **Not** an authorization problem, despite the 403 |\n| `401` with an HTML logon page | Is `sap-client` set? Without it a client-dependent Gateway rejects the logon in a way that looks like a wrong password |\n| `UNABLE_TO_GET_ISSUER_CERT_LOCALLY` | The certificate chain is incomplete; supply the root CA via `SAP_CA_CERT`. The message names the issuing CA |\n\nIf `SAP_TLS_VERIFY=false` is in effect, **every tool result** carries a warning about it — not just the startup log. A disabled check that nobody sees after the first day tends to stay for years; on the production system that prompted this work it was set, and nobody noticed.\n\n#### Streamable HTTP is stateless\n\n`--transport http` serves MCP in **stateless** mode: every request is self-contained and the server neither issues nor expects an `mcp-session-id` header. There is no server-side session to resume, so a client may reconnect at any time without losing state.\n\nClients that *require* a session id — they read `mcp-session-id` from the initialize response and send it back on subsequent requests — will fail against this server. The n8n MCP Client, Claude Desktop, and the MCP Inspector all work without it.\n\nThe legacy `--transport sse` mode is different: it keeps a per-connection session and expects a `?sessionId=` query parameter on `/messages`. Prefer `http` for new setups.\n\n#### Several SAP systems: `destinations.json`\n\nThe `SAP_*` variables describe **one** SAP system. To run one server against several — a production system next to a test client, or several customers' systems behind one n8n — put the connections into a `destinations.json` and point the server at it:\n\n```bash\nguniweb-sap-mcp --transport http --port 8808 --destinations /config/destinations.json\n# or: SAP_MCP_DESTINATIONS=/config/destinations.json\n```\n\n```json\n{\n  \"version\": 1,\n  \"destinations\": {\n    \"default\": {\n      \"baseUrl\": \"https://s4prod.example.com\",\n      \"authType\": \"basic\",\n      \"username\": \"MCP_USER\",\n      \"password\": \"${S4PROD_PASSWORD}\",\n      \"sapClient\": \"100\"\n    },\n    \"s4test\": {\n      \"baseUrl\": \"https://s4test.example.com\",\n      \"authType\": \"basic\",\n      \"username\": \"MCP_USER\",\n      \"password\": \"${S4TEST_PASSWORD}\",\n      \"sapClient\": \"200\"\n    }\n  }\n}\n```\n\n- **One entry = one connection.** Every entry accepts exactly the fields the `SAP_*` variables accept (`authType` and its credentials, `sapClient`, `baseUrl`) — all authentication types work per destination.\n- **Personal SAP login per request** — `\"authType\": \"user-basic\"`: the destination knows system and client only; SAP user and password come with each request (`X-SAP-Username`/`X-SAP-Password`, in n8n the credential of the [GuniWeb SAP node](https://github.com/guniweb/n8n-nodes-guniweb-sap)). Everyone acts in SAP as themselves — authorizations, change documents and audit trail on the real user, no shared technical account. No fallback to a technical user (`401` without a login), caches per user, passwords never logged. HTTP transport only. Details in the [setup guide](https://github.com/guniweb/guniweb-sap-mcp/blob/main/docs/setup-guide.md#personal-sap-login-per-request-user-basic).\n- **Secrets stay out of the file.** Any string may contain `${VARIABLE}`; it is resolved from the environment at load time (Docker secrets, `.env`). A placeholder that cannot be resolved makes the file invalid — an empty password would otherwise surface as a misleading `401` from SAP.\n- **Hot reload.** The file is watched; a change is picked up on the next request, no restart. `kill -HUP <pid>` forces a reload. An invalid file **never** replaces the running configuration — the error is logged and the last valid one stays in force.\n- **Backwards compatible.** Without `--destinations` nothing changes: the `SAP_*` variables are the single destination `default`. With the file, `SAP_*` (if set) is added as `default` unless the file defines one — so an installation can migrate without a gap.\n- **Which destination answers?** Without a token: the one named `default` (or the only one). With a token: the token's destination — see below.\n\n**Tokens: one Bearer value per n8n credential selects the system.** The n8n MCP Client sends exactly one `Authorization` header, so a token does two jobs at once — it authenticates the request and picks the destination. SAP passwords never leave the server; n8n only holds the token.\n\n```json\n{\n  \"version\": 1,\n  \"destinations\": { \"s4prod\": { \"...\": \"...\" }, \"s4test\": { \"...\": \"...\" } },\n  \"tokens\": [\n    { \"hash\": \"sha256:9f2c…\", \"destination\": \"s4prod\", \"label\": \"n8n prod workflows\" },\n    { \"hash\": \"sha256:1a7e…\", \"destination\": \"s4test\", \"label\": \"n8n dev\", \"revoked\": false }\n  ]\n}\n```\n\n- The file holds only the **SHA-256 hash**; the plain token (`gsm_<destination>_<random>`) is printed **once** by `tokens issue` and cannot be recovered afterwards. Put it into an n8n **Bearer Auth** credential on the MCP Client node. As soon as at least one token exists, requests without a valid token get `401` — `--api-key` keeps working alongside and serves the `default` destination.\n- Lookup is constant-time over all entries; `\"revoked\": true` disables a token on the next request (hot reload, no restart). After 10 failed attempts within a minute the client address gets `429` with `Retry-After` — also for a valid token — as a brake on guessing (a reverse proxy with a real rate limit is still the right place for that). Tokens are never written to the log; rejections are logged with address and reason.\n**Managing the file — no hand-editing needed.** The same binary carries the admin commands; every command validates the result exactly like the server does before writing (never leaves an unloadable file), writes atomically, and a running server picks the change up on the next request:\n\n```bash\nexport SAP_MCP_DESTINATIONS=/config/destinations.json      # or --destinations <path> on each call\n\nguniweb-sap-mcp destinations add s4prod --base-url https://s4prod.example.com --auth-type basic \\\n    --username MCP_USER --password '${S4PROD_PASSWORD}' --sap-client 100\nguniweb-sap-mcp destinations add legacy --from-env         # copy the current SAP_* environment (migration)\nguniweb-sap-mcp destinations list                          # names, URLs, auth type, client, token count — never secrets\n\nguniweb-sap-mcp tokens issue s4prod --label \"n8n prod\" --read-only --tiers core,odata\n#   → prints the plain token ONCE on stdout (everything else goes to stderr, so TOKEN=$(…) works)\nguniweb-sap-mcp tokens list                                # label, destination, hash prefix, policy, status\nguniweb-sap-mcp tokens revoke \"n8n prod\"                   # by label or ≥ 8 hex chars of the hash\nguniweb-sap-mcp destinations remove s4test [--force]       # refuses while tokens point at it, unless --force\n```\n\n`--set field=value` covers any auth type (`--auth-type oauth2 --set clientId=… --set clientSecret='${BTP_SECRET}' --set tokenServiceUrl=…`); `${ENV_VAR}` values are checked against the current environment (`--allow-missing-env` to skip, e.g. when the variable only exists inside the container). In Docker, run the commands inside the container so they see the same file and environment: `docker compose exec sap-mcp guniweb-sap-mcp tokens issue s4prod`.\n\n- **Per-token permissions** — `\"policy\": { \"readOnly\": true, \"tiers\": [\"core\", \"odata\"] }`. A policy only ever *restricts*: `readOnly: true` hides the write tools for that token even when the server runs with `--allow-write` (`readOnly: false` cannot open a read-only server), and `tiers` is a ceiling — tiers outside it are invisible for that token and `sap_enable_tools` cannot switch them on. `tools/list` is filtered per request, so an agent on a read-only token never sees `sap_create` in the first place.\n\n**Admin API — the same operations over HTTP (self-service).** With `--admin-token <secret>` (or `SAP_MCP_ADMIN_TOKEN`, at least 16 characters, e.g. `openssl rand -base64 32`) the server exposes the CLI's operations under `/admin` — for an n8n workflow that provisions a new customer's access, a script, or a person with `curl`, without a shell on the host:\n\n```bash\nguniweb-sap-mcp --transport http --port 8808 --destinations /config/destinations.json --admin-token \"$ADMIN\"\n\nA=\"Authorization: Bearer $ADMIN\"; J=\"Content-Type: application/json\"; U=http://localhost:8808/admin\ncurl -H \"$A\" $U/destinations                                    # list — never secrets\ncurl -H \"$A\" -H \"$J\" -X PUT $U/destinations/s4prod \\           # create (201) or replace (200)\n     -d '{\"baseUrl\":\"https://s4prod.example.com\",\"authType\":\"basic\",\"username\":\"MCP_USER\",\"password\":\"${S4PROD_PASSWORD}\",\"sapClient\":\"100\"}'\ncurl -H \"$A\" -H \"$J\" -X POST $U/tokens \\                       # → { \"token\": \"gsm_s4prod_…\", … } — shown ONCE\n     -d '{\"destination\":\"s4prod\",\"label\":\"n8n prod\",\"readOnly\":true,\"tiers\":[\"core\",\"odata\"]}'\ncurl -H \"$A\" $U/tokens                                          # label, destination, hash prefix, policy, status\ncurl -H \"$A\" -X DELETE \"$U/tokens/n8n%20prod\"                   # revoke by label or hash prefix\ncurl -H \"$A\" -X DELETE \"$U/destinations/s4test?force=true\"      # remove; without force it refuses while tokens exist\n```\n\n- **Separate keys, separate doors.** The admin secret opens *only* `/admin`; the n8n tokens and `--api-key` open *only* `/mcp`. A leaked n8n token cannot mint further access, an admin secret cannot read SAP data. Wrong secrets are counted per client address (`429` after 10 failures per minute); every change is logged with action, target and client address — never the token, never the secret. Without `--admin-token`, `/admin/*` answers `404`.\n- **Effective immediately.** Every write validates like the CLI, writes atomically and reloads the running configuration before answering (`\"reloaded\": true`). If the server cannot load what was written — typically a `${VAR}` that only your shell knows, written with `?allowMissingEnv=true` — the answer says `\"reloaded\": false` with the errors, and the last valid configuration stays in force.\n- **Bootstrap without any SAP configuration.** With `--admin-token` the server starts even if `destinations.json` does not exist yet and no `SAP_*` is set: `/mcp` answers `503` until the first destination (and a token, or a destination named `default`) has been created through the API. `docker run … -e SAP_MCP_ADMIN_TOKEN=… -e SAP_MCP_DESTINATIONS=/config/destinations.json` is a complete first start.\n- Put the API behind the same reverse proxy/TLS as `/mcp`; the admin secret travels as a Bearer header like any other credential.\n\n### 4. Use with n8n\n\n1. Add an **AI Agent** node with an **MCP Client Tool** sub-node\n2. Set the MCP Client URL to `http://sap-mcp:8808/mcp` (Docker) or `http://localhost:8808/mcp`, with a Bearer Auth credential holding your API key or destination token\n3. The AI Agent node discovers the SAP tools automatically. **Which** tools it sees is your decision, not the model's: read-only by default, write tools only with `--allow-write`, and per-token policies narrow it further. Design the workflow as a deterministic, human-authored sequence — the LLM fills in the parameters\n\n## Setup for SAP ECC\n\nECC NetWeaver backends require additional setup beyond the [Quick Start](#quick-start). The MCP server supports ECC 6.0+ (EHP 7+) for OData V2, OData V4, IDoc HTTP/XML, and RFC/BAPI. Cloud-only S/4HANA tenants do not need this section.\n\n### 1. Activate NetWeaver Gateway\n\nIn transaction `SICF`, activate the standard ICF services under `/sap/opu/odata/`. Then use transaction `/IWFND/MAINT_SERVICE` to register the OData services you want to expose.\n\nThe MCP server's catalog auto-discovery probes three paths in sequence and caches the first 200 hit:\n\n- `/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/ServiceCollection` (S/4HANA default)\n- `/sap/opu/odata/iwfnd/CATALOGSERVICE;v=2/ServiceCollection` (lower-case ECC)\n- `/sap/opu/odata/IWFND/CATALOGSERVICE/ServiceCollection` (ECC EHP 7 v=1)\n\nOverride via `ODATA_V2_CATALOG_PATH` if your gateway uses a non-standard mount.\n\n### 2. Install SAP NW RFC SDK (RFC/BAPI tools only)\n\nRequired for the `rfc` tool tier. Skip this step if you only use OData or IDoc.\n\n**Linux:**\n\n```bash\ntar xzf nwrfcsdk_linux.tgz -C /usr/sap/\nexport LD_LIBRARY_PATH=/usr/sap/nwrfcsdk/lib:$LD_LIBRARY_PATH\nnpm install node-rfc\n```\n\n**macOS:**\n\n```bash\ntar xzf nwrfcsdk_darwin.tgz -C /usr/sap/\nexport DYLD_LIBRARY_PATH=/usr/sap/nwrfcsdk/lib:$DYLD_LIBRARY_PATH\nnpm install node-rfc\n```\n\n`node-rfc` is declared as `optionalDependencies` -- if the SDK is missing, the server logs a Pino warning at startup and the RFC tier is skipped. OData and IDoc tools continue to work.\n\n### 3. ECC-Specific Environment Variables\n\nIn addition to the variables documented in [Quick Start](#quick-start):\n\n| Variable                  | Required               | Description                                                                                |\n|---------------------------|------------------------|--------------------------------------------------------------------------------------------|\n| `ODATA_V2_CATALOG_PATH`   | No (auto-discover)     | Override catalog path, e.g. `/sap/opu/odata/iwfnd/CATALOGSERVICE;v=2/ServiceCollection`     |\n| `SAP_TLS_VERIFY`          | No (default `true`)    | Set to `false` to disable TLS certificate verification for self-signed-cert ECC scenarios   |\n| `SAP_CLIENT_CERT_PATH`    | For X.509 client-cert  | Path to PEM-encoded client certificate, e.g. `/etc/sap/client.crt`                          |\n| `SAP_CLIENT_KEY_PATH`     | For X.509 client-cert  | Path to PEM-encoded private key (optional passphrase via prompt)                            |\n| `SAP_SNC_QOP`             | For SNC (RFC)          | SNC Quality-of-Protection level: `1` (auth-only), `2` (integrity), `3`/`8`/`9` (encryption) |\n| `SAP_SNC_MYNAME`          | For SNC (RFC)          | Local SNC name, e.g. `p:CN=mcp-server, O=GuniWeb`                                           |\n| `SAP_SNC_PARTNERNAME`     | For SNC (RFC)          | Backend SNC name, e.g. `p:CN=SAPSERVER, O=ACME`                                             |\n| `SAP_SNC_LIB`             | For SNC (RFC)          | Path to SNC library (`.so` / `.dylib` / `.dll`), e.g. `/usr/sap/sapcrypto/libsapcrypto.so`  |\n\n## Available Tools (22)\n\n### Core Tier (always active)\n| Tool | Description |\n|------|-------------|\n| `test-connection` | Stage-by-stage connection diagnosis (DNS → TLS → auth → client → catalog); names the first failing stage |\n| `sap_list_services` | List entity sets in an OData service (paged, default 50) |\n| `sap_discover_services` | Discover all services with category filtering and search |\n| `sap_get_metadata` | Inspect entity types, properties, keys, and what SAP allows per entity set (creatable/updatable/deletable) |\n\n### OData Tier (active by default)\n| Tool | Description |\n|------|-------------|\n| `sap_read` | Read a single entity or collection |\n| `sap_query` | Query with OData $filter, $select, $expand, paging (default `top` 50) |\n| `sap_nl_query` | Natural language query with structured filters |\n| `sap_create` | Create entities (supports deep insert + draft services) |\n| `sap_update` | Update entities (automatic ETag handling) |\n| `sap_delete` | Delete entities (automatic ETag handling) |\n| `sap_function` | Call V2 function imports or V4 actions/functions |\n| `sap_batch` | Execute multiple operations in a single batch |\n\n### IDoc Tier (on-demand via `sap_enable_tools`)\n| Tool | Description |\n|------|-------------|\n| `sap_idoc_send` | Send IDoc to SAP via HTTP/XML |\n| `sap_idoc_list_received` | List received IDocs from webhook |\n| `sap_idoc_status` | Check IDoc processing status |\n| `sap_idoc_discover` | Discover available IDoc types |\n\n### RFC Tier (on-demand via `sap_enable_tools`, requires SAP NW RFC SDK)\n| Tool | Description |\n|------|-------------|\n| `sap_rfc_call` | Call any RFC-enabled function module with zod-validated parameters |\n| `sap_rfc_metadata` | Fetch function-module signature (imports/exports/tables/exceptions) |\n| `sap_bapi_call` | Invoke a BAPI, parse BAPIRET2 return tables, pin connection for follow-up commit |\n| `sap_bapi_commit` | Invoke `BAPI_TRANSACTION_COMMIT` on the pinned session |\n| `sap_rfc_search_functions` | Find function modules by name pattern via `RFC_FUNCTION_SEARCH` |\n\n### Meta Tool\n| Tool | Description |\n|------|-------------|\n| `sap_enable_tools` | Activate/deactivate tool tiers at runtime |\n\n## Architecture\n\n```mermaid\ngraph TB\n    subgraph n8n[\"n8n\"]\n        Agent[\"AI Agent + LLM\"]\n        MCPClient[\"MCP Client Tool\"]\n        Agent --> MCPClient\n    end\n\n    subgraph Server[\"guniweb-sap-mcp\"]\n        Transport[\"Transport<br/>stdio | HTTP | SSE\"]\n        UserCtx[\"UserContext<br/>Extraction\"]\n        Registry[\"ToolRegistryManager<br/>Core | OData | IDoc\"]\n        Tools[\"17 MCP Tools<br/>3 Resources &bull; 3 Prompts\"]\n        OData[\"ODataClient<br/>V2/V4 Auto-Detect\"]\n        Discovery[\"Service Discovery<br/>16 Domain Categories\"]\n        NLQuery[\"NL-to-OData<br/>Filter Builder\"]\n        HTTP[\"SapHttpClient<br/>CSRF &bull; Redirect &bull; Auth\"]\n        Auth[\"Auth Layer<br/>8 Auth Types\"]\n        Cache[\"Per-User Token Cache<br/>LRU &bull; SHA-256\"]\n\n        Transport --> UserCtx\n        Transport --> Registry\n        Registry --> Tools\n        Tools --> OData\n        Tools --> Discovery\n        Tools --> NLQuery\n        OData --> HTTP\n        Discovery --> HTTP\n        NLQuery --> OData\n        HTTP --> Auth\n        Auth --> Cache\n    end\n\n    subgraph SAP[\"SAP Backend\"]\n        S4[\"S/4HANA<br/>OData V2/V4\"]\n        IDoc[\"IDoc<br/>HTTP/XML\"]\n        BTPSuite[\"BTP Integration<br/>Suite\"]\n        DestSvc[\"Destination<br/>Service\"]\n        TokenSvc[\"XSUAA / IAS<br/>Token Service\"]\n    end\n\n    MCPClient -->|MCP Protocol| Transport\n    HTTP -->|Direct| S4\n    HTTP -->|Via BTP| BTPSuite\n    BTPSuite --> S4\n    HTTP --> IDoc\n    Auth -->|Token Exchange| DestSvc\n    Auth -->|OIDC / OAuth2| TokenSvc\n    DestSvc --> S4\n\n    style n8n fill:#e8f4fd,stroke:#4a90d9\n    style Server fill:#f5f5f5,stroke:#555\n    style SAP fill:#e8f0fe,stroke:#1a6fb5\n```\n\n### Integration Paths\n\n```mermaid\ngraph LR\n    subgraph Path1[\"Path 1: Direct\"]\n        n1[\"n8n\"] -->|MCP| s1[\"MCP Server\"] -->|Basic Auth| sap1[\"SAP S/4HANA\"]\n    end\n\n    subgraph Path2[\"Path 2: BTP Client Credentials\"]\n        n2[\"n8n\"] -->|MCP| s2[\"MCP Server\"] -->|OAuth2| btp2[\"BTP Suite\"] --> sap2[\"SAP S/4HANA\"]\n        s2 -->|CC Flow| tok2[\"XSUAA\"]\n    end\n\n    subgraph Path3[\"Path 3: BTP Principal Propagation\"]\n        n3[\"n8n + JWT\"] -->|MCP| s3[\"MCP Server\"] -->|JWT Exchange| dest3[\"Destination Svc\"] --> sap3[\"SAP S/4HANA\"]\n    end\n\n    subgraph Path4[\"Path 4: SAML Bearer (no BTP)\"]\n        n4[\"n8n + JWT\"] -->|MCP| s4[\"MCP Server\"] -->|\"SAML Assertion<br/>RSA-SHA256\"| sap4[\"SAP OAuth2<br/>Endpoint\"]\n    end\n\n    style Path1 fill:#e8f4fd,stroke:#4a90d9\n    style Path2 fill:#fef9e7,stroke:#e8a317\n    style Path3 fill:#eafaf1,stroke:#5b8c5a\n    style Path4 fill:#fdf2e9,stroke:#8b5e3c\n```\n\n### Request Flow (OData Write)\n\n```mermaid\nsequenceDiagram\n    participant LLM as LLM (via n8n)\n    participant Tool as MCP Tool\n    participant Registry as ToolRegistry\n    participant OData as ODataClient\n    participant HTTP as SapHttpClient\n    participant SAP as SAP Gateway\n\n    LLM->>Tool: sap_create(serviceUrl, entitySet, data)\n    Tool->>Registry: isToolVisible?\n    Registry-->>Tool: yes\n    Tool->>OData: create(entitySet, data)\n    OData->>HTTP: request(POST, url, data)\n\n    Note over HTTP,SAP: CSRF Token Fetch\n    HTTP->>SAP: GET /sap/opu/odata/sap/SRV/<br/>x-csrf-token: fetch<br/>sap-client: 324\n    SAP-->>HTTP: x-csrf-token: TOKEN<br/>set-cookie: SESSION\n\n    Note over HTTP,SAP: Mutating Request\n    HTTP->>SAP: POST /sap/opu/odata/sap/SRV/EntitySet<br/>x-csrf-token: TOKEN<br/>Cookie: SESSION\n    SAP-->>HTTP: 201 Created + entity data\n\n    HTTP-->>OData: response\n    OData-->>Tool: created entity\n    Tool-->>LLM: JSON result via MCP\n```\n\n## Supported SAP Systems\n\nThe MCP server supports the following SAP backends. Connector availability depends on the target system; see footnotes for prerequisites.\n\n| Connector             | ECC 6.0+ (EHP 7+)         | S/4HANA on-prem        | S/4HANA Cloud (Public) |\n|-----------------------|---------------------------|------------------------|------------------------|\n| **OData V2**          | ✅ supported [^1]         | ✅ supported           | ✅ supported           |\n| **OData V4**          | ⚠️ partial [^4]           | ✅ supported           | ✅ supported           |\n| **IDoc** (HTTP/XML)   | ✅ supported [^1]         | ✅ supported           | ❌ N/A [^3]            |\n| **RFC/BAPI**          | ✅ supported [^1][^2][^5] | ✅ supported [^2][^5]  | ❌ N/A [^3]            |\n\nAdditionally validated against:\n\n- SAP BTP Integration Suite (as intermediary for S/4HANA Cloud RFC/IDoc paths)\n- SAP API Business Hub Sandbox\n\n[^1]: NetWeaver Gateway ICF services must be activated (transaction `SICF`). See [Setup for SAP ECC](#setup-for-sap-ecc).\n[^2]: Requires SAP NW RFC SDK 7.50+ on the host. `node-rfc` is declared as `optionalDependencies`; missing SDK degrades gracefully with a startup warning.\n[^3]: S/4HANA Cloud Public Edition does not expose RFC or classic IDoc-HTTP for external consumers. Use SAP BTP Integration Suite as an intermediary.\n[^4]: OData V4 in ECC is rare; most ECC NetWeaver Gateway services are V2. V4 is the default for S/4HANA.\n[^5]: SNC library (`SAP_SNC_LIB`) is OS-specific (`.so` / `.dylib` / `.dll`) and must be installed separately.\n\n## Documentation\n\n- [Setup Guide](https://github.com/guniweb/guniweb-sap-mcp/blob/main/docs/setup-guide.md) -- Installation, all 8 auth types, CLI flags, named destinations and tokens, Docker deployment\n- [Architecture](https://github.com/guniweb/guniweb-sap-mcp/blob/main/docs/architecture.md) -- Technical architecture, request flows, design decisions\n- [API Reference](https://github.com/guniweb/guniweb-sap-mcp/blob/main/docs/api-reference.md) -- All 22 tools, resources, and prompts with parameters\n- [Examples](https://github.com/guniweb/guniweb-sap-mcp/blob/main/docs/examples.md) -- SAP workflow examples with step-by-step instructions\n- [SAP interfaces, tool by tool](https://github.com/guniweb/guniweb-sap-mcp/blob/main/docs/sap-interfaces.md) -- which SAP endpoint each of the 22 tools calls, what is bounded, what leaves your network — for Basis teams, license managers and auditors\n- [Changelog](https://github.com/guniweb/guniweb-sap-mcp/blob/main/CHANGELOG.md) · [Releases](https://github.com/guniweb/guniweb-sap-mcp/releases) · [Issues](https://github.com/guniweb/guniweb-sap-mcp/issues) · [Discussions](https://github.com/guniweb/guniweb-sap-mcp/discussions)\n\n## Governance: how this fits SAP's rules\n\n- **Standard integration interfaces only.** The server speaks OData V2/V4 through NetWeaver Gateway, IDoc over HTTP/XML and RFC/BAPI — the documented, supported ways into an SAP system ([tool-by-tool list](https://github.com/guniweb/guniweb-sap-mcp/blob/main/docs/sap-interfaces.md)). No UI automation, no undocumented interfaces, no bulk replication of SAP data. Paging limits and time budgets are built in so a single tool call cannot turn into a load test of your Gateway.\n- **The write path is governed and auditable.** Read-only by default; write tools exist only with an explicit `--allow-write`; per-token policies can only restrict, never widen; every tool call leaves one log line with a correlation id that also appears on each SAP request it triggered. That is what makes \"human-governed\" verifiable: the workflow author, not the model, decides where writes are possible.\n- **Two things to settle before you automate document creation.** SAP's API usage policy contains terms on autonomous, generative use of its APIs — build workflows as deterministic, human-authored sequences with LLM assistance rather than open-ended autonomous agents (this server is designed for exactly that). And business documents created automatically (a sales order, for instance) may fall under SAP Digital Access licensing for your tenant — clarify that with your SAP account team before you go live. Neither sentence is legal advice; both are the questions your auditor will ask.\n\n## Who is behind this — and how to get help\n\nguniweb-sap-mcp is built and maintained by [GuniWeb](https://guniweb.de) — a consultancy with its own solutions: *processes that work, systems that grow with you*. GuniWeb optimises business processes for mid-sized companies, with more than 20 years of practice, strategic consulting and hands-on implementation, so that a company does not just become more digital but runs better. This server is one of those solutions: it runs in production at customers and is used in teaching and research. The compiled package on npm is **free to use** under the ISC license; the source code is not public.\n\n- **Community** — questions, bug reports and ideas via [Issues](https://github.com/guniweb/guniweb-sap-mcp/issues) and [Discussions](https://github.com/guniweb/guniweb-sap-mcp/discussions), best effort. See [SUPPORT.md](https://github.com/guniweb/guniweb-sap-mcp/blob/main/SUPPORT.md).\n- **Production support** — maintenance subscription with prioritised fixes, compatibility with SAP release changes and a direct channel; optionally with a response-time SLA for the write path: **[guniweb.de/sap-mcp](https://guniweb.de/sap-mcp#support)**\n- **SAP MCP Checkup** — we review your n8n/SAP setup (auth, governance, tool visibility, performance) before it goes live: **[guniweb.de/sap-mcp](https://guniweb.de/sap-mcp#checkup)**\n\nContact: [support@guniweb.de](mailto:support@guniweb.de). Security issues: see [SECURITY.md](https://github.com/guniweb/guniweb-sap-mcp/blob/main/SECURITY.md) — and note the zero-telemetry commitment there.\n\n## Technology\n\nBuilt with:\n- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) v1.x\n- [axios](https://github.com/axios/axios) -- HTTP client with CSRF + redirect handling\n- [jose](https://github.com/panva/jose) -- JWT/JWKS validation for IAS/XSUAA auth\n- [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) -- IDoc XML + SAML assertion building\n- [Zod](https://zod.dev/) v4 -- Schema validation\n- [pino](https://github.com/pinojs/pino) -- Structured JSON logging\n- [tsup](https://github.com/egoist/tsup) -- ESM bundling\n- TypeScript 5.9, Node.js >= 22.18.0\n\n---\n\nSAP, S/4HANA, ECC, NetWeaver, ABAP are trademarks or registered trademarks of SAP SE in Germany and other countries. This project is not affiliated with SAP SE.\n\n## License\n\nISC\n",
  "bytes": 39861,
  "sha": "026a0e72c34b13243b29268461d0e75c3fa34f20b0217c6da6358dc47f9200ce",
  "repo_slug": "guniweb/guniweb-sap-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_guniweb_guniweb_sap_mcp_ff924045/readme"
}