SAP MCP-RFC Bridge
by ChinhHN-DEV
README.md
# MCP-RFC bridge for SAP ABAP (RFC-only / SAProuter / ECC R3)
An MCP server that lets an MCP client (e.g. Claude Code) work with SAP ABAP
objects **over RFC** instead of ADT/HTTP. Because it speaks RFC + SAProuter, it
reaches systems that expose no HTTP/ADT endpoint — including classic ECC / R/3.
> Inspired by the SAP Community blog "Using Claude for SAP ABAP development on
> RFC-only / SAProuter systems (even ECC R/3)". This is an independent,
> prototype implementation — not an official SAP or Anthropic product.
## Architecture
```
Claude Code --stdio/MCP--> server.py (FastMCP)
|
sap/tools.py (wraps standard SAP FMs)
|
sap/connection.py (pyrfc Connection, reconnect)
|
sap/config.py (.env -> conn params, SAProuter)
|
pyrfc --RFC/SAProuter--> SAP system
```
## Tools exposed
| Tool | Purpose | Needs write? |
|-----------------------------|--------------------------------------|:------------:|
| `sap_ping` | Test connection, system info | no |
| `sap_read_table` | Read a table (RFC_READ_TABLE) | no |
| `sap_read_program` | Read ABAP report source | no |
| `sap_read_function_module` | Read FM source + interface | no |
| `sap_read_class` | Read class/interface source | no |
| `sap_read_method` | Read ONE method of a class | no |
| `sap_class_api` | Public API only (+ dependency APIs) | no |
| `sap_dead_code` | Unused-method scan for a package | no |
| `sap_search_objects` | Search TADIR for repository objects | no |
| `sap_ddic_info` | Describe table/structure fields | no |
| `sap_list_dumps` | Recent ST22 runtime errors | no |
| `sap_list_transports` | List transport requests (E070) | no |
| `sap_read_transport` | One transport + its object list | no |
| `sap_where_used` | Where-used via WBCROSSGT index | no |
| `sap_read_screen` | Read Dynpro definition (via Z-FM) | no |
| `sap_read_gui_status` | Read GUI status/CUA (via Z-FM) | no |
| `sap_syntax_check` | Syntax-check source, no save (Z-FM) | no |
| `sap_read_cds` | Read CDS/DDL source (Z-FM) | no |
| `sap_textpool_read` | Read program text elements (Z-FM) | no |
| `sap_run_unit_tests` | Run ABAP Unit for a class (Z-FM) | no |
| `sap_run_atc` | Run ATC check (Z-FM stub) | no |
| `sap_adt_dispatch` | Raw ZMCP_ADT_DISPATCH call | read: no / write: **yes** |
| `sap_activate` | Activate inactive objects (Z-FM) | **yes** |
| `sap_textpool_write` | Write program text elements (Z-FM) | **yes** |
| `sap_write_program` | Create/update program (INACTIVE) | **yes** |
| `sap_run_rfc` | Call an arbitrary RFC-enabled FM | **yes** |
### Custom dispatcher `ZMCP_ADT_DISPATCH`
The Z-FM tools above route through a custom RFC-enabled FM **`ZMCP_ADT_DISPATCH`**
(function group `ZMCP_ADT_UTILS`) installed on the SAP system — the escape hatch
for features no standard RFC FM exposes. It runs *inside* SAP, so it can call
non-remote workbench APIs (`SYNTAX-CHECK`, `RS_WORKING_OBJECTS_ACTIVATE`,
`READ/INSERT TEXTPOOL`, `DDDDLSRC`, ABAP Unit, ATC) and return JSON.
Install the two FMs from [`abap/`](abap/) on any SAP system you want to drive
(see [`abap/README.md`](abap/README.md) for the interfaces and step-by-step
setup). Action → tool map:
| Action | Tool | Underlying ABAP |
|--------|------|-----------------|
| `SYNTAX_CHECK` | `sap_syntax_check` | `SYNTAX-CHECK FOR` |
| `READ_DDLS` | `sap_read_cds` | `SELECT … FROM DDDDLSRC` |
| `RUN_UNIT_TESTS` | `sap_run_unit_tests` | `CL_AUCV_TEST_RUNNER_STANDARD` |
| `ATC_CHECK` | `sap_run_atc` | `CL_SATC_API_FACTORY` (stub — fill in) |
| `ACTIVATE` | `sap_activate` | `RS_WORKING_OBJECTS_ACTIVATE` |
| `DYNPRO_*` / `CUA_*` | `sap_read_screen` / `sap_read_gui_status` | `RPY_DYNPRO_*` / `RS_CUA_INTERNAL_*` |
`RUN_UNIT_TESTS` and `ATC_CHECK` use release-sensitive class APIs — verify/adjust
the ABAP in SE24 for your system (`ATC_CHECK` ships as an explicit stub). Every
write action goes through the same `SAP_ALLOW_WRITE` guard.
**Text elements** use a separate dedicated FM, **`ZMCP_ADT_TEXTPOOL`** (same
function group), driven by `sap_textpool_read` / `sap_textpool_write`. Flag it
Remote-Enabled in SE37 to use it. `sap_textpool_write` defaults to
`WRITE_INACTIVE` (staged; published on program activation), matching the
"nothing goes live until activate" model; pass `active=true` to write directly.
Write tools are disabled unless `SAP_ALLOW_WRITE=true`.
**Sensitive-table policy:** `sap_read_table` refuses tables holding
credentials/PII (USR02, PA0*, SECSTORE*, …). Override the pattern list via
`SAP_TABLE_BLOCKLIST` (comma-separated, `*` wildcards) in `.env`.
**Package whitelist for writes:** set `SAP_ALLOWED_PACKAGES` (e.g.
`$TMP,ZPK_SANDBOX*`) to restrict `sap_write_program` to specific packages.
Unset = no package restriction (only `SAP_ALLOW_WRITE` applies).
**FM deny list:** `sap_run_rfc` refuses high-risk function modules (OS command
execution, arbitrary ABAP, mass delete, raw SQL, user admin, file transfer,
kernel admin) even with `SAP_ALLOW_WRITE=true`. Override the pattern list via
`SAP_FM_DENYLIST` (comma-separated, `*` wildcards).
## Project layout
```
MCP_SAP_PRIVATE/
bootstrap.bat copy to a new machine: clones repo + runs setup
setup.ps1 / setup.bat installer + MCP registration
README.md
mcp/ the MCP server (everything it needs to run)
server.py MCP server entrypoint (FastMCP, stdio)
test_connection.py standalone connectivity check
requirements.txt
.env / .env.example base/shared settings (real .env is git-ignored)
profiles/ one <name>.env per SAP system (multi-system)
ds4.env.example per-system profile template
sap/ Python package (the bridge)
config.py .env -> pyrfc params, SAProuter, SDK discovery
connection.py pyrfc wrapper, reconnect, DLL registration
tools.py FM wrappers behind each MCP tool
vendor/ third-party / licensed binaries
nwrfcsdk/ SAP NW RFC SDK (bundled, auto-detected)
pyrfc-3.3.1-*.whl prebuilt pyrfc wheel (offline install)
abap/ ABAP to install on the SAP system (reusable)
README.md install guide + FM interfaces
zmcp_adt_dispatch.abap ZMCP_ADT_DISPATCH (screens, syntax, activate, …)
zmcp_adt_textpool.abap ZMCP_ADT_TEXTPOOL (text elements)
```
## Deploy via git (clone-and-go)
This repo is meant for a **private** git remote: the SAP NW RFC SDK
(`mcp/vendor/nwrfcsdk`) and the pyrfc wheel are committed, so a fresh clone runs
without any SAP-portal download. Secrets are **not** committed — `.env` and
`mcp/profiles/*.env` are git-ignored; each machine fills them in locally.
### First push (once, from this folder)
```powershell
git init
git add .
git commit -m "SAP MCP-RFC bridge"
git remote add origin https://github.com/ChinhHN-DEV/MCP_SAP_PRIVATE.git
git push -u origin main
```
> The commit includes the ~60 MB SDK. Keep the remote **private** — the SDK is
> licensed and must not be public.
### On a new machine (one command)
Edit the `REPO=` line in **`bootstrap.bat`**, copy just that one file to the new
machine, and run it (or `bootstrap.bat <git-url>`). It clones the repo and runs
`setup.ps1`. Afterwards, create your connection config:
```powershell
# single system:
copy mcp\.env.example mcp\.env # then edit
# or multi-system:
copy mcp\profiles\ds4.env.example mcp\profiles\ds4.env # then edit, re-run setup.ps1
```
## Quick install on a new machine
Copy the whole folder over, then from inside it run **one** of:
```powershell
# Windows (PowerShell)
powershell -ExecutionPolicy Bypass -File .\setup.ps1
```
…or just **double-click `setup.bat`**.
`setup.ps1` finds Python, installs `python-dotenv` + `mcp`, installs the
matching `pyrfc` wheel (from `vendor/`, or downloads the right one), verifies
the bundled SDK, creates `.env` from the template, and registers the `sap-rfc`
MCP server with Claude Code using this folder's current absolute path — so it
works no matter where the folder was moved to. Afterwards just edit `.env` and
run `python mcp\test_connection.py`. The manual steps below are the same thing by
hand.
## Multiple SAP systems
Run one MCP server per system, each selected by a **profile** file. Claude Code
then sees separate, namespaced tool sets (`mcp__sap-ds4__…`, `mcp__sap-qa4__…`)
and picks the right system by server name.
1. Put per-system logon in `mcp/profiles/<name>.env` (copy `mcp/profiles/ds4.env.example`):
```
profiles/ds4.env # DEV, SAP_ALLOW_WRITE=true
profiles/qa4.env # QA, read-only
profiles/prd.env # PROD, read-only
```
Shared policy (`SAP_TABLE_BLOCKLIST`, `SAP_FM_DENYLIST`, …) stays in the base
`.env`; each profile **overrides** the base and holds that system's
credentials + `SAP_ALLOW_WRITE`.
2. Run `setup.ps1` — it registers one server `sap-<name>` per profile file
(falls back to a single `sap-rfc` from `.env` when no profiles exist).
3. Test a specific system:
```powershell
python mcp\test_connection.py --profile qa4
```
Profile selection precedence: `--env-file <path>` > `--profile <name>` >
`SAP_PROFILE` env var > base `.env`. `sap_ping` reports which `profile` answered.
Keep DEV writable and QA/PROD read-only (`SAP_ALLOW_WRITE=false`) so the deny
guards differ per system automatically.
## Setup (manual)
### 1. SAP NW RFC SDK (prerequisite for pyrfc)
`pyrfc` wraps the **SAP NetWeaver RFC SDK** (licensed; from the SAP Support
Portal). This repo bundles the **Windows** SDK at `mcp/vendor/nwrfcsdk`, auto-detected
and registered at runtime (via `os.add_dll_directory`). No PATH edits needed. To
use a different SDK location, set `SAPNWRFC_HOME` — it takes precedence over the
bundled copy.
#### OS runtime prerequisite for the SDK (SAP Note 2573790)
The NW RFC SDK DLL needs the **Microsoft Visual C++ 2015-2022 Redistributable
x64** (`vcruntime140.dll`) present — without it, `import pyrfc` fails with a
cryptic load error. `setup.ps1` warns and links the installer if it is missing.
Patch level of an installed SDK: `findstr Patch sapnwrfc.dll`.
### 2. Install Python dependencies
`pyrfc` has no PyPI wheel for recent Python, so a matching prebuilt wheel is
vendored in `mcp/vendor/`:
```powershell
python -m pip install python-dotenv mcp
python -m pip install mcp/vendor/pyrfc-3.3.1-cp312-cp312-win_amd64.whl
```
The wheel is `cp312 / win_amd64` (Python 3.12, Windows 64-bit) from the
`SAP-archive/PyRFC` GitHub releases. For a different Python version, fetch the
matching wheel from there.
### 3. Configure the connection
```powershell
Copy-Item mcp\.env.example mcp\.env
# then edit .env
```
SAProuter example — either put the full route in `SAP_ASHOST`:
```
SAP_ASHOST=/H/saprouter.example.com/S/3299/H/sapappserver.internal
SAP_SYSNR=00
```
…or keep a plain host and set the router prefix separately:
```
SAP_ASHOST=sapappserver.internal
SAP_SYSNR=00
SAP_SAPROUTER=/H/saprouter.example.com/S/3299
```
### 4. Test connectivity (no MCP needed)
```powershell
python mcp\test_connection.py
```
### 5. Register the server with Claude Code
```powershell
claude mcp add sap-rfc -- python "c:\Users\Administrator\Desktop\MCP_SAP_PRIVATE\mcp\server.py"
```
Or add to your MCP client config:
```json
{
"mcpServers": {
"sap-rfc": {
"command": "python",
"args": ["c:\\Users\\Administrator\\Desktop\\MCP_SAP_PRIVATE\\mcp\\server.py"]
}
}
}
```
## Safety
* **Read-only by default.** Keep `SAP_ALLOW_WRITE=false` unless you intend to
modify objects, and only enable writes against a **DEV** system.
* Writes are saved **INACTIVE** — nothing goes live until you activate it in SAP.
* Use an RFC user with the **minimum** authorizations required.
* `.env` holds credentials — it is git-ignored; never commit it.
## Verified on
Read tools (`sap_ping`, `sap_read_table`, `sap_read_program`,
`sap_read_function_module`, `sap_read_class`, `sap_search_objects`) verified
end-to-end against an **S/4HANA system (SAP_BASIS 758)** reached through a
SAProuter. Class source uses the remote-enabled `SIW_RFC_READ_CLIF_SOURCE`.
## Caveats / limits over pure RFC
* Only **remote-enabled** function modules are callable over RFC. Many workbench
FMs (e.g. the `SEO_*` class APIs, the ABAP `SYNTAX-CHECK`) are **not** RFC-
enabled, so some ADT-style features simply aren't reachable this way.
* **CDS/DDL source read is not available** over pure RFC on the tested system —
there is no remote-enabled FM that returns DDL source text. Use ADT for that.
* **Object activation and syntax check** are likewise not wired up: no clean
remote-enabled FM was found on the tested release. Writes therefore stay
**INACTIVE**; activate in SAP GUI/ADT.
* `RFC_READ_TABLE` cannot select rows wider than 512 bytes or long/deep fields;
long WHERE clauses are auto-wrapped to the 72-char OPTIONS line limit.
* FM availability/interfaces can still vary by release — validate on your target.
```
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues