abap-adt-mcp

MCP serverAI & models

ABAP development from Claude and other MCP hosts: source, transports, tests, ATC, debugger

Unavailable. This server has no hosted endpoint yet, so ahel can't serve it.

Connect ahel once, and every AI you use reads what you have installed.

From the project's README

As published by williansaez/abap-adt-mcp in README.md.

Let Claude read, write, test and check ABAP code on your SAP systems.

English · Português (Brasil) · Deutsch

abap-adt-mcp is a Model Context Protocol server. Run it next to Claude Desktop, Claude Code or any other MCP host, point it at one or more SAP systems, and the model gets the same ADT REST endpoints Eclipse uses: search objects, read and edit source, create transports, activate, run ABAP Unit and ATC, read short dumps, query tables. One server exposes 173 tools over as many SAP systems as you configure, S/4HANA Cloud and on-prem alike.

Use it deliberately, and prefer development systems. A destination without a policy block is fully writable within your SAP authorizations. Per-destination guard rails (read-only, allowed packages, denied tables) are enforced by the server itself, whatever the host approves, so a careless prompt cannot reach the wrong system.

Table of contents

  • What is new in 2.0.0
  • Setup
  • What to ask the model
  • Workflows in detail
  • Built-in prompts
  • Other ways to install
  • Authentication
  • Keeping it safe
  • Audit log
  • S/4HANA Cloud versus on-prem
  • Configuration reference
  • HTTP transport (optional)
  • Tool catalog (all 173 tools, by toolset)
  • Compared with SAP's official ADT MCP Server
  • Skills and plugin
  • Troubleshooting
  • Testing and contributing
  • License

What is new in 2.0.0

Released 2026-09-08. The full list is in CHANGELOG.md; what matters when you upgrade:

  • Node.js 22.12 or newer is required (breaking). Node 18 and 20 are past end of life and receive no security fixes; a server holding SAP credentials should not run on them. On an older Node, npm prints EBADENGINE and the server is untested; install the current LTS and restart the host. The container image was already on node:22-alpine.
  • tls.servername on a destination. For a system reached by IP address or short hostname whose certificate carries the fully qualified name: the name is verified and sent as SNI, verification stays on, and insecureTls is no longer the only way through that landscape. listSystems shows servername NAME.
  • Certificate errors teach the fix. A failed handshake reaches the model as kind: "tlsCertificate" with a hint that names the destination: unknown issuer gives the openssl s_client line for that host and points at tls.ca, a name mismatch quotes the names Node reported and points at tls.servername, an expired certificate says that only renewal fixes it. insecureTls is mentioned last.
  • insecureTls stays, per destination, off by default, announced at startup; SECURITY.md records why.
  • Supply chain. puppeteer-core 25 removes the last open Dependabot alert from the dependency tree (npm audit reports zero vulnerabilities); Dependabot now waits a cooldown before proposing updates and groups security updates into one pull request; dotenv is loaded quietly so stdout stays a clean JSON-RPC channel.

Upgrading from 1.x needs no configuration change: systems.json, the policies, the tool names and the environment variables are unchanged.

Setup

Three things before you start:

  • Node.js 22.12 or newer (22 or 24 LTS; 2.0.0 dropped Node 18 and 20). Download the LTS installer from nodejs.org; it bundles npm and npx, which is all the host needs. No terminal is required to check: if Node is missing, the host's log says spawn npx ENOENT when it tries to start the server (see step 2).
  • Access to the SAP system. On S/4HANA Cloud (public edition) there is nothing to configure on the SAP side for named users: your user needs the business role that allows Eclipse ADT on the tenant (SAP_BR_DEVELOPER in the standard delivery); if Eclipse ADT works for you, this server works too. On-prem, the /sap/bc/adt service must be active in transaction SICF (a Basis task) and your user needs the usual ADT development authorizations. Only unattended oauth clients need a Communication Arrangement, see Authentication.
  • A Chromium browser (Chrome, Edge or Brave) on the machine when you use browser SSO.

1. Describe your SAP systems

Create a folder .abap-adt-mcp in your home directory and a file systems.json inside it, one entry per system (a "destination"). Without a terminal: on macOS open Finder, press Shift-Cmd-G, enter ~, create the folder (Finder asks you to confirm a name starting with a dot; Shift-Cmd-. shows hidden folders), then save the file there from any text editor. On Windows the folder is C:\Users\<you>\.abap-adt-mcp, created in File Explorer like any other. One S/4HANA Cloud tenant with browser SSO needs exactly this:

{
  "DEV": {
    "url": "https://myXXXXXX.s4hana.cloud.sap",
    "client": "080",
    "authType": "sso",
    "default": true
  }
}

url is mandatory; client is the client your SSO session lands on (on the tested tenants the development system logged on to 080 and the customizing and test systems to 100; the About entry in the launchpad's user menu shows it); authType defaults to sso and "default": true lets you omit the destination name in every call. The key (DEV) is your choice and is the name you will use in chats. Several systems, with guard rails, look like this (or copy systems.example.json):

{
  "DEV": {
    "url": "https://myXXXXXX.s4hana.cloud.sap",
    "client": "080",
    "authType": "sso",
    "default": true,
    "policy": { "allowedPackages": ["Z*"] }
  },
  "PRD": {
    "url": "https://myYYYYYY.s4hana.cloud.sap",
    "client": "100",
    "authType": "sso",
    "policy": { "readOnly": true, "deniedTables": ["PA*", "HR*", "USR02"], "allowFreeSql": false }
  },
  "ONPREM": {
    "url": "https://sap.example.com:44300",
    "client": "100",
    "authType": "basic",
    "user": "DEVELOPER",
    "password": "${env:ONPREM_PASSWORD}",
    "policy": { "allowedPackages": ["Z*", "$*"] },
    "tls": { "ca": "/etc/ssl/corp-ca.pem" }
  }
}

The pattern for any productive or test system is the PRD entry: add "policy": { "readOnly": true } and the server refuses every write there, whatever the model is asked. sso opens a real browser once for S/4HANA Cloud named users; basic is for on-prem users and Communication Users; oauth is for unattended clients. ${env:VAR} pulls a secret from the environment so it never sits in the file, policy is enforced by the server, and tls.ca adds a corporate CA with verification kept on (tls.servername names the certificate when the system is reached by IP address). $* (local packages) is listed only on the on-prem entry because the tested Public Cloud tenant refuses $TMP.

If you have a terminal, restrict the file to your user:

chmod 600 ~/.abap-adt-mcp/systems.json

You can skip this step when the file holds no inline passwords (an SSO-only file, or secrets referenced as ${env:VAR}): the server then only prints a warning if the file is readable by others. It refuses to start only when a shared-readable file contains inline passwords, client secrets or git passwords. Windows has no file modes; the check is skipped there.

2. Register the server in your host

The package is on npm as abap-adt-mcp (published through trusted publishing with provenance), so npx is all you need.

Claude Code, one line:

claude mcp add abap-adt-mcp -e SAP_SYSTEMS_FILE=$HOME/.abap-adt-mcp/systems.json -- npx -y abap-adt-mcp

Claude Desktop (Settings > Developer > Edit Config, then quit and reopen the app). Replace me with your own user name; on Windows write the path as C:/Users/<you>/.abap-adt-mcp/systems.json:

{
  "mcpServers": {
    "abap-adt-mcp": {
      "command": "npx",
      "args": ["-y", "abap-adt-mcp"],
      "env": { "SAP_SYSTEMS_FILE": "/Users/me/.abap-adt-mcp/systems.json", "MCP_TOOLSETS": "focused" }
    }
  }
}

MCP_TOOLSETS=focused publishes the 114 development tools instead of all 173, which keeps the tool schemas from eating the chat's context window; drop it when you need the debugger, traces, abapGit, RAP or refactoring toolsets. The same JSON works in Cursor, Cline and other hosts that read an mcpServers map; VS Code names the map servers instead, so rename the top-level key there (docs/HOSTS.md has the per-host form). The key abap-adt-mcp is the name the host shows for the server and the prefix of every tool (mcp__abap-adt-mcp__searchObject in Claude Code); public ABAP skills written for this server look for that name, so a different key only stops those skills from recognising the server, nothing else breaks.

After the restart, Claude Desktop lists abap-adt-mcp with a status under Settings > Developer, and the tools menu below the chat input (the sliders icon) shows the server with its tools. If nothing appears, read the host's log: at the time of writing Claude Desktop writes mcp.log and mcp-server-abap-adt-mcp.log to ~/Library/Logs/Claude on macOS and %APPDATA%\Claude\logs on Windows, and Claude Code shows the state with /mcp. Everything the server prints (startup warnings, the audit-file warning, MCP_PROFILE_GATE=warn messages) goes to stderr and lands in that log. Both Claude Desktop and Claude Code ask before running a tool you have not approved permanently; that dialog is host behaviour and independent of the destructiveHint annotation, so treat it as a courtesy and the policy block as the guarantee.

3. Say hello

Open a new chat and type (replace DEV with the key you chose in systems.json):

List my SAP systems, log in to DEV and show me the source of class CL_ABAP_CHAR_UTILITIES.

The model calls listSystems, login (a browser window appears for SSO destinations; tick "stay signed in" and later logins are silent), searchObject and getObjectSource. When the source comes back, you are done. login is optional in every mode: the dispatcher performs the browser login before the first call on an SSO destination, and basic and oauth destinations authenticate on their first request. Call it explicitly only to force a fresh login or to prove the credentials before anything else. Asking for healthcheck returns the server version, the destination names, the default destination, the active toolsets and the tool count; systemProfile tells whether a destination is S/4HANA Cloud or on-prem and which toolsets it cannot serve.

What to ask the model

The server is a toolbox the model picks from: ask in plain language and it chooses the sequence. Things that work well from the first session:

AskTools the model reaches for
"Explain what method GET_DATA of ZCL_ORDER_SERVICE does."searchObject, getMethodSource
"Where is table ZTABLE still used, and by which programs?"whereUsed, sourceTextSearch, grepPackage
"Show me the fields and associations of CDS view ZI_PRODUCT."cdsViewInfo, objectStructureElements
"Add a null check at the top of GET_DATA, activate and run the unit tests."resolveTransport, syntaxCheckCode, editObjectSource (with activate=true), unitTestRun, objectDiff
"Create class ZCL_HELLO in package ZDEMO that prints Hello World, with a unit test."validateNewObject, resolveTransport, createObject, setObjectSource, createTestInclude, unitTestRun
"Run ATC on package ZFIN and apply every quickfix that is safe."createAtcRun, atcWorklists, atcQuickfixProposals, atcApplyQuickfix, atcSummary
"What changed in transport DEVK900123? Review it and tell me if it is safe to release."transportDetails, transportUnifiedDiff
"Why did the last short dump of user DEVELOPER happen? Propose a fix."dumps, dumpDetails, getObjectSource
"Is ZCL_ORDER_SERVICE ready for ABAP Cloud? Which SAP objects block it?"apiReleaseState, createAtcRun
"Select the ten newest rows of ZTABLE where STATUS = 'X'."runQuery (or tableContents when the data preview refuses a table)
"Try this snippet and show me the output."runSnippet
"Which toolsets does DEV support? Is the debugger available there?"systemProfile

On a plain on-prem system the create example also works with $TMP and no transport; the tested S/4HANA Cloud tenant refused $TMP, so there you name a customer package and its transport (see S/4HANA Cloud versus on-prem).

Habits the server bakes in, so you do not have to spell them out: write tools lock and unlock by themselves; activate=true activates in the same call; every error is JSON with kind, hint and nextTools, so the model recovers instead of retrying blindly; expired sessions are re-authenticated and the call retried once; large results are paged inside a 40,000-character budget (MCP_MAX_RESPONSE_CHARS) and report hasMore; long calls send MCP progress notifications to hosts that pass a progressToken (plus a heartbeat every 10 seconds). The canonical create and edit flows travel in the MCP instructions field, and every tool carries readOnlyHint/destructiveHint annotations so hosts that gate approval by annotation can ask only on writes.

Workflows in detail

The full tool-by-tool sequences, argument shapes and recipes are in docs/WORKFLOWS.md; this section is the short version.

Every tool except listSystems and healthcheck takes an optional destination; it is required when several systems are configured and none is marked default (or named in SAP_DEFAULT_DESTINATION).

URLs and names. searchObject returns the object URL, for example /sap/bc/adt/oo/classes/zcl_example; the source URL is that plus /source/main; class includes (implementations, test classes) use the URLs from classIncludes as they are. The tools inherited from several upstream generations name that URL differently (objSourceUrl, objectSourceUrl, objectUrl, classUrl, url, mainUrl), so the dispatcher maps the names onto each tool's schema and strips or appends /source/main where needed: the value from searchObject can be passed to any of them. Class-level tools (getMethodSource, setMethodSource, whereUsed, cdsViewInfo) also accept the plain name.

Find and read code. searchObject finds objects by name. By content, sourceTextSearch uses the ADT text index and grepPackage greps package sources client-side with context lines (the fallback when a tenant has no text index). packageTree, whereUsed, cdsViewInfo, typeHierarchy and classComponents give IDE-style navigation. getObjectSource reads a source (paged with startLine/maxLines, version=inactive for unactivated code), getMethodSource one method, and exportPackageSources writes a package tree to disk in abapGit layout for local tools.

Edit safely. Writes lock, write and unlock by themselves and activate when you pass activate=true:

  1. resolveTransport(objSourceUrl) returns the transport that already records the object, the newest modifiable one for its package, or needsTransport: false for local packages; createIfMissing=true creates one when none exists.
  2. syntaxCheckCode on the intended source: optional for a one-line change, cheap insurance for anything larger.
  3. editObjectSource(objectSourceUrl, replacements=[{oldText, newText}], activate=true, transport) for targeted changes (the server re-reads SAP first; each oldText must match exactly once, otherwise the call fails with "0 matches" or the line numbers of every match and nothing is written), setMethodSource(classUrl, methodName, source, activate=true, transport) to swap one METHOD ... ENDMETHOD block in the implementation (pass the full block or only the body; the definition part stays as it is; include and className select local or test classes; an unknown method is refused with the list of methods present), setObjectSource for full rewrites.
  4. Read the activation field of the result; fix and write again, or activateByName / activatePackage later.
  5. unitTestRun(url), then objectDiff(objectUrl) to show what changed against the previous revision.

lock/unLock only hold a lock across several writes; listLocks and forceUnlock recover from a failed write. A lock held by another session (an open Eclipse window, for example) is reported as foreign: dropSession and forceUnlock cannot release it, only that session or SM12 can.

Create objects and transports. loadTypes (pick the objtype, for example CLAS/OC), validateNewObject, then resolveTransport(objSourceUrl="/sap/bc/adt/packages/<pkg>", devClass="<pkg>") for the package itself, since the object has no URL yet (or createTransport), then createObject(objtype, name, parentName=<pkg>, description, parentPath="/sap/bc/adt/packages/<pkg>", responsible, transport), setObjectSource with activate=true, createTestInclude, unitTestRun. creatableTypeDetails tells which fields each type requires; packages (DEVC/K) need swcomp, and cloud backends need responsible.

Unit tests and ATC. unitTestRun after every change (paged with startIndex/maxItems); unitTestEvaluation drills into results. ATC: createAtcRun(mainUrl, variant) on an object, package or transport (a variant name such as ABAP_CLOUD_DEVELOPMENT_DEFAULT is resolved to a worklist for you), then atcWorklists or atcSummary (totals by priority, check and object), atcQuickfixProposals and atcApplyQuickfix for deterministic fixes, atcDocumentation for unfamiliar checks; exemptions go through atcExemptProposal and atcRequestExemption.

Review a transport. transportDetails lists objects, owner, tasks and status; transportUnifiedDiff compares every source object recorded on the transport against the version predating it, including LIMU class includes and methods, REPS includes and FUNC modules (messages and DDIC are skipped with a reason). The comparison is against the current source, so on an already released transport later changes to the same objects show up too. It runs on S/4HANA Cloud tenants (the LIMU coverage came out of a RAP session there, see docs/FIELD-NOTES.md). objectDiff covers objects with several revisions. userTransports, transportRelease, transportSetOwner and transportAddUser complete the picture.

Data. runQuery(sqlQuery) runs an ABAP SQL SELECT through the ADT data preview over tables and CDS views (by entity name, released API views included), for example SELECT carrid, connid, fldate FROM sflight WHERE carrid = 'LH' ORDER BY fldate DESCENDING. rowNumber caps how many rows SAP returns (default 100) and startRow/maxRows page the result. Statements are wrapped to the preview's 255-character line limit before sending (a single literal longer than that still fails). Tables whose DDIC dataMaintenance is restricted are refused by the preview: tableContents(ddicEntityName) reads them (S_TABU_DIS/S_TABU_NAM still apply). Keys come back in internal format, so getDataElementProperties and getDomainProperties tell you about leading zeros and conversion exits.

Dumps and debugger. dumps(from, to, user, contains) returns compact summaries (runtime error, exception, program, termination point with source URL and line, top of the stack) and dumpDetails(dumpId) the full analysis; getObjectSource around terminatedAt.line and whereUsed find the cause. The debugger and traces toolsets exist only where the backend exposes them (systemProfile tells) and only when the toolset is published (focused leaves both out). Without a debugger the paths are: a dump (dumps), reproducing the bug with runSnippet or runClass on a development system and reading the output, and traces where the backend serves them. When the debugger is available, debuggerListen needs debuggingMode, terminalId, ideId and user, as in Eclipse.

ABAP Cloud readiness. apiReleaseState takes one of four inputs: names (comma-separated, optionally typed such as TABL:MARA), objectUrl, source (pasted ABAP text) or sourceUrl (a .../source/main URL the server reads and scans). It checks the SAP objects against SAP's official cloudification repository (released, deprecated with successors, classicAPI, noAPI; editions cloud, btp, pce2023, pce2022) plus the backend's /sap/bc/adt/apireleases answer, so the model never recalls release states from memory.

Run code. runSnippet(code, packageName) wraps throwaway ABAP in a temporary IF_OO_ADT_CLASSRUN class, creates, activates and runs it, returns the console output and deletes the class again, also when activation or the run fails (a failed deletion is reported as cleanupError; keep=true keeps it). On-prem packageName defaults to $TMP; on S/4HANA Cloud pass a customer package, its transport and responsible, and the create and delete are recorded on that transport. runClass runs an existing class. Both need S_DEVELOP, so development systems only.

abapGit, RAP generator, refactoring, services. abapGit: gitRepos, gitCreateRepo, gitPullRepo, stageRepo, pushRepo, checkRepo, switchRepoBranch, with per-destination gitUser/gitPassword keeping remote credentials out of the conversation. RAP generator: rapGenIsAvailable, rapGenGetContent, rapGenValidateContent, rapGenPreview, rapGenGenerate (transport required), then activateObjects on the generated objects and rapGenPublishService. Refactoring: renameEvaluate, renamePreview, renameExecute; the same triple for extractMethod*; changePackagePreview and changePackageExecute. Business services: fetchServiceDetails(name), bindingDetails, publishServiceBinding, unPublishServiceBinding.

Built-in prompts

Six ready-made workflows travel as MCP prompts. Each names the exact tools to call, in order, and says where it must stop and ask:

Shortened here. Read the whole README on GitHub.

Signals

GitHub stars
4
Forks
1
Last commit
Sep 2026
Weekly downloads
587
Advanced
Delivery
abap-adt-mcp MCP server → your ahel gateway (mcp.ahel.ai) → every connected AI client.
Catalog kind
mcp-server
Gateway key
io-github-williansaez-abap-adt-mcp
Source
github.com/williansaez/abap-adt-mcp