SAP-MCP
Provides integration with SAP on-premise systems via ADT, enabling source code navigation, editing, activation, data queries, report execution, debugging, and runtime diagnostics.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@SAP-MCPShow me the source code of class ZCL_ORDER"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
SAP-MCP
MCP server for SAP on-premise via ADT: a single process running both the MCP endpoint and web admin, connecting to multiple SAP systems simultaneously, with debugger and runtime diagnostics tools. Tool names follow the PascalCase convention of vibing-steampunk, plus multi-system model and admin page.
No ABAP objects need to be installed on SAP to use — the only exception is RunReport, and the server installs it for you (see Group D section).
Installation
Windows — double-click install.bat, or run in terminal:
install.batIt checks for Python 3.10+, creates .venv, installs dependencies, and creates systems.json from the template file. Then open systems.json and fill in the real SAP system URL / user / password, then run run.bat.
Other platforms:
python -m venv .venv && . .venv/bin/activate
pip install -e .
cp systems.example.json systems.json # sửa URL, user, password
python -m sap_mcpsystems.json contains passwords and is already in .gitignore — don't commit it.
Running the server
Windows — double-click run.bat, or:
run.bat :: cổng 8765, chế độ focused (50 tool)
run.bat 8766 :: đổi cổng
run.bat 8766 expert :: đổi cổng + bật đủ 66 toolrun.bat automatically sets the console to UTF-8 (logs contain Vietnamese, cp1252 console would crash Python), automatically creates systems.json from the template if missing, and clearly reports which PID is holding the port instead of letting uvicorn throw a raw socket error.
Open http://127.0.0.1:8765 to add/edit/test systems. MCP endpoint is at /mcp.
Connecting to an MCP client
Copy mcp.example.json to .mcp.json in the project directory, or merge the mcpServers section into the client's existing config file:
{
"mcpServers": {
"sap-mcp": {
"type": "http",
"url": "http://127.0.0.1:8765/mcp"
}
}
}Adjust the port if you run run.bat with a different port. The server must be running before the client connects — this is a streamable-http transport, the client doesn't start the process itself like with stdio.
System configuration (systems.json)
Field | Default | Description |
| — |
|
|
|
|
|
|
|
| — | Basic auth |
|
| Set |
| — | Path to custom CA (replaces |
|
| Regular HTTP ceiling, seconds |
|
| Write access only when enabled |
|
| Packages allowed for writing |
| — | Additional limits by object name |
|
| Transportable package requires TR |
|
| Group D (debugger + code execution) only when enabled |
|
| How long to stay at breakpoint, seconds |
|
|
|
Objects in the standard SAP namespace are always rejected, not configurable off.
debug_timeout is not just a number for show. Code stopped at a breakpoint holds the HTTP request that ran it, so the regular timeout (30s) cuts in mid-inspection while you're looking at variables: the background thread dies and the report result is lost, DebuggerDetach returns The read operation timed out instead of data. This ceiling is only raised while debugging (when there's a listener, or when stopped at a debuggee) — raising it for every run means a hung report holds an SAP work process for half an hour with no one watching.
Environment variables
Variable | Default | Description |
|
| Config file path |
|
| HTTP port |
|
|
|
| — | Disable feature groups, e.g. |
Group codes: C transport requests, D debugger, P runtime diagnostics (dumps, traces) — see Tools section below. Core tools belong to no group and are always enabled. Disabling both (SAP_MCP_DISABLED_GROUPS=D,P) brings focused/expert back to 30/45 tools.
Tools
Administration ListSystems GetConnectionInfo GetSystemInfo
Read GetSource GetObjectStructure GetClassInfo GetPackage
GetFunctionGroup SyntaxCheck
· expert: GetProgram GetClass GetInterface GetInclude GetFunction
GetClassInclude
GetSource can read a section instead of the whole object: around="SELECT" gets the window around the first occurrence outside comments, or from_line/to_line gets the exact line range. Each section starts with a comment line noting it's a section — only the full version can be used with UpdateSource; overwriting with a window deletes the rest. GetPackage has a max_objects ceiling and says so when truncated.
Search SearchObject GrepObjects GrepPackages
· expert: GrepObject GrepPackage
Data RunQuery GetTableContents
RunQuery runs Open SQL SELECT and returns the result table; GetTableContents builds the SELECT for you. No allow_write needed because SAP itself rejects write commands at this endpoint:
DELETE FROM t001 … → 400 Invalid query string. Only SELECT statement is allowed.The barrier for READING is the SAP user's permissions in systems.json — any table that user can read, the agent can read too, including HR tables. Don't configure a superuser.
Source navigation FindDefinition FindReferences
FindDefinition(system, 'CLAS', 'ZCL_X', symbol='cl_salv_bs_runtime_info') — the server finds the symbol in the source (ignoring comments) then resolves it there, returning the type, name, and component list. It doesn't use navigation/target even though the name sounds more appropriate: it returns the input uri itself when it can't resolve, i.e., a false positive. The working path is abapsource/codecompletion/elementinfo, and it requires the entire source in the body.
FindReferences returns where-used. SAP returns a tree mixing three node types; only indices with gradeDirect are real usages. gradeComponent is a component of the object being looked up — counting it would make an uncalled class appear to have 6 usages.
Write WriteSource EditSource Activate ActivatePackage CreatePackage
GetInactiveObjects LockObject UnlockObject
· expert: CreateObject UpdateSource DeleteObject
Miscellaneous CompareSource CloneObject PrettyPrint ImportFromFile
ExportToFile
Group C ListTransports · expert: GetTransport CreateTransport
ReleaseTransport DeleteTransport
Group D — debugger SetBreakpoint DeleteBreakpoint DebuggerListen
DebuggerPoll DebuggerStopListener DebuggerAttach DebuggerDetach
DebuggerGetStack DebuggerGetVariables DebuggerStep RunClass
RunReport RunUnitTests
Requires allow_debug: true. Usage flow:
SetBreakpoint— the line must be an executable statement, not a declaration. No need to count lines:statement="SELECT"lets the server find it (it skips comments so it won't land on a non-executable line) and reports the line number.DebuggerListen— returns immediately, listener runs in the backgroundRunClass/RunReport/RunUnitTests— runs the codeIf the breakpoint fires, step 3 returns
Stopped at breakpoint …immediately (not data).DebuggerPollreports status at any time.DebuggerAttach→DebuggerGetStack/DebuggerGetVariables/DebuggerStepDebuggerDetach— releases the debuggee; the code finishes and the result of step 3 is returned here (or atDebuggerPollif it runs long)
If no breakpoint fires, step 3 returns the result directly like a normal tool.
Why the three code-running tools run in the background. When code stops at a breakpoint, SAP holds the HTTP request that ran it — the call only returns after the debuggee is released. Calling synchronously would hang the tool itself and the agent could never call DebuggerAttach to release it: a self-deadlock. These three tools therefore run on a separate session in a background thread and respond as soon as the listener catches the debuggee.
Each system uses three separate HTTP sessions when debugging: one for the listener + debug session (stateful, held for tens of seconds), one to run code (can be blocked until the debuggee is released), one to set/delete breakpoints. Without separation they'd block each other: running code on the listener's session can only squeeze into the gap between two long-polls — exactly when SAP has no listener registered, so breakpoints never fire.
Debugging reports with selection screens. External breakpoints don't catch dialog sessions — pressing F8 in SE38 means the debugger sees nothing (tested on a real system). Use RunReport instead of RunClass in step 3: it runs the report in an external session, so breakpoints fire.
RunReport intercepts ALV display while still getting the data
(cl_salv_bs_runtime_info), so a report ending in ALV doesn't dump mid-way.
Accepts both PARAMETERS and SELECT-OPTIONS (parameter names starting with
S_) and variants.
RunReport writes to SAP, so it needs both allow_write and allow_debug,
not just allow_debug like the other debugger tools. The server installs two
objects into $TMP itself, no action needed:
ZCL_MCP_RUNNER— intermediary class, generic and never modified. It doesSUBMIT (mv_report) WITH SELECTION-TABLE mt_sel, i.e., the report name and the entire selection screen are runtime data.ZMCP_RUNNER_ARGS— a program consisting of a single comment line, rewritten before each run. The class reads it at runtime withREAD REPORT.
*@MCP TOKEN 24b8bff8dfb8477b
*@MCP REPORT ZPG_DEMO
*@MCP MAX 100
*@MCP SEL S_BUKRS S I BT
*@MCP LOW 1000
*@MCP HIGH 2000Why it still has to write: IF_OO_ADT_CLASSRUN~MAIN( out ) accepts no parameters — no query params, no body. An object's source is the only parameter-passing channel ADT REST exposes.
The most important consequence is safety: nothing agent-supplied becomes ABAP code anymore. The previous version embedded filter values into ABAP literals, so a single stray quote could inject arbitrary commands into the SAP system — that had to be escaped to be safe. Now the value sits on a comment line and reaches SAP via the RSPARAMS table, so there's no syntax left to break. Only the newline character is forbidden (it would create a fake parameter line), and values longer than 45 characters are rejected because RSPARAMS-LOW is CHAR45 — SAP would silently truncate, meaning wrong filters with no one knowing.
Each run carries a token; the class returns that token and the server cross-checks. If a parameter write fails but execution continues, the report runs with old parameters and the result gets labeled as the new run — the token is what prevents that kind of silent error.
Compared to vibing-steampunk (requires the ZADT_VSP plugin: 1 interface, 3 classes, WebSocket handler), RunReport needs less and doesn't require manual SAPC + SICF configuration:
vsp ( | SAP-MCP ( | |
ABAP objects to install | 4 | 2 |
SAPC + SICF configuration | needs basis admin | no |
Server self-installs | no | yes |
Class modified on each run | no | no |
SELECT-OPTIONS | no (hardcoded | yes |
Writes to SAP on each run | no | yes (one comment file) |
The last row is the price of not needing an admin to install anything: vsp passes parameters via WebSocket so it never touches the system, RunReport passes them via source because ADT REST doesn't expose any other channel. In exchange, the object written is a file of only comments — there's no syntax to break, and the class containing the logic stays untouched.
Group P — runtime diagnostics ListDumps GetDump StartTrace ListTraces
GetTrace DeleteTrace GetSQLTraceState · expert: DeleteTraceRequest
Short dump (ST22). ListDumps filters by user/error/program/since; GetDump returns summary (what happened, error analysis, abort location, call stack), source (the source code at the crash point), full, or meta.
Performance measurement (SAT/ATRA). StartTrace('ZPG_X', 'report') → run the code → ListTraces → GetTrace. GetTrace by default returns a per-call time profile, sorted in descending order; view='db' returns database access by table — number of calls, buffer reads, time. RunReport(..., trace=True) compacts the whole flow: it automatically sets a trace request limited to that exact report.
NET µs % GROSS µs LẦN GỌI TỪ VIỆC
3800 51.2 3800 1 CL_HTTP_SERVER_NET=======C DB: Exec Static
368 5.0 4185 1 SAPLHTTP_RUNTIME Call M. …SEND_RESPONSEThree things measured on NW 758, contradicting what vibing-steampunk assumed — each would silently break the tool if followed blindly:
vsp does | measured on NW 758 | |
Feed dump Accept type |
| 406 — must be |
Dump filtering | sends | SAP ignores it, returns the whole list |
TREX | reads it as a feed | returns a Fiori URL, no records |
So ListDumps filters on the MCP server side, and the SQL query is taken from dbAccesses of the ABAP trace instead of from ST05. GetSQLTraceState is still useful for detecting a trace left enabled — it slows down the whole system while being invisible from the outside.
StartTrace requires an object name. An unlimited trace request immediately picks up the very HTTP call that started it: the measurement is all ICFSERVICE/HTTP_HEADER_REG — it measures the ADT machinery, not your code — and yet the trace still looks like a real data set. With a limiting constraint, the measurement lands on the next run (T001, DDFTX, VARID, …).
StartTrace, DeleteTrace, and DeleteTraceRequest need allow_debug: they change the behavior of the system, and a leftover request silently measures a later run. The three read tools (ListDumps, GetDump, GetSQLTraceState) need nothing.
Status
The core tool set, the D group (debugger, including RunReport), and the P group (dump + trace) are complete — 50 tools in focused mode / 66 in expert mode, running on NetWeaver 758. The DDIC/i18n, abapGit, and ABAP helper parts are not yet implemented.
Known limitations
1. ImportFromFile / ExportToFile have no path restriction. These two tools accept any path the model gives them. import_from_file only checks os.path.isfile; export_to_file only checks os.path.isdir — no allowlist, no restriction to a workspace directory, no blocking of .. or absolute paths. This means any agent — even one influenced by slight changes to what it reads from SAP — can read any file readable by the process and push it into SAP, or write any SAP source file to any writable path. The current mitigation: run the server only on a host you control, under an account that can access only the files you want the agent to have.
2. The REST admin route has no authentication. / and /api/systems* are protected only by binding to 127.0.0.1. Anything that reaches loopback on that port can list, add, modify, or delete system configurations and trigger connection tests. Do not expose this port outside your local machine, and do not run it on shared hosts.
3. The debugger can read every variable in memory. DebuggerGetVariables returns the actual value at a breakpoint, including sensitive data sitting in variables — passwords, keys, personal data. This is the nature of debugging, not a flaw. In addition, RunClass executes arbitrary ABAP. Therefore, allow_debug is off by default and should only be enabled on development systems.
4. RunReport uses a single shared parameter file. ZMCP_RUNNER_ARGS in $TMP is overwritten before each run. In a single server, runs are already serialized (one execution channel per system), but two servers, or two users sharing a system, still overwrite each other’s parameters. A token in the output detects this and turns it into an error, instead of returning wrong data. vibing-stames, on the other hand, avoids the issue entirely with a dedicated per-session internal WebSocket, at the cost of having to configure the SICF manually.
Architecture
transport/ (HTTP, auth, CSRF) → adt/ (code) → tools/ (formatting + MCP registration). The adt/uri.py file is the single source of URI construction. The D group also uses a separate AdtSession per system (transport/debug_pool.py, grouped by channel) because the listener runs in the background, debug sessions must maintain state across multiple calls, and code already running can stop at a breakpoint — none of those sessions should be taken from the shared SessionPool.
Tools run on a worker thread, not on the event loop. FastMCP calls synchronous endpoints directly on the event loop, so if left alone, one SAP call would block the whole server: the agent would not call DebuggerPoll while RunReport was waiting, two different systems would block each other, and the admin UI would hang. _internal/registration.py wraps every tool with anyio.to_thread.run_sync before registration. The per-system serialization remains and is intentional — it exists in SessionPool, because a lock handle for SAP is only valid on one connection per system.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Hosted Amazon Seller and Vendor MCP server for Claude, ChatGPT, Cursor, Codex, Gemini, Copilot.
Official Microsoft MCP Server to query Microsoft Entra data using natural language
GibsonAI MCP server: manage your databases with natural language
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/NhatPD-VNEXT/SAP-MCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server