Skip to main content
Glama
mcp-abap-abap-adt-api

mcp-abap-abap-adt-api

by Hollway

DISCLAIMER: This server is still in experimental status! Use it with caution!

ABAP-ADT-API MCP-Server

195 tools, read-only guardrails and 1,469 tests. See the CHANGELOG for how it got there. Not published to npm — clone the repository and build it from source.

Description

The MCP-Server mcp-abap-adt-api is a Model Context Protocol (MCP) server designed to facilitate seamless communication between ABAP systems and MCP clients. It is a wrapper for abap-adt-api and provides a suite of tools and resources for managing ABAP objects, handling transport requests, performing code analysis, and more, enhancing the efficiency and effectiveness of ABAP development workflows.

The server is not published to a package registry: clone the repository, build it, and point your MCP client at dist/main.js. Installation has the details.

Related MCP server: vibing-steampunk

Features

  • Objects: read, write and create ABAP objects, including patchObjectSource for changing part of an object instead of re-uploading all of it, editObject for the whole lock/patch/unlock/activate sequence in one call, createAndWrite for create + write + activate, and createInclude for report includes.

  • Dictionary: createDataElement and createDomain create a DDIC object and give it its definition in one call, and get/setDataElementProperties and get/setDomainProperties read and change one. createObject alone leaves a DDIC object with no type, which cannot be activated. createTable and changeTableFields create a transparent table and add or remove its fields the way SE11 does, and createPackage makes a package - each where ADT cannot. deleteDdicObject deletes a domain, data element, table or structure the way SE11 does, or withdraws one that never left this system.

  • Reading source: sourceOutline lists the blocks of a program or class with their line numbers, and findInSource searches a source - and the includes of a report - for text or a regular expression. ADT itself can only locate a class method.

  • Class members: addMethod, addAttribute and deleteMethod change a class as one operation. A method lives in two places in one source - the declaration in a visibility section, the implementation before the closing ENDCLASS - so adding one by hand is two edits whose line numbers must both survive the first of them. setObjectDescription changes the description of a class or an interface, which is metadata rather than source, and with component the short texts of a method, an attribute, a constant or an event and of its parameters, written as ABAP Doc; addMethod takes the same texts for a new method.

  • Function modules: getFunctionModule takes the module name alone, finds its group and answers with the signature as data; listFunctionGroup lists a group without the SAPGUI padding nodeContents pads every row with; createFunctionModule creates one with its interface, which is how a signature is set at all, since ADT keeps it in the source text.

  • Transactions (SE93): createTransaction makes a report or a dialog transaction and deleteTransaction removes one - ADT has no transaction editor. The SE93 module checks nothing about the program, screen or variant it is given, so these are checked first.

  • Running ABAP: runSnippet wraps a piece of ABAP in a throwaway class implementing IF_OO_ADT_CLASSRUN, runs it and deletes it again - the only way ADT executes code. It answers what no read can: what a function module returns for these inputs, what a method does with this data. A dump comes back as its ST22 header rather than as a bare 500.

  • Calling what is there: callFunction takes a function module name and values, reads its signature, generates the call and runs it - the answer carries the exporting, changing and tables parameters by name, sy-subrc turned back into the name of the classic exception it stood for, and a class-based exception with its text. callMethod does the same for a static method, whose parameters classComponents does not report at all. Both execute code, and both end in ROLLBACK WORK unless commit is set.

  • The dictionary, read whole: tableFields answers a table with its includes spliced in where they sit - for EKPO that is 702 fields rather than the 307 its own definition lists - each with its data element, domain, type, length, check table, unit or currency field, conversion exit and text. tableIndexes and tableKeys answer the secondary indexes and the foreign keys with the fields they are built on. All three read dictionary tables and execute nothing.

  • History: revisions reads the version history by name (the "version" a revision carries is the transport request; the number is the revision field), and compareRevisions diffs two of them - or the active version against the inactive one, which shows an edit that is written but not activated.

  • Impact: impactOf rolls a where-used answer up into the objects that depend on one, with the places inside them and their packages. Raw, that answer is a flat list that is really a tree - on a widely used class it runs to hundreds of rows and past the response cap. snippets fetches the source of the places actually listed, in the same call. abapPath walks that same data as a chain instead of a roll-up, breadth-first from the target back through its callers, to answer whether and how one object's code reaches another. callsFrom answers the other direction, which no backend call does: it reads the object's own source and reports what that code reaches - methods, constructors, function modules, forms and programs, transactions, database tables, the dictionary types its declarations name, what it inherits and implements - with every dynamic call listed as unresolved rather than dropped. abapGraph runs that scan over a whole package and turns the answers into a shape: the edges between its objects, the entry points nothing inside calls, the hubs everything does, the objects connected to nothing, the call circles, and what the package as a whole depends on outside itself.

  • Activation: activateSafe activates and then verifies, because activation can report success without having activated anything.

  • Tests: runTests activates the object first and reports which test methods ran, which passed, and every failure with its ABAP Unit message - a bare test run against an inactive object answers with an empty list that reads like success.

  • Locks: listLocks and unlockAll make the locks this server holds visible, and they are released when it shuts down.

  • Transports: filterable transport lists, transportDetails for the objects and tasks of one request, transportReadiness for what stands between a request and its release, transportConflicts for the objects of it that sit in somebody else open request, objectTransports for everywhere one object has travelled, plus creation, release and ownership tools. registerInTransport puts an object into a request by hand, for the writes that do not register themselves - it resolves a request to your own open task, names what each of the 67 exceptions of TR_APPEND_TO_COMM_OBJS_KEYS means, and verifies the entry in E071 instead of trusting sy-subrc. moveToTask moves entries that sit on a request itself, in nobody's task, into a task.

  • Code analysis: syntax check (reusing the source last read or written), code completion, references, ATC - atcCheck runs the checks over an object or a package and reports the findings, atcDocumentation gives the rule text - traces and the debugger. whereUsedMethod and typeHierarchy take a method or class name and work the cursor position out themselves.

  • Enhancements and texts: objectEnhancements shows the enhancement implementations injected into a source, which the source itself does not reveal; get/setTextElements reach the text symbols and selection texts that live outside it - the write locks the text pool rather than the object, and activates both rows it leaves inactive. On a release that serves no /sap/bc/adt/textelements at all they fall back to READ/INSERT TEXTPOOL in a throwaway class, which needs neither lock nor activation; the answer says which way it went in via. That path also writes the program title, which ADT serves nowhere, and registers the object in a request - registerInTransport does that on its own too, resolving a request to your own task and verifying the entry in E071 rather than trusting sy-subrc.

  • Whole packages: packageTree walks a package and its sub-packages and resolves where each object's source lives, readSources reads many objects in one call, and searchInPackage searches every source in a package. nodeContents answers one level and hands most objects a SAPGUI bridge URI that serves no content.

  • Messages (SE91): getMessages, setMessages and createMessageClass read and write the messages a MESSAGE statement raises, and deleteMessages removes them, which ADT does badly. deleteObject on a message class deletes it the SE91 way, long texts included. They come inside the message class document, which objectStructure reads and then discards, so until now a report could be written raising messages that did not exist.

  • Diagnosable errors: SAP's own exception type, T100 key and localized message are passed through instead of an axios status line.

  • Session recovery: the ADT session is re-established automatically, and read-only calls are retried once.

  • Guardrails: a read-only mode, tool profiles, per-tool read-only/destructive annotations and a cap on oversized answers.

Prerequisites

  • An SAP ABAP System reachable via ADT (ABAP Development Tools). You'll need the system URL, a username and password, and the client number. Ensure the /sap/bc/adt service is active in transaction SICF (your basis administrator can help).

  • Node.js and npm — download the LTS version from nodejs.org. Verify with node -v and npm -v.

Installation

The server speaks two transports, chosen with MCP_TRANSPORT:

  • stdio (default) - one process per user, started by the MCP client, credentials from the environment. Give your client the path to dist/main.js.

  • http - one process for everybody, each caller sending their own SAP credentials as HTTP Basic on every request. See Serving several users over HTTP.

Clone it, build it, and point your client at dist/main.js with the environment variables below.

Environment variables

Variable

Meaning

SAP_URL

The system; required by both transports.

SAP_USER, SAP_PASSWORD

Credentials, required over stdio only - over HTTP every caller sends their own and the server holds none. The user variable is SAP_USER - not SAP_USERNAME.

SAP_CLIENT, SAP_LANGUAGE

Logon client and language.

SAP_READONLY

1 hides every tool that changes the system and refuses it if called anyway. Use it for a system that must only be read.

SAP_READONLY_ALLOW

Groups or tool names let through the read-only fence, e.g. debugger for a system that must not be developed on but does need debugging. Exclusion wins over an allowance, and these tools keep readOnlyHint: false.

SAP_TOOLS_EXCLUDE

Groups or tool names to hide, comma or space separated, e.g. debugger,traces,atc,git. Groups: auth, transport, object, class, codeAnalysis, lock, source, deletion, activation, registration, node, discovery, unitTest, prettyPrinter, git, ddic, enhancement, textElement, messageClass, package, rap, serviceBinding, query, feed, debugger, rename, atc, traces, refactor, revision, operations, sapGraph, health.

SAP_TOOLS_INCLUDE

The other way round: groups or tool names to serve, to the exclusion of everything else. Cheaper to write when a session needs ten tools rather than all of them. Exclusion still wins, and healthcheck is served either way.

SAP_TOOLS_PROFILE

A ready-made include list: min, ddic, graph, atc, read or dev. See the tool list is context.

SAP_MAX_RESPONSE_CHARS

Cap on a single answer (default 200000). Over it, the answer is replaced by an envelope with the size and a preview.

SAP_GRAPH_DIR

Where sapGraphSync keeps the code graph of each system, one SQLite file per host, path and client (default ~/.abap-graph). Needs Node 22.13 or newer; the rest of the server does not.

SAP_GRAPH_PREFIXES

First letters of customer objects the graph loads (default Z,Y).

SAP_GRAPH_MAX_AGE_MIN

After how many minutes sapGraphExplore first reloads what changed (default 60; 0 never).

LOG_LEVEL

error, warn (default), info or debug. All logging goes to stderr - over stdio, stdout carries the protocol. Over HTTP, info adds one line per request carrying its id, the caller and the tool.

NODE_TLS_REJECT_UNAUTHORIZED

0 accepts a self-signed certificate (development only).

Connection settings can also come from a .env file next to the server, but that is only a fallback: when several instances run against different systems, a typo in one client entry would silently connect to whatever .env points at. The server prints its target system and where the settings came from on startup, and healthcheck reports both.

The HTTP transport takes a further set, all optional: MCP_HOST, MCP_PORT, MCP_MAX_BODY_MB, MCP_REQUEST_TIMEOUT, MCP_ALLOWED_ORIGINS, SAP_SESSION_IDLE_TTL, SAP_SESSION_IDLE_TTL_LOCKED, SAP_ASSUMED_SESSION_TIMEOUT, SAP_MAX_SESSIONS, SAP_MAX_CONCURRENT, SAP_ADMIN_TOKEN. Each is explained in .env.example and in the section below.

The tool list is context

Every tool this server offers is described to the model before it is asked anything, and the whole list costs about 190,000 characters. A session that reads code never calls the debugger, the traces, ATC or git, and pays for them all the same. SAP_TOOLS_PROFILE names a ready-made set, SAP_TOOLS_INCLUDE adds to it (groups or single tool names), and SAP_TOOLS_EXCLUDE still wins over both:

Profile

Tools

Tool list

What it serves

(none)

191

196k

Everything.

min

21

22k

Find an object, read it, walk its package.

ddic

43

57k

Dictionary work: tables, structures, data elements, domains, and the sources around them.

atc

23

17k

Quality checks: the ATC group, plus enough navigation to reach the object a finding points at.

graph

29

37k

Understanding a system nobody documented: sapGraphExplore over a graph kept on disk (sapGraphSync), abapGraph, callsFrom, impactOf, abapPath, where-used, and the reads that feed them.

read

85

111k

Everything that reads: the above plus history, analysis, enhancements and text elements.

dev

143

163k

Reading plus writing, activation, transports and tests — no debugger, traces, ATC, git, RAP or service bindings.

healthcheck also says which build answered - the version, when it was compiled and when the process started - because several servers share one build directory and a stale process announces the same version a restarted one does. It is served whatever the list says — a server that cannot say which system it is on is a worse trade than one extra tool — and it reports toolsExposed, toolsTotal and toolListChars, so the cost is a number rather than a guess, and it names any token in these settings that matches no tool and no group, because a typo in an include list narrows the server in silence. A tool left out is refused if called anyway, naming what is served instead.

A narrowed list is the only real lever: what is left is not padding. Every description here records something the backend does that its documentation does not, which is what keeps a model from calling activateByName and believing it.

Windows tip: give command the full path to node.exe and args the absolute path to dist/main.js — a bare node is not always on the PATH an MCP client starts with.

Build from source

  1. Clone the Repository

    git clone https://github.com/Hollway/mcp-abap-adt-api.git
    cd mcp-abap-adt-api
  2. Install Dependencies

    npm install
  3. Configure Environment Variables

    An .env.example file is provided in the root directory as a template for the required environment variables. To set up your environment:

    a. Copy the .env.example file and rename it to .env:

    cp .env.example .env

    b. Open the .env file and replace the placeholder values with your actual SAP connection details:

    SAP_URL=https://your-sap-server.com:44300
    SAP_USER=YOUR_SAP_USERNAME
    SAP_PASSWORD=YOUR_SAP_PASSWORD
    SAP_CLIENT=YOUR_SAP_CLIENT
    SAP_LANGUAGE=YOUR_SAP_LANGUAGE

    Note: The SAP_CLIENT and SAP_LANGUAGE variables are optional but recommended.

    If you're using self-signed certificates, you can also set:

    NODE_TLS_REJECT_UNAUTHORIZED="0"

    IMPORTANT: Never commit your .env file to version control. It's already included in .gitignore to prevent accidental commits.

  4. Build the Project

    npm run build
  5. Run the Server

    npm run start

    When integrating a source build into an MCP client, point command at node with an absolute path to the build output:

    {
      "mcpServers": {
        "mcp-abap-adt-api": {
          "command": "node",
          "args": ["PATH_TO_YOUR/mcp-abap-adt-api/dist/main.js"],
          "disabled": false,
          "autoApprove": []
        }
      }
    }

Serving several users over HTTP

MCP_TRANSPORT=http turns the server into one process that many people share. Every request carries its own SAP credentials as HTTP Basic; the server stores none of them and has no account of its own.

// what a client puts in its MCP configuration
{
  "mcpServers": {
    "abap-adt-api": {
      "type": "http",
      "url": "https://abap-mcp.example.internal/mcp",
      "headers": { "Authorization": "Basic <base64 of SAPUSER:password>" }
    }
  }
}

One session per user, not per request

The credentials identify a pooled SAP session, reused across requests and closed on a schedule. That is the whole point of the design: a session per request would leave one behind on the backend for every call ever made, and a lock taken by one request would be unusable by the next, because the handle dies with the session that took it.

Each pooled user costs two sessions on the backend: the stateful one that holds locks and does the writing, and a stateless clone that serves the reads. Both are logged off when the session is closed.

Sessions are closed when they have been idle past a limit, when a caller runs logout, when an operator deletes one, or on shutdown. Closing releases the locks first, deliberately, and says in the log what was released. The pool never pings SAP to keep a session warm: a session nobody is using is meant to expire on its own, taking its locks with it.

Two idle limits, because the cases differ. A session with no locks is cheap to close (SAP_SESSION_IDLE_TTL, 15 min). One that still holds locks is kept far longer (SAP_SESSION_IDLE_TTL_LOCKED, 28 min) - but not forever, because those locks block everyone else on the system, including people working outside this server.

The locked limit is measured from the last use of the stateful session, not from the last request: reads travel on the clone, so somebody who spends half an hour reading is busy the whole time while the session holding their locks sits untouched - and SAP times each session out separately. It must also stay comfortably under the backend timeout (SAP_ASSUMED_SESSION_TIMEOUT, 1800 s by default in SAP), or the session will be gone before the server can release its locks; the server warns at startup when it is not.

Matching the log against SAP's own session list

Every session opened, stateless clone opened and session closed line carries the id of the SAP session it is about:

[pool] JSMITH: session opened (1/25) [sap-session=DB883C5FADA111F18000005056A2C8B3]

That value is SECURITY_CONTEXT-LINK - the handle SAP keeps for an HTTP security session, listed in SM05 and readable directly:

SELECT * FROM security_context WHERE link = 'DB883C5FADA111F18000005056A2C8B3'

Sessions SAP lists that no log line claims were abandoned: a process killed rather than stopped leaves them behind until the backend times them out. That is what the id is for.

Note it is not SM04's "session key" (T82_U11195_M0). That is an internal dialogue-session key and nothing an HTTP client holds corresponds to it; in SM04 these sessions are recognised by user, client host and start time.

The id is half of the SAP_SESSIONID cookie - the half that identifies. The other half authenticates and is never written anywhere: a log that carried it would hand whoever reads it the session, and logs outlive sessions.

Endpoints

Endpoint

Purpose

POST /mcp

The MCP endpoint. HTTP Basic required; 401 with WWW-Authenticate if absent, 413 for a body over the limit, 503 with Retry-After when the pool is full or too many calls are already running.

GET /health

Liveness. Reports the system, the number of sessions and whether SAP has been unreachable - but stays ok regardless, because restarting this process would not fix SAP.

GET /ready

Readiness. Red only while shutting down. Deliberately not red for a full pool (busy is not unfit, and failing would stop even the 503 naming who holds the sessions) nor for SAP being down (a dependency every replica shares: failing it empties the service of endpoints without helping).

GET /metrics

Counters for the process, the state of the pool, and a passive sap signal built from real traffic - no probe, no technical user.

GET /sessions

Who is connected, for how long, and what they have locked.

DELETE /sessions/<user>

Close one session. Refused with 409 while it is serving a request.

The last two are off unless SAP_ADMIN_TOKEN is set, and then require it in X-Admin-Token: they name who is connected and end other people's sessions.

Deploy one replica

The pool lives in the memory of one process. With two replicas and no affinity, a lock handled by one pod and the write that follows handled by another would open a second session for the same person, and the write would be refused for a lock the second pod knows nothing about - while the first pod holds it until it times out.

So: one replica per SAP client, which also matches one server per client. If it ever has to scale, add session affinity on the Authorization header first, and test that a lock survives two consecutive requests through the load balancer.

Set terminationGracePeriodSeconds above the drain: on SIGTERM the server turns /ready red, lets the requests in flight finish, then logs every session off. Killed before that finishes, it leaves sessions - and their locks - behind until SAP times them out.

What is shared and what is not

Per session: the locks held, the source cache and the per-caller counters. unlockAll releases only its own caller's locks, and a cached read is never served to another user - two people do not have the same authorisations.

Shared by the whole server: SAP_READONLY, SAP_TOOLS_EXCLUDE, SAP_TOOLS_INCLUDE, SAP_TOOLS_PROFILE, SAP_READONLY_ALLOW, SAP_MAX_RESPONSE_CHARS. They describe the system this server talks to, not the person calling.

Custom Instruction

Use this instruction to explain the server to your model:

## mcp-abap-adt-api Server

Tools for working on an SAP system through ADT: reading and changing ABAP
objects, activating them, running tests, and handling transports.

**Finding things**

* `searchObject` resolves a name to an object URI, e.g.
  /sap/bc/adt/oo/classes/zcl_invoice. `objectStructure` describes an object,
  `nodeContents` lists a package, `usageReferences` finds callers. The last two
  reach endpoints that answer a whole level or a whole where-used tree at once -
  40,660 rows for a widely used class, 802 nodes for a large package - so both
  answer with counts over everything and one page of rows, steered by
  `maxResults` and `offset`.
* Position-taking calls - `findDefinition`, the `codeCompletion` family - count
  lines from 1 and columns from 0, and check the position against the source
  before spending a call: the backend answers a line out by one with a
  confident answer about a different object. Leave `source` out and the whole
  stored source is read; never pass a page of one, which the backend rejects as
  incomplete.
* `getObjectSource` takes the URI plus /source/main. It serves the INACTIVE
  version by default, so reading your own edit back proves nothing about what
  the system runs - pass version="active" for that. Use startLine/maxLines
  to page through a large object instead of pulling all of it.
* `sourceOutline` is the table of contents of a source: every REPORT, CLASS,
  METHOD, FORM, MODULE, FUNCTION, INCLUDE and event block with its line.
  `findInSource` searches for text or a regular expression and answers with
  line numbers; searchIncludes follows a report's INCLUDE statements. Use
  these to find a FORM or a MODULE - `fragmentMappings` only knows class
  fragments (CLAS/OM), and a type it does not know is answered with 400.

**Changing an object**

`editObject` does the whole sequence below in one call - lock, patch, unlock,
activate, verify - and reports each step. Nothing is rolled back if a step
fails; the source stays in the inactive version, which is not what the system
executes. The steps by hand:

1. `lock` the object URI (without /source/main). Keep the lockHandle; the
   server also remembers it, and `listLocks` shows what is held.
2. `patchObjectSource` with the source URI and the edits: a line range
   ({startLine, endLine, replacement}), exact text ({anchor, replacement}) or
   an insertion ({insertAfterLine, insertion}). It reads the current source,
   applies the edits and returns a diff. Pass dryRun first if unsure.
   `setObjectSource` still exists, but it replaces the whole object.
3. `unLock` **before activating**: activating while holding the lock fails
   with "user X is already processing Y". This differs from the ADT editor.
4. `activateSafe` with the object name. It activates every inactive part -
   the class, its changed method fragments, its sections, its test include -
   and then checks that nothing is left inactive. Do not trust
   `activateByName`: it has answered success:true without activating.
5. Verify with `getObjectSource` version="active", or `inactiveObjects`
   returning an empty list.

**Creating an object**

`createAndWrite` creates the object and writes its source in one call, then
activates it: validate the name, create, lock, write, unlock, activate. It
knows where the source of a CLAS/OC, INTF/OI, PROG/P, PROG/I, FUGR/F, FUGR/FF
or FUGR/I lives; anything else is `createObject` plus `setObjectSource`. A
package other than $TMP needs a transport request - ask the user which one.

Objects are created in the logon language. That matters: the underlying
library defaults to EN and makes it the master language, and SAP then answers
a read in any other language with an empty description.

**The dictionary**

`createDataElement` and `createDomain` create and define in one call. The type
of a data element comes either from a domain or from a built-in ABAP type, not
both, and the four field labels are cut to the lengths SAP allows (10/20/40/55)
with the answer saying which were cut. `setDataElementProperties` and
`setDomainProperties` change an existing one: the backend PUT replaces the
whole definition, so anything not passed is kept as the system has it. They
take the lock themselves and give it back, and `activate` finishes the job -
demanding a separate `lock` call made a one-field change a three-call sequence.

Not every release serves these over ADT. On a classic ERP system data elements
work while domains answer 404 for every path including validation. There
`createDomain` writes the same arguments the way SE11 does - RS_CORR_INSERT,
DDIF_DOMA_PUT, DDIF_DOMA_ACTIVATE in a throwaway class - and says so in `via`.
`getDomainProperties` reads the same fields from DD01L/DD01T/DD07L/DD07T there,
and `setDomainProperties` changes the domain in two calls - a preview with a
`previewToken`, then the write: DDIF_DOMA_GET, the change on top, DDIF_DOMA_PUT,
activation, and the old definition put back if the activation fails. The values
are matched by low and high, because DDIF_DOMA_PUT numbers them 1..n again and
moves their translations along. On S/4 both work over
ADT, and the whole cycle - create with fixed values, patch the description,
patch the values, build a data element on the domain - was verified there.

Tables are the same story, worse: there is no `/ddic/tables` on the classic
system, and the structure collection serves a table as `define type`, without
its table class or technical settings. Written back over ADT, that text turns
the table into a structure that cannot be activated - so the lock tools refuse
a table outright. `createTable` and `changeTableFields` write it with
DDIF_TABL_PUT instead, which carries the whole definition: header, technical
settings, fields, foreign keys, search help assignments.

`changeTableFields` takes two calls. The first previews the new field list and
answers with a `previewToken`; the second writes, and only with that token on
the table as it was previewed. A table with data in any client - counted at
database level, not through the client-bound data preview - takes only new
non-key fields; removing a field or adding a key field needs it empty. Fields
used by a foreign key, an index or a search help are not removed, and tables
with includes or appends are left to SE11. If the activation fails - a change
that would need a conversion, say - the definition read before is written back
and activated, and the table is as it was. Every added and removed column is
then checked in the database itself.

Each of these registers the object with RS_CORR_INSERT given the request, not
a task, and without `use_korrnum_immediatedly`: that way the entry lands in
the caller's own task of the request. With the flag it lands on the request
header, and with a task number the module refuses (TK 886) while the write
still goes through - an active object recorded nowhere. A task number passed
in is lifted to its request first.

`createPackage` tries ADT and, where `/sap/bc/adt/packages` answers 404, makes
the package the way SE21 does with CL_PACKAGE_FACTORY. A transportable package
always gets software component HOME and transport layer Z*; a $ package is
local.

`deleteDdicObject` deletes a domain, data element, table or structure with
RS_DD_DELETE_OBJ, what SE11 calls, in two calls: a preview of who uses it, the
rows of a table and the requests it is in, then the deletion with its token.
The module refuses rather than asks - a used object, or a table with data in
any client, is not deleted - and some of its refusals return without an
exception, so the deletion is proven from the dictionary tables and, for a
table, from the database. `withdraw: true` is for an object created in a
request still open that never left this system: its entries come off the
requests and its directory entry goes, so nothing travels and the task can be
deleted.

**Working on a package**

`packageTree` walks a package and its sub-packages breadth-first and answers
with every object, its type, the package it sits in and the URL that serves
its source. That last part is the point: `nodeContents` answers one level and
gives most objects a SAPGUI bridge URI
(/sap/bc/adt/vit/wb/object_type/tabldt/object_name/ZFOO) which serves
properties and no content. A limit reached leaves the upper levels complete
and names the packages it did not open. An unknown package is told apart from
an empty one - both answer with an empty node list, so existence is checked
separately.

`readSources` reads many objects in one call, by name and type or by URL, each
reported on its own so one unreadable object does not lose the rest.
`searchInPackage` searches every source in a package for text or a regular
expression - one call instead of a listing plus a read and a search per
object. It reads what it walks, so narrow a large package with objectTypes and
maxObjects.

**Messages**

`getMessages` reads the messages of a message class: number, text, whether the
message is self-explanatory and whether it has a long text. They live inside
the class document and nowhere else - `objectStructure` on the same class asks
the very endpoint that carries them and keeps only the metadata. Standard
classes are big (class 00 holds 875 messages in one 478 KB document), so
narrow the answer with numbers, fromNumber/toNumber or search.
`getMessageLongtext` reads the cause and procedure of one message; the text is
stored per language with no fallback.

`setMessages` adds or changes messages, `createMessageClass` creates the class
and fills it in one call. Only the messages passed are touched - the backend
upserts by number - and a field left out keeps its current value. The class
description is read first and carried over, because the write replaces the
class header. The package goes into the write as well: without it the backend
skips a transported class and still answers 200, which is why every write is
read back. Text is capped at 73 characters, what T100 holds, and the answer
says what was cut. No activation is involved: the message is in T100 as soon
as the call returns.

Two things the backend does badly or not at all. A long text cannot be written
(that resource has no PUT), which needs SE91. And a message is removed only
through `<mc:deletedmessages>` in the class document, which leaves the long
text behind and, when the document language is not the logon language,
deletes one language and keeps the message - answering 200 either way. So
`deleteMessages` does it the way SE91 does instead: under the SE91 lock it deletes the text in every language, the
change record, the supplement and the long text, and records the deletion as
LIMU MESS with function D in the caller's own task - all in one transaction,
and only with the `previewToken` of a preview that showed every language,
the long text and where the message is used.

`deleteObject` on a message class goes the same way rather than through ADT,
whose delete leaves the long text of every message behind and can answer
success on a transported class it did not delete. Previewed first, then one
transaction under the SE91 lock: RS_CORR_INSERT with mode D, T100 and its
companion tables, and the long texts by exact name - class plus three
digits, so a class whose name only starts the same keeps its own.

**Tests**

`runTests` is the one to use: it activates the object first, because no test
runs against an inactive one, and then reports how many methods ran, how many
passed, and each failure with its class, method and ABAP Unit message.

`unitTestRun` returning an empty result does NOT mean the tests passed - it
means none ran. The answer explains why: the object is inactive, or the test
include does not compile. Fix that and run it again.

**ATC**

`atcCheck` is the one to use: it takes the check variant from the system
customizing, opens a worklist, starts the run and reads the findings back -
each with its priority (1 is the worst), the check that raised it, the message
and the source line it points at. Narrow a large answer with minPriority and
maxFindings, and read the rule behind a finding with `atcDocumentation` and the
documentationUri the report carries.

The steps by hand are a trap worth knowing about. `createAtcRun` wants a
WORKLIST ID in the parameter the library calls `variant`; given a variant name
it answers 500 with nothing to go on. The id comes from `atcCheckVariant`,
which - despite its name - opens a worklist rather than describing a variant.
A package is checked through its SAPGUI bridge URI, because
/sap/bc/adt/packages/ZFOO is refused with "No URI-Mapping defined for URI".

`findingUri` in the report is what `atcContactUri` and the exemption tools
take - but those are built on /sap/bc/adt/atc/items, which an older system
answers with a plain 404. The same goes for the abapGit tools: without the
abapGit ADT plugin, /sap/bc/adt/abapgit/repos is simply not there. Neither is
a fault in the tool.

**Traces**

`tracesList` reports every trace with its `expiration` and `state`, and both
matter: a trace past its expiration date, one still being written (`Active`),
and one that overran its size limit (`Size violation`) are all listed but none
of them can be read. `tracesHitList`, `tracesDbAccess` and `tracesStatements`
answer "wrong input data" or "Data is invalid" for those - the id is not the
problem, and no encoding of it helps.

`tracesStatements` also refuses an aggregated trace, whatever its state: a
trace to be read statement by statement has to be recorded with
`aggregate: false`.

A readable trace is still a large one - a single ADT call came to 8051
statements and a 2548-entry hit list - so `tracesHitList` and
`tracesStatements` cap what they report (`limit`, default 100) and can sort by
gross time first (`heaviestFirst`), which is the order to ask for when the
question is where the time went.

Recording one takes two steps: `tracesSetParameters` answers with a parameters
URI, and `tracesCreateConfiguration` records the next run of that user under
it. Keep the process type narrow - `ANY` records the ADT calls of the session
that set it up and fills the file with them. Tracing an ADT call is expensive
either way: one `runSnippet` under `ANY` filled 60 MB, and the same run
recorded statement by statement overran 400 MB.

**The debugger**

`debuggerListeners` leaves `checkConflict` off. Asking the backend to check for
a conflict when no listener exists at all raises a short dump, so turn it on
only once a listener is known to be there.

The debugger runs on a SAP session of its own. `debuggerListen` answers only
when a process stops at a breakpoint, and the library sends that request with a
timeout of one hundred hours - on the session everything else uses, one
listener would hold the locks and the writes behind it for as long as nobody
hit the breakpoint. It waits on a second stateful session instead, for
`waitSeconds` at a time (60 by default), and says what it is still waiting for;
calling again rejoins the same listener, and an answer that arrived while
nothing was waiting is kept for the next call rather than dropped. Deleting the
listener goes over the main session deliberately - inside the debug session it
would queue behind the call it is meant to end.

Whether anything ever stops is a property of the system, not of this server.
Measured on a classic ERP system: the backend accepted the breakpoints and
answered with their ids, and then nothing halted - not a background job, not a
task started with `STARTING NEW TASK`, not a class executed through `runClass`,
with and without `systemDebugging`. The listening request itself was cut with
504 after about three and a half minutes by the proxy in front of the system,
which is what ends a listener there. Prove a stop once with something harmless
before relying on the debugger for real work.

**Transports**

`userTransports` lists a user's requests, filterable by status (D
modifiable, R released), owner, number and description. `transportDetails`
answers what is inside one request - its tasks and every object recorded in
it - and resolves a task number to the request holding it. `transportInfo` on
an object URI shows which request would take a change. Ask the user which
request to use rather than creating one.

Three reads answer what is asked before a release, from the organizer tables
themselves:

* `transportReadiness` gives a verdict per check - status and owner, tasks
  still open under the request, an empty request, objects that also sit in
  other open requests, objects with an inactive version saved and never
  activated, and locks this server still holds. A task number is resolved to
  the request above it.
* `transportConflicts` names the objects of a request that are also recorded
  in somebody else open request, with whose request it is. On one customizing
  request measured live, 49 of its 79 objects sat in 110 other open requests;
  each object costs a read, so the number examined is capped and the answer
  says how many were left out.
* `objectTransports` answers where one object has travelled, newest first,
  including the entries recorded under its parts - REPS and REPT for a report,
  METH and CLSD for a class - which a search by the object name alone never
  shows.

The activation check reads `REPOSRC` and the dictionary tables rather than ADT,
because ADT cannot answer it: `objectStructure` reports version `active` for an
object whose inactive version was saved years ago, and asking for version
`inactive` explicitly answers with the active one rather than with nothing.

Pass the number of the request, not of a developer task: a task number is
refused by a write with "not a change request".

**Errors**

Failures carry SAP's own diagnosis: adtType (the ADT exception), t100 (the
message key), localizedMessage, status, and diagnostic - "sap" for a real
rejection, "transport" for an HTTP failure with no answer from SAP. A dead
session is recovered automatically for read-only calls, which then come back
marked sessionRecovered; a call that writes is never repeated for you,
because its lock handle died with the session - re-lock and retry.

**Notes**

* SAP is decoupled from the local file system. Reading source returns it as a
  tool result only; local copies are for your own reference.
* Writes land in the inactive version, so a botched write never touches what
  is running until it is activated.

Efficient Database Access

SAP systems contain vast amounts of data. It's crucial to write ABAP code that accesses the database efficiently to minimize performance impact and network traffic. Avoid selecting entire tables or using broad WHERE clauses when you only need specific data.

  • Use WHERE clauses: Always use WHERE clauses in your SELECT statements to filter the data retrieved from the database. Select only the specific rows you need.

  • UP TO 1 ROWS: If you only need a single record, use the SELECT SINGLE statement, if you can guarantee that you can provide ALL the key fields for the SELECT SINGLE statement. Otherwise, use the SELECT statement with the UP TO 1 ROWS addition. This tells the database to stop searching after finding the first matching record, improving performance. Example:

    SELECT vgbel FROM vbrp WHERE vbeln = @me->lv_vbeln INTO @DATA(lv_vgbel) UP TO 1 ROWS.
      EXIT. " Exit any loop after this.
    ENDSELECT.

Checking Table and Structure Definitions

When working with ABAP objects, you may encounter errors related to unknown field names or incorrect table usage. Use the following tools to inspect DDIC (Data Dictionary) objects:

  • tableFields: The fields of a table or structure as the dictionary knows them, with the .INCLUDEs expanded: data element, domain, type, length, decimals, key and not-null flags, check table, the field holding a unit or a currency, the conversion exit and the text in the connection language. Start here when the question is what is in a table. keysOnly, fields and maxFields narrow the answer for a table with hundreds of them.

  • tableIndexes, tableKeys: The secondary indexes with the fields they are built on, and the foreign keys with the check table, what fills its key, the cardinality and whether the check is enforced.

  • getStructureSource: The definition of a table or structure as DDL text, plus the parsed field list with types and key flags. The definition rather than the dictionary: a field's type is the name of its data element, and an .INCLUDE stays a line of text. It is the one to read - or write - when the definition itself is the point; both TABL/DT and TABL/DS come from the same endpoint.

  • objectStructure: Retrieves the structure/metadata of an ABAP object (including DDIC tables and structures) from its object URI. Use searchObject first to resolve the object name to a URI. For a table it answers metadata only - no fields.

  • ddicElement: Retrieves details of a DDIC element (e.g. a data element or domain).

  • ddicRepositoryAccess: Reads DDIC repository information for a given path.

  • tableContents: Retrieves the contents (rows) of a table, not its definition. Use runQuery for ad-hoc SELECTs. Both answer compact by default - columns described once, each row an array, columns blank or zero in every row named in emptyColumns; format: "objects" gives the endpoint answer as it is.

Troubleshooting

  • The client won't start the server: ensure Node.js is installed (node -v, npm -v) and that npm run build has produced dist/main.js. Give the client absolute paths for both node and the script — the PATH an MCP client starts with is not always your shell's.

  • SAP connection errors: verify your credentials (SAP_URL, SAP_USER, SAP_PASSWORD, SAP_CLIENT), confirm the system is reachable, that your user has ADT authorizations, and that /sap/bc/adt is active in SICF.

  • TLS / self-signed certificate errors: for development only, set NODE_TLS_REJECT_UNAUTHORIZED=0 (env var or in the client env block).

  • Every call suddenly fails with status 400: the ADT session died. The server detects that shape (an HTTP failure with no exc:exception body), re-authenticates and retries read-only calls, marking the answer sessionRecovered. A call that writes is not repeated: re-lock the object and try again. healthcheck says whether the session is alive.

  • A change seems to have no effect: it is probably still inactive. getObjectSource serves the inactive version by default - read it with version="active", and check inactiveObjects is empty after activating. Prefer activateSafe.

  • logout and then nothing works: the underlying client cannot log in again after logout; restart the server process. Use dropSession to release a session instead.

  • An answer comes back as {"status":"truncated"}: it exceeded SAP_MAX_RESPONSE_CHARS. Narrow the request (startLine/maxLines, rowNumber, the userTransports filters) or raise the limit.

  • A tool is missing from the list: check SAP_READONLY, SAP_TOOLS_EXCLUDE and SAP_TOOLS_PROFILE/SAP_TOOLS_INCLUDE - healthcheck reports the active profile, and calling the tool anyway answers with the reason it is not served.

Development

npm install
npm run build      # compile src/ to dist/
npm test           # unit tests (no SAP system needed)
npm run smoke      # end-to-end checks against a real system, read-only

npm run smoke takes the same environment variables as the server and never locks, writes or activates anything:

SAP_URL=... SAP_USER=... SAP_PASSWORD=... SMOKE_CLASS=CL_SALV_TABLE npm run smoke

SMOKE_ATC_OBJECT=ZCL_SOMETHING adds the ATC checks to the run. They are off by default: ATC over a large class takes minutes. SMOKE_TRACE_ID=... adds the trace-reading checks, which need a trace that is closed, unexpired and not aggregated - no system is guaranteed to hold one.

The changelog

One working session, one version. Each session of work on this server adds a new version section at the top of CHANGELOG.md: how the tool and test counts moved, what it added, and - separately - what the live runs proved wrong. That last part is the useful one. Every defect listed there had passed the unit tests before a real system rejected it, so the entry records what the backend does rather than what its documentation implies.

Contributing

Contributions are welcome! Please follow these steps to contribute:

  1. Branch from main

  2. Create a New Branch

    git checkout -b feature/your-feature-name
  3. Commit Your Changes

    git commit -m "Add some feature"
  4. Push to the Branch

    git push origin feature/your-feature-name
  5. Open a Pull Request

License

This project is licensed under the MIT License.

Available Tools

177 tools
abapDocumentationA
Read-only

The ABAP keyword or object documentation for a position in a source - the F1 of ADT. Answers with the help text as it is written for that release, which is worth reading before guessing at a statement variant.

Inputs: body (required), line (required), column (required), objectUri (required), +1 more.

abapPathA
Read-only

Whether one object's code reaches another, and through what. Walks usageReferences backwards from "to" through its callers, breadth-first, until it reaches "from" or runs out of budget - the same data impactOf uses, just followed as a chain instead of rolled up one level. Pure ADT data, no ABAP parsing: a call made only dynamically (CALL METHOD (name)) is invisible to it, same as usageReferences itself.

Inputs: toUrl, toName, toType, fromUrl, +5 more.

activateByNameA

Activate an ABAP object by name and URL, then check the inactive list to see whether it really happened. The backend call behind this answers success:true for objects that stay inactive - notably a freshly created class - so the answer carries verified/stillInactive as well, and success is lowered to false when anything is left inactive. activateSafe is still the better tool: it activates exactly the rows inactiveObjects reports, including method fragments.

Inputs: objectUrl (required), objectName (required), mainInclude, preauditRequested.

activateObjectsA

Activate the objects you name, as rows shaped like the ones inactiveObjects returns. Prefer activateSafe, which activates and then proves it: this call can answer success:true while the object stays inactive, and activating only the class row leaves changed method fragments behind, so the old implementation keeps running under an already-active signature. Every row needs a non-empty adtcore:parentUri, which the inactive list leaves empty for programs.

Inputs: objects (required), preauditRequested.

activateSafeA

Activate an object and prove it worked. Reads inactiveObjects, activates exactly those entries (class plus its method fragments, sections and test include), then re-reads inactiveObjects and fails if anything is still inactive. Prefer this over activateByName, which can answer success:true without activating anything.

Inputs: objects, objectUrl, parentUri, objectName, +1 more.

addAttributeB

Add an attribute or a constant to a class - DATA, CLASS-DATA or CONSTANTS - into the section you name, then activate. Defaults to a private DATA, which is what an attribute usually is; a constant needs a value, and READ-ONLY is only accepted on a public one. Pass dryRun to see the diff without writing.

Inputs: type (required), className (required), attributeName (required), value, +7 more.

addMethodA

Add a method to a class: the declaration goes into the visibility section, the implementation before the closing ENDCLASS, and the whole lock/write/unlock/activate sequence follows. The signature is passed as data and the ABAP is built here, in the indentation the class already uses. This is otherwise two patchObjectSource edits into two different parts of one source, with line numbers that both have to be right. A method that is already there is refused with the line it is on, rather than declared twice. Pass dryRun to see the diff without writing.

Inputs: className (required), methodName (required), dryRun, static, +10 more.

adtCompatibiliyGraphA
Read-only

The ADT compatibility graph of this system: which protocol versions its collections speak. Diagnostic, for a call refused as an unsupported version.

Inputs: no named parameters.

adtCoreDiscoveryB
Read-only

The core discovery document of the ADT service - what healthcheck calls to prove the connection is alive.

Inputs: no named parameters.

adtDiscoveryA
Read-only

The ADT service document: every collection this system offers, with its URL and the object types it serves. This is where the address of an unfamiliar collection comes from.

Inputs: no named parameters.

annotationDefinitionsA
Read-only

The CDS annotations this system defines, with their value ranges - what may be written in a DDLS source before an activation refuses it.

Inputs: no named parameters.

atcChangeContactA

Change who is responsible for an ATC finding - it moves the finding into another worklist.

Inputs: userId (required), itemUri (required).

atcCheckA
Read-only

Run the ATC checks over an object or a whole package and report what they found: for each object, every finding with its priority, the check that raised it, the message, and the source line it points at. This is the whole sequence in one call - the check variant from the system customizing, a worklist, the run, the worklist read back - and getting it wrong is what made a run answer 500 (the run needs a worklist id where the library asks for a variant). Findings are ordered by priority, 1 being the worst.

Inputs: variant, objectUrl, maxResults, objectName, +5 more.

atcCheckVariantA
Read-only

Opens a worklist for an ATC check variant and answers with its id. It reads nothing about the variant, whatever the name suggests: the id it returns is what createAtcRun needs as its worklistId - passing the variant name there is what makes the run answer 500. atcCheck does this and the run and the report in one call.

Inputs: variant (required).

atcContactUriB
Read-only

The contact URI of an ATC finding - who is responsible for it.

Inputs: findingUri (required).

atcCustomizingA
Read-only

How ATC is set up on this system: the check variants and their priorities - what atcCheck runs against by default.

Inputs: no named parameters.

atcDocumentationA
Read-only

The documentation of one ATC finding: what the check means and what it wants instead. Takes the documentation URI that atcWorklists reports for a finding. Returns the document as it comes from the backend, which is HTML.

Inputs: docUri (required).

atcExemptProposalA
Read-only

The exemption proposal for an ATC finding: what would be requested, before requesting it.

Inputs: markerId (required).

atcRequestExemptionA

Ask for an ATC finding to be exempted, with a reason - it goes to an approver, it is not granted here.

Inputs: proposal (required).

atcUsersA
Read-only

The users ATC knows for exemption approval - who can be named as an approver. Search with filter rather than reading the whole address book of the system.

Inputs: limit, filter.

atcWorklistsA
Read-only

An existing ATC worklist by id, with the findings it holds. Prefer atcCheck, which opens a worklist, runs the checks and reports them in one call.

Inputs: runResultId (required), timestamp, usedObjectSet, includeExempted.

bindingDetailsA
Read-only

What a service binding exposes: its services, versions and the entities behind them - read before publishing or unpublishing one.

Inputs: binding (required), index.

callFunctionA

