sap-mcp-server
by romankorim
README.md
# sap-mcp-server
An MCP server that gives Claude (Desktop, Code, API) read access and approval-gated write access to SAP ECC and S/4HANA through OData and RFC/BAPI, without the proprietary SAP NetWeaver RFC SDK. The OData side talks to the SAP Gateway over HTTP. The RFC side runs through a small Node bridge on [open-rfc](https://www.npmjs.com/package/open-rfc), an Apache-2.0 licensed package that implements the RFC protocol without the SAP SDK. Every call is written to an audit log.
## Why
SAP Joule ships with S/4HANA Cloud and with recent S/4HANA on-premise releases connected to BTP. ECC 6.0 has no Joule, and the only supported route to one is a migration. This server is the sidecar pattern: SAP stays as it is, the model runs where you choose (Claude Desktop, Claude Code, the Claude API, Amazon Bedrock), and the only thing between them is this process. It uses the two interfaces most SAP systems already expose, OData on the Gateway and RFC on the application server, and writes every call to an audit log.
## Tools
| Tool | Path | What it does |
|---|---|---|
| `sap_ping()` | OData | Reads the Gateway service catalog; connection test |
| `list_services(filter_text)` | OData | Lists OData services registered on the Gateway, optionally filtered by substring |
| `odata_query(service, entity_set, filter, select, top, orderby)` | OData | Reads any entity set (`$filter`, `$select`, `$top` capped at 200, `$orderby`) |
| `get_material(material)` | OData | Material master. S/4HANA `API_PRODUCT_SRV` by default; on ECC point `SAP_SVC_MATERIAL` to a Z service |
| `list_production_orders(plant, top)` | OData | Production orders. S/4HANA `API_PRODUCTION_ORDER_2_SRV` by default; on ECC a Z service |
| `list_purchase_orders(supplier, top)` | OData | Purchase orders. S/4HANA `API_PURCHASEORDER_PROCESS_SRV` by default; on ECC a Z service |
| `create_purchase_requisition(material, quantity, plant, confirm)` | write | Without `confirm=True` returns a proposal only. The SAP write itself (`BAPI_PR_CREATE`) is not implemented yet |
| `rfc_ping()` | RFC | `RFC_SYSTEM_INFO`: SID, host, release, database |
| `read_table(table, fields, where, max_rows, skip_rows)` | RFC | `RFC_READ_TABLE` on any table the RFC user is authorized to read. One row is limited to 512 bytes, so pass a field list on wide tables |
| `table_fields(table)` | RFC | `DDIF_FIELDINFO_GET`: field names, types, lengths, descriptions |
| `bapi_call(name, params)` | RFC | Calls a BAPI or function module from the read-only allowlist in `rfc_tools.py` (GETLIST, GETDETAIL, GETOPENITEMS and similar). The server rejects names outside the list |
| `ted_search(preset, cpv, days, country, only_open, limit)` | TED | Searches EU public tenders on Tenders Electronic Daily by CPV code. Needs no SAP connection |
| `ted_notice(publication_number)` | TED | Details of one TED notice: lots, deadlines, estimated values, CPV codes, place of performance |
## Requirements
- Python 3.10 or newer
- Node.js 22.14 or newer for the RFC tools (open-rfc requires it). The OData and TED tools work without Node
- An SAP user with the authorizations you intend to grant: Gateway service access for OData; `S_RFC` plus `S_TABU_DIS` or `S_TABU_NAM` for `read_table`
- Network access from the machine running this server to the Gateway HTTP(S) port and to the application server RFC port (`33NN`, where `NN` is the system number)
## Install
Clone the repository (`git clone https://github.com/romankorim/sap-mcp-server.git`), then:
```sh
cd sap-mcp-server
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
cd rfc-bridge && npm install && cd ..
chmod +x run.sh
```
## Configure
```sh
cp .env.example .env
```
Edit `.env`:
| Variable | Meaning |
|---|---|
| `SAP_BASE_URL` | OData root on the Gateway, usually `https://<host>:<port>/sap/opu/odata/sap` |
| `SAP_CLIENT` | SAP client number |
| `SAP_USER`, `SAP_PASSWORD` | SAP user for OData and, unless `SAP_RFC_USER` is set, for RFC |
| `SAP_VERIFY_TLS` | `1` (default) verifies the server certificate; `0` only for test systems with self-signed certificates |
| `SAP_ASHOST`, `SAP_SYSNR` | Application server host and system number for RFC |
| `SAP_LANG` | RFC logon language, default `EN` |
| `SAP_RFC_CLIENT`, `SAP_RFC_USER`, `SAP_RFC_PASSWORD` | Optional separate technical user for RFC |
| `AUDIT_LOG_PATH` | Audit log file; a relative path is resolved next to `server.py`. Default `audit.jsonl` |
| `NODE_BIN` | Path to the node binary when it is not on PATH. Claude Desktop starts servers with a minimal PATH, so on macOS this is usually needed |
| `TED_LANG` | Preferred language of multilingual TED fields, default `eng` |
| `SAP_ALLOW_SENSITIVE_TABLES` | `1` disables the sensitive-table denylist in `read_table`; leave unset in production |
| `SAP_SVC_*`, `SAP_ES_*` | OData service and entity set names behind the three convenience tools; see `.env.example` |
`.env` is re-read on every tool call, so a changed host or password takes effect without restarting the client. Values in `.env` take precedence over the process environment. Single-quote values that contain spaces or shell characters.
## Run
```sh
./run.sh
```
`run.sh` loads `.env`, picks `.venv/bin/python` when it exists (otherwise `$PYTHON` or `python3`) and starts `server.py` on stdio. MCP clients start it the same way; you only run it by hand to see startup errors. On Windows call the venv interpreter directly with `server.py` as the argument; the server reads `.env` itself.
## Claude Desktop
Quit Claude Desktop completely before editing the config file. The app rewrites `claude_desktop_config.json` from memory when it quits, so it overwrites any edit made while it is running.
File location: macOS `~/Library/Application Support/Claude/claude_desktop_config.json`, Windows `%APPDATA%\Claude\claude_desktop_config.json`.
```json
{
"mcpServers": {
"sap": {
"command": "/absolute/path/to/sap-mcp-server/run.sh",
"args": []
}
}
}
```
Start the app again. The `sap` tools appear in the tools list; `sap_ping` and `rfc_ping` are the first calls to try.
## Claude Code
```sh
claude mcp add sap -- /absolute/path/to/sap-mcp-server/run.sh
```
## Security model
- Read-only by default. All OData tools use GET. All RFC tools call read-only function modules.
- `bapi_call` accepts only names in the `READ_BAPIS` set in `rfc_tools.py`. Extending the list is a code change, not a runtime option.
- `read_table` reads whatever the RFC user is authorized to read, except tables that hold credentials, secrets or personal data: `USR*`, `USH*`, `RSEC*`, `SEC*`, `PA0*`, `PA9*`, `HRP*`, `T77*`, `RFCDES`, `USRBAPI`, `SECSTORE` and the `SSF_PSE` tables are refused unless `SAP_ALLOW_SENSITIVE_TABLES=1` is set for an audited admin session. Restrict the user with `S_TABU_DIS` or `S_TABU_NAM` on the SAP side as well.
- Writes are two-step. `create_purchase_requisition` returns a proposal unless `confirm=True` is passed; the intended flow is that a person reviews the proposal and the client calls again with `confirm=True`. The SAP write itself is not implemented yet.
- Credentials come from `.env` or the environment; the server never logs them. The audit record stores the SAP user name, not the password.
- TLS certificate verification is on by default.
- The audit log is one JSON line per call, appended to `AUDIT_LOG_PATH`:
```json
{"ts": "2026-10-01T09:12:44+00:00", "user": "SAPUSER", "tool": "read_table", "args": {"table": "MARA", "fields": "MATNR,MTART,MATKL", "where": "MTART = 'FERT'", "max_rows": 20, "skip_rows": 0}, "status": "ok", "ms": 412, "note": "rfc_user=SAPUSER"}
```
Fields: `ts` (UTC), `user` (`SAP_USER`), `tool`, `args` (the call arguments, so filters and WHERE clauses end up in the log), `status` (`ok`, `error`, `proposed`, `not_implemented`, or `http_<code>` for failed OData requests), `ms`, `note`. Treat the file as sensitive: it contains every query sent to SAP.
## Known limits
- `RFC_READ_TABLE` returns each row as one character line of at most 512 bytes. Wide tables need an explicit field list. Selecting `RPRCTR` from `FAGLFLEXT` fails on the systems we tested.
- The `where` argument is split into 72-character `OPTIONS` lines on whitespace. A single token longer than 72 characters is rejected.
- `odata_query` caps `top` at 200.
- On ECC the S/4HANA `API_*` OData services do not exist. Point `SAP_SVC_*` and `SAP_ES_*` to your own Z services, or use the RFC tools.
- `create_purchase_requisition` does not write to SAP yet.
- The TED API answers with HTTP 429 above roughly 10 requests per minute.
- OData V2 only.
- The RFC bridge starts a Node process per call, with no connection pooling. Each call opens a new logon, which adds latency.
## Roadmap
- `BAPI_PR_CREATE` write path behind the existing `confirm=True` gate
- S/4HANA `API_*` style read services on ECC through Z Gateway services
- OData V4
## Maintainer
Maintained by iteractive.ai, AI implementation for SAP and enterprise systems. Commercial support and production deployments: https://iteractive.ai/services/claude-sap-mcp
## License
MIT, see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues