Ciltress/sap-abap-mcp
About
SAP ABAP MCP - ADT and JSON RPC
Details
- Author
- ciltress
- Categories
- Developer Tools, API
Jump to
Setup
Install Ciltress/sap-abap-mcp in your MCP client (Claude Desktop, Cursor, Windsurf, and others).
Repository: https://github.com/ciltress/sap-abap-mcp
Follow the installation instructions in the repository README, then restart your MCP client.
128 tools— object CRUD, source editing, locks, transports, activation, syntax checks, code completion, ABAP Unit, ATC, DDIC, abapGit, refactoring, traces, the debugger, and RFC calls.
How many a client actually sees is smaller, twice over.Profiles(ABAP_MCP_PROFILE) trade surface for context:corelists 9 tools wherealllists 129, which is ~2,700 tokens against ~17,800 on every turn for a client that cannot fetch tool schemas on demand. And the serverasks the system what it supportsbefore listing anything, so a release without the abapGit plugin is never offered the 10 tools that would answer 400. On DEV that leaves 116.
This is the SSO fork ofmcp-abap-abap-adt-apiby mario-andreschak. The main differences: Kerberos SSO instead of Basic Auth, the JSON-RPC/RFC tools, a router derived from the tool definitions, and a documented tool reference.
- Two password-less logon modes— SPNEGO/Kerberos with the logged-on Windows user's ticket, or an X.509 client certificate for a service or technical user that has no Kerberos identity. No SAP password is stored or sent either way, and both self-heal when the session expires.
- Read any object by name—readAbapObjectresolves a name to its source in one call; no ADT URL to discover or hand-craft.
- Describe a table—describeAbapTablereturns fields, DDIC types, key flags and check tables.
- Object management— search, read, create, modify, delete and activate ABAP objects.
- Survey a naming convention—searchPackagesfinds packages by pattern (["ZPP_","Z_PP"]) and expands each into its objects grouped by type, in one call.
- Source workflow— lock → edit → syntax check → activate → unlock, with transport handling.
- Code intelligence— completion, definitions, usage references, ABAP Doc, pretty printer, ATC, ABAP Unit, refactoring (rename, extract method).
- Call RFC function modules—callFunctionViaJsonRpcexecutes an RFC-enabled function module and validates the request against its real signature, read from the system.
- Batch RFC calls in one LUW—callFunctionsViaJsonRpcsends several function modules in a single request, which is what lets an update BAPI and itsBAPI_TRANSACTION_COMMITshare one LUW.
- Data access—tableContentsand ad-hocrunQuerySELECTs.
- See the running system—listLoggedOnUsersanswers "who is logged on" fromTH_USER_LIST, the data behindSM04;readProfileParametersreads RZ11 values in one round trip; andcheckLogonConfigurationsays which authentication the system actually accepts.
- Bundled agent skills— 54 skills underskills/, for ABAP (Clean ABAP, RAP, CDS, ATC, abapGit…) and general engineering (TDD, code review, diagnosing bugs), served as resources and viareadSkill.
- Self-documenting— the guides below are served by the server itself, as MCP resources (abap-adt://guides/…) and through thereadServerGuidetool, so an agent can look up a workflow or an argument mid-task without leaving the session.
- An SAP ABAP system reachable over ADT./sap/bc/adtmust be active inSICF. For the RFC tools,/sap/gw/jsonrpcmust be active as well (SAP_GWFND), and your user needsS_RFCfor the function groups you call.
- A way to log on without a password, one of:
- A working Kerberos login— the SAP system must accept SPNEGO and you must hold a valid ticket (klist). This is the default, and it needsWindowsas shipped: the bootstrap shells out toC:\Windows\System32\curl.exe --negotiate, which needs curl's Schannel/SSPI backend. On other platforms pointSSO_CURL_PATHat a curl built with GSS-API support.
- An X.509 client certificate— for a service or technical user that has no Kerberos identity. Needs neither curl nor Windows. SAP has to be set up for it: the ICM port must request a certificate (VCLIENTinicm/server_port_<n>, which overridesicm/HTTPS/verify_client), the issuing CA trusted inSTRUST, and aCERTRULEmapping to the user. Full setup — including how to check those from this server — indocs/Authentication.md.
- An OAuth 2.0 client— for an SAP BTP ABAP environment, where there is no Kerberos realm and no ICM to configure, or for an on-premise system that publishes ADT throughSOAUTH2. A BTP service key is already one of these. See§11.
git clone --recurse-submodules https://github.com/Ciltress/sap-abap-mcp.git cd sap-abap-mcp npm install npm run build
--recurse-submodulesmatters: the general engineering skills live in a submodule, and without it that directory is empty and the server offers 35 fewer skills. Already cloned? Rungit submodule update --init --recursive.
Thenpx mcp-abap-abap-adt-apipackage on npm is theupstreamserver and doesnotinclude the SSO bootstrap or the RFC tools. Build this repository from source instead.
Copy.env.exampleto.envand fill in your system:
SAP_URL=https://your-sap-server.example.com:44301 SAP_USER=YOUR_SAP_USER SAP_CLIENT=100 SAP_LANGUAGE=EN
SAP_URLandSAP_USERare required;SAP_CLIENTandSAP_LANGUAGEare optional but recommended. Three of the four logon modes neednoSAP_PASSWORDat all.
Never commit.env; it is already in.gitignore.
The threeABAP_MCP_variables exist for one reason:a client that cannot fetch tool schemas on demand pays for the entire tool list on every single turn.Claude Code defers schemas and should stay on the default; an 8B model with a 128k window is spending a sixth of its context before the conversation starts, and that is where "the same prompt works half the time" comes from.
ABAP_MCP_PROFILE— unset meansall, so an existing setup is unchanged.
Counts includehealthcheck, which sits outside every profile because it is the tool that answers "which profile am I running?".
A profile isnota convenience filter. A tool outside the active profile is not listedandnot routed, so it cannot be called — which is what makesanalysta guarantee that nothing edits source rather than a smaller menu. Out-of-profile calls get an error that says so, instead of "unknown tool", so there is no point retrying. An unrecognised profile name stops the server at startup rather than falling back toall— silently serving 129 tools to something that asked for 9 is the exact failure profiles exist to prevent.
coreis small because one tool,editAbapSource,isthe write cycle: lock, write, activate, unlock, releasing the lock even when a step fails. The four separate steps stay indevandall.
ABAP_MCP_MAX_RESPONSE_BYTES— the tool list is a fixed cost a profile can shrink; an answer is unbounded. Oncorethe whole tool list is ~11KB while oneadtDiscoveryis ~42KB. Unset follows the profile (core24,000 bytes,analyst32,000,dev48,000,allno ceiling);0removes it.
An over-budget answer iswithheld and replaced by valid JSON—status:"truncated", the originalbytes, thebudget, a 2,000-bytepreviewand anextStep— never by a cut-off fragment, which would not parse and would just invite the identical retry.
ABAP_MCP_GATE— before listing anything the server asks the system what it supports, and withholds the tools whose ADT collections are absent. On DEV that is the 10 abapGit tools and the 3 service-binding tools, which would otherwise answer HTTP 400. It costs one discovery round trip per process, can only ever shorten the list, and any failure leaves every tool listed.ABAP_MCP_GATE=offskips it.
healthcheckreports all three: the active profile,responseBudgetBytes, and anything withheld.
For a service or technical user, add a certificate — that alone switches the mode:
SAP_USER=CLAUDEAGENT # the user CERTRULE maps the certificate to SAP_CERT_FILE=C:\Users\svc_agent\SNC\sec\claudeagent.p12 SAP_CERT_PASSPHRASE=<PKCS#12 password / PSE PIN>
This ismutual TLS, not SNC— ADT is HTTPS. A certificate that already works for RFC/SNC can be reused and itsCERTRULEmapping carries over, but theSNC0ACL plays no part and the ICM needsicm/HTTPS/verify_client.docs/Authentication.mdcovers the differences, thesapgenpse export_p12/ OpenSSL 3 trap, and how to read a rejected certificate.
An OAuth 2.0 client, for BTP and for SOAUTH2
For an SAP BTP ABAP environment, or an on-premise system that publishes ADT behind an authorisation server. Setting the client id switches the mode:
SAP_OAUTH_TOKEN_URL=https://your-tenant.authentication.eu10.hana.ondemand.com/oauth/token SAP_OAUTH_CLIENT_ID=sb-abap-agent!t1234 # 'clientid' in a BTP service key SAP_OAUTH_CLIENT_SECRET=<'clientsecret'>
On-premise the endpoint is on the SAP host itself —https://<host>:<port>/sap/bc/sec/oauth2/token— and the client is the one registered inSOAUTH2.
For a system with neither Kerberos nor certificates — a sandbox, a trial, anything off the domain:
SAP_USER=CLAUDEAGENT SAP_PASSWORD=<the password>
The last resort, and not interchangeable with the other two.A missing ticket or an unmapped certificate is simply refused; awrong passwordcounts againstlogin/fails_to_user_lockand locks that user for every consumer of it, not just this server. The implementation refuses to retry a rejected password for that reason — one failed logon, latched, however many tools are called. Prefer a certificate for anything unattended.docs/Authentication.md§10has the detail.
Point the client at the built entry point with absolute paths:
{ "mcpServers": { "sap-abap-dev-100": { "command": "node", "args": ["C:/path/to/sap-abap-mcp/dist/index.js"], "env": { "SAP_URL": "https://your-sap-server.example.com:44301", "SAP_USER": "YOUR_SAP_USER", "SAP_SYSTEM_ID": "DEV", "SAP_CLIENT": "100", "SAP_LANGUAGE": "EN" } } } }
The client'senvblock wins over.env. Runnpm run startto launch the server by hand, ornpm run devto drive it through the MCP Inspector.
For a client that carries every tool schema on every turn, add a profile to that same block:
"env": { "…": "…", "ABAP_MCP_PROFILE": "core" }
The server speaks MCP overstdio, so there is no port to publish — the client starts the container and talks to it over stdin/stdout.
{ "mcpServers": { "sap-abap-dev-100": { "command": "docker", "args": ["run", "-i", "--rm", "--env-file", "C:/path/to/.env", "abap-adt-mcp"] } } }
All four logon modes work in here, but a credential is something the container has to begiven, and--env-fileis not dotenv — Docker strips no quotes, reads noexport, and drops no trailing# comment, so a value dotenv would have cleaned up arrives verbatim. Windows paths in.envhave to be replaced by the mounted ones. Kerberos is the mode that needs the most from the container and OAuth the least: a token is fetched over the network, so nothing has to be mounted at all.
Certificate mode— mount the key material read-only, and the CA that signsSAP'scertificate with it:
docker run -i --rm --env-file .env \ -v /host/certs:/certs:ro \ -e SAP_CERT_FILE=/certs/agent.p12 \ -e SAP_CA_FILE=/certs/corporate-root.pem \ abap-adt-mcp
Kerberos mode— the image carries a curl built against GSS-API andkinit, so what is left is a realm and a credential:
docker run -i --rm --env-file .env \ -v /etc/krb5.conf:/etc/krb5.conf:ro \ -v /host/agent.keytab:/krb5/agent.keytab:ro \ -e SAP_KRB_KEYTAB=/krb5/agent.keytab \ -e SAP_KRB_PRINCIPAL=SVC_AGENT@CORP.EXAMPLE.COM \ abap-adt-mcp
OAuth 2.0 mode— nothing to mount; the credential is fetched:
docker run -i --rm --env-file .env \ -e SAP_OAUTH_TOKEN_URL=https://your-tenant.authentication.eu10.hana.ondemand.com/oauth/token \ -e SAP_OAUTH_CLIENT_ID='sb-abap-agent!t1234' \ -e SAP_OAUTH_CLIENT_SECRET=<clientsecret> \ abap-adt-mcp
docker run -i --rm --env-file .env \ -e SAP_USER=YourUser \ -e SAP_PASSWORD=YourPassword \ abap-adt-mcp
Three things to know before you reach for it:
- A keytab, not your own ticket.A container cannot borrow the session's credential the way an interactive logon does. On a Linux host you can mount the ticket cache you already have (-v /tmp/krb5cc_1000:/krb5/ccache:ro -e KRB5CCNAME=FILE:/krb5/ccache), but it expires with the host's.From a Windows host neither works: the TGT lives in the LSA cache and cannot be written to a file — use certificate mode, which needs none of this. The keytab is the only credential that runs unattended, and the only one that outlives the ticket lifetime.
- Verify SAP's certificate, or know that you are not.The image trusts only the public CA bundle, so an internal CA the host trusts is unknown in here and the handshake fails withUNABLE_TO_GET_ISSUER_CERT_LOCALLY. Mount the root CA and pointSAP_CA_FILEat it. Carrying a desktop.envin wholesale hides this instead:NODE_TLS_REJECT_UNAUTHORIZED=0is a development setting and has no business in a deployed image.
- Build from a--recurse-submodulesclone.skills/Developmentis a submodule; without it the image ships 35 fewer skills.
The image is Debian rather than Alpine for one reason: Alpine's curl is built without GSS-API, and such a curl does not fail — it simply never sends a token, and SAP answers the same 401 an expired ticket produces. What is missing is named at startup, on stderr, before a session is attempted. To ask the container what credential it ended up with, give it a command instead of the server:
docker run --rm --env-file .env -v /host/agent.keytab:/krb5/agent.keytab:ro \ -e SAP_KRB_KEYTAB=/krb5/agent.keytab abap-adt-mcp klist
docs/,skills/andAGENTS.mdare copied into the image on purpose — the server reads them at runtime to servereadServerGuide,readSkilland theabap-adt://resources.docs/Authentication.md§7has the full container setup, mode by mode.
A server is bound to one system and one client for its whole life — neither can be switched at runtime. So register one entry per system/client and give each itsSAP_SYSTEM_ID:
"sap-abap-dev-100": { "env": { "SAP_SYSTEM_ID": "DEV", "SAP_CLIENT": "100", "…": "…" } }, "sap-abap-dev-200": { "env": { "SAP_SYSTEM_ID": "DEV", "SAP_CLIENT": "200", "…": "…" } }, "sap-abap-q01-100": { "env": { "SAP_SYSTEM_ID": "Q01", "SAP_CLIENT": "100", "…": "…" } }
Each server then announces itself in the MCPinstructionsit returns at connect time:
This server is bound to SAP systemDEV, client 100 (https://…:44301). It cannot switch system or client at runtime — both are fixed by the environment it was started with. If a request names a different system or client, use the MCP server configured for that one; if none is registered, say so rather than acting here.
That is what lets an agent route "please check this in DEV client 200" to the right server without calling anything.healthcheckreports the same identity for a server that needs asking directly.
Read any object by name — no URL discovery needed:
{"tool":"readAbapObject","args":{"objectName":"ZCL_MY_CLASS"}} {"tool":"describeAbapTable","args":{"tableName":"T000"}}
Survey a whole naming convention — patterns are normalised for you, sozpp_labbecomesZPP_LAB:
{"tool":"searchPackages","args":{"patterns":["ZPP_","Z_PP"]}} // -> each package with its objects grouped by type, sub-packages, and a \truncated\ flag
Call a function module — the signature is read first and the request validated against it:
{"tool":"readAbapFunctionModule","args":{"functionModuleName":"STFC_CONNECTION"}} {"tool":"callFunctionViaJsonRpc","args":{"functionModuleName":"STFC_CONNECTION", "inputParameters":{"REQUTEXT":"hello"}}}
A BAPI and its commit must travel inonebatch, or the commit lands in its own LUW and the BAPI's changes are lost:
{"tool":"callFunctionsViaJsonRpc","args":{"calls":[ {"functionModuleName":"BAPI_USER_LOCK","inputParameters":{"USERNAME":"DEVUSER"}}, {"functionModuleName":"BAPI_TRANSACTION_COMMIT","inputParameters":{"WAIT":"X"}} ]}}
The full write cycle (lock → modify → check → activate → unlock), the debugger, ATC and every other workflow are indocs/MCP-Tools.md§4.
Three tools cover most of what you need, and each takes anamerather than an ADT URL:
{"tool":"readAbapObject", "args":{"objectName":"ZCL_MY_CLASS"}} {"tool":"describeAbapTable","args":{"tableName":"T000"}} {"tool":"searchPackages", "args":{"patterns":["ZPP_","Z_PP"]}}
readAbapObjectreturns the metadataandthe source in one call. When a name belongs to several objects —ZPP_EXT_LABEL_DATAis both a function group and a function module — it picks the more specific one and tells you, viaambiguous:trueandalternatives; passobjectTypeto force the choice. Objects with no source come back withhasSource:falseand a pointer to the right tool.
describeAbapTablegives field names, DDIC types, lengths, key flags, data elements, domains andcheck tables— the check table is the foreign-key target, which is the quickest way to see how two tables join.objectStructurereturns no fields for a table, andtableContentsreturns rows rather than a definition, so neither answers "what does this table look like".
Rules worth putting in your client's system prompt
- Prefer the by-name tools.Only fall back tosearchObject→objectStructure→getObjectSourcewhen you need the intermediate results. Never hand-craft a/sap/bc/adt/...path.
- Select efficiently.SAP tables are large. Always constrainSELECTs with aWHEREclause, and useSELECT SINGLE(all key fields known) orUP TO n ROWSotherwise.
SELECT vgbel FROM vbrp WHERE vbeln = @lv_vbeln INTO @DATA(lv_vgbel) UP TO 1 ROWS. EXIT. ENDSELECT.
SAP is decoupled from your file system: reading source returns it as a tool result only, and writing a local file changes nothing in SAP. Local copies are useful for diffing, nothing more.
Earlier READMEs listedGetTable,GetStructureandGetTypeInfo. Those belong to the separatemcp-abap-adtproject, not to this server.
npm run build # tsc -> dist/ npm test # jest: parser, tool catalogue, JSON-RPC handler (no SAP system needed) npx tsc --noEmit # type check only
Against arealsystem (needs a Kerberos ticket), the end-to-end check is:
Sign in to leave a review
Use Google, GitHub, or an email account so ratings stay tied to real people.
No reviews posted yet.