Call a function module with values and get what it returned, as data. Takes the module name alone - the function group is looked up - reads its signature, generates the call and runs it through a throwaway class in $TMP. The answer carries the exporting, changing and tables parameters by name, sy-subrc turned back into the name of the classic exception it stood for, a class-based exception with its text, and the true row count of every table. Values are checked against the signature before anything is sent: an unknown parameter name and a missing mandatory one are refused with the list of what the module takes. IMPORTANT: this executes the module on the target system as the connected user. Nothing it changed is kept - the call is followed by ROLLBACK WORK - unless commit is set, and a module that commits internally cannot be taken back at all. It counts as a writing tool and is refused in read-only mode.

Inputs: name (required), commit, dryRun, values, +4 more.

callMethodA

Call a static method of a class with values and get what it returned, as data. The signature is read from the class source, because classComponents lists methods without their parameters. Returns the returning parameter, the exporting and changing ones by name, and any exception with its text. Static methods only: an instance method needs a constructor call, and runSnippet is the way to do that. IMPORTANT: this executes the method on the target system as the connected user. Nothing it changed is kept - the call is followed by ROLLBACK WORK - unless commit is set. It counts as a writing tool and is refused in read-only mode.

Inputs: className (required), methodName (required), commit, dryRun, +4 more.

changePackagePreviewA
Read-only

What moving an object to another package would involve: the refactoring the backend proposes, with the old and new package and the transport it would be recorded in. Preview only - this server does not execute the move, because the library offers no evaluate step and the payload has to be assembled here, which is not something to run unverified against a real package. Do the move itself in ADT or SE80 once the preview looks right.

Inputs: objectUrl (required), newPackage (required), transport, objectName, +3 more.

checkRepoA
Read-only

Check an abapGit repository before pulling: what would change, and whether anything local stands in the way.

Inputs: repo (required), user, password.

classComponentsA
Read-only

What a class is made of: its methods with their visibility, its attributes, its types and its interfaces - read from the class rather than from its source. This is the cheap answer to "what can this class do"; the source of one method is then found with fragmentMappings or read whole with getObjectSource.

Inputs: url (required).

classIncludesC
Read-only

The includes a class is made of - definitions, implementations, macros, test classes - with the URL of each. That is how the test class of a class is read or written separately from its main source, and which include names revisions and getTextElements take.

Inputs: clas (required).

codeCompletionA
Read-only

Completion proposals for a cursor position: what may be written at line/column of this source. Needs the source and the position, exactly like the editor, so it is worth having when composing a call against an unfamiliar interface; codeCompletionFull adds the insert text and codeCompletionElement the details of one proposal.

Inputs: line (required), column (required), source (required), sourceUrl (required).

codeCompletionElementA
Read-only

The details behind one completion proposal: its type, its documentation, where it comes from. Follows codeCompletion for the entry you want to know more about.

Inputs: line (required), column (required), source (required), sourceUrl (required).

codeCompletionFullB
Read-only

Completion proposals with the text to insert and the position to insert it at, for a cursor position in a source. The fuller form of codeCompletion.

Inputs: line (required), column (required), source (required), sourceUrl (required), +1 more.

collectionFeatureDetailsB
Read-only

What one collection of the discovery document offers: its capabilities, its supported types and its versions.

Inputs: url (required).

compareRevisionsA
Read-only

What changed between two versions of one object, as a unified diff. Name each side by a revision number from revisions, by a transport request, or by "active" (what the system executes), "inactive" (the working version) or "latest" (newest in the history). With neither side given it compares the two newest versions, which answers "what did the last change do"; "active" against "inactive" shows an edit that is written but not activated. The diff anchors on lines that occur once on each side, so an inserted method reads as an insertion instead of two rewritten methods.

Inputs: to, from, context, objectUrl, +4 more.

createAndWriteA

Create an object and put its source in it, in one call: validate the name, create, lock, write the source, unlock, activate with verification. Creating and writing are separate calls in ADT, and an object created without source is an empty shell that fails activation - this keeps the two halves together. Report includes (PROG/I) are created the way createInclude does it, so they work here too; pass mainProgram. Nothing is rolled back on failure: the answer says which step stopped it.

Inputs: name (required), source (required), objtype (required), description (required), +8 more.

createAtcRunA

Starts an ATC run over one object. The first parameter has to be a WORKLIST ID, not a check variant name, whatever it is called here: the backend answers 500 for a variant name. Get the id from atcCheckVariant, or use atcCheck which does the whole sequence.

Inputs: mainUrl (required), variant (required), maxResults.

createDataElementA

Create a DDIC data element and give it its definition, in one call: validate, create, lock, write the type and the four field labels, unlock, activate. The type is either a domain or a built-in ABAP type, not both. createObject alone leaves an element with no type, which cannot be activated. Nothing is rolled back if a step fails - the answer reports each step.

Inputs: name (required), description (required), packageName (required), label, +17 more.

createDomainA

Create a DDIC domain and give it its definition, in one call: validate the name, create the object, lock it, write the type and output format (and value table or fixed values), unlock, activate. createObject alone leaves a domain with no definition, which cannot be activated. Nothing is rolled back if a step fails - the answer reports each step, and a domain that was created but not written is still there to correct or delete.

Inputs: name (required), length (required), datatype (required), description (required), +15 more.

createFunctionModuleA

Create a function module in an existing group and give it its signature and body in one call: create, write, activate, verify. The signature is passed as data and the ABAP interface is built here - which is how it is set at all, since ADT keeps the interface in the source text. The group has to exist (create one with createAndWrite for FUGR/F) and a group outside $TMP needs a transport request.

Inputs: name (required), description (required), functionGroup (required), dryRun, +7 more.

createIncludeA

Create a report include (PROG/I). Use this instead of createObject for includes: abap-adt-api builds the creation body without the reference to the main program, so the backend rejects it (400/500) whatever parameters are passed. This posts the include document together with its context reference.

Inputs: name (required), description (required), mainProgram (required), packageName (required), +3 more.

createMessageClassA

Create a message class and fill in its messages in one call: validate the name, create the class, write the messages, read them back. No activation is involved. Nothing is rolled back - the answer says which step stopped and in what state the class was left. Outside $TMP a transport request is required.

Inputs: name (required), description (required), packageName (required), dryRun, +3 more.

createObjectB

Create a new ABAP object. Report includes (PROG/I) are the one type this cannot create - it refuses them and points at createInclude.

Inputs: name (required), objtype (required), parentName (required), parentPath (required), +3 more.

createStructureA

Create a structure (TABL/DS) and give it its fields in one call: validate the name, create, write the definition, syntax check, activate, verify. Fields are passed as data - the DDL is built here, including the opening keyword, which differs between releases and is taken from the object the backend just created. A quantity or currency field needs its unit annotation or the activation refuses it: pass unitField or currencyField and the reference is built and qualified for you. A transparent table (TABL/DT) cannot be created over ADT at all on a classic ERP system - see the note in getStructureSource. Nothing is rolled back: the answer says which step stopped and in what state the object was left.

Inputs: name (required), fields (required), description (required), packageName (required), +5 more.

createTestIncludeA

Create the test include of a class - the place ABAP Unit tests live, which a new class does not have. It writes an empty include; runTests then runs what is put in it.

Inputs: clas (required), transport, lockHandle.

createTransportA

Create a workbench request. It becomes yours and stays open until it is released, so create one per piece of work rather than per object, and reuse the number for every write that belongs together. Ask first if the user has a request in mind - an unwanted request is visible to the whole team and has to be deleted by hand.

Inputs: DEVCLASS (required), REQUEST_TEXT (required), objSourceUrl (required), transportLayer.

createTransportsConfigA

Create a transport configuration for the organizer. Shared setup; most work needs only createTransport.

Inputs: no named parameters.

ddicElementA
Read-only

A dictionary element as the DDIC sees it: a data element, a domain or a type, with its properties. For a table or structure with its fields use getStructureSource.

Inputs: path (required), getExtensionViews, getSecondaryObjects, getTargetForAssociation.

ddicRepositoryAccessB
Read-only

Read dictionary metadata through the DDIC repository access endpoint - types, fields and domains as the dictionary itself sees them.

Inputs: path (required).

debuggerAttachA

Attach to the process that has stopped at a breakpoint, which is what debuggerListen reported. Only after this do the stack and the variables mean anything; the attachment holds the stopped process, so let it go (debuggerStep with terminate, or delete the listener) rather than leaving a work process frozen.

Inputs: user (required), debuggeeId (required), debuggingMode (required), dynproDebugging.

debuggerChildVariablesB
Read-only

Open one variable that has parts: the fields of a structure, the rows of an internal table, what a reference points at. Takes the variable id from debuggerVariables.

Inputs: parent.

debuggerDeleteBreakpointsA
Destructive

Remove breakpoints that were set earlier - the ids come from debuggerSetBreakpoints. Worth doing even after a failed session: a forgotten breakpoint stops a productive program the next time it runs.

Inputs: ideId (required), breakpoint (required), terminalId (required), requestUser (required), +2 more.

debuggerDeleteListenerA
Destructive

Stop listening for breakpoints and free the session. Do it when a debug session is over or abandoned - a listener left behind blocks the next one for the same user.

Inputs: user (required), ideId (required), terminalId (required), debuggingMode (required).

debuggerGoToStackA

Move the debugger view to another frame of the stack, so that debuggerVariables shows what is visible THERE. It changes the view, not the position of the program - the process still stands where it stopped.

Inputs: urlOrPosition (required).

debuggerListenA

Start listening for a breakpoint and WAIT until something hits one - the call does not return until a process stops, or the wait times out. That is the shape of the whole debugger here: set breakpoints, start listening, then run the program from somewhere else (SAPGUI, a job, a service call), and this returns when it stops. Nothing in this server can trigger the program for you, so a listener with nothing to trigger it just waits. It occupies the session; delete the listener when you are done.

Inputs: user (required), ideId (required), terminalId (required), debuggingMode (required), +2 more.

debuggerListenersA
Read-only

Which debug listeners exist for this user and terminal - who would catch a breakpoint right now. Read this before starting one: a second listener for the same user is refused, and an old one left behind is the usual reason a debug session cannot be started. checkConflict is off by default because the backend raises a short dump for it when no listener exists at all - turn it on only once you know one is there.

Inputs: user (required), ideId (required), terminalId (required), debuggingMode (required), +1 more.

debuggerSaveSettingsA

Change how the debugger behaves for this user: system debugging, update debugging, how much of a table it reads. They are user settings and stay until changed back.

Inputs: settings (required).

debuggerSetBreakpointsA

Set breakpoints on lines of a source, or on a statement, for the debug session that follows. They belong to the user and survive until deleted, so they will also stop a colleague running the same code with your user. Set them BEFORE debuggerListen; the ids that come back are what deletes them again.

Inputs: user (required), ideId (required), clientId (required), terminalId (required), +6 more.

debuggerSetVariableValueA

Change a variable in the stopped process, as the debugger lets you. The program then carries on with the new value, which is a way to reach a branch that the data would not otherwise reach - and a way to make a productive program do something it never would. Only for a session you are deliberately steering.

Inputs: value (required), variableName (required).

debuggerStackTraceA
Read-only

The call stack of the process stopped at a breakpoint: which programs and methods it came through, with the line each is on. Needs an attached session (debuggerAttach); use debuggerGoToStack to look at variables from a frame further up.

Inputs: semanticURIs.

debuggerStepA

Step the stopped process: into, over, out, to a line, or terminate it. Each step answers with where it now stands, so the stack and the variables have to be read again. Terminating ends the debugged program - which is how a stopped work process is let go when the session is over.

Inputs: steptype (required), url.

debuggerVariablesA
Read-only

The variables visible in the current stack frame, with their values. Needs an attached session. A structure or a table comes back as a node to open with debuggerChildVariables rather than as its contents.

Inputs: parents (required).

deleteMethodA
Destructive

Remove a method from a class - both its declaration and its implementation - and activate. A declaration inside a METHODS: chain is handled: the entry goes, and if it was the last one the chain is closed so it still compiles. What it will not do is guess at a line that declares two methods at once; that is refused and named, because rewriting it wrong takes somebody else's method with it. Pass dryRun to see the diff first, and check usageReferences before removing a method that anything calls.

Inputs: className (required), methodName (required), dryRun, activate, +1 more.

deleteObjectA
Destructive

Delete an object. It needs an edit lock (this server passes the handle it holds, so lock the object first) and, outside $TMP, a transport request. Deleting does not release the lock - the backend leaves it, pointing at an object that no longer exists - so this releases it and forgets it, and drops the cached source with it. Not undoable from here: the object is gone and only a transport of the deletion travels on.

Inputs: objectUrl (required), transport, lockHandle.

dropSessionA
Read-only

Clear the local session cache (releases held locks server-side; the next call logs on again).

Inputs: no named parameters.

dumpsA
Read-only

Short dumps from ST22, newest first, as the HTML page ST22 itself shows. The header of one carries the runtime error, the exception and the program that died.

Inputs: query.

editObjectA

The whole edit in one call: lock, patch, unlock, activate and verify. This is the sequence a change to an ABAP object needs, and each step has a way to go wrong on its own - the lock must be released BEFORE activating, or activation fails with "user is already processing", and an activation is only believable once the inactive list comes back empty. Nothing is rolled back if a step fails: the source stays written to the inactive version, which is not what the system executes, and the answer says exactly how far it got. Pass dryRun to see the diff without locking anything.

Inputs: edits (required), objectSourceUrl (required), dryRun, activate, +3 more.

extractMethodEvaluateA
Read-only

First of the three steps that pull a range of lines out into a method: it asks the system whether the range CAN be extracted and what the new method would need - which variables come in, which go out. Nothing is written. Then extractMethodPreview, then extractMethodExecute.

Inputs: uri (required), range (required).

extractMethodExecuteA

Third step of extract-method: apply what the preview showed. This WRITES the object, so it needs the object unlocked by anything else and, outside $TMP, a transport request. Nothing is rolled back if the activation afterwards fails.

Inputs: refactoring (required).

extractMethodPreviewA
Read-only

Second step of extract-method: the edits the refactoring would make, from the evaluation you pass back in. Still nothing written - this is where the new signature and the changed call site can be read before agreeing to them.

Inputs: proposal (required).

featureDetailsA
Read-only

What one discovery feature offers, by title - the capabilities behind a collection.

Inputs: title (required).

feedsB
Read-only

The ADT feeds this system publishes (dumps, system messages) with their URLs - the index behind dumps.

Inputs: no named parameters.

findCollectionByUrlA
Read-only

Which discovery collection serves a given URL - the reverse lookup of adtDiscovery, for when an address is in hand and its capabilities are not.

Inputs: url (required).

findDefinitionA
Read-only

Where the symbol under a cursor position is defined - the F3 of ADT. Takes the source URL with a line and column, and answers with the object and position of the declaration, so it needs the source read first to count the position. To go the other way, use usageReferences or impactOf.

Inputs: url (required), line (required), endCol (required), source (required), +3 more.

findInSourceA
Read-only

Find text or a regular expression in the source of an ABAP object and get the matching line numbers with context - without pulling the source through the caller. This is how to locate a FORM, a MODULE or any statement in a report: there is no ADT fragment type for those, and asking fragmentMappings for one is answered with 400. For a report, searchIncludes follows its INCLUDE statements too.

Inputs: pattern (required), objectSourceUrl (required), regex, version, +5 more.

findObjectPathC
Read-only

The workbench path of an object, from the package down to the object itself. That is where the package of an object comes from - its own metadata does not carry it - which is why activation uses this to fill in the parent URI the inactive list leaves empty.

Inputs: objectUrl (required).

fixEditsA
Read-only

Turn one proposal from fixProposals into concrete edits: the ranges and the replacement text. It computes them and does NOT write - apply them with patchObjectSource or editObject.

Inputs: source (required), proposal (required).

fixProposalsB
Read-only

Quick-fix proposals the system offers for a position in a source - the same list as the light bulb in ADT (create the missing method, add the missing variable). What comes back is passed to fixEdits to get the actual edits.

Inputs: url (required), line (required), column (required), source (required).

fragmentMappingsA
Read-only

Locate a named fragment of an object and get the line and column where it starts - the cheap way to find one method in a class of a few thousand lines. type is an ADT fragment type, /: CLAS/OM for a class method, CLAS/OA for an attribute. There is no working fragment type for a FORM of a report; use findInSource for that.

Inputs: url (required), name (required), type (required).

getDataElementPropertiesA
Read-only

Read the definition of a DDIC data element: its domain or built-in type, the four field labels, search help and parameter id. Takes the data element name; the object URL is accepted as well.

Inputs: name, version, dataElementUrl.

getDomainPropertiesA
Read-only

Read the definition of a DDIC domain: data type, length, decimals, output format, value table and fixed values. Takes the domain name; the object URL is accepted as well.

Inputs: name, version, domainUrl.

getFunctionModuleA
Read-only

A function module by name alone: which group it lives in, its package, and its signature as data - importing, exporting, changing, tables and exceptions, with types, defaults and which parameters are passed by value. The name is all that is needed; the group is looked up. Without this, reading a module meant knowing its group to build the URL and then reading the interface out of the ABAP by eye, because ADT serves the signature as the first statement of the source (not as the *"-block SE37 shows). Pass includeSource for the body as well - it is often a thousand lines, so it is off by default.

Inputs: name (required), version, maxLines, startLine, +2 more.

getMessageLongtextA
Read-only

Read the long text (cause and procedure) of one message, as the HTML the backend serves. The text is stored per language and there is no fallback: a message documented in EN answers nothing in RU. Writing a long text is not possible over ADT - that resource refuses PUT.

Inputs: number (required), className (required), language.

getMessagesA
Read-only

Read the messages of a message class (SE91): number, text, and whether the message is self-explanatory or has a long text. The messages live inside the class document, so this is the only way to see them - objectStructure on the same class shows the metadata alone. Standard classes are large (class 00 has 875 messages), so pass fromNumber/toNumber or numbers to narrow it down.

Inputs: className (required), search, numbers, toNumber, +2 more.

getObjectSourceA
Read-only

Retrieves source code for ABAP objects. ADT serves the INACTIVE version by default, so reading your own edit back proves nothing about what runs - pass version="active" to see the live code. For large objects, use startLine/maxLines to page through the source instead of retrieving it all at once.

Inputs: objectSourceUrl (required), options, version, maxLines, +1 more.

getStructureSourceA
Read-only

Read the definition of a table or structure as ADT serves it: the DDL text plus the parsed field list with types and key flags. Works for TABL/DT and TABL/DS alike - both come from the same endpoint. This is the way to see a table's fields: searchObject answers a table with a SAPGUI bridge URI and objectStructure shows only its metadata.

Inputs: name (required), version.

getTextElementsA
Read-only

Read the text elements of a program, class or function group: text symbols (TEXT-001), selection texts (the labels of PARAMETERS and SELECT-OPTIONS) or list headings. These are stored per language outside the source, so getObjectSource never shows them.

Inputs: url, category, objectName, objectType.

getTransportConfigurationA
Read-only

One transport configuration by URI, with everything it defines.

Inputs: url (required).

gitCreateRepoA

Link a package to an abapGit repository and pull it. This WRITES the objects of that repository into the package - the largest write in this server.

Inputs: repourl (required), packageName (required), user, branch, +2 more.

gitExternalRepoInfoA
Read-only

Whether an external git repository can be reached with these credentials, and which branches it offers - the check before linking it.

Inputs: repourl (required), user, password.

gitPullRepoA

Pull an abapGit repository into its package. This WRITES every object the repository carries, overwriting what is there, and needs a transport outside $TMP.

Inputs: repoId (required), user, branch, password, +1 more.

gitReposA
Read-only

The abapGit repositories linked on this system, with their packages, branches and state.

Inputs: no named parameters.

gitUnlinkRepoA
Destructive

Disconnect a package from its abapGit repository. The objects stay; the link and its state go, and reconnecting means setting it up again.

Inputs: repoId (required).

hasTransportConfigA
Read-only

Whether this system has transport configurations at all - the check before offering the organizer tools.

Inputs: no named parameters.

healthcheckA
Read-only

Check ADT connectivity. Calls the backend and reports which SAP system this server talks to (url, client, language, user), the session state, the active tool profile and the round-trip latency; on failure it reports whether the session is dead.

Inputs: no named parameters.

impactOfA
Read-only

What depends on an object, or on one method of it: the objects that use it, the places inside them, and the package each belongs to. This is usageReferences rolled up into an answer that can be read - that call returns a tree of package, object and member rows, which for a widely used class means hundreds of rows and over a hundred thousand characters for a list of forty objects. Usages in test includes are marked as such, standard SAP objects are left out unless asked for, and depth=2 follows the callers one step further to show what depends on them in turn. A method with no callers looks the same as one reached only dynamically, so an empty answer says so rather than reading as proof.

Inputs: depth, snippets, objectUrl, maxObjects, +7 more.

inactiveObjectsA
Read-only

The objects that are written but not activated. Read in the session this server does its writing in, because the sessions disagree: a read from elsewhere can still report an object as inactive after it has been activated. Use it as the proof that an edit went live - an empty list for your object is that proof.

Inputs: no named parameters.

isProposalMessageA
Read-only

Whether a message from a check is an ATC proposal rather than a plain finding - which decides whether an exemption can be requested for it.

Inputs: proposal (required).

listFunctionGroupA
Read-only

What is in a function group: its modules, its includes, and the global data and types declared in its TOP include. nodeContents answers this, but each row carries a SAPGUI bridge URI padded with spaces to 30 characters, so a group of thirty modules costs about 14,000 characters to list; here it is the names and the source URLs.

Inputs: functionGroup (required), includeGlobals.

listLocksA
Read-only

List the object locks this server currently holds, with their lock handles and when they were taken. Locks are otherwise invisible and outlive the edit that needed them.

Inputs: no named parameters.

loadTypesA
Read-only

The object types the creation endpoints accept, with the templates behind them - what a wrong objtype is checked against.

Inputs: no named parameters.

lockA

Take an edit lock on an object, which every write needs. The handle it returns is what setObjectSource, patchObjectSource and deleteObject take - and it lives and dies with the ADT session, so a lost session voids it and the object has to be locked again. This server remembers the handle per object, so the write tools find it themselves and listLocks shows what is held. The lock must be RELEASED BEFORE ACTIVATING: activation refuses to run while the same session holds it. Prefer editObject, which takes and releases the lock around the change for you.

Inputs: objectUrl (required), accessMode.

loginA
Read-only

Authenticate with the ABAP system. Use it to recover a dead session; read-only calls now recover on their own.

Inputs: no named parameters.

logoutA
Read-only

Terminate the ABAP session and clear its cookies. WARNING: this client cannot log in again afterwards - the server process has to be restarted. To just release the session use dropSession.

Inputs: no named parameters.

mainProgramsA
Read-only

Which programs an include belongs to - the question a report include cannot answer about itself. It is needed to create or syntax-check an include, both of which want the main program, and an include used by several reports answers with all of them.

Inputs: includeUrl (required).

nodeContentsA
Read-only

One level of the repository tree: what is directly inside a package, or inside a function group. Two things to know. Most rows hand back a SAPGUI bridge URI that serves properties and no content, so it is not the way to read sources - packageTree resolves the real source URLs, and listFunctionGroup does it for a group. And an unknown package answers exactly like an empty one, with no nodes at all, so only a repository search tells them apart.

Inputs: parent_type (required), user_name, parent_name, parentnodes, +2 more.

objectEnhancementsA
Read-only

Enhancement implementations active on an ABAP source, with the position each one is injected at and, with includeSource, its code. Read this before drawing conclusions from a standard include: the code that actually runs is the include plus its enhancements, and the source itself does not show them. For a program include, pass contextUri (the main program) - one include can belong to several programs.

Inputs: sourceMainPath (required), contextUri, includeSource.

objectRegistrationInfoA
Read-only

The workbench registration of an object: which transport layer and package it belongs to, and whether it can be changed here. This is what says an object is foreign or read-only before a lock fails on it.

Inputs: objectUrl (required).

objectStructureA
Read-only

The metadata of one object: its name, type, package, who changed it when, its master language, and the links ADT offers for it - among them the source URL, which is how the address of an unfamiliar type is found. It does NOT return content: a class answers with its includes, a table with its properties (getStructureSource has the fields), and a message class with the metadata after the messages have been discarded (getMessages has those).

Inputs: objectUrl (required), version.

objectTypesA
Read-only

The object types this system knows, as ADT names them. Diagnostic: useful when a type code is in doubt, since a wrong one is refused with a 404 that says nothing.

Inputs: no named parameters.

packageSearchHelpA
Read-only

Search help for package names, as the input help in ADT offers them - a name check before a creation that would fail on the package.

Inputs: type (required), name.

packageTreeA
Read-only

List what is in a package, following its sub-packages: every object with its type, the package it sits in and the URL that serves its source where there is one. nodeContents answers one level and gives most objects a SAPGUI bridge URI that serves no content; this resolves the source locations and walks the tree breadth-first, so a limit leaves a complete picture of the upper levels and names the packages it did not open.

Inputs: packageName (required), maxDepth, maxObjects, objectTypes, +1 more.

patchObjectSourceA

Change part of an ABAP object without re-uploading it. Reads the current source, applies the edits, writes the result and returns a unified diff of what changed. Use it instead of setObjectSource for a small change to a large object: setObjectSource replaces the whole object, so it costs the entire source and risks disturbing lines you never meant to touch. Pass dryRun to see the diff without writing.

Inputs: edits (required), objectSourceUrl (required), dryRun, transport, +1 more.

prettyPrinterA
Read-only

Reformat a source the way the ADT pretty printer would, following the setting of this user. It answers with the formatted text and writes nothing - the write is yours to make. It does not fix indentation of continuation lines the way a person would: aligning parameters to a column is not something it does.

Inputs: source (required).

prettyPrinterSettingA
Read-only

How pretty-printing is set for this user: whether keywords go upper or lower case, and how identifiers are treated. Worth reading before formatting a source that is not yours, because the setting decides what the reformat does to every line.

Inputs: no named parameters.

publishServiceBindingA

Publish a service binding, which makes its service reachable on this system. Outward-facing: the endpoint goes live for anyone who can reach the host.

Inputs: name (required), version (required).

pushRepoA

Push staged changes of an abapGit repository to the remote. Outward-facing: it writes to the git remote under the credentials configured there.

Inputs: repo (required), staging (required), user, password.

rapGenIsAvailableA
Read-only

Whether the RAP generator (business service generation) answers on this system. False on a classic ERP system, true on a recent S/4 - ask this before assuming a RAP-based approach is possible here. Only the check is wrapped; the generator itself is not.

Inputs: generatorId.

readSourcesA
Read-only

Read the source of several objects in one call, by name and type or by source URL. Each object is reported on its own, so one unreadable object does not lose the rest, and the answer stops adding sources once the character budget is used up - it says which objects it did not reach.

Inputs: objects, version, sourceUrls, maxTotalChars, +1 more.

reentranceTicketA
Read-only

A single-use ticket for opening SAPGUI on this system without logging on again - what ADT uses when it hands an object to the GUI. It is a credential: it stands for your session, so treat it like one.

Inputs: no named parameters.

remoteRepoInfoA
Read-only

What a remote abapGit repository holds: its branches and their heads, read with the credentials passed in.

Inputs: repo (required), user, password.

renameEvaluateA
Read-only

First of the three steps of a rename: it asks the system what the symbol at this position is and where it is used, so the rename can be planned. Nothing is written. Then renamePreview, then renameExecute.

Inputs: uri (required), line (required), endColumn (required), startColumn (required).

renameExecuteA

Third step of a rename: apply it. This WRITES every object the preview listed, so all of them must be free of other locks and, outside $TMP, in a transport request. It is not a transaction: a failure part way through leaves the objects already renamed as they are.

Inputs: refactoring (required).

renamePreviewA
Read-only

Second step of a rename: every object and line the rename would touch, from the evaluation you pass back in. Read this before agreeing - a rename reaches objects you did not open, and the preview is the only place that shows how far it goes.

Inputs: renameRefactoring (required), transport.

revisionsA
Read-only

The version history of an object: who changed it when, and under which transport request. Takes a name (with objectType), or objectUrl for a type it cannot address. Mind what the backend means by "version": that field is the transport request that carried the change, while the version NUMBER is the "revision" field - and that is what compareRevisions takes. A long-lived object has a long history (ZCL_APP answers with 90 versions, most of them copies left by transport releases), so the newest 20 come back unless limit says otherwise, and author/transport/titleContains narrow it down.

Inputs: limit, author, objectUrl, transport, +4 more.

runClassA

Execute a class that implements IF_OO_ADT_CLASSRUN - the F9 of an ADT editor - and return its console output. The class has to exist and implement that interface; to run a piece of ABAP that does not, use runSnippet, which wraps it in such a class for you. This EXECUTES CODE on the system as the connected user, so it counts as a writing tool and read-only mode refuses it. A runtime error comes back as a bare 500; the reason is in ST22 (runSnippet reads the dump for you).

Inputs: className (required).

runQueryA
Read-only

Run an Open SQL SELECT and get the rows back - joins, aggregates, GROUP BY, whatever the ABAP SQL console accepts. Reading only, and only SELECT: the endpoint refuses anything that writes, and for logic around the data (call a function module, compute, loop) use runSnippet. Row limits are the ones the backend applies, so ask for what you need with UP TO n ROWS.

Inputs: sqlQuery (required), decode, offset, rowNumber.

runSnippetA

Run a piece of ABAP on the system and return what it printed. The snippet is wrapped in a throwaway class implementing IF_OO_ADT_CLASSRUN, created in $TMP, activated, executed with runClass and deleted again - which is the only way ADT executes ABAP at all. Write output with out->write( lv_x ) or out->write_text( ... ); out is the console object the interface hands to main. It answers the questions no read can: what a function module returns for these inputs, what a class method does with this data, what a calculation comes to. It runs code on the target system as your user, so a snippet that writes changes data - it counts as a writing tool and is refused in read-only mode. On a syntax error the activation messages come back with the source, and the class is removed either way.

Inputs: code (required), dryRun, className, keepClass, +3 more.

runTestsA

Run the ABAP Unit tests of a class and report what happened, activating it first if it has inactive parts. This is what unitTestRun should feel like: tests do not run at all against an inactive object, so a bare run answers with an empty list that reads like success. Returns a summary - how many methods ran, which failed, and each failure with its message - with the full ADT result available on request.

Inputs: raw, url, flags, activate, +1 more.

searchInPackageA
Read-only

Search the sources of a whole package for text or a regular expression, following its sub-packages: one call instead of a package listing plus a read and a search per object. Answers with the matching objects, the line numbers and the lines, and says how far it got - this reads every source it walks, so narrow it with objectTypes and maxObjects on a large package.

Inputs: pattern (required), packageName (required), regex, maxDepth, +6 more.

searchObjectA
Read-only

Find objects by name in the repository, with * as a wildcard - the quickest way from a name to a URI, a type and a package. The objType filter is the quick-search filter of the backend and does not take every sub-type: FUGR/FF answers with an empty list while the unfiltered search returns the module. So when a filter comes back empty, search without it and pick the type from the rows.

Inputs: query (required), max, objType.

setDataElementPropertiesA

Change the definition of an existing DDIC data element. The backend PUT replaces the whole definition, so anything not passed is kept as the system has it. The type is either a domain or a built-in ABAP type, not both. The lock is taken and released here unless you pass a handle or this server already holds one; outside $TMP a transport request is needed. Writes the inactive version - pass activate to finish the job, or run activateSafe afterwards.

Inputs: name, label, domain, length, +21 more.

setDomainPropertiesA

Change the definition of an existing DDIC domain. The backend PUT replaces the whole definition, so anything not passed here is kept as the system currently has it - read, merge, write happens on this side. The lock is taken and released here unless you pass a handle or this server already holds one; outside $TMP a transport request is needed. Writes the inactive version - pass activate to finish the job, or run activateSafe afterwards.

Inputs: name, style, length, activate, +15 more.

setMessagesA

Add or change messages of an existing message class. Only the messages passed are touched: the backend upserts by number, so the rest of the class is left alone - and for the same reason a message CANNOT be deleted through ADT. A field left out keeps its current value. The class description is read first and carried over, because the write replaces the class header. The text is capped at 73 characters (what T100 holds) and the answer says what was cut. Takes and releases the lock itself; no activation is needed - the message is in T100 as soon as this returns. Outside $TMP a transport request is required.

Inputs: messages (required), className (required), transport, description.

setObjectSourceA

Replace the whole source of an ABAP object. For a small change prefer patchObjectSource, which reads, edits and writes without sending the entire object.

Inputs: source (required), lockHandle (required), objectSourceUrl (required), transport.

setPrettyPrinterSettingA

Change the pretty-printer setting for this user - it is a user setting and stays until changed back. It decides what prettyPrinter does to keywords and identifiers, so changing it changes how every later reformat looks.

Inputs: style (required), indent (required).

setTextElementsA

Write the text elements of a program, class or function group. The write replaces the whole set for that category, so pass every element you want to keep - read them first with getTextElements. The lock is taken on the text elements resource - not on the object - and released again, unless you pass a handle or this server already holds one; outside $TMP a transport request is needed. Pass activate to finish the job, or run activateSafe afterwards: a text write leaves both the object and its text pool inactive.

Inputs: elements (required), url, activate, category, +5 more.

setTransportsConfigA

Change a transport configuration - which requests a user sees in the transport organizer. It is shared setup, not a per-call filter.

Inputs: uri (required), etag (required), config (required).

sourceOutlineA
Read-only

Table of contents of an ABAP source: every REPORT, CLASS, METHOD, FORM, MODULE, FUNCTION, INCLUDE and event block with the line it starts on. Read this before paging through a long report or class - it turns "where is that subroutine" into one call. Text scanning, so it works on programs, includes and function groups alike.

Inputs: objectSourceUrl (required), kinds, version, searchIncludes.

stageRepoA

Stage the local changes of an abapGit repository for a commit: which objects would go, with their state.

Inputs: repo (required), user, password.

switchRepoBranchA

Switch an abapGit repository to another branch. It changes what the next pull would write, and a pull after it can rewrite the whole package.

Inputs: repo (required), branch (required), user, create, +1 more.

syntaxCheckCdsUrlA
Read-only

Syntax check for a CDS object, which is addressed differently from ABAP: the DDL source URL goes in as the main URL. For ordinary ABAP use syntaxCheckCode.

Inputs: cdsUrl (required).

syntaxCheckCodeA
Read-only

Perform ABAP syntax check. Provide the source in "code", or omit it to reuse the source last read/written for "url" via getObjectSource/setObjectSource (cached this session).

Inputs: url (required), code, mainUrl, version, +1 more.

syntaxCheckTypesA
Read-only

Which syntax-check flavours this system offers, as the check endpoint understands them. Diagnostic; syntaxCheckCode picks the right one itself.

Inputs: no named parameters.

systemUsersA
Read-only

The users of this system, as the transport tools offer them - who a request can be handed to or shared with. Search with filter rather than reading the whole address book of the system.

Inputs: limit, filter.

tableContentsA
Read-only

Read rows of one table or view by name, with an optional WHERE clause - the quickest look at data when you know the table. Reading only: ADT serves no write here. For a join, an aggregate or anything over more than one table use runQuery; to see what FIELDS a table has use getStructureSource, because this answers with data and not with a definition. There is no offset in the backend, so paging fetches offset+rowNumber rows and returns the tail - pass an ORDER BY to make the window stable.

Inputs: ddicEntityName (required), decode, offset, sqlQuery, +1 more.

tableFieldsA
Read-only

The fields of a table or structure, with the includes spliced in where they sit. Each field carries its position, key and not-null flags, data element, domain, ABAP type, length and decimals, the table its value is checked against, the field holding its unit or currency, and its text in the connection language. This is the answer to "what is in this table": getStructureSource gives the DDL text, where the type of a field is the name of its data element and an .INCLUDE is a line of text rather than the fields it brings - for EKPO, most of them. Reading only, and it executes nothing, so it works on a system where nothing may run.

Inputs: name (required), fields, keysOnly, maxFields, +2 more.

tableIndexesA
Read-only

The secondary indexes of a table, each with the fields it is built on in order, whether it is unique and what it is called in the database. The answer to why a SELECT does or does not have an index to use. Reading only.

Inputs: name (required).

tableKeysA
Read-only

The foreign keys of a table: for each field, the table its value is checked against, the fields the two are joined on, the cardinality and whether the check is enforced on input. The answer to "where do the values of this field come from". Reading only.

Inputs: name (required).

tracesCreateConfigurationA

Create a trace configuration: which user and which process to record, and for how long. Recording starts when that user next runs something. Needs parametersId - the URI tracesSetParameters answered with - plus traceUser, traceClient, processType, objectType, expires and maximalExecutions. A bare parameters id and a plain date are accepted here and normalised; sent to the backend as they stand, both answer a flat 400 that names neither field.

Inputs: config (required).

tracesDbAccessA
Read-only

The database accesses of one trace: which tables, how many rows, how long - where a slow run met the database.

Inputs: id (required), withSystemEvents.

tracesDeleteA
Destructive

Delete a recorded trace. Final, and the measurement cannot be taken again from the same run.

Inputs: id (required).

tracesDeleteConfigurationA
Destructive

Delete a trace configuration, so nothing more is recorded under it.

Inputs: id (required).

tracesHitListA
Read-only

The hit list of a trace: what was called how often and for how long - the fastest read of where the time went. The hit list of a single ADT call runs to megabytes, so the answer is capped: pass heaviestFirst for the expensive end of it.

Inputs: id (required), limit, heaviestFirst, withSystemEvents.

tracesListA
Read-only

Traces recorded for this user, newest first, with their ids - the way into tracesHitList, tracesStatements and tracesDbAccess. Each run carries how long it took and when its file expires: an expired trace is still listed, but reading it answers "wrong input data".

Inputs: user, limit, includeLinks.

tracesListRequestsB
Read-only

The recorded requests of a trace - one per unit of work measured. The entry point into a trace before its statements.

Inputs: user.

tracesSetParametersA

Set what the next trace records: statements, database access, aggregation. It applies to traces started afterwards, not to one already recorded.

Inputs: parameters (required).

tracesStatementsA
Read-only

The statements of one trace, with their times - where a run spent itself. Takes a trace id from tracesList. The trace has to have been recorded with aggregate false: the backend refuses statements for an aggregated one. One ADT call is some 8000 statements, so the answer is capped - pass heaviestFirst for the expensive end of it.

Inputs: id (required), limit, options, heaviestFirst.

transportAddUserB

Add a developer task for another user inside a request, so their changes can travel in it. Their objects then sit in their own task under the same request number.

Inputs: user (required), transportNumber (required).

transportConfigurationsA
Read-only

The transport configurations available in the organizer, with their ids - what transportsByConfig takes.

Inputs: no named parameters.

transportDeleteA
Destructive

Delete a transport request or a task inside it. Only works while it is still open and empty of anything you want to keep: the objects in it stay as they are, only the request goes. Released requests cannot be deleted at all. Not undoable - ask before doing it to a request you did not create.

Inputs: transportNumber (required).

transportDetailsA
Read-only

What is inside one transport request: its own header (owner, description, status), its tasks and the objects recorded in it. This is the answer to "what does this request change" - the alternative was a SELECT on E071 through runQuery. Objects are returned as a flat list; pass raw=true for the ADT structure.

Inputs: transportNumber (required), raw, owner, includeTasks, +1 more.

transportInfoA
Read-only

Which transport request a change to this object would go into, and which ones are available for it - what ADT asks before it opens the transport dialog. Worth calling before a write outside $TMP, because a write with no request fails at the last step. Mind the difference the backend does not spell out: a REQUEST is what a write takes, a task inside it is refused with "not a change request".

Inputs: objSourceUrl (required), devClass, operation.

transportReferenceC
Read-only

What a transport reference points at: the object behind one entry of a request.

Inputs: pgmid (required), obj_name (required), obj_wbtype (required), tr_number.

transportReleaseA
Destructive

Release a request, which sends its objects on to the next system. Not undoable: a released request cannot be reopened, and the only way back is another request. It fails while any task inside it is still open, and while the objects have syntax errors. Ask before releasing anything - this is the step that changes another system.

Inputs: transportNumber (required), IgnoreATC, ignoreLocks.

transportsByConfigA
Read-only

Transport requests of one organizer configuration, filtered as that configuration defines. For your own open requests use userTransports, which filters and shortens.

Inputs: configUri (required), targets.

transportSetOwnerA

Hand a request over to another user. The new owner sees it in their list and yours loses it; the objects and tasks inside stay as they are. Only an open request can change hands.

Inputs: targetuser (required), transportNumber (required).

typeHierarchyA
Read-only

Subclasses or superclasses of a class or interface. Pass className or interfaceName and the declaration is located in the source here - the backend resolves a hierarchy from a cursor position rather than from a name, which is why url/body/line/offset are only the escape hatch. Defaults to descendants; set superTypes to walk upwards.

Inputs: url, body, line, name, +5 more.

unitTestEvaluationA
Read-only

The evaluation behind a unit-test run, for the alerts a run reported. Prefer runTests, which activates first and reports each failure with its assert message.

Inputs: clas (required), flags.

unitTestOccurrenceMarkersB
Read-only

The markers ADT draws next to code covered by a unit test - which lines a run touched.

Inputs: url (required), source (required).

unitTestRunA

Run ABAP Unit tests for an object. An empty result does not mean the tests passed - it means none ran, and the answer then explains why (inactive object, or an include that does not compile).

Inputs: url (required), flags.

unLockA

Release an edit lock. Pass the handle, or leave it out and the one this server recorded for that object is used (see listLocks). Do it before activating - activation refuses while the session still holds the lock - and remember that deleting an object does NOT release its lock, so deleteObject releases it for you.

Inputs: objectUrl (required), lockHandle.

unlockAllA

Release every lock this server holds. Use it to clean up after an edit that was abandoned; call listLocks first if you want to see what will be released.

Inputs: no named parameters.

unPublishServiceBindingA
Destructive

Take a published service offline. Outward-facing and immediate: anything calling that endpoint stops working.

Inputs: name (required), version (required).

usageReferencesA
Read-only

Where-used for the symbol at a cursor position, or for the whole object when no position is given. The answer is a flat list that is really a tree - one row per package, per object, and per place inside it - so it is large: a widely used class answers with hundreds of rows and over a hundred thousand characters, past the response cap. Prefer impactOf, which asks this and rolls it up; use whereUsedMethod for one method by name.

Inputs: url (required), line, column.

usageReferenceSnippetsA
Read-only

The source lines around each usage, for references you already have from usageReferences - pass those rows back in. One more backend call and a much larger answer, so ask for it when the call site itself matters.

Inputs: references (required).

userTransportsA
Read-only

List a user's transport requests. Returns a flat, filterable list of requests (number, description, owner, status D=modifiable/R=released, target); pass raw=true for the full ADT payload, which with targets=true can exceed 400k characters. Note that targets=false makes the backend answer with empty lists on some systems, so leave it on unless you know otherwise.

Inputs: user (required), raw, owner, status, +4 more.

validateNewObjectA
Read-only

Ask the system whether a new object may be created under this name, in this package, with this description - the check ADT runs before a creation dialog is accepted. It answers with the reason a name is refused (already taken, reserved, wrong namespace). Careful with the answer: some collections reply 200 with an empty body, which the library reads as failure, so a free name can look refused; createObject and createAndWrite handle that themselves.

Inputs: options (required).

whereUsedMethodA
Read-only

Who calls this method. usageReferences needs the line and column of the name inside the source, which means reading the class first and counting characters; here the method name is enough. Returns the callers with the object they sit in, and their source snippets with snippets=true.

Inputs: method (required), snippets, className, interfaceName, +1 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 177 tools

    Disambiguation3/5

    Many tools have clearly distinct purposes, and the descriptions are thorough. However, there are redundant tools like unitTestRun vs runTests and activateByName vs activateSafe vs activateObjects, which overlap in function and require reading descriptions to choose correctly. With 177 tools, some clusters (source read/edit, debugger, ATC) risk confusion despite good documentation.

    Naming Consistency4/5

    The vast majority follow a consistent camelCase convention with verbs first (getObjectSource, setTextElements, activateSafe). Minor deviations exist: noun-based tools like revisions, dumps, systemUsers, and inconsistent prefixes (hasTransportConfig vs transportConfigurations vs getTransportConfiguration) create slight irregularity, but the overall pattern is predictable.

    Tool Count1/5

    At 177 tools, the count is far beyond the 25+ threshold and even the 50+ extreme-mismatch line. While the ADT API is broad, the sheer number makes the server unwieldy for an agent to navigate, and many tools could be consolidated (e.g., multiple debugger step/attach tools, several ATC worklist variants).

    Completeness5/5

    The surface covers nearly every ADT domain: source CRUD, DDIC (domains, data elements, structures, tables), transports, git, debugging, ATC, traces, refactoring, function modules, unit tests, and data querying. Lifecycle operations are present for most object types, with activation verification and rollback awareness. No obvious critical gaps for the stated purpose.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables interaction with SAP ABAP systems through ABAP Development Tools (ADT), providing access to repository objects, source code, where-used analysis, and SQL queries with support for both on-premise and BTP systems.
      3
      -
    • A
      license
      C
      quality
      D
      maintenance
      Facilitates interaction with SAP ABAP systems via ADT APIs, enabling object management, transport handling, and code analysis.
      100
      207 npm
      MIT