MCP serverMCP servertypeExposes tools to a coding agent over MCP.See every one →What this type is →independently maintainedindependently maintainedoriginPublished by one person or a small company outside SAP. Says nothing about quality; the readings say that.See every one →

fr0ster/mcp-abap-adt

Full CRUD across on-premise and ABAP Cloud systems, with five authentication methods and three transports: a library under Apache-2.0 and a standalone server under AGPL-3.0-only.

Built for pair programming rather than autopilot, in the author's words, and the breadth is in what it will connect to: ECC and S/4HANA on-premise and ABAP Cloud on BTP; legacy systems below BASIS 7.50 are not supported at present, parked on a branch until tried against a live one. Authentication through basic credentials, JWT/XSUAA, a service key selected by name from a local directory, a client certificate or Kerberos — the last two, by its own account, not yet tested against a real SAP system — with RFC as an alternative connection type; transports over stdio, HTTP or SSE. The tool surface covers where-used, object metadata, repository navigation and structure, dependency and impact exploration, and CRUD on classes, interfaces, function groups, programs, DDIC and CDS artifacts validated through ADT flows. A separate configurator CLI writes the client configuration.

Facts

Last pushLicenceApache-2.0

Where it sits

Lives in
Provides
Agent access to your codeABAPAgent access to your codecapabilityLetting a coding agent read the project or the system you actually have, reach its model, its objects and its checks, and act on it. In file-based environments that works out of the box; complex systems need dedicated access capabilities. Whatever the environment, the agent also needs project- and framework-specific context, so it understands the thing in front of it rather than guessing from what it remembers of its training data. The transport is incidental: an MCP server, a language server or a CLI answer the same question, and what differs between them is how much of the real thing the agent can see. Distinct from [MCP servers](capability:mcp-servers), which is a protocol adapter on your own running service, and from [agent guidance](capability:agent-guidance), which is knowledge handed over as files rather than read from the thing itself.
Optional
SAP Authorization and Trust Management serviceSAP Authorization and Trust Management serviceartifactbrowser-based JWT authentication issued via an XSUAA service keySAP NetWeaver RFC SDKSAP NetWeaver RFC SDKoutside the atlascompiling and running the RFC module needed for RFC and SNC connections to the ABAP systemDockerDockeroutside the atlasrunning the server as a container image instead of an npm install

Who is behind it

Oleksii KyslytsiaOleksii KyslytsiaPersonMaintains this
In the atlas: maintains 2, contributes to 2
@fr0ster
maintainerHenry MaoPersonContributes to this
In the atlas: contributes to 4
@calclavia
mario-andreschakPersonContributes to this
In the atlas: maintains 3, contributes to 5
@mario-andreschak
Frank FiegelPersonContributes to this
In the atlas: contributes to 3
@punkpeye
raaleksandr-epamPersonContributes to this
In the atlas: contributes to 2
@raaleksandr-epam
4 contributors

Before choosing this

Conditions

PracticalFunctional boundary

Two licences, by which package you install: the standalone server, @mcp-abap-adt/core, is AGPL-3.0-only, a licence to read before shipping anything built around it; the library, @mcp-abap-adt/lib, is Apache-2.0. The destination model also expects service keys sitting in a local directory, which is a secrets decision to make deliberately.

PolicySAP API Policy 4.2026a§ 1.1, 1.2, 2.2.2from Agent access to your codeAn MCP server calls ADT or another SAP API of a running system for an agent.
  • § 1.1 Published APIs. Published APIs are made available for the documented purposes identified by SAP, including integration, extensions and data exchange.
  • § 1.2 Non-Published APIs. Customer and third-party applications must not access non-published APIs except where documentation permits it or SAP otherwise authorizes it.
  • § 2.2.2 AI call sequences and data extraction. Outside SAP-endorsed architectures, data services or expressly identified pathways, the policy prohibits specified agentic API use and systematic or large-scale extraction.

An agent that plans and executes ADT calls is the case section 2.2.2 names. Check that the endpoints are published for the intended use, and that the design runs within a pathway SAP identifies for it.

SAP API Policy 4.2026a →

What it declares

Every artifact

Type
MCP server
Origin
Independently maintained
Runtime
Node.js

As an MCP server

Runs as
central serverlocal process
Transport
stdioHTTPSSE
Authentication
user and passwordOAuth / OIDCservice keyX.509Kerberos
Read-only
nostates it is not
Guardrails
4 quoted from README show all
  • Enable/disable handler groups

  • By default, sessions are stored in-memory (secure, lost on restart)

  • NTLM is hard-rejected:

  • Certificate and Kerberos auth pass full unit coverage but have not been tested against a real SAP system.

For agent access to your code

Read
sourcesearchwhere-usedDDICCDStable data
Change
writecreateactivatetransportsgit
Check
syntaxATCABAP Unit
Run
run codedumps
property statedstated nonot said

Tasks this contributes to1Beta

Let an agent change ABAP in an on-premise systemAn agent reads and changes ABAP in a system the customer runs themselves, on the classic development model, and the change is activated there.Standard ABAPCovers, under conditionsthe entry that names the oldest systems
What it does

Names on-premise ECC and SAP S/4HANA in its own supported list, and goes below every other entry on this door: legacy systems on a BASIS release under 7.50, where vibing-steampunk's floor sits. It states full create, read, update and delete on ABAP artifacts rather than a read-only posture, and documents two authentication routes it marks as on-premise only, client certificates over mTLS and Kerberos through SPNEGO.

Conditions

Client certificate and Kerberos authentication are documented as on-premise HTTP only, so the route that fits a classic system is not the one that fits a BTP one.; A legacy system under BASIS 7.50 is served by a separate tool set rather than the full one, which the README lists on its own page.

Remaining work

Reading which tools the legacy set actually holds for the release in front of you, since the entry's reach below 7.50 is stated as a separate surface rather than the same one.

All options on the Agent access to your code page →

Connections11

fr0ster/mcp-abap-adt connectionsfr0ster/mcp-abap-adtPROVIDES→ provides1provides authored Reaches ABAP Cloud and on-premise systems behind five authentication methods, with full create, read, update and delete rather than read-only. fr0ster/mcp-abap-adt — README: https://github.com/fr0ster/mcp-abap-adtAgent access to your codeUSED BY← optionally used by1optionally used by derived If: at least one of mcp-abap-adt or vibing-steampunk is connected System access for every skill that touches the system: reading and writing source, running ATC, running ABAP Unit and activating. Through: inverted from its requires edge CoVeles/claude-abap-dev — README, Built on: https://github.com/CoVeles/claude-abap-devclaude-abap-devifNEEDS→ connects to4connects to stated reaches an ECC or S/4HANA system run on premise through ADT, where agents read and change ABAP repositories fr0ster/mcp-abap-adt — README: https://github.com/fr0ster/mcp-abap-adt fr0ster/mcp-abap-adt — README: https://github.com/fr0ster/mcp-abap-adtABAP platformconnects to stated reaches an ABAP Cloud system on BTP through ADT fr0ster/mcp-abap-adt — README: https://github.com/fr0ster/mcp-abap-adtSAP BTP ABAP environmentconnects to stated ADT access to the ABAP repository on an on-premise ECC system mcp-abap-adt README: https://github.com/fr0ster/mcp-abap-adtSAP ERP (ECC)connects to stated ADT access to the ABAP repository on an on-premise or private-cloud S/4HANA system mcp-abap-adt README: https://github.com/fr0ster/mcp-abap-adtSAP S/4HANA→ optionally needs3optionally needs stated If: deployed via the project's Docker images running the server as a container image instead of an npm install mcp-abap-adt: docs/deployment/README.md: https://raw.githubusercontent.com/fr0ster/mcp-abap-adt/main/docs/deployment/README.mdDockerifoptionally needs stated If: JWT authentication selected with a service key (ABAP or XSUAA) browser-based JWT authentication issued via an XSUAA service key mcp-abap-adt README: https://github.com/fr0ster/mcp-abap-adtSAP Authorization and Trust Managementserviceifoptionally needs stated If: RFC or SNC connection type selected compiling and running the RFC module needed for RFC and SNC connections to the ABAP system mcp-abap-adt README: https://github.com/fr0ster/mcp-abap-adtSAP NetWeaver RFC SDKifLINEAGE← inspired1inspired derived Through: inverted from its lineage edge@mcp-abap-adt/core→ inspired by1inspired by stated fr0ster/mcp-abap-adt — README: https://github.com/fr0ster/mcp-abap-adtmcp-abap-adt
Where fr0ster/mcp-abap-adt runsYour SAP BTP subaccountOn-premisefr0ster/mcp-abap-adt — README, checked 2026-09-11: “Multiple transports: **stdio**, **HTTP**, **SSE**”stdio or HTTP or SSEdocumentedmay needif JWT authentication selectedwith a service key (ABAP orXSUAA)documentedmay needif RFC or SNC connection typeselecteddocumentedmay needif deployed via the project'sDocker imagesdocumentedoutbounddocumentedoutbounddocumentedconnectsdocumentedconnectsYour MCP client, actorYour MCP clientSAP NetWeaver RFC SDK, SoftwareSAP NetWeaver RFC SDKSoftwareDocker, SoftwareDockerSoftwareABAP platform, SAP solutionABAP platformSAP solutionSAP S/4HANA, SAP solutionSAP S/4HANASAP solutionfr0ster/mcp-abap-adt, modulefr0ster/mcp-abap-adtSAP Authorization and Trust Management service, service instanceSAP Authorizationand TrustManagement serviceSAP BTP ABAP environment, service instanceSAP BTP ABAPenvironmentSAP ERP (ECC), SAP solutionSAP ERP (ECC)SAP solution
SAPnot SAP's, or the project's ownstated in the documentdocumented onlyunder a condition

README

quoted from fr0ster/mcp-abap-adt under Apache-2.0, read
fr0ster/mcp-abap-adt/README.mdOn GitHub

mcp-abap-adt logo mcp-abap-adt: Your Gateway to ABAP Development Tools (ADT)

Stand With Ukraine

mcp-abap-adt is an MCP server for ABAP ADT in SAP ECC/S/4HANA (on-premise) and SAP BTP ABAP Cloud systems. It gives agents controlled access to real ABAP repositories through ADT, so analysis and changes are grounded in system data instead of assumptions. It is built for AI-assisted pair programming (AIPNV: AI Pairing, Not Vibing), not autopilot vibe coding.

Primary workflows:

  • Deep ABAP analysis: where-used, object metadata, repository navigation, object structure, semantic analysis, dependency and impact exploration.
  • High-level ABAP development: rapid CRUD and iterative updates for RAP and classic ABAP artifacts (classes, interfaces, function groups/modules, programs, DDIC, CDS/view/service artifacts), validated through ADT flows.

Why teams use it:

  • Full CRUD (not read-only): create, read, update, and delete ABAP artifacts
  • Works with On-Premise (ECC/S/4HANA) and ABAP Cloud (BTP) systems
  • Legacy systems (BASIS < 7.50) are not supported at present: that support is parked on the parked/legacy-support branch until it can be tried against a live legacy system
  • Three authentications over stdio: basic (HTTP or RFC), SNC (passwordless, RFC), JWT with browser login (service key, ABAP or XSUAA); a JWT you already hold goes in the x-sap-jwt-token header of mcp-abap-adt-http / mcp-abap-adt-sse
  • Multiple transports: stdio, HTTP, SSE
  • Several installation variants: the full server (@mcp-abap-adt/core) or the compact one (@mcp-abap-adt/compact), each connecting to the system over HTTP, RFC or SNC — see Installation variants
  • Rich tool surface for ABAP objects, metadata, transports, and search

Authorization & Destinations (Important): A destination is the filename of a service key stored locally. You place service keys in the service-keys directory, and use --mcp=<destination> to select which one to use. This is the primary auth model for on‑prem and BTP systems. See Authentication & Destinations.

You can configure MCP clients either manually (JSON/TOML) or via the configurator CLI (@mcp-abap-adt/configurator, repo: mcp-abap-adt-conf).

Table of Contents

  1. Getting Started
  2. Architecture
  3. Quick Start
  4. Use Cases
  5. Target Users
  6. Capabilities (High-Level Focus)
  7. Terminology
  8. Authorization & Destinations
  9. Registries
  10. Features
  11. Documentation
  12. Dependencies
  13. Running the Server

Getting Started

Pick a variant first — full or compact server, and HTTP, RFC or SNC to the system. HTTP needs nothing but Node.js 22 or 24; RFC and SNC need the SAP NW RFC library when the server runs — SAP GUI's 64-bit sapnwrfc.dll on Windows, else the SAP NW RFC SDK; the RFC module comes prebuilt for Linux x64 (glibc), Windows x64 and macOS ARM64, and npm install compiles nothing. Installation variants has the table and the commands; RFC Setup the steps. To run without Node.js on a machine, build your own portable executable — Node.js and your SAP NW RFC SDK inside.

Install the server and configure your client using the configurator:

npm install -g @mcp-abap-adt/core
npm install -g @mcp-abap-adt/configurator

# stdio (destination)
mcp-conf --client cline --name abap --mcp TRIAL

# HTTP (streamable HTTP)
mcp-conf --client copilot --name abap --transport http --url http://localhost:3000/mcp/stream/http

Full configurator usage (separate repo): CLIENT_INSTALLERS.md.

Terminology

Destination: a local service key filename. You store service keys in the standard service-keys directory, and pass the filename (without extension) via --mcp=<destination> to select which system to use.

See docs/user-guide/TERMINOLOGY.md for the full list.

Authorization & Destinations

Destination-based auth is the default. Drop service keys into the standard platform folder and use the filename as your destination:

mcp-abap-adt --mcp=TRIAL --system-type=cloud

Standard service key paths:

  • Unix (Linux/macOS): ~/.config/mcp-abap-adt/service-keys/<destination>.json
  • Windows: %USERPROFILE%\\Documents\\mcp-abap-adt\\service-keys\\<destination>.json

mcp-abap-adt (stdio) supports exactly three authentications, each a destination stated in a service key, a .env, or both; a JWT you hold is sent in headers to the HTTP or SSE server:

Authentication.env keys
Basic (HTTP or RFC)SAP_AUTH_TYPE=basic, SAP_USERNAME, SAP_PASSWORD
SNC (RFC only, passwordless)SAP_AUTH_TYPE=snc, SAP_SNC_PARTNERNAME, optional SAP_SNC_QOP, SAP_SNC_LIB, SAP_SNC_MYNAME — no user, no password
JWT, browser loginSAP_AUTH_TYPE=jwt, SAP_GRANT_TYPE=authorization_code, a service key (ABAP or XSUAA) or SAP_UAA_*
JWT you holdthe x-sap-jwt-token header of mcp-abap-adt-http / mcp-abap-adt-sse (stdio serves none)

A jwt .env must state SAP_GRANT_TYPE. The mcp-auth command that writes such a .env comes from @mcp-abap-adt/auth-broker-cli. The browser login listens on port 61001 unless --browser-auth-port says otherwise.

A created object always carries its responsible person and, where known, its master system. The servers name them once per destination — its credentials and its ABAP system — when its connection is created, by the rules of @mcp-abap-adt/responsible-rules: the system's systeminformation answer (its user, its system id) wins, on-premise and on ABAP Cloud alike; a tool argument that names the value (a transport's owner, say) still states it for that call. When the system gives no information, ABAP Cloud is an error (cloud_without_systeminformation for a 404, else system_lookup_failed); on-premise the responsible is SAP_RESPONSIBLE / x-sap-responsible if stated, else the login (SAP_USERNAME, x-sap-login), and the master system SAP_MASTER_SYSTEM / x-sap-master-system when stated; with no user at all the call is refused system_user_unknown, and a stdio start ends with exit 1. A 401 is answered as it came. The library applies no rules unless an embedder passes them. See Authentication & Destinations. Coming from 19.x? See the 20.0 migration note — RFC on connection 19 and sap-rfc-lite 0.3.2: nothing compiled on install, a prebuilt binding for Linux x64, Windows x64 and macOS ARM64 only; over Streamable HTTP state_handle is an argument of every tool, and a long session keeps one SAP session across calls. Coming from 18.x? See the 19.0 migration note — the server split into a package per transport and tool set, the system type required, the low set gone, the auth chain on 6.0. Coming from 17.x? See the 18.0 migration note — the debugger's embedder surface moved to adt-clients 29, and a debugger start needs at least one breakpoint. From 16.x, the 17.0 note first.

For full details (paths, .env, direct headers), see Authentication & Destinations.

Architecture

The project ships a library and the servers — and a compact variant of each server — because the two usage patterns want different licences. Embedding the tools in a network service should not drag in the obligations of a server that service never runs.

PackageLicenceWhat it is
@mcp-abap-adt/libApache-2.0The ADT tool handlers and the embeddable MCP server. No transport: the host supplies one.
@mcp-abap-adt/coreAGPL-3.0-onlyThe standalone stdio server, mcp-abap-adt: one destination through the auth broker. Depends on the library.
@mcp-abap-adt/httpAGPL-3.0-onlyThe Streamable HTTP server, mcp-abap-adt-http: credentials from each request's x-sap-* headers.
@mcp-abap-adt/sseAGPL-3.0-onlyThe HTTP+SSE server, mcp-abap-adt-sse: credentials from the x-sap-* headers of each SSE connection's GET.
@mcp-abap-adt/compactAGPL-3.0-onlyThe compact server over stdio, mcp-abap-adt-compact: one tool per operation, the object type in the arguments. core's launcher and configuration (from server-runtime) with the compact tool set.
@mcp-abap-adt/compact-httpAGPL-3.0-onlyThe compact server over Streamable HTTP, mcp-abap-adt-compact-http: http's launcher and configuration (from server-runtime) with the compact tool set.
@mcp-abap-adt/compact-sseAGPL-3.0-onlyThe compact server over HTTP+SSE, mcp-abap-adt-compact-sse: sse's launcher and configuration (from server-runtime) with the compact tool set.
@mcp-abap-adt/server-runtimeAGPL-3.0-onlyThe launch code of every server: the stdio, Streamable HTTP and SSE launchers and servers, and the destination layer. No command of its own: every server package depends on it, so it is installed with each server; you never install it yourself.
@mcp-abap-adt/background-runs-memoryApache-2.0The in-memory store of background runs (IBackgroundRuns of the library): the servers' store, for an embedder that keeps runs in its process. Every server package depends on it.
@mcp-abap-adt/responsible-rulesApache-2.0The rules that name a call's responsible, user and system from systeminformation (SystemContextRules of the library), and the system type the binaries require. Every server package passes them; the library applies none, so an embedder opts in.
@mcp-abap-adt/core-ro, http-ro, sse-roAGPL-3.0-onlyThe read-only servers, mcp-abap-adt-ro, mcp-abap-adt-http-ro, mcp-abap-adt-sse-ro: core's, http's and sse's launcher with the readonly tools (with system and search), batch mode and, on stdio, the leftover-session list and logoff — no high, debugger, background-run or resume tool.
@mcp-abap-adt/compact-ro, compact-http-ro, compact-sse-roAGPL-3.0-onlyThe read-only compact servers, mcp-abap-adt-compact-ro, -compact-http-ro, -compact-sse-ro: the 17 compact tools that change nothing, HandlerBatch and, on stdio, the leftover-session list and logoff.
@mcp-abap-adt/compact-readonly-entries, compact-modifyApache-2.0The compact tools as libraries, split into the half that changes nothing and the half that writes (compact-readonly-entries was compact-readonly before 19.0.0).

Eighteen packages, twelve binaries: four kinds of binary — core, core-ro, compact, compact-ro — on each transport. The binary fixes the tool set (19.0.0; there is no --exposition): install the one for your transport and the tools you want to run a server — @mcp-abap-adt/core, http or sse (readonly, high, the debugger), their -ro packages (the readonly tools alone), compact, compact-http, compact-sse, or their -ro packages (the compact tools that change nothing). Install @mcp-abap-adt/lib to embed the tools in your own application. Every binary and its tools: Handler sets.

Each server is installed on its own (npm install -g @mcp-abap-adt/compact): a server package brings everything it runs on — server-runtime, lib and the rest — and depends on no other server package, so installing two servers, in one command or in two, is fine and neither needs the other. Since 19.2.0 a process that loads two copies of @mcp-abap-adt/lib anyway does not start: it prints Two copies of @mcp-abap-adt/lib are loaded in this process (…): reinstall <package> (npm install -g <package>). and exits 1 — see One lib per server process.

1. Standalone MCP Server (Default)

Run as a standalone MCP server; a binary is its transport (there is no --transport option):

mcp-abap-adt --mcp=TRIAL --system-type=cloud # stdio (@mcp-abap-adt/core): one destination
mcp-abap-adt-http --system-type=<onprem|cloud> # Streamable HTTP (@mcp-abap-adt/http): x-sap-* headers per request
mcp-abap-adt-sse --system-type=<onprem|cloud> # SSE (@mcp-abap-adt/sse): x-sap-* headers per SSE connection
mcp-abap-adt-compact --mcp=TRIAL --system-type=cloud # the compact tool set over stdio (@mcp-abap-adt/compact)
mcp-abap-adt-compact-http --system-type=<onprem|cloud> # ... over Streamable HTTP (@mcp-abap-adt/compact-http)
mcp-abap-adt-compact-sse --system-type=<onprem|cloud> # ... over SSE (@mcp-abap-adt/compact-sse)
mcp-abap-adt-ro --mcp=TRIAL --system-type=cloud # the readonly tools alone over stdio (@mcp-abap-adt/core-ro)
mcp-abap-adt-compact-ro --mcp=TRIAL --system-type=cloud # the compact tools that change nothing (@mcp-abap-adt/compact-ro)
                                       # (and -http-ro, -sse-ro, -compact-http-ro, -compact-sse-ro)
2. Embeddable Server (For Integration)

Embed MCP server into existing applications (e.g., SAP CAP/CDS, Express). This needs @mcp-abap-adt/lib only — not the AGPL server:

npm install @mcp-abap-adt/lib
import { EmbeddableMcpServer } from '@mcp-abap-adt/lib/embeddable';

const server = new EmbeddableMcpServer({
  connection,              // Your AbapConnection instance
  logger,                  // Optional logger
  exposition: ['readonly', 'high'],  // Handler groups to expose
  // A tool both groups serve under one name (Get<Type>) is listed once.
  // readOnlyDedupStrategy: a custom IReadOnlyDedupStrategy hides more.
});
await server.connect(transport);

See Handlers Management → EmbeddableMcpServer dedup strategies for how readonly tools are deduped against high and how to plug a custom IReadOnlyDedupStrategy for role-based rules.

Quick Start

  1. Install server: pick the installation variant — full or compact, HTTP, RFC or SNC — and follow the Installation Guide
  2. Configure client (auto): Use mcp-conf from @mcp-abap-adt/configurator (repo: mcp-abap-adt-conf, docs: CLIENT_INSTALLERS.md)
  3. Configure client (manual): See Client Configuration
  4. Use:

Use Cases

  • Impact analysis / where-used before changes: map object usage and probable blast radius.
  • Dependency audit: inspect links across classes, interfaces, DDIC, CDS/views, and RAP artifacts.
  • Migration and cleanup prep: extract repository facts to plan refactoring or cloud-readiness work.
  • RAP and ABAP iterative development: create/update artifacts quickly with ADT-backed operations.
  • Automated documentation and RAG ingestion: pull structured facts from ABAP systems for downstream tooling.

Target Users

  • ABAP developers and ABAP architects
  • RAP developers
  • Team leads and tech leads who need fast repository visibility
  • Teams building RAG/agent workflows for SAP landscapes

Capabilities (High-Level Focus)

Key examples of high-value workflows and tools:

  • Repository and impact analysis: GetWhereUsed, DescribeByList, GetObjectStructure, GetObjectInfo, SearchObject, GetPackageTree, GetPackageContents
  • Code and semantic introspection: GetAbapAST, GetAbapSemanticAnalysis, GetIncludesList
  • RAP development: CreateBehaviorDefinition, UpdateBehaviorDefinition, CreateBehaviorImplementation, UpdateBehaviorImplementation, CreateServiceDefinition, UpdateServiceDefinition, CreateMetadataExtension, UpdateMetadataExtension
  • CDS/View development: CreateView, UpdateView, GetView, DeleteView
  • ABAP OO CRUD: CreateClass, UpdateClass, GetClass, DeleteClass, CreateInterface, UpdateInterface, GetInterface, DeleteInterface
  • Function module/group CRUD: CreateFunctionGroup, UpdateFunctionGroup, GetFunctionGroup, DeleteFunctionGroup, CreateFunctionModule, UpdateFunctionModule, GetFunctionModule, DeleteFunctionModule
  • Transport and activation support: CreateTransport, GetTransport, ActivateObject
  • Quality and testing: RunATC, GetATCRunStatus, GetATCFindings (ABAP Test Cockpit — one run over several objects, findings read back by worklist), ABAP Unit per test carrier — CreateUnitTest/UpdateUnitTest/DeleteUnitTest/RunUnitTest for a class, CreateCdsUnitTest/RunCdsUnitTest for a CDS view, CreateProgramUnitTest/RunProgramUnitTest for a report, CreateFunctionGroupUnitTest/RunFunctionGroupUnitTest/RunFunctionModuleUnitTest for a function group (runner: 'async', the default, waits for the run within a bound, and GetUnitTestResult reads one that outlasts the wait; runner: 'sync' is one request to /abapunit/testruns answering the finished result — for a system that has that endpoint alone; a function module's synchronous run takes function_group_name and covers every test of its group) — CheckClass and the rest of the Check* family
Long reads take a timeout

The long reads on an approved list (where-used and search, package walks, data preview, the dump feed, profiler traces, … — the list in MIGRATION-19.0) run on an ABAP session of their own and take an optional timeout in seconds, with no default. When it passes the read is cancelled — its session logged off — and nothing was changed. Set it below your MCP client's request limit (the TypeScript SDK's client cancels at 60 s): a tool call cannot be cancelled — a client's cancel aborts nothing, the call runs to its end. No other tool takes a timeout: a unit test, ATC, class or program run goes on to its end.

Batch mode

Calls none of whose requests depends on another's answer can be recorded and sent together, in one ADT batch envelope, instead of one request each: BatchBegin switches the server into batch mode, every such call is then recorded, answers nothing and takes the next number (from 1, in the order made), BatchEnd sends them all and leaves batch mode whatever happens, and BatchAnswers reads each call's answer by its number — the answer the tool gives outside batch mode for what SAP answered. The compact binaries serve the same as one tool, HandlerBatch (action: 'begin' | 'end' | 'answers'). Every binary serves it, the -ro ones included, on every transport.

A tool that builds a request from an earlier answer is refused in batch mode — a high-level create or update, a fallback after a 404, a poll. Run such work as phases, each its own batch, with the single-step tools: create (compact HandlerCreate, one request), lock (HandlerLock; the handles read with BatchAnswers), write (HandlerUpdate with lock_handle, one request; the whole metadata document in source_code for PACKAGE, DOMAIN, DATA_ELEMENT and FUNCTION_GROUP), unlock (HandlerUnlock), read back (HandlerGet, one request per part: source or metadata), activate (HandlerActivate, one request; a list, an object_adt_type and the types the per-object activation leaves to a run answer { run_id }), and the run's state (HandlerGet part: activation_status, then activation_results, by run_id, as often as you choose); a unit-test run is one request with the synchronous runner (HandlerUnitTestRun with runner: 'sync', answering the finished result) or, with the asynchronous runner (runner: 'async', the default), its start (HandlerUnitTestRun, answering run_id), its status (HandlerUnitTestStatus, as often as you choose, until FINISHED) and its result (HandlerUnitTestResult, one request); a profiled run is its schedule (HandlerProfileSchedule, answering profiler_id), the run under it (HandlerProfileRun with that profiler_id) and its trace (HandlerProfileList, as often as you choose, then HandlerProfileView); a deletion is a deletion check (HandlerDeletionCheck, one request) and then the delete (HandlerDelete, the deletion request alone). A batch holds one kind of request — LOCK, UNLOCK or stateless, as its first recorded call decides — so each batch end sends one envelope; a call of another kind is refused batch_other_kind: record it in the next batch. Every tool's description ends with one sentence saying how it behaves in batch mode: recordable, recordable for the arguments it names, or run outside batch mode. Over Streamable HTTP, send the state_handle BatchBegin answers as the state_handle argument (or in the x-mcp-state-handle request header) on every call of the batch. See Batch Mode, with the list of every recordable call.

Registries

Published in the official MCP Registry and listed on Glama.ai.

Features

  • 🏗️ Domain Management: GetDomain, CreateDomain, UpdateDomain - Create, retrieve, and update ABAP domains
  • 📊 Data Element Management: GetDataElement, CreateDataElement, UpdateDataElement - Create, retrieve, and update ABAP data elements
  • 📦 Table Management: GetTable, CreateTable, GetTableContents - Create and retrieve ABAP database tables with data preview
  • 🏛️ Structure Management: GetStructure, CreateStructure - Create and retrieve ABAP structures
  • 👁️ View Management: GetView, CreateView, UpdateView - Create and manage CDS Views and Classic Views
  • 🎓 Class Management: GetClass, CreateClass, UpdateClass - Create, retrieve, and update ABAP classes
  • 📝 Program Management: GetProgram, CreateProgram, UpdateProgram - Create, retrieve, and update ABAP programs
  • 🔧 Behavior Definition (BDEF) Management: GetBehaviorDefinition, CreateBehaviorDefinition, UpdateBehaviorDefinition - Create and manage ABAP Behavior Definitions with support for Managed, Unmanaged, Abstract, and Projection types
  • 📋 Metadata Extension (DDLX) Management: CreateMetadataExtension, UpdateMetadataExtension - Create and manage ABAP Metadata Extensions
  • ⚡ Activation: ActivateObject - Universal activation for any ABAP object
  • 🚚 Transport Management: CreateTransport, GetTransport - Create and retrieve transport requests
  • 🔍 Enhancement Analysis: GetEnhancements, GetEnhancementImpl, GetEnhancementSpot - Enhancement discovery and analysis
  • 📋 Include Management: GetIncludesList - Recursive include discovery
  • 🔍 System Tools: GetInactiveObjects - Monitor inactive objects waiting for activation
  • 🌐 Service Binding Preview: GetServiceBindingPreviewUrl - The browser URL that opens a published service binding's Fiori preview, beside its OData service and $metadata URLs. Composed from the binding, its service definition and the exposed root view — no document carries it. OData V2 and V4; a Web API binding has no preview and says so. On SAP BTP the preview URL carries the browser host (abap-web), where the BTP logon answers, while the service URLs keep the ADT host
  • 🧪 Runtime Diagnostics: RuntimeCreateProfilerTraceParameters, RuntimeListProfilerTraceFiles, RuntimeGetProfilerTraceData, RuntimeGetDumpById - Profiling and dump analysis with JSON payloads
  • 📡 Runtime Feeds: RuntimeListFeeds, RuntimeListSystemMessages, RuntimeGetGatewayErrorLog - Feed reader (dumps — filtered by user, runtime error, exception, object, package or component, and read past SAP's 100 entries per request — system messages, gateway errors), SM02 system messages, Gateway error log
  • 🐞 Debugger (in mcp-abap-adt, -http, -sse and the compact write binaries; in no -ro binary): ABAP and AMDP — breakpoints (line, exception, statement, message; a line breakpoint may carry a condition), a background run of a class or report caught at them, another user's requests and terminal mode, stack, variables, watchpoints, steps, leaving a debuggee running, the debugger settings, memory sizes and snapshots. A debug session is named by a state_handle and survives between tool calls on every transport. In user mode a breakpoint catches every request of the request user (the connected SAP user unless another is named): where that must not be possible, install a -ro binary. See Debugger
  • 🔗 Long session over Streamable HTTP (mcp-abap-adt-http, -compact-http and their -ro): every tool takes an optional state_handle argument that routes the call to the instance holding that state (a lock, a batch, a debug session); SessionOpen / SessionClose (compact: HandlerSession) keep one instance and its SAP session for a run of calls and log it off at the close, or at the idle bound. See @mcp-abap-adt/http
  • 🚀 SAP BTP Support: JWT/XSUAA authentication with browser-based token helper
  • 🔑 Destination-Based Authentication: Service key-based authentication with automatic token management (see Client Configuration)
  • 💾 Freestyle SQL: GetSqlQuery - Execute custom SQL queries via ADT Data Preview API

ℹ️ ABAP Cloud limitation: Direct ADT data preview of database tables is blocked by SAP BTP backend policies. The server returns a descriptive error when attempting such operations. On-premise systems continue to support data preview.

Documentation

For Users
  • Docs Index - Full documentation index
  • Installation Guide - Installation variants (full or compact; HTTP, RFC or SNC) and platform guides
  • RFC Setup - RFC and SNC: the SAP NW RFC library at run time, the prebuilt RFC module and where to build one
  • User Guide - End-user docs (auth, config, tools)
  • Authentication & Destinations - Destination-based auth and service keys
  • Batch Mode - Record independent calls, send them together in batch envelopes at BatchEnd, read their answers by number; which calls are recorded
  • Tool Search - The three texts each listed tool carries — description for the model, keywords for BM25, semantic for vector search — and where a client reads them
  • Debugger - The debug set of the write binaries: debug sessions and their handle, the SAP ids, what keeps a session alive on each transport, what ends it
  • Handlers Management - The twelve binaries and the tools each serves
  • Configurator: @mcp-abap-adt/configurator (repo: mcp-abap-adt-conf) provides the mcp-conf CLI to auto-configure clients
  • Tools by level
For Administrators
  • Security - Never more secure than its host: who may call the server, HTTPS, TLS to SAP, credentials and state handles, a deployment checklist
  • Deployment Docs - MCP Registry, Docker, release notes
  • Server Configuration - YAML config reference
For Developers
  • Architecture Documentation - System architecture and design decisions
  • Development Documentation - Testing guides and development resources
  • CHANGELOG.md - Version history and changes
  • docs/MIGRATION-20.0.md - RFC on connection 19 and sap-rfc-lite 0.3.2: nothing compiled on install, prebuilt bindings for three platforms, the RFC library at run time, the portable build on 0.3 only; Streamable HTTP: held state by the state_handle argument, and the long session
  • docs/MIGRATION-19.0.md - A package per transport and tool set (seventeen packages, twelve binaries), the system type required, the low set gone, the auth chain on 6.0, logoff at every session end
  • docs/MIGRATION-18.0.md - The debugger on adt-clients 29: what an embedder of lib/debugger changes; a start needs a breakpoint
  • docs/MIGRATION-17.0.md - The HTTPS certificate is verified; a message class needs a responsible
  • docs/MIGRATION-16.0.md - Authentication on auth-broker 4: four authentications, destinations
  • docs/MIGRATION-14.0.md - The compact facade moved to its own packages and --exposition=compact became the mcp-abap-adt-compact command
  • docs/MIGRATION-13.0.md - What a consumer on the 12.x contract changes
  • docs/MIGRATION-12.0.md - What a consumer on the 11.x contract changes

Dependencies

Two packages do the work:

and six packages declare the contracts both of them and this project are written against — @mcp-abap-adt/interfaces-adt, -adt-connection (where IAbapConnection and IAdtWireResponse live), -network, -auth, -auth-sap and -utils. They replace the single @mcp-abap-adt/interfaces umbrella, which is no longer published: a consumer naming a contract package directly gets one copy of it in the tree and takes its majors one domain at a time.

The verdict on an ADT answer is a strategy, not a default: since adt-clients 23 no member judges its own answer, and @mcp-abap-adt/adt-strategies holds the readings this project passes to every call. That is what keeps a refusal ADT embeds in an HTTP 200 — an activation that did not activate, a delete that was refused — from reaching a caller as success.

Everything above is installed by npm install and published to npm. @mcp-abap-adt/sap-rfc-lite is optional and only needed for the RFC transport (and so for SNC). It has no install script: it carries a prebuilt binding for Linux x64 (glibc), Windows x64 and macOS ARM64 and compiles nothing on install; elsewhere a binding is built by hand. At run time it needs the SAP NW RFC library — SAP GUI's 64-bit sapnwrfc.dll in System32 on Windows, else the SAP NW RFC SDK. Check it is installed with npm ls -g @mcp-abap-adt/sap-rfc-lite. See RFC Setup.


Running the Server

After installing globally with npm install -g, you can run from any directory:

# Show help
mcp-abap-adt --help

# Default stdio mode (for MCP clients; the destination comes from --mcp, or from a .env file named with --env or --env-path: a .env in the working directory is read only when named)
mcp-abap-adt --system-type=<onprem|cloud>

# Streamable HTTP on a custom port (@mcp-abap-adt/http; credentials from x-sap-* headers)
mcp-abap-adt-http --port=8080 --system-type=<onprem|cloud>

# Use stdio mode with auth-broker (--mcp parameter)
mcp-abap-adt --mcp=TRIAL --system-type=cloud

# Use env destination from platform sessions store
mcp-abap-adt --env=trial --system-type=cloud

# Use explicit .env file path
mcp-abap-adt --env-path=/path/to/my.env --system-type=<onprem|cloud>

# SSE (@mcp-abap-adt/sse; credentials from the opening GET's x-sap-* headers)
mcp-abap-adt-sse --sse-port=3001 --system-type=<onprem|cloud>

# The compact servers take their full counterpart's options; the binary fixes the tools
mcp-abap-adt-compact --env-path=/path/to/my.env --system-type=<onprem|cloud> # npm install -g @mcp-abap-adt/compact
mcp-abap-adt-compact-http --port=8080 --system-type=<onprem|cloud> # npm install -g @mcp-abap-adt/compact-http
mcp-abap-adt-compact-sse --sse-port=3001 --system-type=<onprem|cloud> # npm install -g @mcp-abap-adt/compact-sse

# The read-only servers: the same options, the tools that change nothing
mcp-abap-adt-ro --env-path=/path/to/my.env --system-type=<onprem|cloud> # npm install -g @mcp-abap-adt/core-ro
mcp-abap-adt-compact-http-ro --port=8080 --system-type=<onprem|cloud> # npm install -g @mcp-abap-adt/compact-http-ro
Development Mode
npm install
npm run build
npm test

The checkout is not runnable as a server by itself: server/ takes @mcp-abap-adt/lib as a package. To run a build, pack both and install the tarballs together — see From source.

Environment Configuration

Env resolution:

  1. --env-path=<path|file> (or MCP_ENV_PATH) for explicit .env file.
    • Absolute path: used as-is.
    • Relative path or file name only (e.g. my.env): resolved from current working directory.
  2. --env=<destination> for destination file in standard sessions store:
    • Unix: ~/.config/mcp-abap-adt/sessions/<destination>.env
    • Windows: %USERPROFILE%\\Documents\\mcp-abap-adt\\sessions\\<destination>.env

Nothing is looked up in the working directory: a server started inside someone else's project must not take their settings. A .env there is read only when you name it (--env-path=./.env).

Whichever file is chosen is read and written back with a renewed token, whatever --unsafe says. A destination is read once per process: a change to its .env from outside takes effect on restart. .env and environment variables hold secrets and the session; a YAML config file holds configuration only and refuses a secret-looking key.

Example .env file:

SAP_URL=https://your-sap-system.example
SAP_CLIENT=100
SAP_AUTH_TYPE=basic
SAP_USERNAME=your-username
SAP_PASSWORD=your-password

A JWT you already hold is not served over stdio: send it in the x-sap-jwt-token header to mcp-abap-adt-http or mcp-abap-adt-sse (@mcp-abap-adt/http, @mcp-abap-adt/sse), or create the session with mcp-auth.

For RFC connection:

SAP_URL=http://your-onprem-system.example:8000
SAP_CLIENT=100
SAP_AUTH_TYPE=basic
SAP_USERNAME=your-username
SAP_PASSWORD=your-password
SAP_CONNECTION_TYPE=rfc

SAP_CONNECTION_TYPE=rfc in the --env / --env-path .env selects RFC, as do --connection-type=rfc, the process environment and YAML connection-type: rfc. Precedence: CLI, then the process environment (which the .env value joins, never over one already set), then YAML. Over RFC the host comes from SAP_URL and the system number from its port (80NN → NN); a port that follows no such rule needs SAP_SYSNR in the process environment. See RFC Setup Guide for prerequisites (SAP NW RFC SDK, a C++ toolchain, SAPNWRFC_HOME before the install).

Through a message server and a logon group (load-balanced logon): add SAP_MSHOST with SAP_MSSERV or SAP_SYSID (one of the two is required; SAP_MSHOST with neither is refused by @mcp-abap-adt/connection, naming both), and optionally SAP_GROUP (the logon group; PUBLIC when absent — connection's default, not the server's) and SAP_SAPROUTER (works with either addressing). Each is also --mshost, --msserv, --sysid, --group, --saprouter, or the YAML key of that name, with the same precedence as the connection type; in the .env they belong to the --env / --env-path file. With SAP_MSHOST the RFC wire takes no host and no system number from SAP_URL, and SAP_SYSNR is not read; SAP_URL is still the system's URL. The server passes the five fields as configured and adds nothing. Logon through a message server is implemented and unit-tested, but not yet measured on a live system (as of 2026-10-20): if the SDK refuses SAP_MSSERV without SAP_SYSID, add SAP_SYSID.

SAP_URL=http://your-onprem-system.example:8000
SAP_CLIENT=100
SAP_AUTH_TYPE=basic
SAP_USERNAME=your-username
SAP_PASSWORD=your-password
SAP_CONNECTION_TYPE=rfc
SAP_MSHOST=your-message-server.example
SAP_SYSID=ABC
SAP_GROUP=PUBLIC
# Optional: SAP_MSSERV=sapmsABC, SAP_SAPROUTER=/H/your-router.example/S/3299/H/

For SNC (passwordless logon over RFC, no user or password):

SAP_URL=http://your-onprem-system.example:8000
SAP_CLIENT=100
SAP_CONNECTION_TYPE=rfc
SAP_AUTH_TYPE=snc
SAP_SNC_PARTNERNAME='p:CN=<system>, O=<org>, C=<country>'
# Creates need a responsible person; over SNC no login is known
SAP_RESPONSIBLE=<your ABAP user>
# Optional: SAP_SNC_QOP, SAP_SNC_LIB, SAP_SNC_MYNAME

The credential of the installed SNC product (for example a Secure Login Client) is mapped to an ABAP user by its SNC name. SAP_CONNECTION_TYPE=rfc (or --connection-type=rfc) is required: SNC logs on over RFC only, and an SNC destination with HTTP is refused. SNC needs the SAP NW RFC library (SAP GUI's on Windows, else the SDK) and @mcp-abap-adt/sap-rfc-lite, an optional dependency — see RFC Setup.

Not supported since 16.0: SAP_AUTH_TYPE=certificate and kerberos, saml, and a jwt grant other than authorization_code and none, in a .env or service key are refused at startup, naming the authentication (a saml destination with no grant: cannot be used: grantType). See the migration note for what to do instead.

Generate a .env (JWT):

# Install the CLI globally (one-time setup) — it ships mcp-auth
npm install -g @mcp-abap-adt/auth-broker-cli

# Log in and write a .env that states its authentication and grant (--type xsuaa for an XSUAA key)
mcp-auth --service-key ./service-key.json --output ./mcp.env --type abap
# or a destination: mcp-auth --destination TRIAL --type abap   (`mcp-auth --help` lists the rest)

The .env states SAP_AUTH_TYPE and SAP_GRANT_TYPE; a jwt .env without a grant is refused at startup.

.env comments rule: only full-line comments are supported (lines that start with #).
Inline comments are not parsed, so keep comments on separate lines.

Claude recommendation: place the service key in the service-keys directory and use --mcp=<destination> (avoid manual JWT tokens).

Command-Line Options

Authentication:

  • --mcp=<destination> - Named destination: service-keys/<destination>.json and sessions/<destination>.env, field by field
  • --auth-broker-path=<path> - Custom path for auth-broker service keys and sessions
  • --browser=<name> - Browser for a login: system (default), auto, chrome, edge, firefox, none, headless — mapped by the platform; none / headless: the URL on stderr (Authentication)
  • --browser-program=<program> - The program that opens the login URL instead (excludes --browser)
  • --renewal=<mode> - refresh-then-login (default) or refresh-only (never a login)
  • --browser-auth-port=<port> - Browser login callback port, 1-65535 (default: 61001); an invalid value is refused at startup
  • --login-timeout=<seconds> - Ends a browser login not finished after this many seconds, 1-86400; no default (a login ends on its answer, the IdP's refusal or the end of its session; a cancel ends nothing)
  • --connection-type=<http|rfc> - SAP connection transport: http (default) or rfc
  • --mshost, --msserv, --sysid, --group, --saprouter - RFC through a message server and a logon group (SAP_MSHOST, … — see above)
  • --unsafe - Write named destinations' sessions to disk. By default they are kept in memory (one login per process). A .env you name is written back either way

When --mcp=<destination> is specified, automatic fallback loading of ./.env is skipped.

Examples:

# Named destination, session kept in memory (default)
mcp-abap-adt --mcp=TRIAL --system-type=cloud

# Named destination, session persisted to sessions/TRIAL.env
mcp-abap-adt --mcp=TRIAL --unsafe --system-type=cloud

# Custom base directory for service-keys/ and sessions/
mcp-abap-adt --mcp=TRIAL --auth-broker-path=~/prj/tmp/ --unsafe --system-type=cloud

# Browser login on another callback port
mcp-abap-adt --mcp=TRIAL --browser-auth-port=61005 --system-type=cloud

See Client Configuration for complete configuration options.

Handler logging switches
  • AUTH_LOG_LEVEL=error|warn|info|debug — sets base log level for handler logger; DEBUG_AUTH_LOG=true also enables debug.
  • HANDLER_LOG_SILENT=true — fully disables handler logging.
  • DEBUG_CONNECTORS=true — verbose connection logging in high-level handlers.
  • DEBUG_HANDLERS=true — enables verbose logs for selected read-only/system handlers.

Development

Testing
npm test
Test logging switches
  • TEST_LOG_LEVEL=error|warn|info|debug — controls test logger verbosity (DEBUG_TESTS/DEBUG_ADT_TESTS/DEBUG_CONNECTORS force debug).
  • TEST_LOG_FILE=/tmp/adt-tests.log — writes test logs to a file (best-effort).
  • TEST_LOG_SILENT=true — disables test logging pipeline (console output muted).
  • TEST_LOG_COLOR=true — adds colored/prefixed tags to test log lines.
  • All console.* in tests are routed through the test logger with a [test] prefix.
Building
npm run build
Developer Tools
# Generate tool documentation
npm run docs:tools

# See tools/README.md for more developer utilities

Contributors

Thank you to all contributors! See CONTRIBUTORS.md for the complete list.


Acknowledgment: This project was originally inspired by mario-andreschak/mcp-abap-adt. We started with the core concept and then evolved it into an independent project with our own architecture and features.

License

Two licences: the libraries Apache-2.0, the servers AGPL. Which one applies depends on which package you install.

PackagesLicence
@mcp-abap-adt/lib, background-runs-memory, responsible-rules, compact-readonly-entries, compact-modifyApache-2.0LICENSE, NOTICE; each package's own LICENSE
@mcp-abap-adt/server-runtime, core, http, sse, compact, compact-http, compact-sse and the six -ro packagesAGPL-3.0-onlyserver/LICENSE; each package's own LICENSE

All eighteen are published from this repository with one command, in dependency order (Release):

npm run release:dry        # rehearses every package, touches nothing
npm run release:publish    # @mcp-abap-adt/lib first, then the packages that depend on it

release:publish skips a version already on the registry, so re-running after a failure resumes rather than starting over. It aborts on the first failure instead of publishing a package on top of one that is not there.

Note that npm publish and npm run are different commands. npm publish release asks npm to publish a package named release, which is somebody else's package on the registry.

Copyright © 2025–2026 Oleksii Kyslytsia

All are distributed in the hope that they will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

What this means. Running any of them, on your own data, carries no conditions.

Embedding the library in your own application carries no obligation to open your application: Apache-2.0 asks for the notice and the licence text to travel with it, and nothing more.

Distributing the standalone server, or running a modified version of it as a network service, means passing on the same freedoms under AGPL section 13 — including the source. That is why the two are separate packages: installing the library never puts the server in your dependency tree.

The packages underneath are LGPL-3.0-only — @mcp-abap-adt/adt-clients, adt-strategies, connection, logger, auth-broker, auth-providers, auth-stores and the contract packages interfaces-adt, interfaces-adt-connection, interfaces-network, interfaces-auth, interfaces-auth-sap and interfaces-utils — and the library links them at runtime. It was four of them when this paragraph was written; the rule is the whole scope now, libraries LGPL and servers AGPL or GPL, and the MIT that a few of the auth packages still carried was an oversight rather than an offer. LGPL does not reach your own code, but its terms do travel with those packages whatever this project is licensed as. Plan for that, not for the notice on this repository.

Other terms are possible. Apache-2.0 is what the library is offered under publicly, not the only way it can be offered. The copyright holder may license the same code separately to a party who needs different terms; that takes nothing away from anyone who received it under Apache-2.0, which is permanent. CONTRIBUTORS.md records what keeps that option open, including the rule that no LGPL code from the packages underneath is ever copied into this tree.

History. Releases through 8.13.0 were MIT and stay MIT; 9.x was GPL-3.0-only. A licence change is not retroactive — anyone may still take an earlier release under the licence it carried. See CONTRIBUTORS.md for the full account of how the relicensing was lawful.

Apache-2.0, read

History

Latest

v20.1.0

Features 1, Changed 5, Fixes 3

Notes

Both ABAP Unit runners, chosen per call with runner: 'async' | 'sync' (#286, #313); a write whose cleanup UNLOCK fails says the object may be left locked, and a save refused by CTS_WBO_API 019 / 020 names the request that holds the object (#314, #316); on @mcp-abap-adt/connection 19.1.0, which resends a request lost on a dead kept-alive socket once on a new connection (#314's socket hang up); the 7-day release age dropped (#315). The eighteen packages are all at 20.1.0, every sibling range at ^20.1.0 — @mcp-abap-adt/compact-modify needs @mcp-abap-adt/lib 20.1.0 for runner on HandlerUnitTestRun.

Added
  • Both ABAP Unit runners, chosen per call: runner: 'async' | 'sync', default 'async' (#286) — a system may serve only one of the two endpoints (an on-premise system was reported with /sap/bc/adt/abapunit/testruns alone, without /abapunit/runs). On RunUnitTest (class), RunProgramUnitTest (report), RunFunctionGroupUnitTest and RunFunctionModuleUnitTest in the full sets, and on compact HandlerUnitTestRun. async is what each did before: the full-set tools start the run, wait for it within a bound and answer the result or the run_id of a run that outlasts the bound (read on with GetUnitTestStatus / GetUnitTestResult); HandlerUnitTestRun answers { success, run_id } (read on with HandlerUnitTestStatus / HandlerUnitTestResult). sync is one POST /sap/bc/adt/abapunit/testruns, answered { success, runner: 'sync', finished: true, run_result } with the finished result; a failing test is an alert in it, not an error. Another value is refused before any request. Recordable in batch mode with runner: 'sync' (one request each). RunCdsUnitTest is unchanged: there is no synchronous runner for a CDS unit test.
    • A function module's synchronous run addresses the module under its group, so RunFunctionModuleUnitTest takes function_group_name, optional, required with runner: 'sync' (refused before any request without it). The run covers every test of the module's function group — nothing filters it to the module's own tests (measured by adt-clients on premise, BASIS 816, 2026-10-23; the asynchronous run answered the same there).
    • The four full-set start tools take scope (own_tests, foreign_tests, add_foreign_tests_as_preview; the shape compact HandlerUnitTestRun takes), optional, sent to either runner; without it adt-clients' defaults apply (own tests run, foreign tests listed as a preview). Foreign tests are test classes linked to the object by an ABAP Doc "! @testing relation. Measured on premise (BASIS 816, 2026-10-23) with a function group whose test class carries "! @testing FUNC:<module>, the module's run: runner: 'sync' with the defaults answered both test methods as a preview (isForeign="true", unexecuted), and with scope: { foreign_tests: true } executed them (one passed, one failed); runner: 'async' executed them with the defaults and with foreign_tests: true alike. The scope description says so.
    • HandlerUnitTestRun with runner: 'sync' runs every test class of each container_class named: the endpoint addresses whole classes, so a test_class does not narrow it; title and context are not sent. It runs class tests only, as before.
    • Measured through the stdio binaries from this branch's packed packages (2026-10-23), each object with one passing and one failing test: on premise (BASIS 816) the class, a report, a function group and its module, and on an SAP BTP ABAP trial the class, a function group and its module (no reports on ABAP Cloud). Both runners answered the same two methods, one passed and one failed, for every tool; sync took 150–340 ms against async's 530–640 ms on premise, and 0.73–0.98 s against 1.97–2.37 s on the trial.
Changed
  • The generated tool pages show a parameter written as a reference with its type and default (tools/generate-tools-docs.js): part, timeout, runner and the like were listed as any; the type and default now come from the build, as the description already did.
  • Dependency: @mcp-abap-adt/adt-clients ^37.2.0 (@mcp-abap-adt/lib): runSync on the class, report, function group and function module test runners.
  • Dependency: @mcp-abap-adt/connection ^19.1.0 (@mcp-abap-adt/lib, server-runtime; was ^19.0.0): a request lost on a dead kept-alive socket — one taken from the pool whose socket ended with no byte of an answer read (ECONNRESET, EPIPE) — is sent once more on a new connection with the same cookies, session headers, CSRF token and body, so the cleanup UNLOCK of #314, which failed with socket hang up and left the enqueue lock with the session, now reaches SAP; https: keeps its sockets alive too (an idle pooled socket closed after 5 s); the logoff goes out on a connection of its own. Never resent: a request that read any byte of an answer, one on a socket opened for it, the logoff, the resend itself. The tree still holds one @mcp-abap-adt/sap-rfc-lite, 0.3.2.
  • Sibling ranges at ^20.1.0 in every manifest — dependencies, peerDependencies and devDependencies; @mcp-abap-adt/compact-modify's peer @mcp-abap-adt/lib included: HandlerUnitTestRun's runner is read by lib 20.1.0, and against lib 20.0.0 it would be ignored.
  • The 7-day release age is dropped. .npmrc no longer sets min-release-age=7 or min-release-age-exclude[]=@mcp-abap-adt/*. The age gate conflicted with the gating npm audit: a security fix younger than the gate could not be installed while the audit blocked CI (seen 2026-10-22 with markdown-it 14.3.3). The defences that stay: allowScripts with strict-allow-scripts=true (no dependency install script runs), lockfile hashes, npm ci --ignore-scripts in CI and the images, and the gating npm audit. Dependabot's own cooldown is unchanged. The 20.0.0 entry below describes the rule as it was then.
Fixed
  • docs/architecture/REQUEST_HEADERS.md names the versions this release depends on (it still named adt-clients ^36.0.2 and lib 19.0.0).
  • A write whose cleanup UNLOCK fails says the object may be left locked (#314). When the UNLOCK after a write is refused by SAP or throws in the client (socket hang up), cleanup keeps its fields (message, origin or error: "client_threw", request, failures) and adds left_locked: true, lock_handle, the locked object ({ type, name }; a class include names its class) and warning: "the object may be left locked: the unlock failed (<the unlock's message>); it is released when the SAP session ends (logoff) or by its owner" — on a failed write, on a thrown one and beside a write that landed. Every handler that locks, writes and unlocks in one call names its object; the compact update writes under the caller's own lock and has no cleanup. The failure renderer copies the four fields by their types only (true, a non-empty string, { type, name } of two non-empty strings, a string); nothing else passes. An unlock that answers adds none. Documented in AUTHENTICATION.md.
  • A save refused as "already locked in request …" names that request (#314). A tool failure whose T100 messages hold CTS_WBO_API 019 (already locked in a request) or 020 (a task was passed; SAP names the request that holds the object) carries hint: { transport_request, text }: the request number, taken from the message's values by its shape (system id, K, six digits), and fixed words for each number saying to pass that request as transport_request — only when the call sent no request or another one — the call's own transport_request argument, which every way a handler is invoked keeps for the call's whole run (the server's tool calls, batched and background calls, a handler group registered on its own, HandlerExporter.getHandlerEntries() — which now wraps every handler, with or without a resolver), and the corrNr of the failed request's URL, since some writes send it as query parameters the request trace does not keep. When SAP names the very request the call sent — a deleted object's entry still recorded there refuses its re-create with 019 even so — the hint is { text } alone, with no transport_request: the entry is still in that request, passing it again does not help, and the entry is dealt with first (removed from the request with the request's tools). This holds for full and compact tools alike, since every failure is rendered in one place. Nothing is resent. A message with no value of that shape, or any other message, gives no hint. The tool descriptions are unchanged.
  1. v20.0.0

    Breaking 1, Security 8, Features 4, Changed 2, Fixes 1

    Notes

    The RFC transport on @mcp-abap-adt/connection 19 and @mcp-abap-adt/sap-rfc-lite 0.3.2, which compiles nothing on install: a platform without a prebuilt RFC binding no longer gets one from npm install. Migration: docs/MIGRATION-20.0.md. Over Streamable HTTP, state_handle is a declared argument of every tool, and a long session keeps one server instance and its SAP session across calls (#308).

    BREAKING
    • Dependencies: @mcp-abap-adt/connection ^19.0.0 (@mcp-abap-adt/lib, server-runtime) and @mcp-abap-adt/sap-rfc-lite ^0.3.2 (optional, lib). connection 19 changes no API: it moves its own RFC dependency to sap-rfc-lite ^0.3.2, so the tree holds one sap-rfc-lite, 0.3.2 — the one connection loads. sap-rfc-lite 0.3 has no install script and, from 0.3.2, ships no binding.gyp: npm install and npm ci compile and download nothing for it, also under --ignore-scripts and an npm that runs no unapproved install script. It carries a prebuilt binding for Linux x64 (glibc), Windows x64 and macOS ARM64, loaded at run time; its prebuilt bindings carry no path of the machine they were built on, so the RFC library is found by the operating system's loader. Windows with SAP GUI's 64-bit sapnwrfc.dll in System32 needs no SDK. On any other platform — Linux on ARM, an Intel Mac, Linux with musl (Alpine) — installing no longer builds the RFC binding (0.2.x compiled it with the SDK and a C++ toolchain): the server installs and serves HTTP as before, and RFC is refused with @mcp-abap-adt/sap-rfc-lite is not available until you build a binding from sap-rfc-lite's repository and install the package you built (its README, "Installation"; docs/MIGRATION-20.0.md). The documentation says so where it stated RFC's prerequisites — RFC Setup, Installation, the three platform guides, the package READMEs, Docker, the CLI options, the --help text and the package descriptions: no C++ toolchain and no SAPNWRFC_HOME before npm install; at run time the RFC library — SAP GUI's on Windows, else the SDK, its lib directory on PATH, LD_LIBRARY_PATH or DYLD_LIBRARY_PATH of the server process.
    Added
    • Streamable HTTP: state_handle is a declared argument of every tool (#308) on mcp-abap-adt-http, -http-ro, -compact-http and -compact-http-ro: an optional string, "The state_handle a long session, a lock or a batch answered: the call runs on the server instance that holds that state. Without it the call runs on a new instance." The pool already routed by a tools/call's state_handle argument, but only the debugger tools declared it, so a model, which sets no header, could not reach a lock taken with HandlerLock or an open batch. The debugger tools keep their own declaration, required where it was; a tool that does not use the argument gets it taken out of its arguments before its handler. The x-mcp-state-handle header still works and still wins when both are sent; a JSON-RPC batch carrying state_handle is still answered 400. The held-lock refusal and the descriptions of HandlerLock, HandlerUpdate, HandlerDelete, HandlerUnlock, HandlerBatch, BatchBegin, DebugBatchBegin and the compact debugger batch name the argument first, the header as the alternative. stdio and SSE register the schemas they registered before.
    • Streamable HTTP: the long session (#308). SessionOpen / SessionClose (mcp-abap-adt-http, -http-ro) and HandlerSession with action: 'open' | 'close' (-compact-http, -compact-http-ro), read-only, served by no stdio or SSE binary. Open keeps an instance and its SAP connection and answers { state_handle }: every call carrying it runs on that instance, on its held SAP session — no new connection, no systeminformation lookup, no logoff between the calls; opened with the handle of an instance that already holds state, or of an open session, it answers that same handle and keeps what is held. Close answers { closed: true, held: { locks, batch, debug_session } }; once that answer is written the pool takes the instance out of its index and runs the instance close on it: every connection it owns logged off — which releases the locks taken in it — awaited to each logoff's write and its answer with no timer of the server's; an open batch and its answers dropped with nothing sent; the MCP server closed. From then on the handle answers state is not available at once; the shutdown awaits a close still running. Close without a handle or with an unknown one answers state is not available. A session never closed ends at the idle bound (--state-idle-minutes, default 30). In batch mode — a communication batch or a debugger batch — the session tools run at once and are never recorded: open leaves the batch open, close drops it. No list tool: the model keeps its handles; the handle is a bearer secret and the server does not identify the caller. Tool counts: mcp-abap-adt-http 256, -http-ro 71, -compact-http 39, -compact-http-ro 19 (on premise).
    • The tool pages render the Streamable HTTP surface: docs/user-guide/AVAILABLE_TOOLS.md, AVAILABLE_TOOLS_READONLY.md, AVAILABLE_TOOLS_HIGH.md and compact/docs/AVAILABLE_TOOLS.md carry a "Streamable HTTP Binaries" section — the declared state_handle and the session tools, from the registration the HTTP server uses — listed in each page's navigation.
    • MIGRATION-20.0, section "Streamable HTTP: held state by argument, and the long session": what a 19.x Streamable HTTP user sees change, and how to adopt the argument and the long session (nothing is required).
    Changed
    • The portable build takes sap-rfc-lite 0.3's binding as it is (npm run portable:build; PORTABLE.md). 0.3 reads its own package.json through a literal require, which the bundle inlines, so the bundle-time replacement of 0.2's run-time read, and the check that failed the build when that replacement did not happen, are gone. In their place the build refuses a stage holding any copy of sap-rfc-lite older than 0.3.0, naming each: such a copy cannot load inside an executable, and a package that pins an older range loads its own — so a version published before 20.0.0 (--version) is refused. On Linux the prebuilt binding carries no RUNPATH, and nothing beside it found the SDK (measured: ldd finds neither libsapnwrfc.so nor libsapucum.so in its folder): it now gets a RUNPATH of $ORIGIN in place of its SONAME entry, whose string becomes $ORIGIN — the binding is loaded by its path, never by that name, and an image whose SONAME string is shared with any other name is refused; a binding that does carry a RUNPATH has it rewritten as before. --build-from-source is gone: npm compiles nothing for sap-rfc-lite whatever it is told. Measured on Linux x64 with these packages packed before their release and staged together (one sap-rfc-lite, 0.3.2): both executables built, and npm run portable:smoke passes all 18 checks. Not run on Windows or macOS.
    • Handles are unique among those the pool holds (#308): an instance's handle (128 bits from randomBytes) is drawn again when the value drawn is already held, at its creation and at every rotation, so two long sessions never share a handle or an instance.
    Fixed
    • The http and compact-http READMEs said another credential "retires" a held instance; it never did. A request carrying a handle with another credential (a renewed token, a new user and password) works on the held instance: its SAP session and its locks are kept and the new credential is put in force for its next sends (BaseMcpServer.getConnection); the connection is replaced only when SAP ended its session. The docs and a code comment now say so.
    Security

    The repository's own install, lockfile, workflows and image build; the packages' code and dependency ranges are unchanged by this section.

    • The lockfile is refreshed within the declared ranges, under the 7-day release age below: among others @modelcontextprotocol/sdk 1.32.1, express 5.3.0, zod 4.6.7, dotenv 18.0.8, ws 8.22.0, undici 8.12.1, qs 6.16.1, xml-crypto 6.4.0, ts-jest 29.4.15; a few transitive packages resolve one release back (@napi-rs/keyring 2.1.1, jose 6.2.12, postcss 8.5.30), because their newest releases are younger than 7 days. npm audit finds nothing, dev dependencies included.
    • overrides: js-yaml@^3 gets argparse ^2.0.1. Jest reaches js-yaml 3 through babel-plugin-istanbul → @istanbuljs/load-nyc-config, and js-yaml 3's argparse 1 brought sprintf-js (GHSA-hp3w-g68c-fv3c, moderate, no fixed release). js-yaml 3 uses argparse only in its command-line bin/js-yaml.js, which nothing here runs; load-nyc-config calls only .load(). The override replaces argparse 1 under js-yaml 3, and sprintf-js leaves the tree.
    • @modelcontextprotocol/inspector is no longer a dev dependency, and with it the 131 packages only it pulled (vite among them). It served one .vscode/launch.json configuration, now removed; TESTING_AUTH runs the Inspector as the user's own command (npx @modelcontextprotocol/inspector …), outside the checkout.
    • No dependency runs an install script. allowScripts in package.json denies the four that have one: esbuild (its postinstall checks or fetches the binary that its platform package, an optional dependency, already installs — the portable build's suites pass without it), @parcel/watcher (it compiles only when asked to build from source; its binary comes from its platform package), unrs-resolver (its postinstall fetches a binding only when the platform package is missing), and fsevents (macOS, its install runs node-gyp rebuild though the package ships its binary). .npmrc sets strict-allow-scripts=true, so a new dependency with an unreviewed install script fails the install instead of running it (measured: with esbuild left out of allowScripts, npm ci stops with ESTRICTALLOWSCRIPTS). npm reads these settings in full from 11.19.0; npm 10, bundled with Node.js 22, reads none — so CI and the release workflow install with npm ci --ignore-scripts — and install the packed tarballs, outside the checkout and its .npmrc, with --ignore-scripts too — and the from-source instructions (Installation and the three platform guides) say the same. .npmrc does not set ignore-scripts=true: it would also skip the packages' own prepublishOnly gate. docker/Dockerfile and docker/Dockerfile.inspect (the Glama reference image) install with --ignore-scripts too, and the portable build's two installs as well: no package of the production tree needs an install script, and the Glama image's one (tldjs, under mcp-proxy, which would refresh its public-suffix list from the network) is not needed — measured: the image built from core 19.2.0 answers --version, and mcp-proxy starts.
    • New versions age before they are resolved (npm 11.19.0 and later): .npmrc sets min-release-age=7, except for the project's own @mcp-abap-adt/* scope, whose packages are released to each other the same day (min-release-age-exclude); their dependencies still wait the 7 days.
    • Nothing calls npx, which fetches from the registry what it does not find installed: the scripts of the root and of the seventeen packages call biome and tsc from the installed dependencies, the pre-commit hook calls lint-staged, the workflows lint through npm run lint:check, and the suites that run a script through tsx start this Node with tsx's own CLI resolved from node_modules.
    • CI and the release workflow gate on npm audit --audit-level=moderate: any known advisory of moderate severity or above fails the run, dev dependencies included — they run in CI and on every developer's machine. Every job has a timeout-minutes (45 for the test jobs, whose slowest cell takes about 21 minutes; 10 for the release job). src/__tests__/unit/ci/workflowInventory.test.ts holds the workflows, the package scripts, the git hook, allowScripts (one entry per dependency with an install script, each denied) and .npmrc to all of this; GitHub Actions says so.
    • The release guide and the migration note say the same: RELEASE.md installs with npm ci --ignore-scripts (then npm run prepare for the hook) and its "What the workflows check" names the whole-tree audit at moderate, the --ignore-scripts installs, the job timeouts and the Glama reference image; MIGRATION-20.0, "Building from a checkout", says what a contributor installing this repository does now.
  2. v19.2.0

    Changed 7, Fixes 5

    Notes
    Changed
    • Package listings with include_descriptions: true read the walk as single requests, several at a time, instead of ADT batch envelopes plus a metadata read per object — GetPackageContents, GetPackageTree and compact HandlerGet part contents, on any system. Inside one envelope SAP answers the node structure parts after the first with other parts' DESCRIPTION texts, so the batched walk could not use them and read every object's metadata instead; a node structure sent alone carries its own. Each description is now the node structure's own short description, the one the sequential path always answered — in the logon language; the metadata read answered adtcore:description in the object's master language, so on a system whose objects are mastered in another language the text changes language (measured: 1 057 of the 1 251 objects both describe). Measured on an on-premise system: a flat package of 2 033 objects 24.3 s → 1.2 s, a three-level package of 2 282 objects 36.2 s → 6.3 s; every description equal to the sequential read's, and objects of types the metadata read did not cover (about a third of that flat package) now carry theirs. Without descriptions nothing changes: one envelope per phase of each level.
    • Dependencies: @mcp-abap-adt/adt-clients ^37.0.0, @mcp-abap-adt/adt-strategies ^0.15.0, @mcp-abap-adt/interfaces-adt ^20.1.0 (@mcp-abap-adt/lib; compact-modify for interfaces-adt). adt-clients 37's getSystemInformation answers present, absent (404 only) or failed; GetServiceBindingPreviewUrl reads it so: a 404 gives a URL without a client or language, as before, and any other status is reported beside the URL as system_information_failure (a 401 ends the call). A 405, a 501 or a 2xx that is not the record were read as absent before and are now reported as that failure. adt-strategies 0.14.0 refuses an answer to a message read that is no message class document (ADT_OBJECT_NOT_FOUND); GetMessageClassMessage answers that verdict. adt-strategies 0.15.0 adds analyseObjectProperties, which GetTransaction passes.
    • A failure a reading raised on a 2xx answer carries raw_body only with detail: 'raw', for every tool. SAP did not refuse there: its body is an ordinary document, and the failure's message, code and messages already say what is wrong with it — measured: GetMessageClassMessage for a number its class does not hold answered 613,386 bytes, now 312. A refusal SAP made (any other status) carries raw_body at every detail, as before; nothing else in a failure changes. A tool without a detail parameter answers such a failure without the body.
    • A new package, @mcp-abap-adt/server-runtime: the launch code of every server (#303). The stdio launcher, StdioServer and the destination layer (/stdio, /stdio/server, /auth), the Streamable HTTP launcher and server (/http, /http/server) and the HTTP+SSE launcher and server (/sse, /sse/server) moved there from @mcp-abap-adt/core, http and sse, unchanged. It has no command, and you never install it yourself: every server package depends on it and installs it. The release is eighteen packages; the twelve binaries, their names, options, tool sets and versions are unchanged.
    • No server package depends on another (#303). The nine wrappers (compact, compact-ro, core-ro, compact-http, compact-http-ro, http-ro, compact-sse, compact-sse-ro, sse-ro) no longer depend on core, http or sse: each of the twelve binary packages depends on server-runtime, @mcp-abap-adt/lib, background-runs-memory, responsible-rules and its own compact halves. lib is in dependencies of the twelve binary packages only; server-runtime, compact-readonly-entries, compact-modify, background-runs-memory and responsible-rules take it as a peer dependency (and server-runtime takes background-runs-memory and responsible-rules the same way). An embedder that installs compact-readonly-entries (with or without lib) gets one lib as before: npm installs a missing peer.
    • The eight Streamable HTTP and SSE server packages — http, compact-http, http-ro, compact-http-ro, sse, compact-sse, sse-ro, compact-sse-ro — now install @mcp-abap-adt/auth-broker, @mcp-abap-adt/auth-stores and @mcp-abap-adt/interfaces-auth-broker, through server-runtime, whose dependencies they are, for the stdio destination layer. None of them loads them: only the runtime's stdio paths reach them (dependencyRule.test.ts).
    • @mcp-abap-adt/core (., /launcher, /auth), @mcp-abap-adt/http and @mcp-abap-adt/sse (., /launcher) keep their exports, as re-exports of server-runtime's paths. Their StdioServer, StreamableHttpServer and SseServer still answer their own package's version when constructed without one. Their launcher paths (@mcp-abap-adt/core/launcher, @mcp-abap-adt/http/launcher, @mcp-abap-adt/sse/launcher) take 19.1.0's options: main({ toolSet, program, version }) and launch(config, { toolSet, program, version }, deps) compile and run as before — packageName, the package a second copy of lib asks to reinstall, is optional there and defaults to that path's own package; a value given passes through. On server-runtime's own paths (/stdio, /http, /sse) packageName is required.
    Fixed
    • GetTableContents (and compact HandlerGetData with object_name) answers the rows asked for and the table's true row count (#302). It read /sap/bc/adt/datapreview/ddic?rowNumber=N, which, measured on an on-premise system (BASIS 816), answers N+1 rows and totalRows 0 — a table of four rows asked for two answered three and total_rows: 0. It now reads the freestyle preview (/sap/bc/adt/datapreview/freestyle?rowNumber=N, as GetSqlQuery does) with SELECT * FROM <table>, which answers at most N rows and the true total_rows (measured: the same table, two asked → two rows, total_rows 4). The answer's shape and detail levels are unchanged; row_number is the max_rows asked.
    • GetMessageClassMessage answers the one message, not the whole class (#302). ADT answers a message read with the class document, every message in it (measured: 613,165 bytes for one message of a class of 900), and the tool handed that document on under metadata. It now reads the message numbered msgno out of that one answer and answers its fields under message — msgno, msgtext, selfexplainatory, documented, lastchangedby, lastmodified, name and any other the system gives the message (measured: 280 bytes). New detail parameter: full adds the message element as parsed, its links included; raw answers the class document as SAP sent it. A number absent from the class is still refused (ADT_OBJECT_NOT_FOUND). The answer's shape changed: message replaces metadata and part, and detail: 'raw' gives the class document as SAP sent it.
    • GetTransaction reads the transaction (#302): it answered {"message":"Not implemented"} as a success. It now reads the transaction's object properties in one request (getUtils().getObjectProperties of /sap/bc/adt/vit/wb/object_type/trant/object_name/<name>) and answers the transaction under object (name, text, type, package, …) and its facets under properties in the system's order — application component chain, package chain, type, owner, language, creation and the rest, each with facet, name, displayName and, where given, text (measured on premise: 200, 22 facets). New detail parameter: full adds the document's parse, raw the document. The read passes analyseObjectProperties (adt-strategies 0.15.0): a 200 that is no object properties document holding the object (an empty body, a sign-in page, a truncated document) is refused ADT_OBJECT_NOT_FOUND. A name the system does not know is SAP's refusal as it came (measured: 422, ExceptionUnprocessableEntity). It no longer promises the program, screen or authorization object.
    • A wrapper installed together with its former base loaded two copies of @mcp-abap-adt/lib, and its tool calls were refused (#303). With 19.1.0, installing a wrapper and its base globally in one command (npm install -g @mcp-abap-adt/core @mcp-abap-adt/compact, likewise http with http-ro, sse with compact-sse-ro, …) put the base at the global root with its own lib: the server entered a request in one copy and the handlers read the other, so no request state was shared (measured for compact + core, http-ro + http, compact-sse-ro + sse), and a compact create was refused with system_context_missing. What made the second copy was one binary package depending on another; 19.2.0 has no such dependency. Measured with the packed release, every layout loads one copy and shares the request state: global, a wrapper with its base in one command or in two (either order); local, hoisted and --install-strategy=nested; a runtime at a later patch than the binary; an embedder's compact-readonly-entries with lib. A second copy now stops the start, before any parameter is read — --version and --help included — with exit 1 and one line on stderr: Two copies of @mcp-abap-adt/lib are loaded in this process (<file>, <file>): reinstall <package> (npm install -g <package>). (lib itself refuses nothing, so an embedder sees no change.) If you installed a wrapper and its base in one command before 19.2.0, update both to 19.2.0 — in one command (npm install -g @mcp-abap-adt/core @mcp-abap-adt/compact) or each on its own: measured, both layouts load one copy on 19.2.0.
    • The portable build builds again, and its smoke check checks what it exists to check (npm run portable:build, npm run portable:smoke; PORTABLE.md). With @mcp-abap-adt/sap-rfc-lite 0.2.3 and later the build stopped at the RFC addon was not built: the package installs a prebuilt binding under prebuilds/<platform>-<arch>/ and compiles nothing, and the build looked only for a compiled build/Release/sapnwrfc.node. It now takes the binding the package's own loader resolves (a compiled one first, else the prebuilt), rewrites whatever library search path it carries to its own folder, and keeps --build-from-source for a compile against your SDK; no C++ toolchain is needed otherwise. Inside the executable sap-rfc-lite's run-time read of its own package.json failed, so no RFC call could load the stack; its version is now fixed at build time, as core's is. The smoke's MCP and RFC checks failed since 19.0.0, whose stdio logs on at its start: MCP initialize and tools/list now run in inspection-only mode (no destination, nothing connected; the compact server's count is no longer a fixed 25), and the RFC check reads the start against a host that does not resolve, which must stop at the RFC logon naming its key (RFC_COMMUNICATION_FAILURE) — a module or SDK that does not load reads could not be connected at start: Error. Measured on Linux x64 with the published 19.1.0 (--version=19.1.0; 19.2.0 is not on npm yet): both executables built from the prebuilt binding and every check passed; with the SDK library taken out of nwrfcsdk/lib the RFC check failed. Not run on Windows or macOS.
  3. v19.1.0

    Changed 1, Fixes 1

    Notes
    Changed
    • Package listings answer no descriptions unless asked: include_descriptions defaults to false in GetPackageContents, GetPackageTree and compact HandlerGet part contents, which takes the parameter now (#300). GetObjectInfo takes include_descriptions too (default false): its leaves carry a description only when true, on the batched path from each leaf's metadata as before; enrich (the search for each node's description and package) is unchanged. Whether to pay for the descriptions is the consumer's choice; by default the server answers the fastest listing. With include_descriptions: true, on a destination that serves ADT batch, each description is read from the object's metadata, one read per object, in one more envelope after the walk: measured on an on-premise system, about 23 s for a flat package of 2 033 objects whose walk alone takes about 1.5 s. Without a communication batch the node structures are asked with descriptions only when true. Migration: a consumer that relied on the descriptions — of package objects or of GetObjectInfo's leaves — passes include_descriptions: true.
    Fixed
    • include_descriptions: false no longer reads object metadata on the batched path of GetPackageContents and GetPackageTree: the metadata pass ran for descriptions the answer did not carry. Inside one envelope SAP answers almost every node structure part after the first with other parts' DESCRIPTION texts (measured: 2 023 of 2 033 objects of one package), so those parts' descriptions stay unused and the descriptions come from the metadata.
  4. v19.0.0

    Breaking 18, Features 14, Changed 42, Fixes 4, Other 1

    Notes

    The server split by transport and tool set into packages of their own — seventeen packages, twelve binaries — on the 6.0 auth chain (auth-broker 5, auth-providers 6, auth-stores 5) and the removal chain (connection 18, interfaces-adt-connection 3, interfaces-adt 20, adt-clients 36): the system type required, the low set gone, a logoff at every session end, no cancellation of a started tool. Migration: docs/MIGRATION-19.0.md.

    BREAKING
    • A package per transport and tool set; the binary fixes both. @mcp-abap-adt/core (mcp-abap-adt) serves stdio only; Streamable HTTP is @mcp-abap-adt/http (mcp-abap-adt-http), HTTP+SSE @mcp-abap-adt/sse (mcp-abap-adt-sse); the compact tool set is @mcp-abap-adt/compact, compact-http and compact-sse; the read-only sets are @mcp-abap-adt/core-ro, http-ro, sse-ro, compact-ro, compact-http-ro and compact-sse-ro (bins mcp-abap-adt-ro, -http-ro, -sse-ro, -compact-ro, -compact-http-ro, -compact-sse-ro). Publish order: RELEASE.md.
    • --transport is gone (and MCP_TRANSPORT, YAML transport): any value stops every binary at start, naming the binary to run. --exposition is gone (either spelling, YAML exposition, compact's ro / rw): passing it stops the start with one sentence per word naming the package that serves what was asked. The debugger is no longer opt-in: every write binary serves it; the -ro binaries serve no debugger, no background run and no high tool.
    • Each binary reads only its own parameters: another binary's option stops the start in its CLI and YAML forms, naming the binary that serves it; its environment variable is not read. Values are checked in every form (a bad port or flag is refused at start; TLS needs --tls-cert and --tls-key together). A config file that mixes transport, mcp, http: and sse: keys is refused by every binary.
    • The system type is required (--system-type, SAP_SYSTEM_TYPE, YAML system-type: onprem, cloud, legacy): no default, never guessed from the credential (a jwt destination is no longer taken for cloud) or the URL. A stdio destination does not start without it, nor does a Streamable HTTP or SSE binary (exit 1 before anything listens). Inspection-only stdio needs none and filters no tool. system_type_required is new.
    • The low set is gone from mcp-abap-adt, -http and -sse (112 tools, the *Low reads of the system group included): use the compact binary of the transport, which does what the low set did, a step at a time; the migration note maps every low tool to its compact call. LowLevelHandlersGroup, HandlerExporter's includeLowLevel and the 'low' handler set are gone from lib.
    • HTTP and SSE take credentials only from the x-sap-* headers — of each request (Streamable HTTP), of the GET that opens each SSE connection (SSE). No destination, no broker, no store, no login over HTTP or SSE: x-mcp-destination is answered 400, --allow-destination-header and the default destination are gone, and the service-keys / sessions mounts are not read.
    • stdio no longer serves jwt / none (a token you hold): such a destination stops the start, with the hint to send the token in x-sap-jwt-token to mcp-abap-adt-http / -sse, or to create the session with mcp-auth. stdio serves basic, snc and jwt / authorization_code.
    • Only the chain's own session format is read (auth-stores 5): a session record of an older format stops the start, naming the destination, with the mcp-auth command that regenerates it. Removing SAP_ISSUED_* is no longer a remedy.
    • stdio connects at its start, before the MCP transport opens — the startup check builds the credential, the interactive login happens then, the system information is asked — and any start failure ends the process with exit 1. A shutdown during the start exits 1.
    • The responsible person, the user and the system are asked of the system (systeminformation) once per destination, when its connection is created, on-premise and on ABAP Cloud alike, and the system's answer wins over anything configured. On-premise, when the system gives no information, the responsible if stated, else the login, else system_user_unknown; on ABAP Cloud no information is an error (cloud_without_systeminformation for a 404, else system_lookup_failed). A 401 is answered as it came. These rules are @mcp-abap-adt/responsible-rules, which the servers pass; lib applies none: an embedder looks nothing up and requires no system kind unless it passes the resolver and the rules.
    • A tool call cannot be cancelled. A client's cancel aborts nothing: the call runs to its end, its tool once. The client's close (stdio's stdin, an SSE stream) and the process shutdown log off every ADT session at once and interrupt the running tools — nothing waits for a running tool, no state undo is sent — then exit; 18.x waited up to 30 s. Over Streamable HTTP a dropped request closes nothing: the instance ends after its work, or at its state's stop or the idle bound. A crash outside a tool runs the forced close too.
    • An instance's calls on its connection run one at a time, and only LOCK and UNLOCK are sent stateful: writes under a lock and activation go stateless. The session_id / session_state parameters and GetSession are gone; HandlerLock answers no session_id.
    • A tool's failure is the method's error as it is, plus the step where it arose; a method's own throw is client_threw, never SAP's answer; a SAP refusal is no longer turned into an empty result, null or "not found"; tools reading several items carry each item's failure, and a 401 ends the whole call.
    • No deadline of the server's or the libraries': SAP_TIMEOUT_* are not read; no login timeout (it was 30 s; --login-timeout is the opt-in bound) and no shutdown deadline (--shutdown-timeout, no default).
    • RuntimeRunClassWithProfiling and RuntimeRunProgramWithProfiling are deleted: RuntimeRunClass / RuntimeRunProgram with profile: true. ResumeLeftoverSession does not exist: a stdio process takes up its destination's one leftover session at its start.
    • @mcp-abap-adt/compact-readonly is renamed @mcp-abap-adt/compact-readonly-entries (the same exports).
    • Docker: one image per binary (docker/Dockerfile, build argument VARIANT, context docker/); the 18.x image (--allow-destination-header over HTTP) is gone. Destinations are served by a stdio image only; every image needs the system type; compose publishes the listener ports on the host's loopback only.
    • Embedders: lib's chain takes no request signal (IAbapRequestOptions.signal, IAdtClientOptions.signal, ADT_REQUEST_ABORTED and IAdtError.refusal are gone); AbapConnection answers unknown by default; on an injected connection an operation that needs an ABAP session of its own takes it only from freshConnection (called with no argument), and is refused without it; ConnectionContext replaces the destinations argument; AuthBrokerFactory, IDestinations and the destination layer moved to @mcp-abap-adt/core/auth; StreamableHttpServer and SseServer moved to @mcp-abap-adt/http / @mcp-abap-adt/sse; ServerConfigManager, ArgumentsParser, showHelp and the transport parsing left lib; makeAdtRequestWithTimeout, getTimeout, getTimeoutConfig, restoreSessionInConnection, describeAuthError and maskSensitiveValue are gone. The note lists every change of the public surface.
    Added
    • Tool search texts beside each description (#270, #271): every tool of every set carries keywords (for lexical search, BM25: the terms, synonyms and object kinds users ask with) and semantic (for vector search: what a user wants when the tool is the answer), from one table, tools/tool-search.json, shipped and bundled. tools/list carries them on each tool as _meta["mcp-abap-adt/search"]; lib exports them as @mcp-abap-adt/lib/tool-search (toolSearchText(name), toolSearchTexts()). A client that knows none of it reads description as before. Two labelled query sets, for a reader and a developer, are run by BM25 over name, parameters and keywords in every test run, and by vectors over semantic through a local Ollama where one answers: the tuning set (the #270 and #271 cases among them) finds every required tool within the top 10 read and top 5 write tools — a regression guard on those queries —, and the held-out set, never used to tune the texts, gets no worse than its recorded baseline. See docs/user-guide/TOOL_SEARCH.md.
    • @mcp-abap-adt/lib/adt-headers: the server's ADT request-header machinery for embedders that call adt-clients members themselves — DEFAULT_ADT_HEADERS (the starting pair per operation, with AdtOperation / adtOperation), AdtHeaderPairs / AdtHeaderPort (the pairs a system named in a 406 / 415, kept per system and operation), adtSystemKey (the system key the server uses) and withAdtHeaders (a call with the system's pair, sent once more with the type SAP named). A 406 with T100 SADT_RESOURCE 037 names nothing and is not corrected. See docs/architecture/REQUEST_HEADERS.md.
    • Background runs: background: true on RuntimeRunClass, RuntimeRunProgram and compact HandlerProfileRun — the run goes on an ABAP session of its own and the call answers a run_handle at once; GetBackgroundRun, ListBackgroundRuns, DiscardBackgroundRun (compact HandlerBackgroundRun*). Kept by @mcp-abap-adt/background-runs-memory (new), 16 per caller, in the process's memory; an embedder passes an IBackgroundRuns of its own.
    • Leftover SAP sessions (stdio): a runtime file per HTTP session the server holds, deleted only once its logoff completed — written, and its flushGoodbye() settled (SAP answered it or its connection failed), never on the write alone; LogoffLeftoverSession deletes its file the same way — so a process killed from outside, or one the forced close ended before its logoffs were answered, leaves a record: the next start takes up the destination's one leftover session, and ListLeftoverSessions / LogoffLeftoverSession (compact HandlerLeftoverSession*) list and log off the rest, sending nothing to SAP to learn who calls. The files are bearer secrets (owner-only on Linux and macOS).
    • A timeout on the approved long reads (GetWhereUsed, SearchObject, SearchSource, GetPackageContents, … — the list in the note; compact HandlerWhereUsed, HandlerSearch, …): seconds, no default; each runs stateless on an ABAP session of its own, logged off when it ends; when the timeout passes its session is logged off and the call answers timed_out: true.
    • UpdatePackage and DeletePackage (high) for what UpdatePackageLow / DeletePackageLow did; DeletePackage asks the deletion check first.
    • Compact as the low set's analogue: HandlerUpdate under a lock_handle, HandlerLock / HandlerUnlock for BEHAVIOR_IMPLEMENTATION, package create / update / delete / check, HandlerValidate and HandlerCheckRun for packages and function modules, source_code to check unsaved source, the creation parameters of a class (superclass, final, create_protected), HandlerGet's object_structure, node_structure and virtual_folders parts; HandlerTransportSortAndCompress, HandlerTransportMoveObjects and HandlerGet of a TRANSPORT; HandlerDeletionCheck (object_type, object_name), the one deletion-check request, SAP's verdict as it came (29 compact tools, 17 that change nothing).
    • --renewal=refresh-then-login|refresh-only, --browser mapped by the platform (system, auto, chrome, edge, firefox, none, headless) and --browser-program, --login-timeout, --shutdown-timeout, --auth-debug (stdio, command line or YAML only).
    • @mcp-abap-adt/responsible-rules (new): the servers' rules for the responsible, the user and the system, and the system type row.
    • RFC through a message server and a logon group (stdio): SAP_MSHOST, SAP_MSSERV, SAP_SYSID, SAP_GROUP, SAP_SAPROUTER — also --mshost, --msserv, --sysid, --group, --saprouter and YAML mshost, msserv, sysid, group, saprouter, with --connection-type's precedence (the --env / --env-path .env joins the environment) — reach connection 18's SapConfig as configured. The server adds no default (group's PUBLIC is connection's) and checks nothing: mshost with neither msserv nor sysid is connection's refusal, naming both, as it came (a stdio start stops with its words). With mshost no RFC host or system number comes from the URL and SAP_SYSNR is not read. lib's getConfig() reads the five from the environment; the HTTP and SSE binaries refuse their CLI and YAML forms like every destination option. Implemented and unit-tested, not yet measured on a live system (as of 2026-10-20).
    • Twelve MCP Registry entries, one per server package.
    • Compact HandlerProfileSchedule: one POST /sap/bc/adt/runtime/traces/abaptraces/parameters with the profiling flags (all optional), answering { success, profiler_id }; a 2xx without a Location header is a failure with step: "schedule trace". Recorded in batch mode.
    • Batch mode, in every binary (the -ro ones included) on every transport: BatchBegin, BatchEnd, BatchAnswers (compact: HandlerBatch with action: 'begin' | 'end' | 'answers'). While a batch is open each call none of whose requests depends on another's answer is recorded instead of sent, answers nothing and takes the next number (from 1, in the order made; one number per call, whatever it put into the batch); every other call is refused (batch_not_recordable, batch_no_timeout) and takes no number. A batch holds one kind of request — LOCK, UNLOCK or stateless, as its first recorded call decides — and a call of another kind is refused batch_other_kind (" is a request; this batch holds requests — record it in the next batch"), no number: each phase (create, lock, write, unlock, read back, activate; a unit-test run's start, status, result; a trace's schedule, then the run under it) is its own batch. BatchEnd (and HandlerBatch end) sends the recorded calls in one ADT batch envelope on the instance's connection with the credential in force then, holds that connection for the whole send, leaves batch mode whatever happens, splits SAP's answer by number into the map and answers { recorded, sent, received } — or its own error, with no map: the envelope's failure as it came, batch_parts_mismatch (" of parts came; the answers cannot be matched to the calls") or batch_unsplit ("the envelope's answer could not be split into the calls' parts"), every number of the batch then answering that error; nothing is retried or sent twice (a part answered 401, 406 or 415 is that call's answer). BatchAnswers (number or numbers) reads the map by number: a number's answer — the answer the tool gives outside batch mode for what SAP answered — is built by its handler when the number is first read (the numbers of one call in parallel) and kept, until the next BatchEnd replaces the map; a request the handler would send then is refused batch_ended. Only LOCK and UNLOCK go stateful, as adt-clients sends them. BatchBegin answers the instance's state_handle; over Streamable HTTP it travels in the x-mcp-state-handle request header on every call of the batch (the header first, else a tools/call's state_handle argument, as the debugger tools send it). Which calls are recorded is tools/batch-handlers.json (170 entries, listed in docs/user-guide/BATCH_MODE.md), and every tool's description ends with one sentence saying how it behaves in batch mode: recordable, recordable for the arguments it names, or run outside batch mode. --batch-keep-raw-parts (MCP_BATCH_KEEP_RAW_PARTS, YAML batch.keepRawParts, default off) keeps SAP's raw parts beside each answer. The instance's end (its client's close, the pool letting it go, the process shutdown) or the held state's idle bound drops an open batch: nothing recorded is sent, the map goes with it. An embedder gives each instance its owner (batch: new InstanceBatch({ keepRawParts })); without one the batch tools answer batch_mode_unavailable. New refusals: batch_mode_unavailable, batch_mode_on, batch_mode_other, batch_mode_off, batch_not_recordable, batch_other_kind, batch_no_timeout, batch_ended, batch_parts_mismatch, batch_unsplit.
    • The debugger batch (full set DebugBatchBegin, DebugBatchEnd; compact HandlerDebugBatch with action: 'begin' | 'end' | 'answers'; no -ro binary): at an ABAP stop, step, step to line, stack, variables, child variables, stack position and setting a variable are recorded — one request each, a full-set step with no stack read after it — and the end sends them in one debugger batch envelope on the stop's connection, splits the answer into the map (its numbers read with BatchAnswers or HandlerDebugBatch answers, each built when read), ends the batch whatever happens and answers { recorded, sent, received } or its own error, as BatchEnd; the debug session's stop and view stay as they were. At an AMDP break the same batch records step over, continue and the data preview of a table variable — full set AmdpDebugStep, AmdpDebugGetTable; compact HandlerDebugStep / HandlerDebugMaintain of kind: 'amdp' — and the end sends them on the AMDP session's command connection; begin works on the ABAP stop when one is held, else on the AMDP break, and a batch records at its own stop only. Every other tool is refused while it is open. Begin needs an ABAP stop or an AMDP break; one batch of either kind at a time (batch_mode_on, batch_mode_other). See docs/user-guide/DEBUGGER.md.
    Changed
    • BatchEnd (and DebugBatchEnd, HandlerBatch / HandlerDebugBatch end) only sends, receives and splits; a number's answer is built when it is read. BatchEnd answers { recorded, sent, received } or its own error — the envelope's failure as it came, batch_parts_mismatch, batch_unsplit — and with its error there is no map: every number of that batch read answers the same error, a credential refusal and a failure before the envelope included. BatchAnswers resumes each asked number's handler with its parts, the numbers of one call in parallel, and keeps the answer, so a handler runs once; a handler never read is awaited by nothing and goes with its map. Over Streamable HTTP a HandlerLock read by number is held by the instance only while the SAP session its envelope went out on is still the instance's; a lock never read, or one of a lock batch whose BatchEnd failed, stays in SAP until that session's logoff, and an unlock never read leaves its handle in the instance's held locks after SAP unlocked it (a later write under it is sent and SAP refuses it; over Streamable HTTP the pooled instance stays held until it ends) — read every lock number and every unlock number before the next phase's BatchEnd.
    • Tool descriptions state only what each tool does, its parameters and its result, in every set (full, -ro, compact, batch, debugger); the sentence appended for batch mode is one of "Recordable: …", "When …, recordable: …" or "Runs outside batch mode.", and descriptionsFunctional.test.ts holds every served description to the rule.
    • Tool descriptions name the function on what the parameters name (#270, #271): "Reads the CDS view (or classic DDL view) named by ddl_name …", "Lists where the object named by object_name and object_type is used — a class, …, function module (BAPI included), … CDS DDL source …", "Removes the entry of the object named by object_name and object_type from the transport task named by transport_number …"; RuntimeListFeeds leads with the short dumps of a program, a user and a time range; the unit-test writers lead with the carrier and its ABAP Unit test classes. [read-only] means the tool changes no repository object (source, metadata, transports), as in the -ro binaries. Every such tool carries it (every Get<Type>, the Check<Type> syntax checks, the ATC status and findings, the local includes, compact's reads; the runtime reads, the background-run and leftover-session lists beside their [runtime] or [session], every debugger tool beside its [debug], the runs of code and tests (RuntimeRunClass, RuntimeRunProgram, Run…UnitTest, RunATC, compact's HandlerUnitTestRun and HandlerProfileRun), the trace scheduling, the background-run discard, the leftover-session logoff and the batch tools; only a tool that creates, updates, deletes, activates, locks, unlocks or edits a transport lacks it; GetPackageTree was [high-level]), no description names another tool (the Activate<Type>, version, transport, ATC, message-class and background-run tools did), and no first sentence is built around "an ABAP object". descriptionsFunctional.test.ts now also refuses another tool's name, a tool that changes no repository object, or a debugger tool, without [read-only], a first sentence two tools of a set share, and a vague subject in it.
    • The auth chain: @mcp-abap-adt/auth-broker ^5.0.6, @mcp-abap-adt/auth-providers ^6.0.2, @mcp-abap-adt/auth-stores ^5.0.1, @mcp-abap-adt/auth-errors ^2.2.1, @mcp-abap-adt/interfaces-auth ^7.5.0, @mcp-abap-adt/interfaces-auth-sap ^3.4.0; only core depends on the broker and the stores. The ADT chain: @mcp-abap-adt/connection ^18.0.1, @mcp-abap-adt/interfaces-adt-connection ^3.0.0, @mcp-abap-adt/interfaces-adt ^20.0.0 (root and compact-modify), @mcp-abap-adt/adt-clients ^36.0.2, @mcp-abap-adt/adt-strategies ^0.13.0. adt-clients 32's two new result slots of the class, program, function group and include sets, enhancementElements and enhancementOptions, are read verbatim.
    • @mcp-abap-adt/adt-clients ^36.0.2 (a credential refusal rethrown by every member; a batch corrects and resends nothing; an envelope that fails as a whole rejects every part with its one error, whose request is the envelope's own; the Accept and Content-Type of 32 members — create, update, delete, check, activate, the deletion check, the where-used, node structure and virtual folder reads, the data previews, the transport actions, the debugger's requests, the trace scheduling, abapGit's link and pull — are the caller's options.headers, and no request is negotiated, corrected or remembered; extractSupportedAccept / extractSupportedContentType read the types a 406 / 415 names — only SAP's list after Accepted content types: / Supported Media Types:, never the refusal's own content-type; since 36.0.2 three requests SAP refused are fixed, measured on premise: the enhancement's check sends the chkrun:checkObjectList document under Accept application/vnd.sap.adt.checkmessages+xml, Content-Type application/vnd.sap.adt.checkobjects+xml — application/vnd.sap.adt.checkrun.v1+xml as both was refused 415, and the server's default pair for it is the new one —, the feature toggle's validate sends Accept application/vnd.sap.as+xml, and the application log's getObject / getSource send Accept application/vnd.sap.adt.blues.v1+xml / application/json, where application/xml was refused 406), @mcp-abap-adt/connection ^18.0.1 (a session SAP replaced logs off the session that replaced it before its cookies are dropped), @mcp-abap-adt/sap-rfc-lite ^0.2.3 (prebuilds for Linux glibc and win32-x64), @mcp-abap-adt/interfaces-auth-sap ^3.4.0.
    • A tool call that fails on authentication answers JSON (authentication_failed, destination_refused or one of the server's own refusals); no error's message reaches an answer or a log line, and the startup summary shows a secret by its presence only.
    • A failed write to the --env / --env-path file fails the request; a named destination's failed write logs one stderr line whatever DEBUG_AUTH_LOG says.
    • A function group create that SAP refuses 400 "Interface SAPL… has not been created" reads the group back and answers done when it is there (CreateFunctionGroup; compact HandlerCreate answers the 400 as it came — read the group with HandlerGet part metadata).
    • Breaking for compact: HandlerCreate is one request — the POST of the type's adt-clients create() alone, for every type; no flag, no mode. It no longer validates, locks, writes, checks or activates, sends no service-group read for a service binding and no read-back after a function group's 400; the object is created inactive with what the create carries. Chain the rest as calls of their own: HandlerLock, HandlerUpdate with the lock_handle, HandlerUnlock, HandlerActivate. The schema lists only what some type's create sends: activate, fields, source_code, fixed_values and the other domain properties, test_class_source, cds_view_name, a class's abstract, a behavior definition's root_entity and a behavior implementation's behavior_definition are gone from it — their content goes in through HandlerUpdate under a lock, the activation through HandlerActivate; the schema now names the transport's transport_type, target_system, owner and the binding's service_name, service_version, binding_variant, which the create sends. UNIT_TEST and CDS_UNIT_TEST, whose create is a chain, are refused before sending: "HandlerCreate does not create : its create is a chain of requests, not one — use CreateUnitTest / CreateCdsUnitTest of the full set". Recorded in batch mode for every other type (a PACKAGE over HTTP only). The full sets' Create<Type> are unchanged.
    • Breaking for compact: HandlerUpdate is one request under the caller's lock_handle, now required — the type's write alone: the PUT of the source, of the class include or of the metadata document, and for SERVICE_BINDING its publication request; no lock or unlock, check, activation, read before or after the write. PACKAGE, DOMAIN, DATA_ELEMENT and FUNCTION_GROUP take the whole metadata document in source_code and send it as given (read it with HandlerGet part metadata). activate, properties, super_package, description, the domain properties (datatype, length, decimals, conversion_exit, lowercase, sign_exists, value_table, fixed_values) and behavior_definition leave its schema; service_version joins it. Chain the rest as calls of their own: HandlerLock, HandlerUpdate, HandlerUnlock, HandlerActivate. HandlerLock and HandlerUnlock take SERVICE_DEFINITION and SERVICE_BINDING; a binding open in an editing session (its LOCK's 403) is a lock with an empty lock_handle, under which HandlerUpdate SERVICE_BINDING sends the publication. Over Streamable HTTP HandlerLock holds the lock as the pooled instance's state and answers its state_handle; HandlerUpdate and HandlerUnlock are refused, sending nothing, under a lock the instance they reach does not hold, and a request on the handle works on the instance's held connection with its own credential — a renewed token or a new basic pair is the same user, in the same SAP session, the locks kept; the connection is replaced only when SAP ended its session. Recorded in batch mode for every type (a PACKAGE over HTTP only). The full sets' Update<Type> are unchanged.
    • Breaking for compact: HandlerDelete is one request — the deletion request alone, as adt-clients' delete() sends it, for every type it removes: no lock (a lock held on the object is what makes ADT refuse its deletion) and no deletion check, which is HandlerDeletionCheck now; the answer is SAP's, as it came. The class includes LOCAL_TEST_CLASS, LOCAL_TYPES, LOCAL_DEFINITIONS, LOCAL_MACROS and UNIT_TEST (a DELETE on an include answers 405, measured on premise) are written empty by one PUT under the caller's lock_handle, the class's lock (HandlerLock object_type: CLASS; over Streamable HTTP with its state_handle), with no lock or unlock of their own and no activation. CDS_UNIT_TEST deletes its test class. force_new_connection leaves its schema (compact locks and writes a package in an ABAP session of its own, so the call's session holds no saved package); lock_handle joins it. Chain the rest as calls of their own: HandlerDeletionCheck, then HandlerDelete; for an include HandlerLock, HandlerDelete, HandlerUnlock, HandlerActivate. Recorded in batch mode for every type (an include with its lock_handle), and HandlerDeletionCheck too. The full sets' Delete<Type> are unchanged.
    • Breaking for compact: HandlerGet is one request per part — part: source (the default) the source alone, part: metadata the metadata document alone, each answered { part: "source", source } / { part: "metadata", metadata } beside the object's name, the class includes too (GetLocalTestClass, GetLocalTypes, GetLocalDefinitions, GetLocalMacros of the full binaries answer the same shape; their test_class_code, local_types_code, definitions_code, macros_code keys are gone). PACKAGE, DOMAIN, DATA_ELEMENT and FUNCTION_GROUP have no source: part: source, and a call with no part, is refused before anything is sent, naming the type's parts — read them with part: metadata. A FUNCTION_MODULE is read at the group its arguments name: function_module_name as GROUP|MODULE, or with function_group_name; two groups, none, or more than one | are refused before sending. node_structure of a package (DEVC/K) is one request: SAP's blank body (an empty package and a package that does not exist answer alike) is the empty listing with blank stating both readings — HandlerGet part: metadata of the package tells them apart; 18.x read the package itself. urls and contents are unchanged. Batch mode records node_structure of a package too, and no longer lists source for the four types without one.
    • Breaking for compact: HandlerActivate starts a run and answers its id; HandlerGet reads the run. One object of CLASS, PROGRAM, INTERFACE, FUNCTION_GROUP, TABLE, STRUCTURE, DDL, DOMAIN, DATA_ELEMENT, BEHAVIOR_DEFINITION or METADATA_EXTENSION is activated as before, one request answering the verdict. Such an object is named by object_type, by its ADT code in object_adt_type, or as a one-entry objects[]. Every other call — objects[] of several objects or of a type the per-object activation does not map (a function module at its group), an object_adt_type of such a code, PACKAGE, SERVICE_DEFINITION, SERVICE_BINDING — is the one POST /sap/bc/adt/activation/runs, answered { run_id }: the run was accepted and has no verdict yet; a start SAP answers without a run id (no Location) is a failure with step: "start activation run", and nothing more is sent. 18.x then polled the run and read its results, answering accepted, run_status, activated and messages — up to six long-polling status reads a second apart, as read from 18.x's code, not measured. HandlerGet gains part: activation_status (GET /sap/bc/adt/activation/runs/{run_id}, answered { run_id, part, status, document }) and part: activation_results (GET /sap/bc/adt/activation/results/{run_id}, answered { run_id, part, messages, document }), by run_id, one request each, SAP's document as it came, on the instance's session and with no timeout (refused, like UNIT_TEST's); how often to ask is the caller's. All three are recorded in batch mode. The full sets' ActivateObjects is unchanged.
    • Breaking for compact: HandlerProfileRun is one run request, under a profiler_id or plain. With profiler_id it sends the one POST /sap/bc/adt/oo/classrun/{name}?profilerId=… (a program: /sap/bc/adt/programs/programrun/{name}?profilerId=…) and answers { success, class_name | program_name, output, profile: { profiler_id } }; without it, the one plain run request, answered without profile; an empty profiler_id is refused before any request; a failure is the method's error with step: "run with profiler" (plain: "run class" / "run program"). profiling and the profiling flags (now HandlerProfileSchedule's) are gone from it, and trace_id from its answer. 18.x scheduled the trace and ran under it inside the call, and for a class read the trace feed before and searched it after — up to five reads, 2 s apart, as read from 18.x's code, not measured. The caller schedules (HandlerProfileSchedule), runs, and finds the trace with HandlerProfileList; see MIGRATION-19.0. Recorded in batch mode.
    • Breaking for compact: a unit-test run's result is read once. HandlerUnitTestResult, HandlerCdsUnitTestResult and HandlerGet with object_type: UNIT_TEST / CDS_UNIT_TEST send the one GET /sap/bc/adt/abapunit/results/{run_id} and answer { success, run_id, run_result }, the result document SAP answered, parsed (detail: "raw": the document) — whatever SAP answers, a run that has not finished included; a finished run's result carries executionTime on each test method; a failure is the method's error with step: "read unit test result" (CDS: "read CDS unit test result"). Their descriptions say so: "Valid once the status is FINISHED; a finished run's result carries executionTime." 18.x read the status first inside the call — up to five long-polling status reads, back to back, the result only once one said FINISHED, as read from 18.x's code, not measured — and answered finished (with run_status and a message when the run had not finished; HandlerGet also run_status when it had); finished, message and HandlerGet's run_status are gone. The status is the caller's to read, as often as it chooses: HandlerUnitTestStatus / HandlerCdsUnitTestStatus, one status read, with long polling by default as before. All three are recorded in batch mode; HandlerGet with UNIT_TEST / CDS_UNIT_TEST still takes no timeout and runs on the instance's session. The full sets' GetUnitTestResult, GetCdsUnitTestResult, GetUnitTest and GetCdsUnitTest are unchanged.
    • Breaking for compact: HandlerDumpList reads one page. It sends the one GET /sap/bc/adt/runtime/dumps (feed_type system_messages / gateway_errors: their feeds) with $top = top (1 to 100; top 101 is refused before any request, naming the cap) and to, and answers { success, feed_type, count, entries, next_to }, next_to the to of SAP's next link as it came. A failure is the method's error with step: "read dumps" ("read system messages", "read gateway errors"). The description says: "The next link's page starts at the boundary second, inclusive: entries of that second may repeat; match them by dump_id." (measured: the feed answers 100 at most, the next page starts at the boundary second and answered 11 entries for $top 10). 18.x took a top up to 1000 and read the pages inside the call, as read from 18.x's code, not measured; incomplete_second is gone. The consumer follows next_to as to and matches by dump_id. Recorded in batch mode. The full set's RuntimeListFeeds still reads the pages inside the call.
    • Breaking for compact: the compact debugger is four tools, one request per call. HandlerDebugBreakpoints (ABAP setBreakpoints, syncBreakpoints, deleteBreakpoint, createWatchpoint, listWatchpoints, deleteWatchpoint; AMDP syncBreakpoints), HandlerDebugSession (ABAP checkListener, listen for exactly the caller's hold_seconds, stopListener, attach, detachDebugger — sent with no debuggee id on the attaching session, which releases the debuggee (measured 2026-10-17: with an id SAP answers 200 and releases nothing) —, terminateDebuggee, terminateDebugger, setDebuggerSettings; AMDP start, getEvents, stop, deleteDebuggee), HandlerDebugStep (ABAP stepInto, stepOver, stepReturn, stepContinue, stepRunToLine / stepJumpToLine with the line's address, now required, setStackPosition; AMDP stepOver, stepContinue) and HandlerDebugMaintain (ABAP getStack, getVariables, getChildVariables, getMemorySizes, createMemorySnapshot, setVariableValue; AMDP getDataPreview): kind and operation pick the one AbapDebugger / AmdpDebugger method each call sends; a failure is the method's error with the operation's step. HandlerDebugStart, HandlerDebugWait and HandlerDebugView are gone, and HandlerDebugStep's action with them. No listener polls in the background, no catch is attached for the caller, a step reads no stack (the step answer carries no position, measured 2026-10-17: read it with getStack), and only HandlerDebugSession end logs off: it takes the state_handle, logs off each connection the debug state opened and sends nothing else (measured 2026-10-17: an attached debuggee runs on, the AMDP debug session ends, the user's breakpoints stay set until removed with deleteBreakpoint or stopListener), and the state and its handle end with it, so a debug state of either kind and any identity can open after it. The full set's debugger tools are unchanged. Migration: one row per old tool.
    • Breaking for the full binaries: one Get<Type> per type, with part; the Read<Type> tools are gone. A type with a source (class, interface, program, table, structure, DDL, service definition, service binding, behavior definition and implementation, metadata extension, function module, function include) is read by Get<Type> with part: "source" | "metadata" (default "source"): one ADT request per call, the answer carrying only the part asked for, in one shape for every type: { part: "source", source } or { part: "metadata", metadata } beside the name fields (the per-type keys source_code, <type>_data, payload, package, message_class, message are gone; compact HandlerGet answers the same). The metadata-only types (domain, data element, function group, package, message class and its message) keep Get<Type> without part and answer { part: "metadata", metadata }. GetFunctionModule reads at the function_group_name given, one request (it no longer reads the metadata first to check the group); GetProgram reads any program (ReadProgram's invalid_object_type refusal is gone); ReadFunctionInclude is GetFunctionInclude. The -ro binaries serve the Get<Type> tools, the same tools as mcp-abap-adt. Every Get<Type> call, both parts, and compact HandlerGet part: source | metadata of a FUNCTION_MODULE, is recorded in batch mode. ReadVsGetDedupStrategy is gone; EmbeddableMcpServer's readOnlyDedupStrategy defaults to NoDedupStrategy. Compact HandlerGet part: metadata reads the metadata document alone, one request. The note maps every removed Read<Type>.
    • DeletePackage with force_new_connection no longer falls back to the current session.
    • The enhancement reads go through adt-clients 32 (EnhancementType enhoxhb | enhoxhh | enhsxsb, SAP's codes ENHO/XHB, ENHO/XHH, ENHS/XSB, each subtype its own media type). GetEnhancements reads a host's elements with readEnhancementElements on the class, program, function group or include (an include's read names its main program, from its contextRef or program; a function group's SAPL<fg>), no longer a request of its own. GetEnhancementSpot reads with getEnhancement().readMetadata under enhsxsb's own application/vnd.sap.adt.enh.enhs.v2+xml — the shared application/vnd.sap.adt.enhancements.v1+xml it sent answered 406 (measured by adt-clients 32). GetEnhancementImpl reads a source code plugin with getEnhancement().read (enhancement_spot: "enhoxhh", Accept text/plain) and answers not_found when SAP answers no source; a BAdI spot (enhsxsb) is refused before any request, like a BAdI implementation, and a collection name that is none of these is refused without a request, after the repository search names what the object is when it can. A failed read is the method's error as it is with its step (read enhancements, read enhancement spot, read enhancement source); a 401 ends the call as it came, with no search.
    • Since @mcp-abap-adt/adt-clients 33, a credential refused before SAP was asked rejects. Every member now rejects with connection's AuthRefusedError as it came, where it answered ok: false with the refusal's words (a credential SAP refused is a failure carrying SAP's 401). The server ends the call with it as authentication_failed wherever it read many objects or held a lock: nothing after it is requested — not the next include of GetEnhancements (nor an empty includes list for a refused walk), not the next SearchSource object, not the activation after a write's best-effort wait or its unlock; a locked write still tries its unlock, and a credential failure on the write, the unlock or the activation is the answer itself, never a cleanup entry or the held-lock wrapper. GetServiceBindingPreviewUrl ends on it in its system lookup instead of reporting the lookup failed and reading on.
    • Every catchable end runs the orderly shutdown. Besides SIGTERM and SIGINT: SIGHUP (the terminal closed, an SSH session dropped; on Windows the console closed), SIGQUIT, on Windows SIGBREAK; on stdio a broken pipe (EPIPE) on stdin or stdout, taken as the client's end of input (exit 0); an emptied event loop (beforeExit). A crash outside a tool (an uncaught exception, an unhandled rejection) now runs the orderly shutdown too — every logoff let finish — and exits 1 (it ran the forced close before); a crash during a shutdown lets it finish. A second signal and --shutdown-timeout still force the end. SIGKILL, a crash of the runtime and a power loss cannot be handled: their sessions live until SAP's security-session timeout.
    • The orderly end lets every logoff finish. The process's shutdown (stdio's end of input, a signal), a stdio start that fails after its logon (its exit 1, and a launch that rejects, which the binary's exit follows) and an instance's terminal close (the HTTP pool's eviction, an SSE close) follow every logoff written — at the end and by any site before — until SAP answered it or its transport failed (flushGoodbye(), no timer, the answer not read) before they resolve: SAP ends a security session and its user sessions only once it has received the logoff whole (measured 2026-10-19: logoffs sent right before the process ended left the sessions in SM05 until their timeout). The forced close still stops at the writes, so its sessions keep their runtime files, listed by ListLeftoverSessions of the next stdio process.
    • A debug stop deletes the breakpoints before it lets a held debuggee run on, and a debugger's run goes on a stateless session. The ABAP stop sends the listener's stop — which deletes the breakpoints — first, then releases a held debuggee and any release an earlier stop left owed, so a continue from a breakpoint inside a loop runs to the end instead of stopping there again. When the listener's stop fails and a breakpoint's delete by id fails too, no held debuggee is released: each stays held for the next stop, a failure with its step (release the debuggee, release <what>) in fixed words. While a debuggee stays held (or owed a release, ABAP and AMDP alike), the run's connection stays open with it — its logoff would end the session held at the breakpoint — and the stop that releases the debuggee closes it after the release; the instance's close and the process's shutdown log it off whatever is held. The program run of DebugStartListener / AmdpDebugStart opens a stateless connection (measured 2026-10-17: a run on a stateless connection is caught and held as on a stateful one); the listener, the attach, the breakpoints' connection and the AMDP events and commands connections stay stateful, each request on them in its one ABAP session, each logged off by its own SAP_SESSIONID.
    • Streamable HTTP: a tool call with an unknown or ended state handle is answered by the pool. state is not available, the same answer for every such handle, before any instance is made — no logon, no system-information read (measured 2026-10-19: such a call made a new instance that logged on and off). A JSON-RPC array holding a tool call with such a handle is answered the same, one answer per request in it with an id (202 and nothing when none has one). A request without a handle, or a protocol request (initialize, a list) carrying one, gets a new instance as before.
    • A complete debugger stop ends the debugger batch's kept answers with the state. DebugStop, and an AmdpDebugStop that ends the whole debug state, let the answers of the last batch go when a debugger batch gave them, so the state's handle ends with the stop (state is not available afterwards, as for an unknown handle); a refused or incomplete stop keeps them, and a communication batch's answers keep their lifetime.
    • An AMDP stop waits for the cancelled debuggee to end before it stops the session. AmdpDebugStop (and DebugStop's AMDP part) cancels a debuggee standing at a break, waits until the event read reports ON_WARNING, ON_EXECUTION_END or STOP and the run that called it has returned, then sends the stop and logs off (measured 2026-10-19: sent while the cancel was under way, the AMDP debugger's ABAP sessions and RFCs stayed until their timeout; sent after it, nothing stayed). No clock of its own, and it holds no other call: a second stop, the debug state's end (dispose, the instance's close, the shutdown) or the stop call's cancellation ends the wait, and the answer then names the debuggee that had not ended. A cancel SAP refuses sends no stop — the stop answers the refusal and the session stays for the next stop — except at the debug state's end.
    • An AMDP stop that SAP accepted logs off every connection of the session, even when the breakpoint clear was refused. The refused clear is answered with its step as before; the clear is not sent again and nothing is kept open for it (SAP removes a breakpoint left behind on its own). A clear refused before a refused stop still stays for the next stop, with the stop.
    • A credential failure ends a cleanup too, and a 401 SAP answered is one. The debugger's stop (and a start's cleanup, and the AMDP debugger's) sends no request after the credential was refused — no further breakpoint delete, release or stop: the refusal is the answer as it came with its step, and not_attempted names the steps left in fixed words; they stay for the next stop. A start whose reconciling stopListener the credential refused ends with it, nothing armed. A 401 on a locked write's unlock, or on CreateTransportTask's task type change, is the call's failure with its step, not a cleanup entry. GetInclude, GetObjectNodeFromCache and the enhancement type search (GetEnhancementImpl, GetEnhancementSpot) answer a refused credential as the credential failure instead of rewording it or reading on.
    • The read tools that still answered a failure in words answer it in the shape of every other. GetInclude reads through adt-clients (getInclude, analyseException): SAP's refusal is the method's error as it is, no longer its message as text. GetTypeInfo, GetTransport and GetObjectNodeFromCache, which send a request no client method makes, read its failure as adt-clients reads one — status, adt_type, T100 messages, raw_body and the request sent — with the step (read domain … read structure, read transport request, read OBJECT_URI), no longer SAP's body or the error's message as text; GetObjectNodeFromCache fails the call with it instead of answering the node with "ERROR: …" as its object_uri_response. GetPackageContents answers a thrown credential refusal as authentication_failed (it was answered in its words) and a throw of its own as client_threw, as do GetIncludesList, GetAbapAST (step: "parse"), GetAbapSemanticAnalysis (step: "analyse") and GetAbapSystemSymbols outside its walk. A throw while GetAbapSystemSymbols resolves a symbol is that symbol's error (client_threw with its step), not systemInfo.error text, and a thrown credential failure ends the call. GetEnhancements' partial answer after a failed nested walk carries the failure as an entry (step: "list includes"). GetServiceBindingPreviewUrl carries its system lookup's failure as system_information_failure beside the URL (its note says so in fixed words; a 401 there ends the call), no longer its message inside a sentence; a 401 SAP answers to its service URL lookup ends the call with that lookup's error (step: "read service URL") instead of the composed URL answering. A credential failure ends the call before any further request: a 401 SAP answered to one of GetIncludesList's includes (it was listed in unreadable and the walk read on), DescribeByList's typed search (it searched again without the type), GetObjectInfo's enrichment search (the tree was read on).
    • Every ADT session the server opens ends with a logoff — a request's instance, a pooled one at its state's end, a debugger's, a long read's, a background run's, a lock session's.
    • Every flag row of the YAML configuration refuses a list or a map: [true] (or { … }) was accepted for a boolean option and is now refused at start like any other bad value.
    • The paired reads send their two requests at once: GetObjectVersionDiff sends its two version reads together, neither waiting for the other — the second is sent even when the first fails. The answer is unchanged.
    • An SAP user id a transport tool names is sent in upper case, trimmed: CreateTransport owner (compact HandlerCreate TRANSPORT too) and CreateTransportTask target_user; ListTransports matches its user filter in upper case. SAP stores user ids in upper case: a lower-case owner was refused 400 "User … does not exist in the system (or locked)" (measured).
    • The package walks read in ADT batch envelopes, and take several roots. GetPackageContents (and compact HandlerGet part contents), GetPackageTree, GetObjectsList and GetObjectInfo send each level's independent requests in one envelope (POST /sap/bc/adt/communication/batch, inside the one call): per level the root node structures of its packages or nodes, then their type nodes (GetPackageTree reads the package metadata with the first; GetObjectsList every node of the level; GetObjectInfo with enrich one search per node in a last envelope). The descriptions in the answer come from each object's metadata, read in one more envelope after the walk (each object's read sent as that type's Get<Type> metadata read sends it — the same URL and Accept, a package's /sap/bc/adt/packages/<name>; its adtcore:description), not from the node structure parts: measured on the system, a node structure part after the first in an envelope can carry another part's DESCRIPTION, while metadata reads in one envelope answer as they do alone. An access control (DCLS/DL), which has no Get<Type> tool, is read as adt-clients reads its metadata (GET /sap/bc/adt/acm/dcl/sources/<name>?version=active, Accept application/vnd.sap.adt.dclSource+xml). An object whose metadata read fails, or of a type with no metadata read, has no description in the batched answer; otherwise the answer is the sequential version's. Each takes a list beside its single argument, exactly one of them given, read in the same envelopes and answered one entry per root in the order asked, a root's failure (the method's error with its step) its own entry: package_names (GetPackageContents, GetPackageTree, HandlerGet part contents), objects: [{ parent_type, parent_name }] (GetObjectsList), objects: [{ object_type, object_name }] (GetObjectInfo); the single arguments stop being required. A credential refusal — a 401 SAP answered to any root's read included — ends the call as it came, and nothing is read after it. A destination whose first envelope is answered 404 has no communication batch: that call and later ones run the tool's sequential reads as before, one root after another.
    • SearchSource reads in ADT batch envelopes, answers a cursor; concurrency is removed. Inside the one call: the quickSearch of every package mask, one envelope; each level of the package walk (as the package walks above), one envelope each; the source reads of the scanned objects, up to max_objects, in chunks of 300 objects, each chunk's rounds — the main sources and function group structures, then the function groups' includes and function modules — in envelopes of at most 300 reads (fixed, no parameter). The scan targets are ordered by (package, type, name), and the answer carries total_targets, the targets of the walk; a new start_at (default 0) starts the slice [start_at, start_at + max_objects), and a scan that stops before its last target answers next_start_at, to pass as start_at to go on. The hits, no_hits, failures and truncated flags are the sequential version's. time_budget_ms is checked before every envelope: once it has passed nothing further is sent, and the answer holds whole objects only — those of the chunks read to the end — with truncated.by_timeout and next_start_at at the first object not answered (start_at itself when none was); a deadline during the package walk answers next_start_at: start_at and no total_targets, the targets never having been enumerated. After a cut, scanned.objects and scanned.sources count only the objects answered. When next_start_at equals start_at, time_budget_ms passed before the first object was answered: raise time_budget_ms or lower max_objects. Before, a deadline answered the objects read part-way. The sequential version answers whole objects too: those finished before the first one the deadline cut. Closes #89. A credential refusal — a 401 SAP answered to any read included — ends the call as it came, and nothing is read after it. concurrency (parallel source fetches) leaves the schema: an envelope carries a phase's reads at once. A destination with no communication batch runs the sequential reads as before (eight fetches at a time over HTTP, one over RFC).
    • SearchSource is available on cloud systems too. It was declared for on premise only, on the grounds that cloud has no indexed source search; it never used one: it walks the packages and reads the sources, and both answer on an SAP BTP ABAP trial (measured 2026-10-21). ADT's own text search (/sap/bc/adt/repository/informationsystem/textsearch) answers "Source Search is not supported" on both the on-premise test system and the trial, so SearchSource does not call it.
    • The include, structure and enhancement trees read in ADT batch envelopes, and take several objects. One envelope per phase, inside the one call, one level at a time: GetIncludesList — every object's first read (a program's or include's source, a function group's node structure, then its includes node), then per depth up to 20 the source of every include first reached there; GetStructuresList — per level of the tree the structure source of every node, then the table source of the nodes not found as structures, and with include_extensions the where-used scope pair (structure, table) of every node read, the scoped where-used of every scope that answered, and the source of every append candidate; GetEnhancements — the type probes (class, then program, then include, one per phase), the enhancements of every object with include_nested beside its root node structure, the includes node, the metadata of every include up to max_includes, then their enhancements. Each name is read once for the call; the trees, the cyclic and duplicate marks, unreadable, appends_unavailable, append_candidates_unread and includes_unread are the sequential version's. A phase can carry a read the sequential version does not send: the table where-used beside an answered structure where-used, the root node structure beside enhancements that fail, an include the depth-first walk does not expand. A call that is one request — a class's includes, a named object's enhancements without include_nested — sends it as before. Each takes a list beside its single argument, exactly one of them given, read in the same envelopes and answered one entry per object in the order asked, a failed one { object_name, object_type, error } ({ structure_name, error }): objects: [{ object_name, object_type }] (GetIncludesList, GetEnhancements), structure_names (GetStructuresList); the single arguments stop being required. A credential refusal — a 401 SAP answered included — ends the call as it came, and nothing is read after it; on such an extra read too (on the extra table where-used, only a refusal the connection throws). A destination with no communication batch runs the sequential reads as before, one object after another.
    • ListTransports, DescribeByList and GetAbapSystemSymbols read in ADT batch envelopes. One envelope per phase, inside the one call: ListTransports — the search configurations, then the search of every saved configuration (up to 5); DescribeByList — the quickSearch of every object, then the quickSearch without its type of every object whose first answer was empty or failed; GetAbapSystemSymbols — the source of every class and interface symbol found in the code, in one envelope. The answers are the sequential version's. A phase can carry a read the sequential version does not send: ListTransports sends every configuration's search where the sequential version stops at the first that fails, and the first failed one in the order of the configurations is the answer, as before; DescribeByList and GetAbapSystemSymbols read every object or symbol of the phase beside one whose read is refused for the credential. A credential refusal — a 401 SAP answered included — ends the call as it came, the first in the order of the objects or symbols, and nothing is read after it (DescribeByList sends no typeless search). A destination with no communication batch runs the sequential reads as before, one request after another.
    • GetWhereUsed and RuntimeListFeeds read in ADT batch envelopes, and take several objects or feeds. GetWhereUsed with a type filter sends two envelopes for any number of objects: the where-used scope of every object, then the scoped where-used request of every object whose scope answered (and, with enable_only_types, offers every type asked). Each scope is read once, where the sequential version reads it twice with enable_only_types (once to check the types, once to search). Without a filter, several objects are one envelope; one object without a filter is its one request, sent as before (compact HandlerWhereUsed too). RuntimeListFeeds reads the paged feeds (dumps, system_messages, gateway_errors) in rounds, one envelope each: the first page of every feed, then the next page of every feed still paging; descriptors and variants alone are their one request, sent as before (compact HandlerDumpList still reads one page per call). The answers are the sequential version's, with one exception measured on premise (2026-10-18): in a where-used answer without a type filter, the usage_information of a few references (Web Dynpro and BSP objects) depends on the requests the ABAP session ran before — over RFC two identical requests sent one after the other answer it differently, and over HTTP a request's part in an envelope answers it differently from the request sent alone; every other field is the same. Each takes a list beside its single argument, exactly one of them given, read in the same envelopes and answered one entry per object or feed in the order asked, a failed one { object_name, object_type, error } ({ feed_type, error }): objects: [{ object_name, object_type }] (GetWhereUsed, the type filters applying to every object; object_name / object_type stop being required), feed_types (RuntimeListFeeds, the filters applying to every feed). A list is recorded in batch mode by neither tool. A phase can carry a read the sequential version does not send: every object's or feed's read of the envelope beside one refused for the credential. A credential refusal — a 401 SAP answered included — ends the call as it came, the first in the order of the objects or feeds, and nothing is read after it; a refusal the connection throws to the scope read that checks enable_only_types is thrown, as the sequential version throws it. A destination with no communication batch runs the sequential reads as before, one object or feed after another.
    • A read in an envelope that fails as a whole names the envelope's request. In the tools above that read in ADT batch envelopes, every read of an envelope SAP answers with a failure as a whole is that failure, and its request is the envelope's own (POST /sap/bc/adt/communication/batch) where the sequential version names the read's own request; the rest of the failure is the sequential version's.
    • The server sends the request header pairs, and corrects a 406 / 415 once. Every call of an adt-clients member that takes a header pair goes with the server's pair: one default per member and object type — the pair adt-clients 35 sent, completed where it sent one header or none and corrected where SAP refuses it (the enhancement's check) (src/lib/adtHeaders/defaultPairs.ts) — or the pair corrected for the system. A request SAP answers 406 naming the types it serves is sent once more with them as the Accept, one answered 415 naming a type once more with it as the Content-Type; the corrected pair is kept in memory for the system the destination points at and that member — not for the credentials: two destinations to the same system share it — and sent from then on, until a later 406 / 415 corrects it again. A 406 / 415 naming nothing, or what was sent, is the call's answer; the second request's answer is the call's answer. In the tools that read in ADT batch envelopes, right after a phase's envelope answers, the requests SAP answered 406 / 415 go in one more envelope — only those, each sent again by its member with the corrected pair; one round. A read without a header pair (a search, a source or metadata read) is sent as it is and its 406 is its answer: DescribeByList answers an object whose typed search is refused 406 with that search's error and sends no typeless search for it. BatchEnd is unchanged: a recorded call's 406 / 415 is its answer. The correction is the server's, around the member calls, so it applies on any connection — an embedder's injected one included. An embedder shares the corrected pairs between its instances with adtHeaderPairs: new AdtHeaderPairs() (@mcp-abap-adt/lib/embeddable); without it each instance keeps its own. Anything an embedder did with adt-clients' enableAcceptCorrection is gone with it.
    Fixed
    • SearchSource reads only the sources of a function group, each once. It read every FUGR/* node of the group's object structure as an include: a screen answered 500 (SADT_RESOURCE 028) and ended the group as a failure, a GUI status or title answered 404, and an include was read and scanned once for every field, module or class listed inside it. The structure is now read through an objectStructure strategy, and a node is read only when its objecturi names a function module (/fmodules/) or an include (/includes/); a node without one is read by its type, FUGR/FF or FUGR/I.
    • A check runs on the active version unless version is inactive (#181). CheckProgram, CheckFunctionGroup, CheckInterface, CheckBehaviorDefinition, CheckDomain, CheckDataElement, CheckDdl, CheckStructure, CheckTable and CheckPackage (high and low tiers; CheckObject for the same types) passed no status, and adt-clients reads a missing status as inactive, so a sound object with no inactive version reported syntax errors that were not there. CheckTable's new still checks the inactive version.
    • ReadTransportObjects on a request answers each task entry once, with its task number (compact HandlerGet TRANSPORT too). A request's document also carries tm:all_objects, the request's aggregate list, and it was collected beside the tasks' entries, so every object came twice (measured: 684 rows for 342 entries). A task read is unchanged.
    • stdio logs on again after SAP ended its session (#295). In 18.1.0 the connection the start made was reused after a SESSION_REPLACED, so every later call answered NOT_CONNECTED. Now the call that meets the replaced session still answers SESSION_REPLACED (nothing is retried); the next call ends the spent connection, logs on again and runs on the new session. The session SAP opened for the call that met the replacement is logged off at once (connection 18.0.1), so no session of the spent connection stays in SM05 until its idle timeout.
    Measured
    • Live measurements of 19.0.0 (2026-10-15; on premise BASIS 816 and an SAP BTP ABAP trial; Linux, Node.js 26.7.0; SNC on Windows): every transport and binary kind answered with basic, a token and SNC; a token refused by the system was renewed with the refresh token and written back; a logoff released the locks on a shutdown and on an SSE stream close; a long read's timeout left the instance's session and its lock in place; the stdio start took up its leftover session. A shutdown during the start's browser login hung and is fixed (exit 1, the callback port freed). Details: docs/development/tests/TESTING_AUTH.md.
  5. v18.1.0

    Features 3, Changed 1

    Notes

    Two transport organizer actions on @mcp-abap-adt/adt-clients 30.

    Added
    • SortAndCompressTransportTask (transport_number, the task): the organizer's Sort and compress — sorts a task's object list and merges its duplicate entries. A task holding the same entry twice refuses RemoveTransportObject with SCTS_ADT_MSG 009 (long text TR292) until it has run. Measured on premise (BASIS 816, 2026-10-12): 200 with an empty body; a task of 161 entries with four held twice re-read as 157.
    • MoveTransportObjects (transport_number, the source task; target_number; objects, at least one, each object_name, object_type, position in the source and an optional pgmid): the organizer's Move Objects. Measured on premise (same day): one and two entries per call answered 200 echoing them, and both tasks read back with them moved.
    • Neither answer is evidence of the change, so neither says success (as RemoveTransportObject): the terse answer is accepted: true, what was asked, and confirm_with naming ReadTransportObjects on the task — both tasks for a move. A missing argument or an empty objects is refused in fixed words naming the field before anything is sent. Both run under the critical section the other transport object writes have. 159 high-level tools.
    Changed
    • @mcp-abap-adt/adt-clients ^30.0.0, @mcp-abap-adt/adt-strategies ^0.10.2, @mcp-abap-adt/interfaces-adt ^15.3.0 (root and compact-modify). adt-clients 30's two new result slots, sortedAndCompressed and movedObjects, are read statusOnly and structured. @mcp-abap-adt/core, @mcp-abap-adt/compact and the two compact halves require @mcp-abap-adt/lib ^18.1.0.
  6. v18.0.0

    Features 4, Changed 8, Fixes 3

    Notes

    The debugger on @mcp-abap-adt/adt-clients 29 (interfaces-adt 15, adt-strategies 0.10): another user's runs, terminal mode, leaving a debuggee running, the debugger settings — and breakpoints armed as one set. Migration: docs/MIGRATION-18.0.md.

    Added
    • DebugStartListener and DebugTakeOverListener (compact HandlerDebugStart, kind: abap) take request_user (whose requests are caught; needs the debug authorization on that user), debugging_mode (user | terminal) and settings (applied after each attach). Terminal mode catches the requests that carry the session's terminal id and IDE id; the run the server starts carries them in its SAP-PASSPORT, so it needs no configured id (measured on premise, 2026-10-12). The start answers the identity in force.
    • DebugStep (compact HandlerDebugStep): detach and release_debugger, ABAP only — the program runs on, the session listens on.
    • DebugSetSettings: the debugger settings at the current stop; answers the settings in force. 30 debugger tools.
    • SAP_DEBUG_BREAKPOINT_CONFLICT (replace | refuse-if-held): whether a take-over and every later breakpoint change first ask if another debugger listens for the user. The header x-sap-debug-breakpoint-conflict, a destination's .env, the environment, createDebuggerInstance's breakpointConflict.
    Fixed
    • A second DebugSetBreakpoints removed the breakpoints of the first, and an ambiguous refusal re-asked with validationOnly removed the whole set: the system replaces the key's set on every request (measured on premise (2026-10-11), BASIS 816). The session sends its whole set every time, each breakpoint tagged with a clientId the system echoes, and pairs the answer rows by that tag without re-asking.

    • A refuse start (DebugStartListener, compact without take_over) meeting another IDE's listener of the same SAP user replaced that IDE's breakpoints before its listen was refused 409: the user-mode set is the SAP user's with the request user, whatever the IDE id (measured on premise, 2026-10-12). It now asks the system first (checkListener) and is refused before anything is changed; every later DebugSetBreakpoints of that session asks the same way. The check is not atomic; the remaining race is documented.

    • CreateFunctionGroupUnitTest and UpdateFunctionGroupUnitTest left the group's main program inactive, so ABAP Unit found no test class and answered an empty run result. Writing the test include regenerates SAPL<group> with the INCLUDE added; the group was then activated alone with the pre-audit, which is answered 200 with the inactive list instead of an activation, and that passed as a success (measured on premise, BASIS 816, 2026-10-12). The include, the group and its main program are now activated in one run without the pre-audit, and a refused or unfinished run is a failure.

    Changed
    • CreateFunctionGroupUnitTest / UpdateFunctionGroupUnitTest with detail: full answer the activation run (run_id, status) instead of the parsed activation checklist; detail: raw carries the run's results document. terse is unchanged.
    • DebugStartListener and DebugTakeOverListener require breakpoints, at least one; a start without them is refused by the schema, and by the session before anything is sent.
    • The breakpoints answer is the whole set: placed, refused (this call's, each with its definition), lost, unexpected. A delete by an id the session does not hold is refused before the wire.
    • A condition on a non-line breakpoint is refused, naming it.
    • DebugStop stops the listener first, which deletes the key's breakpoints, and deletes by id only when that fails; a start that failed after a change does the same. While the listener's open poll has not answered, the stop sends one more stop, which narrows a race of the system's without closing it (documented in DEBUGGER.md). The identity and the settings stay until a stop undid everything, and a new start is refused until then — except when the system refuses the listener's stop for the user (403 notAuthorized, 404 invalidUser): the breakpoints are let go, not deleted, the stop answers a note naming those left on the system, and the session ends when nothing else failed.
    • A listener conflict (409) names the holder: the system's conflict text and the IDE user.
    • @mcp-abap-adt/adt-clients ^29.1.0, @mcp-abap-adt/adt-strategies ^0.10.1, @mcp-abap-adt/interfaces-adt ^15.1.0 (the breakpoint types lose skipCount and gain clientId; a run takes the debuggee's ids); @mcp-abap-adt/compact-modify declares interfaces-adt, which it imports. @mcp-abap-adt/core, @mcp-abap-adt/compact and the two compact halves require @mcp-abap-adt/lib ^18.0.0.
    • DEBUGGER.md: another user, terminal mode, the breakpoint set and its key, leaving a debuggee running, the settings, what ADT cannot do.
  7. v17.2.2

    Changed 3

    Notes
    Changed
    • A terse stop keeps up to 50 frames, not 5 — the ABAP stop (DebugWait, the step answers, compact's HandlerDebugWait) and an AMDP break alike. Real stacks run to some 20–25 frames, and five cut most of an ordinary one. A deeper stack is cut at 50 from the top, and stack_depth then gives the full depth, so a model knows to ask for detail: full.
    • The AMDP stack under terse keeps no lines for a caller not compiled for debugging. Measured on premise with nested procedures: the system sends the innermost frame first (so the frames kept are the top ones), and for a caller not compiled for debugging it answers the method's first line and native line 0 rather than where the call stands. Such a frame now answers its procedure and object with not_debug_compiled, without line or native_line. full and raw are unchanged.
    • Measured: a catch during a stop. An attach after the listener was stopped still succeeds, so DebugStop releases such a debuggee as it does; a caught debuggee nobody attaches is held about 30 seconds and then runs on. DEBUGGER.md says so.
  8. v17.2.1

    Changed 3

    Notes
    Changed
    • Memory sizes under terse (DebugGetMemorySizes, and HandlerDebugView with what: memory in compact) answer three numbers in bytes — abap_objects_used, internal_used, internal_peak_used — instead of the parsed document; full is the parsed document and raw SAP's, as before. The library reads the whole document as readMemorySizes (numbers, named after its elements) and projects it with terseMemorySizes.
    • An AMDP break carries its call stack. AmdpEvent.stack reads the frames of an ON_BREAK (procedure, ABAP position and line, schema and native line, whether the frame is compiled for debugging); the terse event of AmdpDebugWait and compact's HandlerDebugWait keeps the first five, in the order the system sends them, as procedure, address and native_line, marking a frame not compiled for debugging.
    • @mcp-abap-adt/core and @mcp-abap-adt/compact require @mcp-abap-adt/lib ^17.2.1.
  9. v17.2.0

    Features 7, Changed 3

    Notes

    The debugger: ABAP and AMDP debugging as an opt-in tool set, a debug session that lives between tool calls on every transport, and the instance state that holds it. Five schemas now refuse values their handlers used to bend (see Changed).

    Added
    • The debug set (--exposition=…,debug; opt-in, never part of the default, because a breakpoint catches every request of the connected SAP user), 29 tools:

      • ABAP: DebugStartListener and DebugTakeOverListener (arm breakpoints, listen, attach the first program caught, optionally run a class or report in the background; refuse, or displace, another debugger of the same user), DebugWait, DebugSetBreakpoints, DebugDeleteBreakpoint, DebugListBreakpoints, DebugGetStack, DebugSetStackPosition, DebugGetVariables, DebugSetVariable, DebugStep, DebugStepToLine, DebugTerminate, DebugCreateWatchpoint, DebugListWatchpoints, DebugDeleteWatchpoint, DebugGetMemorySizes, DebugCreateMemorySnapshot, DebugStop;
      • AMDP: AmdpDebugStart (a background run starts once the system confirmed the breakpoints), AmdpDebugSetBreakpoints, AmdpDebugWait, AmdpDebugStep, AmdpDebugGetTable, AmdpDebugCancel, AmdpDebugStop (releases a suspended debuggee first);
      • memory snapshots: MemorySnapshotList, MemorySnapshotGet, MemorySnapshotDelta (need the memory snapshot authorization; the list is empty without it).

      It debugs what it starts itself, in the system's user mode; terminal debugging, other users' requests and attaching to a process it did not start are not offered. Every answer takes detail (terse, full, raw). See docs/user-guide/DEBUGGER.md.

    • A debug session is named by a state_handle (128 random bits) that the starting tool answers and every other session tool takes; an unknown handle and an ended session's get the same answer, state is not available. The handle is a bearer secret: keeping it safe is the deployer's (SECURITY.md, new). The state lives in the server instance: stdio keeps it in the process, SSE in its session, and Streamable HTTP in a pool of instances that routes each tools/call by its state_handle (one request at a time per instance; a batch carrying a handle is refused). Every host stops what an instance holds before letting it go.

    • The idle bound on held state: --state-idle-minutes, env MCP_STATE_IDLE_MINUTES, YAML state-idle-minutes — default 30, at least 30, at most 35791. Held state ends after that many minutes without a tool call on its instance; a call in flight pauses it, a listener's own re-poll does not count. The end is a complete stop, logged on stderr without the handle.

    • The debugger's SAP ids can be stated: headers x-sap-debug-terminal-id and x-sap-debug-ide-id, SAP_DEBUG_TERMINAL_ID and SAP_DEBUG_IDE_ID in the destination's .env or the environment; otherwise random per instance. The system keys its listener conflict by the IDE id, so sharing one is a deliberate choice; with stated ids, a restarted server's first start ends the listener its predecessor left.

    • Library (@mcp-abap-adt/lib): subpaths ./debugger (the debug sessions, their readings and schemas) and ./state (InstanceState, the idle bound); DebugHandlersGroup, defineTool and the ArgsOf type from ./handlers; HandlerExporter option includeDebug (default false); EmbeddableMcpServer options stateIdleMinutes and stateLogger (default: the logger passed, else stderr), and its state, stateHandle, holdsState(), dispose(), shutdownState() and idle(); requestContextFromHeaders reads the two debug headers; readStateIdleMinutes and its names from ./config.

    • Compact: --exposition=ro,debug or rw,debug adds four verbs — HandlerDebugStart (kind: abap|amdp, take_over, run), HandlerDebugWait, HandlerDebugView, HandlerDebugStep (stop ends the session) — 29 tools in all. debug alone is refused.

    • docs/user-guide/DEBUGGER.md; the generated tool reference has a Debug group.

    Changed
    • Tool arguments are checked at any depth, with their ranges. The JSON schema of a tool becomes one zod schema recursively — nested objects and arrays included — with integer as a whole number, and minimum and minItems enforced. What that changes for callers:
      • a fraction is refused where an integer is declared on five tools, whose handlers used to truncate it: RunATC (max_findings), RuntimeRunClass and RuntimeRunClassWithProfiling (max_trace_attempts, trace_retry_delay_ms), GetObjectInfo (maxDepth), GetPackageTree (max_depth);
      • RuntimeRunClass and RuntimeRunClassWithProfiling refuse an out-of-range max_trace_attempts (under 1) or trace_retry_delay_ms (under 0), which their handlers silently replaced with the defaults; the schema always declared these minimums;
      • nested values are validated: GetVirtualFoldersLow's preselection[].values must be an array of strings, as its handler already assumed.
    • Versions: @mcp-abap-adt/core and the three compact packages require @mcp-abap-adt/lib ^17.2.0, and @mcp-abap-adt/compact requires @mcp-abap-adt/core ^17.2.0 — the releases that ship lib/debugger and lib/state.
    • On @mcp-abap-adt/adt-clients 27.0.0 (with adt-strategies 0.8.1, interfaces-adt 13.2.0 and interfaces-adt-connection 2.0.0). Every factory of the client now answers a contract; what that changed here:
      • CreateServiceBinding's last step reads the service group. It called generateServiceBinding, which sent that very read and generated nothing; 27 removed the name. The step stays — after an activation, a binding whose service group cannot be read is reported as the refusal it is — and the activate parameter now says what it does: activate, then read the group.
      • A class and a behavior implementation create carry no responsible person (adt-clients 26.0.1): SAP stores that attribute on them as the creator. The responsible person still travels on every other create; the system-context tests now drive a message class, and pin that a class create sends none and is not refused.
      • The result-set table gives the new slots (ATC check variants, the ABAP and AMDP debuggers, memory snapshots) a reading.
  10. v17.1.0

    Removed 1, Features 2

    Notes

    A personal portable build of both servers; no change to what the servers do.

    Added
    • A personal portable build: one executable per server (mcp-abap-adt, mcp-abap-adt-compact) and platform (Windows x64 and macOS arm64: HTTP, RFC, SNC; Linux x64: HTTP, RFC) — Node.js 24 inside as a single executable application, the builder's SAP NW RFC SDK and the RFC module beside it in nwrfcsdk/lib. Unpack and run: no Node.js, npm, compiler or SAPNWRFC_HOME on the machine. Built from this repository for the platform it runs on with npm run portable:build, portable:build:full or portable:build:compact, from the published packages of the version; checked with npm run portable:smoke. It carries the SDK, so it is never published or handed on. Verified on Linux in a clean container, on Windows 11 with SNC and on macOS arm64 (SNC not verified there). See docs/installation/PORTABLE.md. The build needs only the repository's production dependencies (npm ci --omit=dev --ignore-scripts): it installs esbuild and postject, at the versions the repository declares, into its own cache.
    • @mcp-abap-adt/sap-rfc-lite 0.2.2, picked up by the existing ^0.2.1 range, builds the RFC module on a current macOS (its minimum was 10.15, which current Xcode's libc++ refuses) and under FORCE_COLOR (its binding.gyp read the N-API version through node -p, which colours numbers). The portable build still runs its nested npm install with colour off.
    Removed
    • @mcp-abap-adt/core no longer declares dotenv. The server never imports it; .env files are read by @mcp-abap-adt/lib, which keeps its own dependency, so an install of core carries dotenv once, through lib.
  11. v17.0.1

    Changed 2

    Notes

    Documentation and release metadata; no change to what the servers do.

    Changed
    • The registry and Glama metadata is part of a release. releaseMetadata.test.ts fails until server.json, server-compact.json and the Glama image (docker/Dockerfile.inspect) name the version the manifests carry, the registry entries name their package and mcpName, their descriptions stay within the MCP Registry's 100 characters, and every sibling range is on the current major. The Glama image is pinned to the release of @mcp-abap-adt/core (it installed latest). glama.json names the variants. docs/deployment/RELEASE.md makes these a step of the release PR and gives mcp-publisher one command per registry entry.
    • The compact tool counts are the ones the server lists: 25 tools, 16 that change nothing (@mcp-abap-adt/compact-readonly, --exposition=ro) and 9 that write (compact-modify). The READMEs and the package description said 22 and 13; the generated tool page takes its count from the tool list.
  12. v17.0.0dated in the atlas

    Breaking 2, Changed 3, Fixes 11, Docs 5

    Notes

    The dependency update to @mcp-abap-adt/adt-clients 25 and @mcp-abap-adt/connection 11, which verifies the HTTPS certificate of the SAP system. The installation documentation is rebuilt around the variants — the full or the compact server, over HTTP, RFC or SNC — and the Docker image installs the published server from npm. All five packages (lib, core, compact, compact-readonly, compact-modify) are 17.0.0.

    Migration: docs/MIGRATION-17.0.md.

    Breaking
    • The HTTPS server certificate is verified (@mcp-abap-adt/connection 11). Up to 16.0.x the server accepted any certificate unless TLS_REJECT_UNAUTHORIZED or NODE_TLS_REJECT_UNAUTHORIZED was 1; a system with a self-signed or company-CA certificate is now refused at its first request. Trust the CA with NODE_EXTRA_CA_CERTS, or opt out with TLS_REJECT_UNAUTHORIZED=0 — both in the process environment: a destination's .env is not copied into it, so a line there is not read (it was not on 16.x either). RFC, SNC and plain http:// are unaffected.
    • CreateMessageClass and CreateProgramUnitTest need a responsible person, like every other create since 16.0.0. @mcp-abap-adt/adt-clients 25.0.1 sends adtcore:responsible from the system context for a message class and for an include, where 24.x sent none and the system filled its own default; with no responsible found (SNC or a token you hold, no SAP_RESPONSIBLE) both are now refused with system_context_missing before the create is sent.
    Changed
    • @mcp-abap-adt/adt-clients ~25.0.1 (was ~24.1.0; still pinned to a minor, for the same reason), @mcp-abap-adt/adt-strategies ^0.7.0 and @mcp-abap-adt/interfaces-adt ^12.0.1, which move together — one copy of the contract in the tree. From adt-clients 25: getVersions() reaches the history of DDL sources, access controls, function includes and table types (it asked an address SAP answers 404); a namespaced enhancement is no longer encoded twice; a function module reference without its group is refused by the client too, before any request. RunATC takes the same seven object types: interfaces-adt 12's program and include kinds are not offered by the tool yet.
    • @mcp-abap-adt/auth-broker ^4.1.0, auth-providers ^5.4.0, auth-stores ^3.3.0, interfaces-auth ^3.2.0, interfaces-auth-broker ^1.2.0. Nothing the server serves changes. A malformed service key file no longer puts its bytes — a client secret, a private key — into the thrown error and the log (Node's JSON.parse quotes its input); a log line about a failed SNC library lookup or a failed probe carries fixed words instead of the error's message. The x509 service keys these releases add are not served yet.
    • @mcp-abap-adt/interfaces-adt-connection ^1.0.1, interfaces-network ^2.0.1, interfaces-utils ^1.1.1 (built from the registry, contract unchanged); @modelcontextprotocol/sdk ^1.32.0, dotenv ^18.0.5, fast-xml-parser ^5.11.2, pino ^10.4.0, pino-pretty ^13.2.0; dev: @biomejs/biome ^2.5.15, @modelcontextprotocol/inspector ^2.9.0, @types/node ^22.20.5. TypeScript stays on 6.x and @types/node on 22, as SAP's toolchain does.
    Documentation
    • Installation variants, in one place: docs/installation/INSTALLATION.md opens with the full (@mcp-abap-adt/core) and the compact (@mcp-abap-adt/compact) server, and with what HTTP, RFC and SNC each need on the machine; the README, the platform guides, the compact READMEs and the package descriptions point at it.
    • RFC: the SDK, a C++ toolchain and SAPNWRFC_HOME come before npm install. @mcp-abap-adt/sap-rfc-lite ships no prebuilt binary and is compiled during the install; when it cannot be, npm leaves the optional dependency out and reports success, and the server refuses only the first RFC call. Measured 2026-10-05 on clean installs from the registry. RFC_SETUP.md now gives the toolchain per platform, the check (npm ls -g @mcp-abap-adt/sap-rfc-lite), the runtime PATH on Windows, SAP_SYSNR for a port that does not say the system number, how the SNC library is found, and a troubleshooting entry per failure. It installed sap-rfc-lite locally for a global server and checked it with a require that resolves from the working directory; both are gone.
    • The platform guides no longer put TLS_REJECT_UNAUTHORIZED=0 in their sample .env (it was never read from there), no longer install from a .tgz or through git submodules, and no longer advise --no-optional. The README's development commands (npm start, npm run start:http) named scripts that do not exist; running a build from a checkout is described instead.
    • Client configuration examples (docs/installation/examples/, the Cline and client guides) use --env-path for a path (--env takes a sessions-store name), and cover the variants: full and compact server, RFC/SNC with the process-level env block, a company CA. Checkout-based configs (dist/index.js, bin/mcp-abap-adt.js) and duplicate npx variants are gone; the HTTP and SSE examples carry their paths (/mcp/stream/http, /sse).
    • Docker and release docs match the files: the image has no RFC SDK, so RFC and SNC are not available in it; NODE_EXTRA_CA_CERTS / TLS_REJECT_UNAUTHORIZED are container environment; npm publishing is npm run release:publish of the five packages in dependency order; MCP_REGISTRY.md covers server.json and server-compact.json.
    Fixed
    • @mcp-abap-adt/compact can be registered in the MCP Registry: it had no mcpName, and server-compact.json still named 14.0.1 with a description over the registry's 100 characters.
    • server.json / server-compact.json described a .env in the working directory as a fallback; nothing has been read from there since 16.0.0.
    • The Docker image starts again, and installs the server from npm. docker/Dockerfile built only the library (build:fast) and ran dist/server/launcher.js, which no build produces, so docker-compose.yml and docker-compose.headerless.yml could not start. Nothing is built now: stage 1 installs the published @mcp-abap-adt/core and @mcp-abap-adt/compact with npm install -g into their own prefix (build argument MCP_ABAP_ADT_VERSION, default latest), stage 2 copies that installation into a clean runtime image and runs mcp-abap-adt as the node user. The Node inspector is no longer open on 0.0.0.0:9229 by default. The build context is docker/, and its .dockerignore sends only the Dockerfiles — never service-keys/, sessions/ or a .env. Dockerfile.inspect installs the published core behind a pinned mcp-proxy instead of building a fixed commit from 2025. Dockerfile.package and docker-compose.package.yml, a second way to the same npm package, are removed. Built and run on 2026-10-05: HTTP initialize and the tool list answer, the health check reports healthy.
    • npm run docker:* and npm run smoke:mcp:* run from the repository root. They were declared in server/package.json with paths relative to server/, where neither docker/ nor tools/ is; so were start, start:http, start:sse, dev, dev:http, dev:sse and kill-ports, which ran a checkout that cannot run and are removed, with the tools/dev-*.js launchers they called. tools/mcp-crud-smoke.js spawns the installed mcp-abap-adt for stdio by default.
    • CI packs and installs all five packages and checks that no package took a sibling from the registry instead of this build; it packed only lib and core. It also builds the Docker image and runs both commands in it.
    • --help: a message class is no longer described as the exception to the responsible rule, and the RFC requirement says the module is compiled by npm install.
    • The compose health checks asked /health; the server answers /mcp/health.
    • The integration tests read no .env from the working directory or the repository root. environment.env in tests/test-config.yaml is a sessions-store name, as the docs said, resolved exactly as --env=<name> (under auth_broker.paths.service_keys_dir, else the platform directory); it was resolved against the repository root, and a missing destination fell back to a .env in the working directory or the root — the lookup the server dropped in 16.0.0. Hard mode passes the same file to the server (--env / --env-path, with --auth-broker-path) and refuses to start without a destination instead of defaulting to ./.env. ClassCrudClientDirect, which builds its own connection, primes the system context itself; it passed only through what an auth-broker setup left in the process, and failed the responsible guard with a destination read from environment.env.
    • A test that creates and deletes its own object never touches a shared one. assertNotSharedDependency refuses, before any request, a name listed in shared_dependencies: the function-include suite was configured onto the shared function group, which it would have created and deleted, and the views a BDEF suite creates are checked the same way. deleteView runs in cleanup whether or not the run created the view, so it no longer deletes a shared one — it reports it, and the run fails naming it. A BDEF suite whose test case lacks root_view_name / root_view_source says so, instead of view undefined; the profiling suite requires its class to be the shared, runnable one and shows the answer when no trace id comes back.
    • npm run shared:setup and shared:teardown fail when they cannot reach the system or the config has no shared_dependencies. They passed, having done nothing — a green run that read as "set up".
    • The release notes linked doc/installation/…; the workflows checked out submodules the repository no longer has (the empty .gitmodules is removed).
  13. v16.0.1

    Fixes 2

    Notes
    Fixed
    • initialize reports the installed version in serverInfo.version. Every transport read npm_package_version, which npm sets only for a process it starts itself (npm run, npx); started by an MCP client, by node, or by mcp-proxy (as Glama's image does), the server answered 1.0.0 whatever was installed. mcp-abap-adt now reads @mcp-abap-adt/core's own manifest, and mcp-abap-adt-compact reports its own version, as --version already did. The bin smoke test now checks initialize from an installed tarball as well.
    • @mcp-abap-adt/core carries mcpName again, so the MCP Registry can take it. mcpName stayed in the root manifest — @mcp-abap-adt/lib — when the server moved to server/, and the registry reads it from the package server.json names. No core from 10.0.1 to 16.0.0 had it, and the registry entry stopped at 8.8.1. docs/deployment/MCP_REGISTRY.md now says where it lives and that npm publish precedes mcp-publisher publish.
  14. v16.0.0dated in the atlas

    Breaking 15, Features 8, Changed 5, Fixes 7

    Notes

    Migration: docs/MIGRATION-16.0.md.

    Breaking
    • Authentication runs on @mcp-abap-adt/auth-broker 4, connection 10, auth-providers 5 and auth-stores 3, and the server serves exactly four authentications: basic (HTTP or RFC), SNC (RFC only, no user and no password), jwt / authorization_code (browser login; an ABAP or XSUAA service key) and jwt / none (a token you hold). A destination that states anything else — certificate, kerberos, saml or another jwt grant — is refused at startup, naming the authentication (a saml destination with no grant is refused with lacks: grantType).
    • A .env states its authentication. SAP_AUTH_TYPE has no default, and a jwt .env states SAP_GRANT_TYPE; without them the destination is refused (Destination "X" lacks: authType / grantType, with a hint to regenerate it with mcp-auth generate-env --grant). The mcp-auth command comes from @mcp-abap-adt/auth-broker-cli.
    • An XSUAA service key needs XSUAA_MCP_URL in sessions/<destination>.env: the key carries the UAA, not the system.
    • An --env / --env-path .env is read and written back with a renewed token, whatever --unsafe says. A destination is read once per process: a changed .env takes effect on restart. A named destination's session is read from sessions/<destination>.env only with --unsafe: a jwt / none token there is refused without it (lacks: authorizationToken, with a hint).
    • Nothing is looked up in the working directory. Finding configuration where the process happens to start is a vulnerability: a server started inside someone else's project took their settings and credentials. A .env there is no longer read — pass --env-path=./.env (or --env=<name>, --mcp, YAML); without one there is no default destination (stdio: inspection-only). The working directory is gone from getPlatformPaths too.
    • --auth-broker, MCP_USE_AUTH_BROKER and YAML auth-broker are removed; their only purpose was to switch the working-directory .env off. A leftover form stops the start, naming the form used ("was removed in 16.0.0 — remove it from the configuration") — drop it from client configs.
    • The master system is determined from configuration, or by a request in the cloud. The setup-time master-system lookup is gone (setting a destination's context up builds no connection), and cloud versus on-premise is the kind the connection was built for (SAP_SYSTEM_TYPE, else jwt is cloud), never a guess from the URL. On-premise nothing is asked: the master system is SAP_MASTER_SYSTEM, or left out of the request when none is set. A cloud destination without SAP_CLIENT uses the system's default client.
    • A created object always carries a responsible person; the master system only when known. The responsible comes from, first found: the tool's own argument (CreateTransport's owner); the x-sap-responsible header; SAP_RESPONSIBLE in the destination's own .env (the --env / --env-path file or sessions/<destination>.env), then in the process environment; else the login — on-premise the destination's SAP_USERNAME, the x-sap-login of an x-sap-url connection (not of a destination request), the process SAP_USERNAME; on a cloud system only systeminformation's user. A create (or a transport without an owner) that finds none (SNC or a token you hold, with no SAP_RESPONSIBLE) is refused before any request — "error": "system_context_missing", naming SAP_RESPONSIBLE, the header and the login (or, when a cloud system could not be reached, saying to retry when it is available) — where it used to be sent with an empty or missing responsible. CreateBehaviorImplementation, whose adt-clients class is built with an empty system context, now passes the responsible and master system itself. The one exception is a message class: it is created with the system's own default responsible (adt-clients' message class create takes none, as in 15.x). The master system comes from x-sap-master-system (no tool takes it as an argument), SAP_MASTER_SYSTEM (destination .env, then process), else a cloud system's id; otherwise it is left out of the request, as before — never refused. Reads are unaffected. On a cloud connection an empty value is filled from systeminformation even when a host's request scope carries its key as undefined. SAP_RESPONSIBLE from any source now wins over every login, outside a request scope that carries responsible. The process environment is read once: a change while the server runs is not picked up. The --env file's SAP_RESPONSIBLE, SAP_MASTER_SYSTEM and SAP_USERNAME are no longer copied into the process environment: they are that destination's own, and over HTTP/SSE they had become every other destination's fallback. getSystemContext() no longer reports SAP_USERNAME as responsible.
    • @mcp-abap-adt/adt-clients is pinned to ~24.1.0: the refusal relies on its protected systemContext being read where a value is sent, which a minor release could change. Its service definition, transformation and access control builders write adtcore:responsible="" when the value is empty; the refusal is what keeps that from being sent.
    • An embedder's own cloud connection is no longer recognised by its URL. A connection the factory did not build is cloud only when the server's systemType option or SAP_SYSTEM_TYPE says so; otherwise nothing is asked of the system, and a create without configured values is refused.
    • The browser callback port is 61001 unless --browser-auth-port says otherwise (it was 5000, 4000 and 4001 by transport).
    • x-sap-destination, x-sap-auth-type and x-sap-refresh-token are no longer read; a direct connection is x-sap-url with x-sap-jwt-token, or with x-sap-login and x-sap-password.
    • AUTH_BROKER_PATH is one base directory (the value is not split).
    • DeletePackageLow lost connection_config; a connection comes from the destination alone.
    • @mcp-abap-adt/lib public API: registerAuthBroker, getAuthBroker, ConfigLoader, buildRuntimeConfig and AuthBrokerConfig are gone; AuthBrokerFactory has a new surface (defaultDestination, getBroker, settingsFor, getProvider, settle) and needs a browserStrategy; ConnectionContext carries credential: IAuthProvider; BaseMcpServer.setConnectionContext and the three transport servers take IDestinations. @mcp-abap-adt/core no longer depends on @mcp-abap-adt/auth-broker.
    Added
    • SNC logon over RFC (SAP_AUTH_TYPE=snc, SAP_SNC_PARTNERNAME, optional SAP_SNC_QOP, SAP_SNC_LIB, SAP_SNC_MYNAME): passwordless, through an installed SNC product such as a Secure Login Client. @mcp-abap-adt/sap-rfc-lite stays an optional dependency.
    • Every authentication and connection parameter in three forms, in one table: CLI, YAML and, for seven of them, an environment variable (--browser, --browser-auth-port, --allow-destination-header, --system-type and the rest); precedence CLI, environment, YAML. An invalid port, enum or flag value is refused at startup, naming the parameter. YAML holds configuration only: a key that looks like a secret is refused, naming the key.
    • One connector construction, three credential sources: a destination's provider, the request's headers, or a credential an embedder hands over.
    • requestContextFromHeaders (@mcp-abap-adt/lib/request-context) and the optional IDestinations.systemContextFor: the request scope a request's headers state, and the responsible, login and master system a destination's own .env states. RequestContext gains login (the destination's SAP_USERNAME, or an x-sap-url basic connection's x-sap-login): the responsible when none is stated, on a connection that is not cloud. A resolver's {} means a cloud system that gave nothing; null means not asked.
    • ListTransports filters by the effective responsible alone, no longer by a raw SAP_USERNAME.
    • Shutdown that settles: on SIGTERM, SIGINT or the end of stdin the server stops accepting, waits up to 30 s for logins and refreshes in flight, and flushes every session; a secret that could not be stored, a login or refresh still running at the deadline, a server that did not close, or a failed settle exits 1 with one stderr line per fact (the destination and the error class, never a secret).
    • Destination names are vetted before any file is read (letters, digits, _, ., -), and an env file named with --env / --env-path that does not exist is refused naming the parameter and the path.
    • Errors in the server's own words: Destination "X" lacks: <fields> with a hint, and Destination "X" uses <type> / <grant>, which this server does not support; never a value read from a file. Under HTTP and SSE a refused credential is answered with the provider's own fixed words (status 500) instead of Internal Server Error.
    Changed
    • The docker files documented MCP_DESTINATION, which no code ever read. They now show the real ways: --mcp=<name> in the container command, YAML mcp through --config, or x-mcp-destination with --allow-destination-header; the compose files set AUTH_BROKER_PATH=/app.
    • The documentation describes the four authentications, the parameter table, the .env write-back and the restart rule; the stale npm install -g @mcp-abap-adt/auth-broker and the 5000/4000/4001 port defaults are gone, in the help text as well.
    • The npm tarball carries no working documents: docs/superpowers/ is excluded from files.
    • The tests are type-checked in CI and in the release workflow (#268). npm run test:check ran nowhere, so a test that used a type it did not import ran green while the type check failed; both workflows now run it after the build.
    • MCP_TEST_CONFIG names the integration-test config a run reads (#266), so one checkout carries a config per system; a named file that does not exist fails the run instead of falling back to the template.
    Fixed
    • x-sap-responsible and x-sap-master-system are read. They were written to the connection's metadata and read by nothing; they now enter the request scope over HTTP and SSE, for destination and x-sap-* connection requests alike, and a request with x-sap-* connection headers reads the process configuration too.
    • SAP_LANGUAGE from --env-path reaches the objects it creates (#182). The env file goes to the auth broker's session store, which never fills process.env; the launcher bridged six system-context keys from it and not the language, so every object created through such a session took the library's default language. The bridge now lives in @mcp-abap-adt/lib/config (hydrateSystemContextFromEnvFile, ENV_FILE_CONTEXT_KEYS) and carries SAP_LANGUAGE; a value already in the process environment still wins. The responsible and master system are no longer among its keys: they are read as the file's destination's own (see Breaking).
    • SAP_SYSTEM_TYPE in the --env / --env-path file beats YAML system-type. The argument parser wrote the YAML (or CLI) system type into the process environment before the env file was read, and the file's value, which never replaces one already set, was lost: YAML cloud beside SAP_SYSTEM_TYPE=onprem in the file built the cloud connector. The precedence is now resolved after the file joins the environment, as for the connection type: the CLI, then the environment (the file's value included, never over one set before), then YAML. A word in the environment that is not onprem, cloud or legacy stops the start, naming the key without quoting it. ArgumentsParser.parse() and ServerConfigManager (@mcp-abap-adt/lib/config) no longer write SAP_SYSTEM_TYPE into process.env: an embedder that parses the configuration itself reads systemType / systemTypeSource from the parsed config.
    • The login URL reaches stderr without DEBUG_AUTH_LOG. The browser strategy prints the URL to open (--browser=none / headless, or auto when it could not open one) through the broker's logger, which is silent unless DEBUG_AUTH_LOG=true: the user never saw it and the login timed out. The strategy now gets a logger whose prompts are lines on stderr always; its warnings, errors and debug lines stay behind DEBUG_AUTH_LOG. Nothing goes to stdout.
    • GetInactiveObjects reads the older document and keeps a function module's group (#266). BASIS 7.40 answers adtcore:objectReferences, which the reader did not know: it answered count: 0 over inactive objects. It now reads that document, refuses a root it does not know instead of answering an empty list, and gives each entry its parentName (the group of a function module, from adtcore:parentUri), so the entry can be handed to ActivateObjectLow as it is.
    • The shared test setup activates a function module through its group (#266). The module's own name stood in the group's place in the address, and the whole activation batch answered 500 invalidFunctionGroup.
    • Enhancement types ADT does not expose are named as such (#267). GetEnhancementSpot on a plain enhancement spot, and GetEnhancementImpl on a class enhancement, answer that the object is not available through ADT (Eclipse opens it in SAP GUI) instead of passing a raw 500 on; a BAdI implementation is answered as having no source, and a source code plugin asked under a spot name gets the hint to read it with enhancement_spot "enhoxhh". A plugin read under enhoxhh that fails keeps its own failure. What worked keeps one request and its answer.
  15. v15.1.0

    Features 4, Changed 4

    Notes
    Added
    • Compact reads: search, where-used, data and a package's contents (#259). Compact serves a host without a pipeline of its own, and these are ordinary investigation steps it could not take at all: the command launches without the search group, and nothing routed where-used or data.

      • HandlerSearch — objects by name or mask, optionally by type, capped.
      • HandlerWhereUsed — the objects that use one object, capped with the total kept (on premise, a standard class answered 107,141 references); a function module is addressed under its group.
      • HandlerGetData — rows of a table or CDS view, or what an ABAP SQL SELECT names with its columns, conditions and order.
      • HandlerGet part: contents for a PACKAGE — its members, one line each: name, type, description, capped by max_results (default 100) with the total said (a package of 257 members on premise: 11,260 characters, against 45,705 as the core reader's JSON; the trial's top-level local package lists 11,619 entries and answers 100 of them in 3,491 characters).

      All four read, so they live in compact-readonly and serve --exposition=ro as well: compact is 25 tools, 16 of them read-only.

    • Dumps filtered by what they are about (#261). RuntimeListFeeds with feed_type: dumps and compact HandlerDumpList take runtime_error, exception, object_name, package and component (each contains) beside user (equals), from and to — sent as one $query on the attributes the dumps feed's descriptor declares. contains ignores case: on the BTP ABAP environment (2026-10-01) three spellings of one runtime error matched the same dumps, 66 of 200, and an unknown one none. A value with a blank, a comma or a parenthesis is refused before the request, and a dumps filter on another feed is refused rather than dropped. Needs @mcp-abap-adt/adt-clients 24.1.0.

    • Every dump entry carries its dump_id, the id RuntimeGetDumpById takes; that tool accepts the entry's URI as well.

    • GetUnitTestResult in the readonly group too. Fetching an ABAP Unit run's result by its run_id only reads, so a read-only caller may do it. It is a copy of the high tool — the same name, schema and handler — and stays in high.

    Changed
    • A feed list follows SAP's pages (#261). SAP answers at most 100 entries per feed request whatever $top asks (the descriptor declares paging max="100"; $skip is ignored) and offers the next page as a rel="next" link bounded by to. max_results (compact: top) now reads page after page up to the count asked, at most 1000, and answers next_to when more remain — pass it back as to to read on. Without a count it reads 50, the feed's own page size, the same way. to is inclusive: a page starts with the entries of the previous page's last second again (four on one boundary on the BTP ABAP environment, 2026-10-01), so an entry is answered once, and a page asks for its repeats as well as the entries still wanted. Nothing is remembered between calls, so an answer never ends inside a second: it holds entries newer than next_to, and reading on from it starts with that second whole — nothing repeats, nothing is skipped. An answer may therefore hold a few entries fewer than asked. A second holding more entries than asked is answered whole instead, more than asked, and reading on starts from the second before it; SAP pages by time alone, so a second holding more than one request's 100 is answered as far as SAP gives it and named in incomplete_second.
    • A dump's summary is the dump's own root (#261). RuntimeGetDumpById's summary is read from the default view's dump:dump attributes — runtime error, exception, title, terminated program, time, user — and the source position where it terminated, from the …/runtime/dump/termination link, whose adt://<system> prefix is dropped so the URI is the ADT path. It picked keys by name out of the whole document before, and answered chapter titles. Compact HandlerDumpView view: summary answers that summary alone; ADT's own summary view answered a document without the root.
    • GetPackage and ReadPackage answer a summary of the package — name, description, type, super-package, software and application component, transport layer, ABAP language version, responsible, and the number of sub-packages; with detail: full every sub-package by name and description as well; and the metadata document itself with detail: raw. The document carries every sub-package: a top-level package answered about 950,000 characters on the cloud trial (5,815 sub-packages, no member object), which an MCP client refused, and ReadPackage sent it twice. The same package now answers 327 characters (sub_package_count: 5813); on premise a structure package with 1,222 sub-packages answers 351.
    • A tool present in two groups is exposed once, from high. When a readonly tool shares its exact name with a tool of an exposed high/low group, ReadOnlyHandlersGroup withholds its own copy, whatever dedup strategy is passed — one server cannot register two tools under one name. readonly alone → from readonly; readonly + high → once, from high. HandlerExporter now hands the read-only group the high/low names as well (with NoDedupStrategy, so it still lists Read<X> beside Get<X>), and tools/generate-tools-docs.js documents such a copy under Read-Only.
  16. v15.0.0dated in the atlas

    Breaking 3, Changed 1, Other 8

    Notes
    Changed — BREAKING
    • ABAP Unit tools are one simple operation per test carrier. A tool does one thing to one kind of object, with the fewest parameters that say which; a general tool with an object type and a dozen optional parameters is harder for a model to call right and for a tool search to find. On adt-clients 24, which runs tests per object type.

      carrierwrite the testsrun them
      classCreateUnitTest, UpdateUnitTest, DeleteUnitTest (class_name, test_class_source)RunUnitTest (class_name)
      CDS viewCreateCdsUnitTest (view check, test class, tests — one call), UpdateCdsUnitTest, DeleteCdsUnitTestRunCdsUnitTest (class_name)
      reportCreateProgramUnitTest, UpdateProgramUnitTest (program_name, test_class_source)RunProgramUnitTest (program_name)
      function groupCreateFunctionGroupUnitTest, UpdateFunctionGroupUnitTest (function_group_name, test_class_source)RunFunctionGroupUnitTest, RunFunctionModuleUnitTest

      Every Run* starts the run, waits for it within a bound and answers the result; a run that outlasts the bound answers its run_id, and GetUnitTestResult fetches the result later. Every write locks, writes, unlocks and activates. A report's tests go into a test include the tool creates in the report's package and pulls into the report; a function group's into a test include whose INCLUDE ADT adds to the group itself. Measured on premise (2026-09-30) through all four integration suites, soft and hard mode.

    Removed — BREAKING
    • GetUnitTest, GetUnitTestStatus, GetCdsUnitTest, GetCdsUnitTestStatus, GetCdsUnitTestResult. A Run* tool answers the result itself; GetUnitTestResult answers about any run by its id, whatever started it. The functions stay exported from @mcp-abap-adt/lib for the compact facade, which keeps its granular status and result tools.
    • The low-tier unit-test tools — RunClassUnitTestsLow, GetClassUnitTestStatusLow, GetClassUnitTestResultLow, LockClassTestClassesLow, UnlockClassTestClassesLow, UpdateClassTestClassesLow, ActivateClassTestClassesLow. The lock pair addressed members adt-clients 24 no longer has.
    Migration
    • CreateUnitTest used to start a run (tests[]); it now writes a class's tests. To run them call RunUnitTest with class_name.
    • RunUnitTest takes class_name instead of tests[] and answers the result, not a run id to poll.
    • UpdateUnitTest and DeleteUnitTest took a run_id and always failed; they now take class_name (and test_class_source for the update).
    • CreateCdsUnitTest now also writes the tests (test_class_source) and activates the class; description is gone.
    • Instead of GetUnitTest/GetUnitTestStatus/GetCdsUnitTest*, read the Run* answer; for a run that had not finished, GetUnitTestResult with its run_id.
    • GetUnitTestResult takes run_id alone: with_navigation_uris and format left the tool (the compact result facade still takes them).
    • Compact: HandlerCreate/HandlerUpdate for UNIT_TEST take class_name and test_class_source, and HandlerDelete takes class_name (they took tests[] and run_id); HandlerUnitTestRun and the status and result facades are unchanged.
    • Integration config: the unit-test cases changed and two were added (program_unit_test, function_group_unit_test) — copy them from tests/test-config.yaml.template.
    Changed
    • Eleven tool descriptions say what the tool does, and nothing else — no denials, no other tools' names, no protocol notes. A description is read by a model and indexed by a tool search, and every word it carries pulls the tool toward queries with that word. GetWhereUsed, ReadFunctionModule, GetFunctionModule, RuntimeListFeeds, CreateServiceBinding, CreateServiceDefinition, GetLocalTestClass, UpdateLocalTestClass, DeleteLocalTestClass, AddTransportObject, RemoveTransportObject; and the transport_number, object_name and position parameters of the two transport tools. The …LocalTestClass tools describe the include they read and write; "the unit tests of a class" belongs to the ABAP Unit test tools. No name, schema or behaviour changed.
  17. v14.1.2

    Fixes 2

    Notes
    Fixed
    • Publishing an OData V2 service binding could not succeed. UpdateServiceBinding took service_name and service_version — service_name is required on the tool surface — and dropped both, because the library had nowhere to put them. A V2 publication job resolves the service from them, so the job answered 200 with SEVERITY ERROR naming an EMPTY service and version 0000: "Activating Local Service Endpoint of service ␠ with version 0000 failed". The binding was active and srvb:allowedAction named the very action asked for.

      @mcp-abap-adt/adt-clients ^23.0.5 (was ^23.0.4) takes them per protocol, so this handler now passes them for odatav2 and not for odatav4, where the request names its target on its own. Measured there on all four binding variants: both V2 ones refuse without the fields and succeed with them, both V4 ones succeed without them — so the axis is the protocol, not the UI/Web API category. See that package's ERRATA, "A V2 publication job resolves the service by name and version".

      The two parameter descriptions said the job "no longer carries" these fields. It carries them again for V2, and they now say which protocol uses them and that V4 ignores them.

      Found while removing the probe objects from #248: two V4 bindings unpublished through this tool and the V2 one did not, which made the comparison single-variable.

    • A binding anybody had open could not be published or unpublished at all. UpdateServiceBinding passed analyseLock to its LOCK, so the 403 that means an editing session holds this binding was a refusal: "User … is currently editing ZMCP_E2E_V2". adt-clients' ERRATA is explicit that this one 403 is to be ignored — the publication job needs no lock of the caller's, and Eclipse posts it after its own LOCK's 403 — and names the strategy for it, analysePublicationLock, which answers a lock without a handle instead.

      But analysePublicationLock alone would have opened a second hole, found in review: it forgives by STATUS — 403 — and is built on analyseException, which sees only documents. A 200 naming no lock handle is therefore no failure to it either, and the caller is handed the same empty handle as for the 403. A login page on an expired session is a 200, so "publish when there is no handle" would publish after a lock that never happened — exactly the check analyseLock had been doing.

      So the handler passes its own composed strategy, analysePublicationLockAnswer: analyseLock first, so a 2xx without a handle stays a refusal, and the 403 forgiveness after it. An empty handle then means one thing only, which is what lets the handler publish without a handle and send no UNLOCK — there is nothing to release. withLock is untouched.

      Measured the hard way. A publication whose client gave up at 120 s — the job takes ~133 s — left the lock behind, and the unpublish that followed was refused. With the two changes the same call went through, and the wire shows both fixes at once:

      POST …/bindings/zmcp_e2e_v2?_action=LOCK&accessMode=MODIFY  -> threw 403
      POST …/businessservices/odatav2/unpublishjobs
      params {"servicename":"ZMCP_E2E_V2","serviceversion":"0001"}
      
  18. v14.1.1

    Fixes 1

    Notes
    Fixed
    • The documented way to generate a .env from a service key did not run. Four places told a reader to install @mcp-abap-adt/connection and then invoke mcp-auth auth -k <service-key> — a command from a different package, with flags it does not have. Measured: mcp-auth auth -k … answers Unknown command: auth. Two more places, the runtime help in utils.ts and the launcher's, named sap-abap-auth instead, which is coherent — @mcp-abap-adt/connection really does ship that bin, and it really does still work — but it is the superseded path: token handling moved to @mcp-abap-adt/auth-broker, which is a direct dependency here and ships mcp-auth and mcp-sso.

      All six now say the same measured thing:

      npm install -g @mcp-abap-adt/auth-broker
      mcp-auth --service-key path/to/service-key.json --output .env
      

      Worth naming because half of this was in the RUNTIME help — what a user reads when the connection failed, which is the worst moment to hand them a command that does not exist. Reported from the README; both halves were wrong, in different ways, and neither would have been found by fixing only the name.

  19. v14.1.0

    Features 2, Fixes 2

    Notes
    Added
    • GetServiceBindingPreviewUrl — the browser URL for a published service binding, which no document carries. ADT's Preview button does not ask the server for a URL: it builds one, and the path segment after feap/ is not a token but a ##-joined descriptor of the service, shifted by 20 (0x14) and percent-encoded as UTF-8 — which is why bytes above 0x7F arrive as %C2%XX. The rule was derived from a URL Eclipse produced for an on-premise V2 binding and verified by re-encoding it byte for byte; that pair is the fixture in feapDescriptor.test.ts, and the handler test asserts the tool reproduces that exact URL.

      What it reads, and why each one is needed, all of it measured rather than assumed:

      descriptor partwhere it lives
      servicesrvb:services/@srvb:name — not the service definition: a trial binding answered services="ZUI_TRAVEL" with definition ZTRAVEL_SD
      versionsrvb:content/@srvb:version (minorVersion/patchVersion belong to neither URL)
      protocolsrvb:binding/@srvb:version: V2 → odatav2, V4 → odatav4
      entity set, targetexpose … as <alias> in the service definition — the ALIAS is the entity set
      navigationan association or composition of the exposed root view
      annotation service<service>_VAN, the generated Gateway Vocabulary Annotation (object type IWVB)

      Of the segments, the server reads two — and that was measured, not assumed. The page at …/feap/<segment>/flp.html is static; the resource that decodes the descriptor is <segment>/manifest.json, reachable with the ordinary ADT token, which is how every claim below was settled without a browser. Asking one descriptor after another on a trial (OData V2): the service becomes the manifest's service uri; the entity set becomes ListReport|<entitySet>, and a bogus one answers a list report with no page under it. The navigation, the target entity set and the _VAN segment are not read — empty, bogus and plain wrong values all answered the same nested page, because the server derives the navigation from the service's metadata, and it normalises the CDS name while doing so (_children came back as to_children). So only the entity set is required; the other segments are still filled, because that is the shape Eclipse produces.

      It still refuses to invent what it cannot read. Without an entity set there is no preview_url — the answer carries missing, the entity sets to choose from, the service and $metadata URLs, and, when the binding is unpublished, a note saying nothing answers until it is. A URL with one guessed segment is indistinguishable from a working one until it opens nothing.

      OData V4 is a different descriptor, and the first version of this shipped the V2 one for both. Seven parts, the service's URL PATH first and the BINDING last:

      /sap/opu/odata4/sap/<binding>/srvd/sap/<service>/<version>/##Root##_children##Child##<service>##<version>##<binding>
      

      Decoded from two URLs Eclipse produced for one binding — its root entity set and its child — and both are byte-exact fixtures. Part 5 is the service (a wrong one answers 200 with the nested pages gone), part 7 the binding (the service there answers 401, the authorisation check reads it); the navigation is not read here either. A V4 binding has no _VAN object at all: publication creates a service group (SCO2/SIA6), no IWVB.

      On BTP the preview opens on the browser host. <id>.abap.<region>.hana.ondemand.com serves ADT and answers a browser 401 WWW-Authenticate: Basic — unanswerable, since the credential is a propagated token and the bearer header a pasted URL cannot carry; <id>.abap-web.<region>.hana.ondemand.com serves the BTP logon and then the page. So preview_url carries the abap-web host and the service URLs keep the ADT one. On premise there is no such split and nothing changes.

      client and language are not the caller's to supply. They come from /sap/bc/adt/core/http/systeminformation, which this server already reads for a request's system context, so the URL ends …&sap-ui-language=EN&sap-client=100 by itself. The parameters remain as overrides and an empty client suppresses it. The lookup cannot fail the tool: that endpoint answers null where absent but THROWS otherwise, so a failure is reported in note beside a URL that is still answered.

      The service URL is read from ADT when ADT names one. …/businessservices/<protocol>/<BINDING>?servicename=…&serviceversion=… is what the Service Binding editor reads for its own "Service URL" field; when it answers, that answer wins and service_url_source says system, otherwise the composed URL stays and says composed. Measured on a trial it answers empty even for a service whose $metadata answers 200, so it only adds information.

      A Web API binding has no preview at all — srvb:binding/@srvb:category is 0 for UI and 1 for Web API — and says so, with the service and $metadata URLs, instead of carrying a FEAP URL that opens nothing.

      The exposed root view is read only when a navigation is wanted and none was given, so the usual cost is two requests. A projection spells its navigation a third way — _children : redirected to composition child X — which the first version did not read, and answered associations: [] for a view that has one.

    • part on the compact HandlerGet: source, metadata or urls. Compact is one tool per OPERATION, so "which aspect of this object" is an argument of the read rather than a twenty-third tool — and it is the same parameter the object-oriented tiers still lack, where eight Get* tools fetch a metadata document and discard it while the Read* that would answer it is hidden by the Read-vs-Get dedup.

      metadata routes to the readers that actually return the document (sixteen object types have one); urls routes to GetServiceBindingPreviewUrl and therefore exists for a service binding alone, which is the honest size of it.

      A part the type does not offer is refused by name, never substituted. Asking for urls on a class answers part=urls is not available for object_type=CLASS. It offers: source, metadata. — the refusal carries what to ask instead, and answering the source would be a success meaning something other than the question. The refusal is returned the way the router returns an unsupported operation, so both paths through the surface have one shape. A missing object_type stays the router's own refusal rather than gaining a second voice.

      @mcp-abap-adt/lib/handlers/read grew from 35 to 54 exports for this: the metadata readers and the preview tool. The read-only half still reaches no write route — compactCapabilitySplit checks that against the import graph, not the intention.

    Fixed
    • Hard mode could not start the server at all. The integration harness spawned it with --exposition=readonly,high,low, and high and low are mutually exclusive — correctly so — which validateExposition refuses, so the launcher exited before serving anything: Invalid exposition: 'high' and 'low' are mutually exclusive. Hard mode is the only mode that proves the server starts and registers its tools, so it was the one check that could not run. It went unnoticed because it is opt-in: soft mode, the default for mass regression, calls handlers in-process and never spawns MCP. The exposition is now a config key, integration_hard_mode.exposition, defaulting to readonly,high; one run covers one tier and readonly,low serves the other. Asking for both was never a test of anything.

    • The --exposition help advertised a combination the launcher refuses. The examples listed readonly,high,low (all handlers), so a reader who followed the documentation got a startup error. They now list readonly,low and say in one line that no value serves every handler at once.

  20. v14.0.1

    Fixes 1

    Notes
    Fixed
    • The help text described a surface neither command has, and the compact command had no exposition of its own. 14.0.0 moved the facade out and taught validateExposition to refuse compact, but --help still listed it among the sets — --exposition=compact (compact facade only) — and mcp-abap-adt-compact printed that same help, offering readonly/high/low, which it does not serve, under a USAGE line naming the other command.

      • mcp-abap-adt --help lists readonly, high, low, and says in its place that the compact facade is its own command with its own default.

      • mcp-abap-adt-compact --help names itself in USAGE and documents its sets.

      • mcp-abap-adt-compact --exposition=ro|rw: rw is the default — all 22 tools, every access, which is what a local server gives — and ro serves the 13 that change nothing. ro is not a filter over one list: no create, update, delete, activate, lock, unlock, unit-test run or profiler run is IN the list, so a client cannot call one (verified from installed tarballs: 22 tools with 9 writes against 13 with 0). A set of the object-oriented surface is refused by name, pointing at mcp-abap-adt.

      • ServerConfigManager.generateHelp takes program and expositionSection, so a sibling command documents its own vocabulary instead of inheriting one that does not apply.

      • An empty value is refused, not defaulted. The first version treated "no flag" and "flag with nothing in it" alike, so --exposition="$MODE" with an unset variable opened all 22 tools — writes included — and --exposition=ro --exposition= overrode a deliberate ro the same way (found in review). The default belongs to an ABSENT flag; a flag that is present but says nothing is a caller who meant something and lost it in a shell, so it fails at startup. --exposition with a following flag rather than a value counts as empty too.

      Two mistakes of mine while writing this, both caught before release: the first version REFUSED --exposition on the compact command instead of giving it ro/rw (the user's correction), and the ro branch first wrapped the entries in an object literal, which closes over the startup context — the P1 from PR #240 review, in a new place. It builds a real CompactReadOnlyHandlersGroup now, so the per-request context reaches the handlers.

  21. v14.0.0

    Features 4, Changed 3

    Notes

    Migration: docs/MIGRATION-14.0.md.

    Changed
    • The compact facade left lib for three packages of its own. It exists for a host that cannot build a retrieval pipeline — a small context, no tool-RAG, no way to select tools per request — and must still drive ABAP through MCP. Such a host needs a tool list short by construction, and a package whose default IS that list cannot be misconfigured into something else.

      packagecarriesbinlicence
      @mcp-abap-adt/libhandlers, registries, embeddable — minus the compact toolsnoApache-2.0
      @mcp-abap-adt/compact-readonlythe 13 compact tools that change nothingnoApache-2.0
      @mcp-abap-adt/compact-modifythe 9 that write, lock, activate or executenoApache-2.0
      @mcp-abap-adt/coretransports, launcher, mcp-abap-adtyesAGPL-3.0-only
      @mcp-abap-adt/compactthe compact server, mcp-abap-adt-compactyesAGPL-3.0-only

      Versions move in lockstep: the compact packages are one decomposition of the same lib, and independent numbers would create combinations nobody has run.

    • core no longer serves --exposition=compact. The value is recognised and refused, naming the package that serves it — a configuration asking for a tool list it will not get should fail at startup rather than start with tools missing. This breaks nobody in practice: the consumer that drives this repo indexes all the tools itself and never wanted the facade, and whoever does want it installs the command.

    • One launcher, two commands. main() in @mcp-abap-adt/core/launcher is exported and takes extraGroups, exposition and includeSearch, so mcp-abap-adt-compact is a twenty-line bin over the same configuration, transports and auth rather than a second copy of four hundred lines. It self-starts only when it is the program, so an import does not start a server, and each bin calls main() itself. --version on the compact command answers the compact package's version, not core's.

    Added
    • @mcp-abap-adt/lib/handlers/read and /write — the 35 and 69 handler functions the facade routes to, in halves, because one barrel of all 104 would relink every write route into compact-readonly and defeat its own split. @mcp-abap-adt/lib/compact-shared exports the facade's kernel: object types, the CRUD matrix, the schemas, the lifecycle helpers and dispatchCompact — data and dispatch, no handler, which is what makes it shareable by both halves.

    • The capability split is asserted against the module graph, across packages. compactCapabilitySplit.test.ts walks the imports from each package's entry — relative files AND bare specifiers — and holds that compact-readonly never names @mcp-abap-adt/lib/handlers/write or the modifying package, that its manifest declares only lib as a dependency, that the modifying half DOES reach the write routes (so a split that lost them fails too), and that each of the 22 tool files belongs to exactly one half.

    • The compact surface has its own frozen fixture and its own generated page. lib's surface.json lost 22 rows; they moved to compact/tests/fixtures/, where compactSurface.test.ts freezes them from the packages' entry builders — a ratchet that loses its subject silently is worse than no ratchet. compact/docs/AVAILABLE_TOOLS.md is generated from the BUILT packages (npm run docs:tools in compact/), refusing a stale build, and lib's generator no longer writes a compact page.

    • binSmoke covers the third bin. It packs all five packages, installs the tarballs into an empty directory and runs both commands: mcp-abap-adt --version answers core's version and mcp-abap-adt-compact --version answers compact's.

  22. v13.1.0

    Features 1, Changed 1, Fixes 2

    Notes
    Changed
    • No parameter description names a transport request, and nothing in the tree names the system it was measured on. The rule is the user's and it is about agnosticism, not retrieval: we do not know who will use this, where, or how, so we state only what we know. A tool's text says what the tool does and what its parameters mean; a request number from somebody's landscape is not part of that.

      • 78 transport-number literals removed from parameter descriptions, across 78 files — one real request 73 times, four more once or twice each. Transport request number (e.g., …). Required for transportable objects. now reads Transport request number. Required for transportable objects., and the three transport tools whose text turns on the request/task distinction keep the distinction and lose the example. The worst case was not retrieval: an example request number is a value a model can copy into a write, and it addressed a real transport.
      • The system id is gone from the whole tree — descriptions, code comments, test comments, test data, captured fixtures, the installation guides, the test template and this changelog's own history. What a measurement note knows is the platform and the date, so on E19 (2026-09-26) reads on premise (2026-09-26), and Measured on E19 (BASIS 816) and E98 (BASIS 756) reads Measured on BASIS 816 and on BASIS 756 — the release is the fact that mattered. --env=…/e19.env reads --env=…/your-system.env; a SAP_MASTER_SYSTEM example reads the three-character SID; captured fixtures carry SID, beside the SAPUSER01 they already carried. Test data keeps its numbers under a placeholder prefix (SIDK9…), so every assertion still pairs with its input.
      • One of these was a live defect rather than a wording problem: GetNodeStructureLow's node_id description explained "0000" by naming the system that answered an empty body for it.
      • And then every other name went with them: 255 of 370 tools, 350 parameter descriptions, 169 distinct strings. Class name (e.g., ZCL_MY_CLASS). reads Class name.; Package name (e.g., ZOK_LOCAL, $TMP for local objects). reads Package name.; Optional for local objects ($TMP). reads Optional for local objects. — ABAP Cloud has no $TMP anyway. Constraints that change the call stay word for word ("must already exist", "required for validation", "start with Z or Y", the REQUEST/TASK distinction). What goes is only the example. A couple needed a sentence rather than a deletion: the software component no longer names two components, and the search pattern explains "*" as a prefix match instead of listing masks.
      • Naming rules left with the names, and so did the CTS lesson. A tool works with any object the caller is authorised for, whatever it is called, so "must follow SAP naming conventions", "start with Z or Y", "for the customer namespace", a behaviour pool's conventional name and the worked ABAP snippet in the function-module source parameter are all gone (28 descriptions). The consumer's skill carries the naming rules and answers for the validity of what it sends. The same cut trimmed 104 transport_request descriptions: request is a word a description may use, a number never is, so the text keeps not a task — the one fact that decides whether this call succeeds — and drops how CTS works, which tool to call after it, and CTS_WBO_API 020 / SADT_RESOURCE 017. What stays is a limit that says what fits in the parameter ("up to 26 characters", the field-label lengths) and the transport tools' own text, where request against task IS the subject of the tool.
    Fixed
    • The tool docs now show the text the model receives, not the text a regex could parse out of the source. Three reviewer findings on PR #244 were one defect in three costumes: a Zod .description is a prototype getter and was invisible; a description built as '…' + '…' was cut at the first literal; a template literal printed ${commonObjectTypeSchema.description} verbatim, because no regex evaluates an interpolation. generate-tools-docs.js keeps the source parse for structure — which tools exist, their files, tiers and schema shape — and takes every description from the BUILT definitions through HandlerExporter, the same strings a consumer's RAG indexes. Beside the interpolation, this filled in five compact parameters that were documented as empty because their schema is a shared constant the regex could not follow.

      And reading dist is only correct while dist is current, so that is checked rather than assumed. A reviewer edited DeleteClass's description, ran docs:tools and got success with the previous text in the documents, because the script did not build. docs:tools now builds first, and the generator refuses to run when dist is missing or older than any source file, saying how far behind it is and what to do. The check throws rather than exiting: a unit test requires this module, and a generator that kills the process from inside a test run is its own defect. The "fall back to the parsed text" path is gone — it was the mechanism that let wrong text look like success.

    • The generator dropped everything after the first string literal of a concatenated description. It reads source with regexes rather than importing the built definitions, and the pattern stopped at one literal — so five tools lost a whole sentence each from the generated docs: the legacy refusal notes on GetCdsUnitTest, GetCdsUnitTestStatus, CreateCdsUnitTest and GetStructuresList, and the polling bound on GetCdsUnitTestResult. What made it visible was cosmetic — git diff --check flagged two trailing spaces, which were the seam where the text had been cut (found by a reviewer on PR #244). The generator reads the whole + chain now, in the tool description and in every parameter description.

    Added
    • The description ratchet covers parameter descriptions too, in every class. toolDescriptionsCarryNoLiterals.test.ts checks every description anywhere in an input schema, not only the tool's own text: three classes (a package, a customer-namespace object, a transport number) over both, six assertions. The transport class carries two shapes — <SID>K9… and a plain letters-and-digits number like ER121235, named by the user — and a message number, a release and a short SAP code (CTS_WBO_API 020, BASIS 816, TK127) sit in the self-check as text that must stay allowed. Two more refinements came out of running it:

      • A format placeholder is not a name. YYYYMMDDHHMMSS matched the customer-object pattern on its leading Y; a token built only from the letters of a date is allowed by rule, and the self-check states it.
      • The generated prefixes count. A function group's includes are L<group>… and its main program SAPL<group>, so the customer name starts one character in. The first pattern missed LZOK_FG_MCP01F01 in five descriptions and the widened one found them — the ratchet caught what the sweep did not.

      And it read one shape of schema only, which a reviewer found. Eight tools declare a flat map of Zod fields rather than JSON Schema — no properties wrapper, and in Zod 4 .description is a getter on the prototype, so Object.entries never saw it. CreatePackage and seven others were not checked at all: the literal was put back into CreatePackage's transport_request and every assertion stayed green. The walker now reads description by access instead of by enumeration and recurses through Zod's wrappers, and the coverage guard asserts PER TOOL — the only tools with no parameter text are the two that take no parameters (TAKES_NO_PARAMETERS), because a total of ">500" hid a whole shape reading as zero. Two things the fix immediately caught: SearchSource's packages named a package and a prefix mask in its examples, and ListServiceBindingTypes' response_format was a bare enum with no description at all.

  23. v13.0.2

    Features 1, Changed 1, Fixes 1

    Notes
    Fixed
    • A tool description is search text, and an incidental literal in it retrieves the wrong tools in bulk (#241). "Transport request optional for $TMP objects" sat in 30 Delete* descriptions, so any request mentioning $TMP pulled the delete tools into the top of a consumer's tool-RAG. Measured in cloud-llm-hub's retrieval — two collections, 0.7/0.3 fusion, translation, k=15, developer role:

      queryDelete* in the top 15, beforeafter
      "Які пакети в $TMP маємо?"121
      "Create class … in package $TMP"3, CreateClass third1, CreateClass second
      create CDS / program / update in $TMP3–41–2

      The wording without the literal was never worse on any query.

      $TMP objects reads local objects now, which is also the more accurate sentence: ABAP Cloud has no $TMP at all. The other incidental literals went with it — a sample mask in SearchObject, a message class in ReadMessageClassMessage, a namespace example in SearchSource. Parameter descriptions are untouched here: the calling model reads them, retrieval does not index them, and the literals in them are a larger replacement of their own (254 of 370 tools, 464 occurrences — the follow-up).

    Added
    • toolDescriptionsCarryNoLiterals.test.ts — the rule is kept by a test. No tool description names anything concrete: a package ($TMP, $ANY), a customer-namespace object or prefix (ZCL_DEMO, ZOK*, YFOO_BAR), or a transport number. A bare mask names nothing and stays allowed — Z*, Y*, /NS/Z*, any-namespace — and the test's own self-check pins both lists, so neither a new literal nor a regex tightened against a legitimate mask passes silently.

      Ranking is not what it measures: that is measured by loading the descriptions into a RAG and querying it, which is where the table above comes from. The test keeps the rule in the repository where descriptions are authored and where no RAG runs — without it the next $TMP returns and nothing here notices until a consumer's retrieval degrades. Green on arrival: 370 descriptions, none naming anything concrete.

    Changed
    • docs/user-guide/AVAILABLE_TOOLS*.md regenerated, which also picks up the CheckPackage wording 13.0.1 changed and did not regenerate.
  24. v13.0.1

    Features 1, Fixes 1

    Notes
    Fixed
    • mcp-abap-adt --version died on every installed copy. The launcher read path.join(__dirname, '..', '..', 'package.json'): from server/dist/ that reaches the repo root in a checkout — whose manifest is @mcp-abap-adt/lib, so it answered the LIBRARY's version — and from an installed package it reaches node_modules/@mcp-abap-adt/, the scope directory, which never has a manifest. So the CLI the MCP registry points at answered [MCP] launcher failed: ENOENT … node_modules/@mcp-abap-adt/package.json for anyone who installed 13.0.0. One level up is server/ in a checkout and the package root when installed, and in both it is @mcp-abap-adt/core's own manifest, which is what this CLI should state.

      Found by installing the published 13.0.0 tarball into an empty directory and running each bin — which is how @mcp-abap-adt/auth-broker 3.0.4 had found the same class of defect one release earlier, and 13.0.0 took that fix without performing that check. release:dry reporting Published: 2 Skipped: 0 does not cover it: packing proves a tarball builds, not that anything inside it runs. The three other bins in the installed tree were healthy — mcp-auth and mcp-sso 3.0.4, sap-abap-auth 9.4.2.

    Added
    • binSmoke.test.ts — a bin that does not start is now a failing test. It packs both packages, installs the two tarballs into a temporary directory and runs mcp-abap-adt --version from there, asserting it answers core's own version. Verified in both directions: it fails with the old path (the same ENOENT) and passes with the fix. SKIP_BIN_SMOKE=true skips it where packing is not available, and says so.
  25. v13.0.0

    Removed 2, Changed 7, Fixes 12, Other 4

    Notes

    Migration: docs/MIGRATION-13.0.md.

    No MCP tool changed — not a name, not a parameter, not an answer shape; the generated tool docs moved only their date, which is the check for that. What changed is what a refusal does: a number of them now arrive as refusals where they arrived as success.

    Changed
    • @mcp-abap-adt/adt-clients 22 → 23.0.4, and the release interprets nothing. Every member takes an options.analyse; no member applies a verdict of its own; the readings four of them had built in now ship as named strategies in @mcp-abap-adt/adt-strategies. 79 members grew the parameter in the same release.

      Here that took the call sites accepting an analyse from 275 to 513 in src/handlers alone, and every one of them carries a strategy now. handlerInvariants.test.ts resolves them from the type checker, so a call added without one fails the build; its exception list is gone, because all four entries were members whose tailored default 23 removed — the reason to pass nothing inverted into a reason to pass exactly analysePublication, analysePublicationLock, analyseMessageClassMessage(msgno) and analyseUnitTestStart.

      A call with no strategy has no verdict at all: whatever HTTP accepted is a success, including the refusals ADT embeds in a 200. That is the class of defect this repository has been closing since 8.10.0, and 23 moved the responsibility for it from the library to us.

    • Every reading 22 applied by default is injected now — ATC, the profiler, the feeds, the executors, transports, object versions, run ids, and the read-edit-write of a message class. Most of those defaults are typed unknown, so the compiler flagged none of them: a result set left to its default answers the document, and a projection that reads .length or walks an array runs over a string without complaint.

    • analyseLock refuses a 2xx that names no lock handle and returns SAP's answer as raw_body, on every lock including the LockX tools.

    • A high-level delete runs checkDeletion first and sends no delete for an object the check reports as absent.

    • The rest of the contract: interfaces-adt 9 → 11, the new interfaces-adt-connection 1.0.0 (IAbapConnection and IAdtWireResponse moved there), interfaces-network 2.0.0 as a direct dependency, interfaces-auth 1 → 2, auth-broker 2 → 3.0.4 (one options object, the token provider inside it as provider, and it requires refreshTokens), auth-providers 2 → 4.2.1, auth-stores 1.2.4, connection 9.2.1 → 9.4.2, adt-strategies 0.6.0, logger 0.4.1.

      The last two of those are packaging fixes in the family's own CLIs rather than anything this project calls differently: auth-broker 3.0.4 made @mcp-abap-adt/logger a runtime dependency, because mcp-sso imported it while it was only a dev dependency and died on Cannot find module from an installed tarball; logger 0.4.1 stopped printing PinoLogger initialization error at import, building its PinoLogger on first call instead. Both are worth taking here so this project's tree holds one copy of each at the fixed version.

    • sap-rfc-lite ^0.1.0 → ^0.2.1 is load-bearing on RFC. It is the range connection itself declares, and adt-clients' ERRATA.md says why: a package can be saved only once per ABAP session (PAK/058 — the message says locked, but it is CL_PACKAGE's in-memory instance buffer, not an enqueue), and the connector is what keeps a non-stateful request out of the stateful context — over HTTP from 9.3.1, over RFC from 9.3.2, and from 9.3.4 on a reused conversation whose server context is reset after each call.

      0.2.1, not 0.2.0, and the difference is invisible at run time. 0.2.0 was packed from a stale lib/, so its JS client has no resetServerContext: the connector found no reset and fell back to a new RFC connection per stateless call — correct, and about 1010 s for a full RFC run instead of about 684 s. connection 9.4.2 raises its own minimum for exactly that, which also makes npm replace a 0.2.0 already in a lockfile. Declaring ^0.1.0 at the root, as this project did, put 0.1.x there for an RFC consumer to load; the root range has to move with the connector's.

    • Third-party, in range: @modelcontextprotocol/sdk 1.30.1, axios 1.20.0, express 5.2.1, fast-xml-parser 5.11.1, pino 10.3.1, zod 4.6.5, dotenv 18.0.4, biome 2.5.14, jest 30.5.2, ts-jest 29.4.14, tsx 4.23.15.

      Not taken, and not an oversight: typescript 7 and @types/node 26 (on versions we follow SAP — ts-jest 29.4.14 still declares peerDependencies.typescript: ">=4.3 <7"), and lint-staged 17, which wants node >=22.22.1, above the floor this project declares. Node 26 is supported by auth-broker 3.0.2 and auth-providers 4.2.1 as an install fix — under Node 26 npm skips a release whose engines excludes it and silently takes an older major — and this project's open >=22.0.0 was never subject to that. It is still not a deployment target: the CF buildpack lists 20, 22 and 24.

    Fixed
    • GetInactiveObjects dropped every entry it was meant to list.

    • GetObjectVersions answered {"ok":true} instead of the versions.

    • --browser and --browser-auth-port had stopped reaching the login — ignored since auth-providers 2.0. AuthorizationCodeProviderConfig replaced both fields with an authorization strategy, and TypeScript runs no excess-property check on a const passed to a constructor, so every OAuth callback landed on the package default (61001) rather than the port the IdP has registered.

    • An untyped deletion message is not an error, and every del:message of a deletion answer is read.

    • LockClassTestClasses answered a response object as the lock handle — lockTestClasses answers an IAdtResponse in 23 where it answered the bare handle, and the call goes through an as any. UnlockClassTestClasses had the other half: a refused release reported as success.

    • RuntimeListFeeds lists feed variants, read out of the feed list, and the profiler no longer wipes its own defaults for options a caller did not give.

    • A package is saved once per ABAP session, and over RFC each save gets its own.

    • The polygon and the suites that build their own fixtures stop reporting success over a refusal. 69 calls in src/__tests__ and src/lib/search-source had no strategy; 25 awaits dropped an IAdtResponse entirely, so ensureSharedPackage logged "Created" over a refused create and safeDelete — which classified a thrown message — reported deleted for every object a teardown failed to remove. Two unlocks in a finally ignored a refused release, the shape that left an enqueue on ZMCP_SHR_I_BDFL and locked every later run out; they report it by name now.

    • A group activation is three requests, and only the third says what happened. POST /activation/runs answers 202 with the run id in Location and an empty body — which parses to zero messages and reads exactly like "no errors". So shared:setup logged "Group activation completed successfully" over eleven objects that stayed inactive, with not one SAP message to say why, and ActivateObjectLow's group path answered activated: null for every call. lib/strategies/activationRun.ts waits on getActivationRun (long-polling, bounded) until runs:status settles and then reads getActivationResults, where the chkl:messages are. The tool answers run_status, messages and a real activated now — null only for a run that has not settled — and the setup's retry is driven by the inactive list rather than by the accept. Re-run on a BTP trial 2026-09-28: runs:status finished, 8 message(s), all eight warnings (Key must have the type Inverted Individual on the database), Confirmed active: all 30 object(s).

    • CheckPackage/CheckPackageLow required a super_package they never sent. The guard refused before any request, so a package with no parent — super_package: "", which is what a top-level package has — could not be checked at all: measured on a BTP trial 2026-09-28, the suite's own ZMCP_SHR_PKG answered isError: true with nothing on the wire while the other nine check tools passed. The member takes check({ packageName }, status?, options?), and the parameter's own description had said it does not reach the endpoint all along. It stays accepted and is no longer required; the surface ratchet records the relaxation rather than having its fixture edited.

    • DEBUG_HTTP_WIRE — there was no HTTP wire log at all. @mcp-abap-adt/connection logs the session, the CSRF token and the critical section and nothing about a request, and logWire is an RFC transport option the HTTP transports do not take, so DEBUG_CONNECTORS answered nothing when the question was what did we send and what came back. Finding the CheckPackage cause needed a throwaway script that wrapped makeAdtRequest by hand. The switch lives in connectionFactory now, symmetric with DEBUG_RFC_WIRE: method, URL, params, headers with their values redacted by name, and bodies clipped at DEBUG_HTTP_BODY_CHARS, on stderr.

    • A refused check says what it refused. expect(response.isError).toBe(false) printed Expected: false / Received: true and nothing else, so the CheckPackage refusal was invisible from the suite; all fifteen assertions in that file raise the handler's own payload now.

    Removed
    • fetchNodeStructure from @mcp-abap-adt/lib/utils — a deprecated stub that threw fetchNodeStructure not implemented in AdtClient yet on every call, for a member that has existed for several majors.
    • src/lib/strategies/atcRun.ts — its answering() turned the AdtSAPError that resolveCheckVariant and createWorklist threw back into a response; both answer an IAdtResponse in 23, so it had no callers left.
    Verified
    • On premise, soft mode (PR #230): HTTP 47/47 suites, 105/105 tests; RFC 43/47, 100/105, the four failures all one finding — RfcTransport keeps one ABAP session for the connection's lifetime, so a stateless read after a create on the same connection answers 400 SADT_RESOURCE 007 while a fresh connection answers 200. That is the connector's decision to make, and connection 9.4.x with sap-rfc-lite 0.2.0 is where it is made.

    • On a BTP trial, cloud, on this branch's own versions: the full soft-mode sweep, 48/48 suites and 108/108 tests, nothing left on the system. Two skips remain and both are the platform saying no — programs do not exist on ABAP Cloud. Within it: the auth-broker path (Session ready, every suite on a connection from the broker), the class unit-test workflow including the test-classes lock and its release, the feed descriptors and dump entries parsed, the group activation read from its run's results, and the BDEF+BIMPL suites creating, activating and deleting their own views.

    • The dumps test reads the feed rather than one user's dumps. It required params.dumps_user and skipped without it, so on a machine whose config named no user it never ran; a user filter is not what it is about. It lists what the system shows, picks a dump and reads it — measured on the trial: 5 dump(s) in the feed. The only skip left there is a system with no dumps at all.

    • Unit: 111 suites / 1704 tests. tsc --noEmit clean on all three projects, biome check clean, release:dry reports Published: 2 Skipped: 0.

  26. v12.0.0

    Breaking 2, Features 2, Changed 8

    Notes

    Migration: docs/MIGRATION-12.0.md.

    Added
    • Five tools for a transport request's object list, which is what fr0ster/mcp-abap-adt#221 asked for: ReadTransportObjects, RemoveTransportObject, AddTransportObject, CreateTransportTask and ReadTransportActionLog.

      The problem they answer: deleting an ABAP object does not free its name. The CTS object-directory entry stays on the request that carried it — on purpose, so that transporting the request deletes the object in the target system too — and until it is detached, creating the same name again is refused with CTS_WBO_API 019, even passing that same request as the transport. The ways out were releasing the whole request, shipping everything else in it, or SE09 by hand. It was found the hard way: two shared function groups in an on-premise integration polygon became permanently unusable.

      Two things the tools cannot soften, both measured against an on-premise system on 2026-09-21:

      • position is required for a removal, and is what makes it do anything. Twenty-two objects asked for by pgmid/type/name alone each answered 200 with the usual echo document, and a re-read found all twenty-two still on the task. ReadTransportObjects is where the number comes from — there is nowhere else.
      • target_user is required for a task. Without tm:targetuser the server resolves an empty owner and refuses with 400 SCTS_ADT_MSG 009, "User does not exist in the system (or locked)" — two spaces where the name should be.

      And one thing the answers say rather than hide: a 200 from a user action is not evidence. The endpoint echoes whatever it was asked about, for an entry that exists and for one that never did, so RemoveTransportObject and AddTransportObject answer accepted: true, removed/added: "unknown" and the reading that would settle it — ReadTransportActionLog, or a re-read of the objects.

      Neither carries success, though every other write in this repository does. There it means the write happened; here it would contradict the field beside it, and a reader who stopped at success would skip the re-read that is the only thing establishing the outcome.

      Objects live on tasks: a request displays its tasks' entries and refuses to detach one, saying the entry "does not exist in request/task". Every tool says so where a caller will read it.

    Changed
    • BREAKING: the contract comes from the packages that declare it. @mcp-abap-adt/interfaces is gone from this project's dependencies. It last shipped 51.0.0 and nothing further will: adt-clients 22.0.0, connection 9.2.1 and adt-strategies 0.4.0 all take interfaces-adt@^9 directly, while the facade carries ^7. Keeping it would have put two majors of the contract in one tree, and they differ — 9's IAdtWireResponse extends IHttpWireResponse from interfaces-network@2, 7's does not.

      111 import statements in 108 files under src/, plus server/src/AuthBrokerConfig.ts, now name the package the contract lives in:

      packagenames taken
      @mcp-abap-adt/interfaces-adt@^9.0.018, from IAdtError and IAdtResponse down to TRANSPORT_SEARCH_CONFIGURATIONS_URL
      @mcp-abap-adt/interfaces-utils@^1.1.0ILogger, LogLevel
      @mcp-abap-adt/interfaces-auth@^1.2.0ITokenRefresher, ITokenProvider, ITokenResult
      @mcp-abap-adt/interfaces-auth-sap@^1.0.0SapAuthType, IServiceKeyStore, ISessionStore, IAuthorizationConfig, IConnectionConfig

      Nothing was renamed, so a consumer of this library changes an import path and installs the packages it names. interfaces-network is not among them because nothing here imports from it.

      @mcp-abap-adt/lib/utils no longer re-exports the contract. The star export was described as re-exporting the header constants; those live in interfaces-network now, and measured before removing it, nothing in this repository imported one through there. A consumer who did takes them from @mcp-abap-adt/interfaces-network directly — which is also where cloud-llm-hub already takes them from.

    • BREAKING: a write's body has one channel, options.source. interfaces-adt@9 merged what used to be options.sourceCode (ABAP text), config.document (an XML metadata document), config.ddlCode, config.ddlSource, config.testClassCode and an options.xmlContent that nothing ever read. Every member in adt-clients 22 reads options?.source.

      - await domain.updateMetadata({ domainName, document: patched }, { lockHandle });
      + await domain.updateMetadata({ domainName }, { source: patched, lockHandle });

      This reaches a caller of the MCP tools not at all — no tool parameter changed — and a caller of @mcp-abap-adt/lib's handlers not at all either. It is recorded because of how it was found: the compiler flagged 8 of the ~38 call sites, the ones whose member has two overloads. The rest would have compiled and silently sent no body, so they were found by reading the contract instead of the error list.

    • @mcp-abap-adt/adt-clients ^22.0.0, connection ^9.2.1, logger ^0.4.0, adt-strategies ^0.4.0. The adt-clients major is what carries the five transport members above, with the two required arguments their first run against a server measured. connection 9.2.1 also drops its optional kerberos dependency, so an install no longer builds a native module for a code path nothing reached.

      taskTypeChanged joins READING_BY_SLOT for changeTaskType, new in adt-clients 22. resultSets.test.ts is what found it — resultsFor throws on a slot with no reading, and every transport tool failed until it was declared.

    • Dependencies removed that nothing imports, measured over every .ts, .js, .mjs and .cjs outside node_modules, plus the npm scripts and the husky hooks: xml-js and @mcp-abap-adt/header-validator (both runtime, so both shipped in the tarball), cors, express-rate-limit, serve-handler, commander and open, and the @types/diff and @types/js-yaml stubs that duplicate declarations those packages now ship themselves. pino/pino-pretty lose their duplicate dev entries and stay as runtime dependencies, because @mcp-abap-adt/logger declares them as peers and this repository's .npmrc sets legacy-peer-deps=true.

      jest-util looked like the same case and is not: with install-strategy=nested in .npmrc, ts-jest cannot find it unless this package declares it, and npm test fails before a suite runs. It stays, with that reason written beside it.

    • Third-party majors: js-yaml 5, diff 9, dotenv 18, @modelcontextprotocol/inspector 2. All three runtime majors keep a CommonJS entry, which this build needs.

    • Node and TypeScript versions follow SAP, and CLAUDE.md now says so. @types/node moves down to ^22, matching engines.node rather than the newest release, and the CI and release matrices run 22 and 24 instead of 22/25 and 22/24/25: SAP BTP Cloud Foundry supports 22 and 24, 20 was removed on 2026-04-30, and odd-numbered releases are not on the platform. typescript stays on ^6.x — no CAP package declares a typescript peer, but cds-typer and cds-types are developed on ^6.0.3, ts-jest 29.4.13 declares typescript: ">=4.3 <7", and cloud-llm-hub type-checks against this project's declarations on 6.

    • overrides is gone. It pinned @hono/node-server to ^2.0.5 against an sdk that declared ^1.19.9; sdk 1.30.1 declares ^1.19.9 || ^2.0.5 itself, and a clean resolve of the sdk alone lands 2.1.1 with no help.

    • The earlier statement of this release's dependency set — adt-clients ^20.0.0, adt-strategies ^0.3.0, interfaces ^46.0.1 — is superseded by the rows above; it was written when the five transport tools landed, before the contract split reached this repository.

      adt-strategies 0.3.0 brings analyseActivation reading activationExecuted as three states, so an activation with nothing to do is no longer answered as a refusal.

      Five slots arrive with the client — removedObject, addedObject, createdTask, actionLog, objects — and each gets a reading in READING_BY_SLOT, which is what resultSets.test.ts insists on: a slot adt-clients adds arrives at the table, not as an unshaped answer at a call site.

    Added
    • DEBUG_RFC_WIRE and DEBUG_RFC_BODY_CHARS — the RFC wire can be asked what it carried: the request headers and both bodies on the debug channel, off unless asked for.

      It exists because an investigation stopped for want of it. #222 reports CreatePackage losing superPackage over RFC while the identical call succeeds over HTTP, and the connector logged RFC → POST /sap/bc/adt/packages and a status — nothing about the document, so whether what this repository builds reaches SADT_REST_RFC_ENDPOINT intact could not be answered at all.

      Setting the variable is enough. RfcTransport writes only when it has both the option and a logger, and the server's own path builds its connection with none — so asking for the wire brings one at debug where nothing else supplied it. It writes to stderr: in stdio transport stdout carries JSON-RPC, and a debug switch that interleaves RFC HEADERS: … with the protocol would take the session down, which is worse than printing nothing. A logger the caller already has is used as it is and never redirected.

      An environment variable rather than an argument: the question is asked by whoever is sitting in front of a misbehaving on-premise system, not by code, and an argument would have to be threaded through every call site for a switch nobody sets in production. DEBUG_CONNECTORS already works this way.

      @mcp-abap-adt/connection ^8.1.0 is what makes it possible, and is also why the output is safe to paste into an issue: it replaces the values of Authorization, Cookie and anything matching token, secret, password, credential or an API key with [redacted], keeping the names. Bodies are clipped, not redacted — DEBUG_RFC_BODY_CHARS sets the ceiling, default 2000, 0 for the size alone and Infinity for all of it — so a body carrying a credential would still be logged. This is for a payload under suspicion, not for routine logging.

  27. v11.0.0

    Features 2, Changed 14, Fixes 6

    Notes
    Added
    • ABAP Test Cockpit, as three tools. RunATC composes the three calls an ATC run is — the check variant, a worklist for it, then the run — because AdtAtc deliberately stopped being IAdtRunnable in 19.0.0 and left the composing to whoever calls it. GetATCRunStatus answers the server's own word for the state and the one flag the client derives from it; GetATCFindings reads the worklist into findings a caller can act on — object, source position, priority, which check ran and what it said — with the counts per priority beside them. wait decides only whether the server holds the request: the findings are written into the worklist either way, so worklist_id is in every answer and run_id only when there is a run to poll. A worklist a run has not filled yet says so, rather than reading as a clean check.

    • CheckMetadataExtension and CheckMetadataExtensionLow take version (active | inactive, default active). The DDLX checkruns endpoint does not fall back to whichever version exists, unlike the DDLS one: measured on a live system, an activated extension answers processed for active and notProcessed — "Error while reading the object … from the database" — for inactive, and an extension that was never activated answers the mirror image. The shipped default is inactive, so every activated extension was unanswerable through these tools. Reported by @eseuve in #178.

    Fixed
    • A blank cell no longer moves every value below it up a row. ADT sends an empty cell in a data preview as a self-closing <dataPreview:data/>, which the regular expression reading the document could not match. The cell was dropped, its column came back short, and every value below the first blank attached itself to the wrong record — silently, with nothing in the answer to say so. GetSqlQuery and GetTableContents both carried it. The reading is a parser now, injected on the query and contents slots rather than called from inside the handler, so entities are decoded as well and a short column is padded instead of shortening the rows after it. Found by @mpauspor (#179) and @anggakharisma (#180), each on their own system.

    • The answers that had become refusals. Five behaviours turned into failures during the migration, each because something an adt-clients method used to do inside itself became this repository's job. A check reports its findings again instead of refusing on them (27 CheckX tools — and CheckFunctionGroup, which could not answer at all, since SAP reports MESSAGE(G46) for every function group); a pre-write check informs a write rather than being turned into a false failure by the wrong reading; a write that landed is a success even when the unlock afterwards was refused; a create that worked is confirmed by reading the object back; and a name that is not free is an answer rather than an error. Reported as #183 by @azzarabe for the function-group half.

    • A lock nobody released now reaches the caller. When an unlock fails, the write still succeeded — and the note about the held lock travels on the answer that is actually returned, across the activation that follows, across the steps of a sequence, and out on an exception when the next call throws rather than answers. Previously it was reported instead of the result, then dropped on every path that answered something afterwards.

    • The installation guides no longer recommend a 45-second deadline. .env.example shipped SAP_TIMEOUT_DEFAULT=45000 uncommented and the three platform guides told readers to copy it. @mcp-abap-adt/adt-clients removed that default deliberately: an aborted request is retried into a new session while the ABAP session underneath keeps the enqueue locks, leaving the object locked and inactive. Nothing in the code was cutting anything at 45 s — only our own documentation could arm it.

    • Three development-scope advisories resolved in range — js-yaml (high), browserslist (high) and baseline-browser-mapping (moderate), all transitive under jest. One lock update rather than three separate bumps, and no overrides entry: an override pins a version the tree never asked for and keeps pinning it long after the advisory is history.

    • AUTH_BROKER_PATH survives a Windows drive letter. The value was split on both : and ;, which tears D:\tools\project in two. Quiet on the same drive, invisible from another. Contributed by @mpauspor (#185).

    Changed
    • Legacy support (BASIS < 7.50) is parked, on parked/legacy-support, until it can be tried against a live system. available_in declared legacy on 142 tools and nothing here had ever run against such a system — an environment nobody has verified is a claim rather than a fact. The documentation page regenerates itself the moment any tool declares it again.

    • detail: 'full' on a data preview answers the whole document's parse, not a chosen few of its fields, so nothing a caller could reach through the generic parse before is missing from it.

    • The corpus gained the documents these changes are written against: an ATC worklist with findings in it, a data preview carrying both cell shapes, a transport listing with ?targets=true (where ADT inserts a tm:target level the flat listing has no sign of), and a DDLX checkrun in each version. A reading designed without one is a guess about a shape.

    • Two patterns run through the whole migration and are worth stating once, in general, rather than only inside each task's own entry below.

      A read that used to answer success: true with a null body now answers an error. Every migrated read-modify-write handler — a domain, a data element, a package, a table's metadata, and the rest — goes through sequence(), whose whole discipline is stopping at the first failed step and handing back that step's own answer, untouched. A failed read used to mean "nothing to patch, so write whatever came back" on more than one pre-migration handler, which is the shape null/empty success: true answers came from; a read step that fails now stops the chain before any write is attempted, and the caller sees the read's own failure — origin, message, and (where the strategy fills it in) the server's own T100 key — instead of a write silently confirming nothing happened.

      A write that succeeded under a failed unlock now answers an error carrying operation: 'succeeded'. Every migrated high-tier write holds its lock through the shared withLock (see the eighteen-writes entry below): the body runs, and the release always runs after it, on every path out — a refusal from the body, a throw from it, or neither. When the body itself succeeded but the unlock is refused or throws, the object's change is real and on the server, but the call now answers a failure rather than the write's own success — with operation: 'succeeded' on the error payload, and a cleanup field carrying what the release actually said, so a caller does not read "failed" and conclude nothing happened. This is the most arguable decision in the spec this migration implements: a lock left behind on an object whose new content the caller can no longer see as a success. Surfaces in integration runs as new failures that are not regressions — see the pull request for a fuller account.

    • Six unit-test tools gained the detail parameter, and what they answer changed with it. GetUnitTestStatus, GetCdsUnitTestStatus, GetUnitTest, GetCdsUnitTest, GetUnitTestResult and GetCdsUnitTestResult answer a JSON object built from a parsed document, which is the shape detail exists for — but they had been left without it, each hardcoding a level, so an audit keyed on the shared projection helper could not see them.

      They split two ways, and the change is not the same for both.

      The two status tools read the run's status directly. They used to answer success, the run id, a finished flag, and the whole parse as run_status, at every level. Now terse answers the run id, the finished flag and run_status as the status string itself — "FINISHED" rather than the parsed document; full answers the bare parse, without the surrounding envelope; and raw answers the wire document.

      The four polling tools go through the shared run poller. They used to answer the parse as run_status and, as run_result, the reading object itself with the wire document inside it. Now terse and full answer the parse in both fields and raw answers the wire text.

      Named fields did move, so a caller reading one is not automatically safe: success is gone from both status tools at every level, run_status is a string rather than an object at terse, and at full and raw the status tools answer a document with no named fields at all.

      No test pinned the old shapes, which is why this is recorded here rather than discovered by one.

    • A compiler-pinned ledger of the eighteen adt-clients legacy-class members that ignore what a caller passes them, and what it found (#207, #208).

      AdtClientLegacy (BASIS < 7.50) substitutes four classes — AdtPackageLegacy, AdtUnitTestLegacy, AdtRequestLegacy, AdtUtilsLegacy — whose overridden members ignore arguments a caller passes them, because the factory that returns them is typed as the modern class: the compiler cannot see the mismatch, so nothing before this caught it. Not all eighteen drop the same thing, and calling all of it "the failure strategy" overstates nine of them: nine genuinely drop analyse, the caller-supplied failure verdict modern accepts and legacy ignores (five of AdtPackageLegacy's six members — create/readMetadata/ validate/updateMetadata/delete; four of AdtRequestLegacy's five — create/readMetadata/updateMetadata/delete). The other nine never accepted analyse on modern either: AdtPackage has no public read member at all (it implements metadata-readable, not readable, so legacy's read override is dead code from the type's perspective, not a dropped strategy); AdtRequest's list takes IListTransportsOptions, one configUri field and never an analyse; AdtUnitTest's run/ getStatus/getResult (three) and AdtUtils's activateObjectsGroup/ getTableColumns/getTableContents/getSqlQuery (four) never declared one either. What legacy drops on those nine is something else instead — a positional argument, a run identifier, a table name — a different failure mode. legacyExposure()/legacyEnabledHandlers() (scripts/lib/analyseOmissions.ts) resolve every handler legacy is offered (144 of 326) through the compiler's own type checker — not a syntax walk of specific call shapes, so a receiver reached through a shared helper, a destructured binding, or an awaited factory is seen the same as a direct chain — and name the exact (handler, factory, member) pairs that land on one, pinned by tests/fixtures/legacy-handlers.json and tests/fixtures/legacy-exposure.json and asserted in src/__tests__/unit/legacyExposure.test.ts.

      Sixteen pairs across thirteen files remain, and none can be moved to a sibling member that does accept a strategy: AdtPackageLegacy refuses every package operation (create/read/readMetadata/validate/ updateMetadata/delete) unconditionally, before any request is made, so GetPackage, ReadPackage, UpdatePackageLow and DeletePackageLow always answer a clean UNSUPPORTED_OPERATION refusal on legacy — no masking risk, but the operation reaches nothing, and their descriptions now say so. getUtils().activateObjectsGroup (ActivateObjectLow's multi-object path) never accepted a strategy on modern either — tracked already in #200, now cross-referenced from #207. Each affected tool's description states the limitation and the tracking issue.

      A second, separate ledger — legacyThrows(), pinned by tests/fixtures/legacy-throws.json — names every handler that reaches a factory AdtClientLegacy declares never available (getDomain/getDataElement/getStructure/getTable/getCdsUnitTest/…): the no-arg overload throws synchronously there, rather than answering a failure. Five tools declared legacy in available_in and reached one of these with no disclosure and no guard — CreateCdsUnitTest, GetCdsUnitTest, GetCdsUnitTestResult, GetCdsUnitTestStatus (getCdsUnitTest()) and GetStructuresList (getStructure()/getTable()) — and in three of the four CDS handlers the call sat before answer()'s callback with nothing catching it, so the throw would have escaped as an unhandled rejection rather than a tool error. All five now guard with a new isLegacyConnection() (src/lib/utils.ts) and refuse with a message before making any call, tested the way the program family's cloud guard is (src/__tests__/unit/legacyThrowingFactoryGuards.test.ts): refuses and never reaches the factory on legacy, reaches it on every other system. ActivateObjectLow/DeleteObjectLow also reach several of these factories for specific object types (getDomain/getDataElement/getStructure/ getTable/getBehaviorDefinition/getMetadataExtension), but only when a caller names that type through the generic dispatcher; recorded in legacy-throws.json, not guarded here.

      Building the ledger surfaced a third, more severe defect (#208, not fixed here — out of this ledger's scope): on a legacy system, RunUnitTest, CreateUnitTest and RunClassUnitTestsLow answer their run's result synchronously, but AdtClientLegacy.getUnitTest() returns a new instance every time it is called — even on the same client — and the synchronous result is cached on that instance, not on the client or the connection. So the client that answers GetUnitTest, GetUnitTestStatus, GetUnitTestResult, GetClassUnitTestStatusLow and GetClassUnitTestResultLow afterwards has no memory of the run and always refuses, regardless of whether it passed, failed, or errored — reusing the client across calls would not fix this; the factory call itself would still need to be the same one. Verified by construction against the installed @mcp-abap-adt/adt-clients package with a stub connection, no live SAP system needed: the same AdtClientLegacy instance, asked for getUnitTest() twice, answers the refusal from the second call even though nothing about the client changed. All eight affected tool descriptions (the five named above plus RunUnitTest, CreateUnitTest and RunClassUnitTestsLow themselves) now disclose this; the fix itself needs a design decision (embed the full result in the run tools' own answer on legacy, or cache the factory's own returned instance rather than the client) tracked in #208.

    • The eighteen high-tier writes that hold a lock now release it through one shared withLock, and three genuine behaviour changes survive that migration.

      CreateStructure's syntax check now gates the answer; before, a genuine check failure was logger.warn'd only — the create still answered success and still went on to activate a structure with a known syntax problem. A refused check now fails the call, the same as every other check in this migration (analyseCheck, same as CreateDomain/CreateDataElement/ CreateBehaviorDefinition/CreateMetadataExtension already had).

      UpdateDdl, UpdateInterface, UpdateProgram, UpdateServiceDefinition, UpdateStructure and UpdateTable no longer carry an activation_warnings array in their terse (default) answer — the chkl:messages parse that array came from still happens, but only detail: 'full' or detail: 'raw' on the activation reads it now. Terse writes answer the literal string SUCCESS or a failure payload uniformly across every write tool in this migration; keeping one family's warnings in the terse channel while every other write tool's terse answer carries none would have been the inconsistency, not the fix.

      Everything else that changed shape during the underlying withLock migration and still holds after review: the long-polling wait for write visibility is restored, in its pre-migration position, on every handler that had one. The pre-write syntax check on UpdateClass, UpdateDdl, UpdateInterface, UpdateProgram, UpdateStructure and UpdateTable is restored too, gated by activate exactly as before — it runs only when activate is set, and a refusal there stops the write. No handler restores a post-unlock check: the pre-migration one existed only on these same six, its own catch never rethrew, and it is dropped rather than restored — every check in this migration now carries the check strategy, so a refusal is reported the way every other refusal is, and a check that could never have changed the answer is not worth the round trip. config.packageName is no longer sent on UpdateDomain/ CreateDomain/UpdateDataElement/CreateDataElement's metadata write — the shipped wire function never read it. CreateStructure no longer holds a lock while it creates — nothing between create and check ever wrote through it.

    • ActivateObjectLow (multi-object activation) now answers acceptance, not a verdict. Activating a single object still reads a real pass/fail straight from ADT. Activating more than one object at once — or one object of a type this tool has no dedicated family for — goes through group activation, and ADT's answer there has always meant "the request was accepted and a run was queued," not "activation finished." The tool now says so explicitly: accepted (from whether a run id came back), run_id, and activated: null (there is no verdict on this path — acceptance is not completion). It does not wait for the run. To learn what actually happened, call GetInactiveObjects afterwards — an object still listed there did not activate — keeping in mind that a check made immediately after acceptance can still show an object as inactive that goes on to activate a moment later, since the run has not necessarily finished yet.

      Separately, and left open: group activation cannot judge a refusal the way every other write path in this migration now does — activateObjectsGroup accepts no error strategy at all, so a refusal ADT embeds in its answer is not caught on this path. Tracked as issue #200 in @mcp-abap-adt/adt-clients (giving the member the <E extends IAdtError> shape every sibling member already has); not fixed by this change.

    • RuntimeRunClass and RuntimeRunClassWithProfiling now find a profiled run's trace themselves, and two fields they used to answer are gone. adt-clients 19 split the old composite runWithProfiling into scheduleTrace + runWithProfiler, neither of which waits for a trace or answers a traceId — a run only schedules and executes, and reading a trace is IProfiler.list()/read(), whenever the caller is ready. Both tools still advertise trace_id, max_trace_attempts and trace_retry_delay_ms, so this repository now composes what the library no longer does: it snapshots the profiler feed before scheduling, runs, then polls the feed for an id that was not in the snapshot — by set difference, ordered by the library's own compareRecordedAt (never by feed position, and never by sorting recordedAt as a string, both of which pick the wrong trace on a real feed). max_trace_attempts and trace_retry_delay_ms keep bounding that search exactly as before. A run that finishes before its trace is written still answers success, with trace_id absent rather than fabricated — poll RuntimeListProfilerTraceFiles or RuntimeAnalyzeProfilerTrace afterwards in that case.

      trace_lookup_uris is now accepted and ignored: adt-clients 19's IProfilerListOptions is { user?: string }, the whole interface, so there is nowhere left to put a URI — the profiler feed is one endpoint, not one per lookup URI.

      run_status and trace_requests_status are gone from both tools' answers and cannot come back under any of the three options considered: run and runWithProfiler answer IAdtResponse<string> and ClassExecutor takes no result strategy in its constructor, so there is no transport envelope left to read a status from.

    • RuntimeRunProgram and RuntimeRunProgramWithProfiling (Task 23) no longer answer run_status, and RuntimeRunProgramWithProfiling gained an output field it never had before — neither was documented at the time. ProgramExecutor.run()/runWithProfiler() answer IAdtResponse<string> with no transport envelope to read a status from, the same reason the class tools above lost it; run_status is gone from RuntimeRunProgram's non-profiled answer, its profiled answer, and RuntimeRunProgramWithProfiling's answer — three answers across the two tools. Unlike the class tools, the program tools never had max_trace_attempts, trace_retry_delay_ms, trace_lookup_uris or a trace_id to begin with (program execution has always been fire-and-forget; a trace is located afterwards via RuntimeListProfilerTraceFiles), so none of those were removed. RuntimeRunProgramWithProfiling is deprecated and, by the rule this repository otherwise follows (a deprecated tool gains no field — applied correctly to RuntimeRunClassWithProfiling above), should not have started answering output. Recorded here rather than fixed by this entry.

    • The rest of the profiler/dump/feed readers (Task 25) lose the same transport fields the class/program runners above lost, and two of them answer a genuinely different shape.

      RuntimeListProfilerTraceFiles used to hand response.data — the raw feed XML — to a hand-rolled parser and answer {success, status, payload: <whatever that parse produced>}. IProfiler.list() answers IAdtResponse<IAbapTraceEntry[]> now — already the parsed entries, with named fields (id, recordedAt, user, objectName, state, expiresAt, system, client, host, size, runtime/runtimeABAP/ runtimeSystem/runtimeDatabase, isAggregated, amdpFileSize) — so the tool now answers {success, count, entries: IAbapTraceEntry[]} instead, and drops the status field along with it (see below — it is not only the three tools originally named here). What that costs a caller: the old hand-rolled parse of the raw Atom feed carried whatever the document had — each entry's own <atom:title> and its <atom:link> navigation hrefs (to the trace's hitlist/statements/dbAccesses views and its delete link), and the feed's own metadata. IAbapTraceEntry is a curated, measured field list and carries none of that; a consumer that read those fields off the old freeform payload has nothing left to read them from.

      RuntimeGetProfilerTraceData and RuntimeAnalyzeProfilerTrace read the same view differently too: Profiler.getHitList/getStatements/ getDbAccesses are gone (IProfiler composes ITraceReading since 31.0.0 — one read(traceId, view, options) over three named views instead), and each view already answers its own typed, parsed shape (IAbapTraceHitList's entries, IAbapTraceStatements's statements, IAbapTraceDbAccesses's accesses) rather than the free-form attribute-prefixed object the old hand-rolled XML parser produced. Both tools' payload field now carries that typed shape. What that costs a caller, same as RuntimeListProfilerTraceFiles above: the old hand-rolled parse turned every attribute and element the raw XML document carried into JSON; IAbapTraceHitList/IAbapTraceStatements/ IAbapTraceDbAccesses are curated, measured field lists too (each one says so in its own doc in adt-clients' runtime/traces/types.d.ts), not the whole document — a consumer reading a field the old freeform payload had that one of these three types does not name has nothing left to read it from. RuntimeAnalyzeProfilerTrace's summary (total_records/ top_records) now reads the view's own named collection and ranks by that collection's real numeric field (grossTime.time for hitlist/statements, accessTime.total for db_accesses) — a fix-round-1 correction: the first pass walked the whole typed document for "anything with a number on it", which counted each row's own timing sub-object as a second row and ranked against a fixed list of key names ('runtime', 'calls', 'hits', …) that do not exist on any of the three typed shapes, so top_records was document order with roughly half its slots taken by timing objects rather than real entries.

      RuntimeListProfilerTraceFiles, RuntimeGetDumpById, RuntimeGetProfilerTraceData, RuntimeAnalyzeProfilerTrace and RuntimeCreateProfilerTraceParameters all drop the status field they used to answer (the HTTP status of the underlying request) — IAdtResponse carries no transport envelope to read it from any more, the same reason the class/program runners above lost run_status.

      RuntimeCreateProfilerTraceParameters also changes mechanism: Profiler.createParameters() is gone (IProfiler no longer composes ITraceScheduling as of 19.0.0 — scheduling a measurement moved onto IClassExecutor/IProgramExecutor, the same scheduleTrace member RuntimeRunClassWithProfiling already calls), now reached through AdtExecutor.getClassExecutor().scheduleTrace(...). profiler_id is still answered; status is not (see the paragraph above — this is one of the five, not an exception to it).

      RuntimeGetGatewayErrorLog's error_url branch was silently wrong before this task touched it: the pre-19 code handed the whole envelope object to error: with nothing unwrapping it, which — once gatewayErrorDetail() started answering IAdtResponse<T> instead of T directly — would have serialised as {"ok":true} (its getResult/getError methods dropped by JSON.stringify) rather than the actual detail document. No tsc error ever named this, because nothing in the old code read a field off the envelope that IAdtResponse didn't have. Fixed as part of this task's pass over the same file for its one genuine compile error (errors.length).

      RuntimeListFeeds's variants feed_type now refuses locally rather than calling the library at all: IFeedRepository.variants() gained a required category argument as of 19.0.0 (ADT's own endpoint always required one on the wire; the type simply did not say so before), and the frozen tool surface has no parameter this branch could take a real category from. A fix-round-1 correction: the first pass sent category='', reasoning that an empty category reaches "the same refusal" a categoryless call always did — but the pre-19 wire sent no category parameter at all, and the 19.0.0 wire always appends one, so category='' is a request nobody has measured, not a preserved behaviour. This feed_type now answers a local error explaining why, instead of guessing at what an unmeasured request would do.

      GetPackageTree (Task 25) wording: its pre-check for whether the target package can be read now says "could not be read" rather than "not found" for every refusal — a fix-round-1 correction. The pre-migration code branched on the wire status (404 said "not found"; anything else rethrew the original error unworded); readMetadata's default error strategy carries no status/code to branch on the same way, and the first pass here said "not found" for every refusal regardless of cause (a lock, a permission failure, a connection error), which is wrong for all but one of them.

      handleGetClassUnitTestResult/handleGetClassUnitTestStatus — a fix-round-1 correction, not a shape change: the first pass at these two Task-14-carve-out files cast the answer as AxiosResponse, believing getUnitTest()'s members still answered the pre-19 transport frame. They do not — ITestRunInformation already declares them IAdtResponse<T>, and the shipped class implements exactly that. IAdtResponse has no .data, so both tools answered success with empty content regardless of .ok — a refusal reported as success. Both now unwrap through answer(); neither tool's answer shape changes for a caller once the masking is removed. A fix-round-2 correction on top of that: fix round 1 dropped the outer try/catch both handlers had before it, so a thrown error from createAdtClient/restoreSessionInConnection (unreachable through the server, which always calls a handler inside its own guard, but reachable from a direct caller — a soft-mode integration test, or an embedder) would reject the returned promise instead of answering an error result. Restored.

    • ListTransports asks which saved search to run. A transport listing is a saved search: list() takes a configUri, and when it is not given one it resolves a configuration itself — a second request, behind a protected member, whose answer no strategy of ours could read. On a system holding several saved searches that resolver throws, telling the caller to pass a configUri — which this tool has no parameter for and is not getting one.

      Fixed where it belonged: adt-clients 19.1.0 answers getRequest().searchConfigurations(options), one request with the caller's analyse over it. This handler asks, then lists with what it was given. The request count is unchanged for the ordinary one-configuration system; what changed is that both requests carry a strategy, and that a system with several is searched rather than refused — each of them, merged, capped at five, reported as searched_configurations and configurations_capped. A system with no saved search at all gets a sentence naming the endpoint that answered none, instead of a resolver's internal error.

      A new searchConfigurations slot came with that release, and READING_BY_SLOT declares a reading for it — the ratchet that exists so a slot adt-clients adds arrives in the table rather than unshaped at a call site is what caught it.

    • An activation with nothing to activate is a success, and says so. SAP answers POST /activation with activationExecuted="false", generationExecuted="true" and no msg at all when the object is already active — activating a class twice, or a function group straight after creating one, since a function group is created active (adtcore:version="active" before anything is activated). Every such call used to answer an error.

      The verdict was wrong in @mcp-abap-adt/adt-strategies, and it was fixed there: 0.2.0 carries the reading and the corpus case that proves it (activation-nothing-to-activate). This repository ran its own narrowing strategy in the meantime, with a test asserting that the package still disagreed so the day it stopped would be a red test rather than a discovery — that test went red on the upgrade, and the local module is gone. All 46 handlers are back on the package's analyseActivation.

      What stays here is the consumer's half: a caller sees activated: false with nothing_to_activate: true beside it, rather than a bare false under a success.

    • GetNodeStructureLow no longer reports an error for a node that is simply empty. The migration added a guard for the one ambiguity the corpus captures: /repository/nodestructure answers HTTP 200 with zero bytes both for a package that does not exist and for one that exists and holds nothing, and the two fixtures are byte-for-byte identical. For a DEVC/K parent the handler still pays one extra readMetadata round trip to tell those apart, and still forwards the package's own refusal when the package is gone. For every other parent type it used to throw, on the grounds that no fixture settled the question — which made an ordinary request answer client_threw. It is settled now: CL_ABAP_CHAR_UTILITIES, a standard SAP class, answers CLAS/OC node 0000 with zero bytes on a live system, so outside the package case a blank body is an empty node and is answered as one.

  28. v10.2.0

    Features 1

    Notes
    Added
    • On ABAP Cloud, a request without a responsible person or master system gets them from the system. 10.1.0 let a host scope both per request, but a host that does not know them for a cloud destination had to look them up itself — a second adt-clients import and a second connection type to keep in step. The library now does it.

      Every tool call — through EmbeddableMcpServer/BaseMcpServer, a handler group registered on a server, or the handlers HandlerExporter.getHandlerEntries() returns — runs inside the new withResolvedSystemContext. When the call's effective context lacks responsible or masterSystem and the connection is to ABAP Cloud, it asks the system (systeminformation) and fills only what is missing: the user name as responsible, the system id as master system. On-premise it does nothing. There is one lookup per connection, and only when a request actually lacks a value; concurrent calls share it, and a lookup that throws never fails the call.

      "Lacks a value" means the key is absent. A request scope that carries responsible as undefined has said this request has no responsible, and that answer stands — the same rule 10.1.0 established.

      A lookup that answers nothing is cached as nothing for that connection: the system-information call swallows its own errors and answers null, so a timeout and a system with nothing to give are indistinguishable from here. Only a lookup that throws outright is retried.

      Opt out with systemContextResolver: null on EmbeddableMcpServer or HandlerExporter, or pass your own resolver. A handler group registering itself directly uses the default resolver and has no opt-out yet. withResolvedSystemContext, defaultSystemContextResolver and the SystemContextResolver type are exported from @mcp-abap-adt/lib/request-context.

      Not breaking: a value the request scope or the process context carries still wins, and the wrapped handlers keep their arity, so an embedder that picks handler(context, args) or handler(args) by handler.length is unaffected. @mcp-abap-adt/core is unchanged and stays at 10.0.1.

  29. v10.1.0

    Features 1

    Notes
    Added
    • The responsible person and master system can be set per request. Until now only the master language was request-scoped (#110). responsible and masterSystem lived in the process-wide system context, which is right for one MCP session per process and wrong for a host that serves several SAP users side by side: every concurrent create used whichever user wrote the cache last.

      RequestContext now carries both. Inside runWithRequestContext, a key the scope carries — even as undefined — decides for that request; a key it does not carry leaves the process value in charge. createAdtClient, ListTransports' default user and getSystemInformation() all read through the new getEffectiveSystemContext(), so they agree.

      import { runWithRequestContext } from '@mcp-abap-adt/lib/request-context';
      
      await runWithRequestContext({ responsible: 'JSMITH' }, () => handle());

      Not breaking: outside a scope nothing changes, and a scope that carries only the language keeps the responsible resolved from the environment or the system, exactly as before. @mcp-abap-adt/core is unchanged and stays at 10.0.1.

  30. v10.0.1

    Fixes 2

    Notes
    Fixed
    • Types resolve for every exported subpath, not just two of them. The split renamed ./server to ./embeddable and added /config, /auth, /logger and /request-context. exports was updated; typesVersions was not, so it still pointed server at a dist/server the build no longer produces and named none of the new entries.

      This is invisible on modern resolution, where TypeScript reads exports. On the classic moduleResolution: "node" — which a great deal of CAP and Node tooling still uses — TypeScript reads typesVersions instead, so 10.0.0 offered types for handlers and utils and for nothing else. A consumer importing EmbeddableMcpServer, the entry point the release was about, had to add a paths override to see its types. Runtime was never affected.

      typesVersions is now generated from exports and matches it entry for entry, in both packages.

    • A test holds the two maps together. Nothing here would have caught the drift, because everything in this repository imports by relative path. src/__tests__/unit/packageEntryPoints.test.ts asserts that every exported subpath is named in typesVersions, that both point at the same file, and that every declared entry point exists after a build. Confirmed to fail against the 10.0.0 manifest before being committed alongside the fix.

  31. v10.0.0dated in the atlas

    Breaking 1, Changed 4, Fixes 2, Other 1

    Notes
    Licence
    • Two packages now, under two licences. The project ships a library and a server, and licensing them together forced the stricter of the two onto both. A network service embedding the ADT tools took on AGPL-style obligations because the same tarball also carried a launcher it never ran.

      PackageLicenceWhat it is
      @mcp-abap-adt/libApache-2.0handlers, registries and the embeddable MCP server — no transport
      @mcp-abap-adt/coreAGPL-3.0-onlystdio, SSE and streamable HTTP, the launcher, TLS and the mcp-abap-adt CLI

      Installing the library never puts the server in your dependency tree. That is the whole point of the split.

      9.x was GPL-3.0-only and 8.13.0 and earlier were MIT; both stay as published, because a licence change is not retroactive. CONTRIBUTORS.md carries the full account of why each change was lawful.

      Other terms remain possible. Apache-2.0 is what the library is offered under publicly, not the only way it may be offered. Every inbound contribution so far arrived under MIT or Apache-2.0, both of which grant the right to sublicense, so the copyright holder can still offer the same code separately to a party who needs different terms. Nobody loses what they already received. CONTRIBUTORS.md records the three conditions that keep that open — chiefly that no code from the LGPL packages underneath is ever pasted into this tree.

      The four libraries underneath stay LGPL-3.0-only — adt-clients, connection, interfaces and logger — and the library links all four at runtime. Their terms travel with any distribution regardless of the notice on this repository, so plan against those, not against this line.

    Changed
    • BREAKING: @mcp-abap-adt/core is the standalone server and nothing else. Everything a consumer imported from it moved to @mcp-abap-adt/lib. The CLI, the transports and npm install -g @mcp-abap-adt/core are unchanged.

      BeforeAfter
      @mcp-abap-adt/core/handlers@mcp-abap-adt/lib/handlers
      @mcp-abap-adt/core/utils@mcp-abap-adt/lib/utils
      @mcp-abap-adt/core/server@mcp-abap-adt/lib/embeddable

      Four subpaths are new, for what the server needed and a consumer may want: /config, /auth, /logger and /request-context. The library's root entry works for the first time (see Fixed).

    • No MCP tool changed. No tool was added, removed or renamed, no schema moved, and no handler behaviour is different. This release is the licence boundary and the packaging that expresses it.

    • src/server is src/embeddable, so the source tree says which licence a file is under. Internal only; nothing importable moved because of it.

    • The library's version line continues at 10.0.0 rather than restarting at 1.0.0. It is the same codebase under a new name, and a continuous line is easier to reason about than two.

    Fixed
    • The package's own root entry resolves. main pointed at dist/index.js and no src/index.ts existed, so require('@mcp-abap-adt/core') had never worked — only the subpaths did. @mcp-abap-adt/lib has a barrel.

    • express and dotenv are real dependencies of the server. Both are imported at the top of SseServer, StreamableHttpServer and launcher, and both were listed only under devDependencies, so a published install had nothing to start an HTTP or SSE transport with.

    Migration

    A consumer on 9.x that only ran the server has nothing to do: install @mcp-abap-adt/core@10 and the CLI behaves as before.

    A consumer that imported from the package changes two things — the dependency and the specifiers:

    -  "@mcp-abap-adt/core": "^9.0.0"
    +  "@mcp-abap-adt/lib": "^10.0.0"
    -import { HandlerExporter } from '@mcp-abap-adt/core/handlers';
    -import { setSystemContext } from '@mcp-abap-adt/core/utils';
    -import { EmbeddableMcpServer } from '@mcp-abap-adt/core/server';
    +import { HandlerExporter } from '@mcp-abap-adt/lib/handlers';
    +import { setSystemContext } from '@mcp-abap-adt/lib/utils';
    +import { EmbeddableMcpServer } from '@mcp-abap-adt/lib/embeddable';

    Nothing else changes: the exported names, their signatures and their behaviour are the same objects from the same code. If you were embedding, dropping @mcp-abap-adt/core is the point — it is what takes the AGPL server out of your dependency tree.

  32. v9.0.0

    Security 1, Features 1, Changed 1, Fixes 2, Other 2

    Notes
    Licence
    • This tool is now GPL-3.0-only. It was MIT up to and including 8.13.0, and those versions stay MIT — a licence change is not retroactive.

      A finished tool rather than a library to build on, so it takes the full GPL rather than the LGPL its own dependencies carry: running it and using it on your own data carries no conditions, while distributing it — or a modified version — means passing on the same freedoms, source included.

      Copyright © 2025–2026 Oleksii Kyslytsia.

    Changed
    • Migrated to @mcp-abap-adt/adt-clients ^10.1.0 and @mcp-abap-adt/interfaces ^13.1.0 (from ^8.0.0 / ^11.3.0), plus auth-providers ^1.2.0, auth-broker ^1.0.8 and connection ^1.10.2. Two adt-clients majors are crossed; no MCP tool was added, removed or renamed, and no tool schema changed.
      • Config, state and shared types are imported from interfaces instead of adt-clients, which stopped re-exporting them — 22 handler modules. Two were also renamed: ObjectReference → IObjectReference, SearchObjectsParams → ISearchObjectsParams.
      • A package contents item exposes type, not adtType (interfaces 13 unified the shape as IAdtObjectHit). PackageContentItem in lib/search-source/packageEnumerator.ts is now an alias of IPackageContentItem rather than a hand-maintained copy, so it cannot drift again.
      • UpdateUnitTest and DeleteUnitTest refuse locally instead of asking the server. ADT exposes no resource for updating or deleting a test run; the client method these handlers called threw that refusal from the inside, and adt-clients 9.0.0 dropped it from the declared contract. The tools are unchanged in name, schema and outcome — their descriptions already said "will return an error" — they simply no longer make a round trip to be told no.
      • UpdateCdsUnitTest and DeleteCdsUnitTest keep working through a narrow, documented cast. These are real operations — update writes the local test class source, delete removes the global class, both implemented by AdtCdsUnitTest — but getCdsUnitTest() now returns a capability set that omits them, and the class is not exported. CdsUnitTestWrites describes exactly the two methods the class declares; it should be deleted once the accessor's type covers them.
    Fixed
    • Removed two NUL bytes committed inside a template literal in lib/search-source/packageEnumerator.ts, where the dedup key's separators were \0 instead of spaces. Harmless at runtime — a NUL separates as well as a space — but it made the file register as binary, so grep reported "binary file matches" and skipped it instead of showing the line.
    • ListTransports no longer reports an empty list while the user owns transport requests. The tool returned {"success": true, "count": 0, "transports": []} for every input — any user, any modifiable_only — on a system where the queried user owned 55 modifiable requests, while GetSqlQuery against E070 and GetTransport on the same connection both returned the data. The endpoint negotiates application/vnd.sap.adt.transportorganizertree.v1+xml, a tree: requests sit under status containers one level below the category (tm:root > tm:workbench > tm:modifiable > tm:request), tm:workbench repeats once per transport target, and there is a parallel tm:customizing branch. The parser looked for tm:request only directly under the root or directly under tm:workbench, so every lookup missed and the list came back empty — silently, with success: true. Requests are now collected from anywhere in the tree (the flatter shapes keep working), deduplicated by request number, and a request with no tm:status inherits the status of its container. modifiable_only is additionally enforced client-side, because the tree carries released requests too and it is not established that the endpoint honours the status query parameter. Unit-tested against a reconstructed tree payload; scripts/probe-transport-list.ts dumps the raw payload from a real system so the fixture can be replaced with a capture. (#168)
    Added
    • Diagnostic script scripts/probe-transport-list.ts — dumps the raw /sap/bc/adt/cts/transportrequests payload for a given user and env file, so the transport-list parser can be checked against the shape a system actually returns.
    Security
    • npm audit is back to 0. Four advisories had accumulated in production dependencies since 8.12.1 — js-yaml (GHSA-5p4m-2wfm-xmqj, high), ip-address via express-rate-limit (high), fast-uri via the MCP SDK's ajv (high) and hono via the MCP SDK (moderate) — enough to fail CI's npm audit --omit=dev --audit-level=high gate on main as well, independently of this migration. All four resolved in-range: npm audit fix without --force, touching only package-lock.json. No overrides entry and no direct-dependency range change was needed.
    Notes
    • adt-clients 10.1.0 changes one path that used to "succeed": an update passing super_package for a root package changed nothing and reported success, and now actually moves the package. A caller of UpdatePackage relying on the old no-op will see a real move.
  33. v8.13.0

    Fixes 1

    Notes
    Fixed
    • Tool failures now reach the client as a plain message, with no service prefixes. Previously a failed tool call arrived prefixed with MCP error -32603: — and in the worst case double-wrapped as MCP error -32603: ADT error: McpError: MCP error -32602: …, with the truthful -32602 (invalid arguments) overwritten by a generic -32603. Multi-item content was also collapsed into a single string on the error path. Root causes removed across five layers: return_error no longer prepends Error: ; BaseMcpServer and BaseHandlerGroup return the { isError: true, content } result instead of throwing McpError (which the SDK re-prefixes); 87 handler-level throw new McpError and ADT error: sites were routed through return_error (74 + 13); and the McpError symbol is now barred from src/ except a single backward-compatibility re-export in lib/utils.ts, enforced by a static AST test that forbids every import and use of it elsewhere. Helper functions that feed a success payload keep throwing (a local Error type) so an error can never surface as { success: true }. A custom/external handler group that throws the SDK's McpError is also handled: return_error strips the MCP error -N: prefix at the boundary. Argument-validation errors keep their MCP error -32602: prefix by design — the SDK raises those before our code runs, that code is correct, and they never reach return_error. Verified end to end against a live trial system. (#155)
  34. v8.12.1

    Security 1

    Notes
    Security
    • Cleared the two remaining moderate @hono/node-server advisories (GHSA-frvp-7c67-39w9) via an npm override. @hono/node-server is a transitive dependency of @modelcontextprotocol/sdk (pulled in by the Streamable HTTP transport); even the latest SDK (1.29.0) still pins ^1.19.9, so there is no in-range fix and npm audit fix --force would downgrade the SDK to 1.24.3. Pinning @hono/node-server to ^2.0.5 via overrides resolves 2.0.11 and takes npm audit to 0 vulnerabilities. Safe because the SDK's streamableHttp uses only getRequestListener from the package (verified present and unchanged in 2.0.11); the vulnerable serve-static sub-path is never imported. This deliberately reintroduces a single override (all overrides were removed in #141) — justified here because in-range resolution is impossible until the MCP SDK bumps its own hono dependency. Build, test:check, and the SAP-free server/transport unit tests pass unchanged.
  35. v8.12.0

    Removed 1, Security 1, Changed 1

    Notes
    Removed
    • Version-history tools are no longer advertised for non-versioned object types. function_group, domain, data_element and package were exposed by the version tools but their adt-clients handlers' getVersions/getVersionSource have always thrown "not supported" — the calls were dead on arrival. Removed from:

      • the object_type enum of the generic GetObjectVersions / GetObjectVersionSource / GetObjectVersionDiff (they now accept only the 9 genuinely versioned types: class, program, interface, function_module, table, structure, ddl, behavior_definition, metadata_extension);
      • the per-type high-level surface — the 12 tools Get{FunctionGroup,Domain,DataElement,Package}{Versions,VersionSource,VersionDiff} are gone (39 → 27 per-type version tools, total tool count 365 → 353).

      Those object types remain fully supported by LockObject and every other tool — they are lockable, just not versioned. This surfaced from the adt-clients 8.0.0 migration below: the honest capability types turned an always-failing runtime call into a compile error. Breaking to the tool surface (removed enum values + removed tools) — a client that requested version history for these types received a runtime error before and now gets a schema/Unsupported object_type error instead.

    Changed
    • Migrated to @mcp-abap-adt/adt-clients@^8.0.0 and @mcp-abap-adt/interfaces@^11.3.0 (from ^7.6.0 / ^11.2.0). adt-clients 8.0.0 is a breaking major that narrows every AdtClient.getXxx() return type from the fat IAdtObject to the honest capability composite each handler actually implements (IAdtSourceObject, IAdtNonVersionedObject, or an inline intersection); interfaces 11.3.0 adds those composites. Calling a capability a handler does not implement is now a compile error instead of a silent runtime throw — which is exactly what caught the version-tools defect above. Our direct interfaces range is bumped to ^11.3.0 to keep a single top-level interfaces version aligned with the client and re-export the current type surface.
    Security
    • Cleared two high-severity production advisories (#164). fast-xml-parser 5.9.3–5.10.0 → 5.10.1 (GHSA-8r6m-32jq-jx6q, DOCTYPE entity-expansion reset) and fast-uri 3.0.0–3.1.3 → 3.1.4 (GHSA-v2hh-gcrm-f6hx, host confusion via backslash authority delimiter; transitive via @modelcontextprotocol/sdk → ajv), via npm audit fix (lockfile only, no package.json dependency edits). The CI Security audit (production deps, high+) gate now passes. Two remaining moderate @hono/node-server advisories require a breaking MCP SDK major and are tracked separately.
  36. v8.11.0

    Fixes 1

    Notes
    Fixed
    • Read handlers no longer mask a failed read as success:true + null (#159). 17 readonly handlers (ReadDomain, ReadClass, ReadInterface, ReadProgram, ReadTable, ReadStructure, ReadDdl, ReadDataElement, ReadFunctionGroup, ReadFunctionModule, ReadFunctionInclude, ReadPackage, ReadServiceDefinition, ReadServiceBinding, ReadMetadataExtension, ReadBehaviorDefinition, ReadBehaviorImplementation) wrapped their read/readMetadata calls in inner try/catch blocks that only logged a warn and swallowed the error, then returned success:true with source_code:null/metadata:null regardless. A hard failure — expired token, network error, HTTP 5xx — or a genuinely non-existent object was thus delivered to the caller as a successful (but empty) read. The inner swallows are removed; read errors now propagate to the existing outer catch → return_error, so a failed read returns a structured failure (tool result isError:true). This is the READ-path sibling of the activation/deletion masking fixes. GetStructuresList was not affected — it already surfaces a root read failure via return_error and flags partial append issues explicitly.
  37. v8.10.0

    Changed 1, Fixes 1

    Notes
    Fixed
    • Locked/failed activations no longer report a false success:true. Migrated to @mcp-abap-adt/adt-clients@^7.6.0, which fixes the activation-masking bug (adt-clients #79): ADT's /sap/bc/adt/activation returns HTTP 200 even when activation fails (object locked in another session, syntax errors), and the shared activateObjectInSession helper previously returned that response unchecked. Every high-level Update*/Activate* tool built on it (UpdateDomain, and the whole family across domain, program, table, structure, class, interface, ddl, tabletype, functionModule, functionInclude, metadataExtension, behaviorDefinition, enhancement, unitTest) therefore returned {"success":true,"...":"updated and activated successfully"} on a locked object. With 7.6.0 the client now throws on an explicit activation-failure signal; the handler's catch maps it to a structured McpError (tool result isError:true), so consumers can finally tell the operation failed. Fixes #154.
    Changed
    • Migrated to @mcp-abap-adt/adt-clients@^7.6.0 and @mcp-abap-adt/interfaces@^11.2.0 (from ^7.4.4 / ^10.0.0). adt-clients 7.6.0 sources its public types from interfaces ^11.2.0; bumping our direct interfaces range keeps a single top-level interfaces version aligned with the client. No handler API change.
  38. v8.9.0

    Changed 2

    Notes
    Changed
    • Migrated to @mcp-abap-adt/adt-clients@^7.4.4 and @mcp-abap-adt/interfaces@^10.0.0 (from ^7.4.2 / ^9.2.0). interfaces 10.0.0 is a major bump whose only breaking change is removing the no-op source_code field from the low-level create-params (ICreateAccessControl/ServiceDefinition/EnhancementParams) — source is written via the update flow. This does not affect us: our handlers use the high-level config types (IServiceDefinitionConfig.sourceCode, unchanged), not the low-level create-params. adt-clients 7.4.3 dropped the matching dead create() pass-through.
    • CreateServiceDefinition: removed the now-dead sourceCode from the create() config (create makes the shell only; the body is still written by the follow-up update() from 8.7.1). No behavior change.
  39. v8.8.1

    Changed 1

    Notes
    Changed
    • Migrated to @mcp-abap-adt/adt-clients@^7.4.2 (from ^7.4.0). Two correctness fixes to update()'s read-back: the post-update readiness poll and the final returned read now target the inactive version — the one the write just produced — instead of 'active' (which 404s for a never-activated object and returns stale content for an existing one). So reading back the result of an update() (including the CreateServiceDefinition create → update → activate flow) now returns the freshly written source. @mcp-abap-adt/interfaces stays ^9.2.0. Non-breaking; no tool contract changed.
  40. v8.8.0

    Changed 1

    Notes
    Changed
    • Migrated to @mcp-abap-adt/adt-clients@^7.4.0 (from ^7.3.1). 7.4.0 adds a session-scoped lock registry with an unlockAll() safety net, which complements the uninterruptible critical sections from 8.7.0 by making orphaned locks recoverable. @mcp-abap-adt/interfaces stays ^9.2.0. Non-breaking; no tool contract changed.
  41. v8.7.1

    Fixes 1

    Notes
    Fixed
    • CreateServiceDefinition created the object with an empty body. The service-definition create() POST only registers the shell (metadata: name/description/package) — it does not persist the define service … { … } source, and the handler had no follow-up write step (a regression in the underlying @mcp-abap-adt/adt-clients create flow, which used to run create → lock → update → unlock). The handler now runs an explicit high-level update() (lock → write source → unlock) right after create whenever source_code is provided, then activates — so the service definition is created with its real body. (Root cause is in adt-clients' AdtServiceDefinition.create(); this handler-side fix restores correct behavior immediately.)
  42. v8.7.0

    Changed 1, Fixes 1

    Notes
    Fixed
    • Timeouts no longer leave objects locked and inactive. A mutating tool (Create/Update/Delete) runs a stateful lock → modify → unlock chain as several separate ADT requests. On a slow system a short per-request timeout would abort one of them mid-flight; aborting the socket drops the stateful ADT session and orphans the lock handle, so the follow-up unlock has nothing to release — the object was left locked and inactive (whereas a normal error keeps the session alive, so unlock worked). Every high-level Create/Update/Delete handler (70 tools) is now wrapped in an uninterruptible critical section: beginCriticalSection() before it runs and endCriticalSection() in a finally, via @mcp-abap-adt/connection 1.10.0. While active, the connection raises the request timeout to a large ceiling (SAP_TIMEOUT_CRITICAL, default 600000 ms) so slow write requests run to completion instead of being interrupted. Feature-detected, so it degrades to a no-op on older connections.
    Changed
    • Migrated to @mcp-abap-adt/connection@^1.10.0 (from ^1.9.1), which adds the reference-counted beginCriticalSection() / endCriticalSection() primitives used above. Non-breaking.
  43. v8.6.1

    Changed 1

    Notes
    Changed
    • Declared the SAP connection environment variables in server.json (#142 follow-up). The MCP Registry entry carried no environmentVariables, so registry-based installers (e.g. FLUJO) launched the server with no configuration — it then ran in tool-inspection-only mode and the client timed out waiting for a usable connection. server.json now declares the connection variables (SAP_URL required; SAP_AUTH_TYPE, SAP_SYSTEM_TYPE with choices/defaults; SAP_CLIENT, SAP_JWT_TOKEN (secret), SAP_USERNAME, SAP_PASSWORD (secret), SAP_LANGUAGE, SAP_MASTER_SYSTEM, SAP_RESPONSIBLE), so registry clients can prompt for and inject them. No code change — the server already completes the MCP handshake without env vars (verified); this only surfaces the required configuration to installers. Republished to the MCP Registry via mcp-publisher.
  44. v8.6.0

    Features 1, Changed 1

    Notes
    Added
    • Message Class (MSAG / T100) CRUD tools. Exposes the new message-class client from @mcp-abap-adt/adt-clients 7.3.x as MCP tools:
      • ReadOnly group: ReadMessageClass (class header + all messages), ReadMessageClassMessage (one message by number).
      • HighLevel group: GetMessageClass, CreateMessageClass, UpdateMessageClass, DeleteMessageClass, and per-message GetMessageClassMessage, CreateMessageClassMessage, UpdateMessageClassMessage, DeleteMessageClassMessage.
      • Message classes are not activatable/versioned, so there are no Check/Activate/Versions tools for them. Transport handling matches the other CRUD objects (transport_request sent as corrNr; required for transportable packages). available_in: ['onprem', 'cloud'].
    Changed
    • Migrated to @mcp-abap-adt/adt-clients@^7.3.1 and @mcp-abap-adt/interfaces@^9.2.0 (from ^7.2.1 / ^9.1.0). 7.3.0 adds the Message Class client (getMessageClass() / getMessageClassMessage()); 7.3.1 wires transport_request (corrNr) from config. Non-breaking for existing tools.
  45. v8.5.2

    Security 1, Fixes 1

    Notes
    Fixed
    • npx -y @mcp-abap-adt/core (and any fresh install) crashed with Cannot find module 'yaml' (#143). The runtime config loader (src/lib/config/yamlConfig.ts) imported the yaml package, but yaml was declared only in devDependencies — so it was absent from the published tarball and every consumer install failed at startup. Consolidated all runtime YAML parsing onto js-yaml (already a runtime dependency, via load()), migrated the two integration-test helpers off yaml as well, and removed the yaml package entirely — one YAML library instead of two, and the runtime import now resolves from a declared dependency. Added yamlConfig unit tests to guard it.
    Security
    • Removed all dependency overrides — npm audit is 0 without any of them. The overrides added in 8.5.1 turned out to be crutches: a clean lockfile resolution already reaches patched versions within each maintainer's declared semver range, so forcing transitive versions (an untested parent/child combination) was both unnecessary and a maintenance liability. Dropped the entire overrides block (11 entries) and let the tree resolve in-range. The single dep that stayed stuck — esbuild (low, dev-only, pinned by tsx@4.21 to ~0.27.0) — was fixed the right way: by bumping the direct dev dependency we own, tsx ^4.21.0 → ^4.22.4, whose maintainer moved the pin to esbuild ~0.28.0 (includes the patched 0.28.1). No overrides remain; every fix now comes from a direct-dependency bump or in-range resolution. Verified: npm audit 0, build + test:check + 348 unit tests green. Dev-tooling only, not in the published tarball — no republish required.
  46. v8.5.1

    Security 2

    Notes
    Security
    • Supply-chain dependency remediation (0 shipped high/critical). Fixed the runtime vulnerabilities at their root by migrating to @mcp-abap-adt/adt-clients@^7.2.1 (its security release bumping axios ^1.18.1 + fast-xml-parser ^5.9.3), and bumping this package's own direct deps to match: axios ^1.14.0 → ^1.18.1, fast-xml-parser ^5.5.10 → ^5.9.3, js-yaml ^4.1.1 → ^4.3.0. Added overrides to pull patched transitive deps still on vulnerable versions: form-data ^4.0.6, hono ^4.12.27, @hono/node-server ^1.19.14, fast-uri ^3.1.3, ws ^8.21.0, ip-address ^10.2.0, qs ^6.15.3 (all within the same major — no breaking bumps). npm audit dropped from 57→5; the production (--omit=dev) tree carries no high/critical. The 5 residual advisories are dev-tooling only (MCP Inspector → shell-quote/concurrently, Jest → js-yaml@3, tsx → esbuild, @babel/core) and are not in the published tarball; shell-quote has no upstream patch yet (tracked).
    • Supply-chain hardening (low-noise). Added a CI security gate — npm audit --omit=dev --audit-level=high — that fails the build only on shipped high+/critical vulnerabilities, keeping signal high without nagging on dev/low/moderate noise. Post-install scripts intentionally left enabled (no dep in the tree ships install/postinstall scripts; local scripts and CI actions must keep working).
  47. v8.5.0

    Features 1, Changed 1

    Notes
    Added
    • Version history now surfaces the transport request (#30). GetObjectVersions / Get<Object>Versions now include transportRequest (e.g. DS4K901917) and transportDescription per version when the SAP version feed carries them — so an agent can map an object's versions to the transports that produced them, which is the "transports of an object" piece of #30. Comes from @mcp-abap-adt/adt-clients 7.2.0; no tool/argument change (the new fields are additive on each version entry).
    Changed
    • Migrated to @mcp-abap-adt/adt-clients@^7.2.0 and @mcp-abap-adt/interfaces@^9.1.0 (from ^7.1.0 / ^9.0.0). 7.2.0 parses the per-version transport-request link into the new optional IObjectVersion.transportRequest/transportDescription (requires interfaces ^9.1.0). Non-breaking.
  48. v8.4.0

    Features 1

    Notes
    Added
    • Version diff tools (#30). GetObjectVersionDiff (generic, ReadOnly) and per-object Get<Object>VersionDiff (HighLevel, 13 types — e.g. GetClassVersionDiff, GetProgramVersionDiff) return a unified diff between two object versions, given the two content_uris from a …Versions call. Each fetches both version sources and diffs them (jsdiff, 3 lines of context); the response includes identical and the unified diff. Per-object available_in mirrors the type's Get<Object> tool. Completes #30's "diff between two versions" (2b) alongside the version-history (8.2.0) and per-object high-level (8.3.0) tools.
  49. v8.3.0

    Features 1, Changed 1

    Notes
    Added
    • Per-object high-level version tools (#30). Added Get<Object>Versions and Get<Object>VersionSource for the 13 versioned object types (class, program, interface, function_group, function_module, table, structure, ddl, domain, data_element, package, behavior_definition, metadata_extension) to the HighLevel group — e.g. GetProgramVersions/GetProgramVersionSource, GetClassVersions/GetClassVersionSource. These mirror the per-object Get<Object> read tools (the way GetProgram coexists with the read-only ReadProgram), so development clients that run with the ReadOnly analytics group disabled still get object version history. Each tool's available_in matches its Get<Object> counterpart (e.g. GetProgramVersions is onprem/legacy only). The generic GetObjectVersions/GetObjectVersionSource in the ReadOnly group are unchanged.
    Changed
    • Migrated to @mcp-abap-adt/adt-clients@^7.1.0 (from ^7.0.1). 7.1.0 makes where-used result parsing namespace-prefix agnostic (usagereferences: vs usageReferences:), further hardening append detection in GetStructuresList (#128) on systems that emit the other prefix; it also adds per-group subpath exports (/core, /runtime, …) — not yet adopted here. Non-breaking; no tool contract changed.
  50. v8.2.0

    Features 1, Changed 1

    Notes
    Added
    • Object history tools (#30). Two read-only tools backed by @mcp-abap-adt/adt-clients 7.0.0's version-history API: GetObjectVersions (lists an object's SAP version history — versionId, author, updatedAt, title, and an opaque contentUri per entry) and GetObjectVersionSource (fetches the source of a specific version by its contentUri). Both dispatch by object_type across the same 13 object types as the generic object tools (class, program, interface, function_group, function_module, table, structure, ddl, domain, data_element, package, behavior_definition, metadata_extension); object types without a version endpoint return a clear UNSUPPORTED_OPERATION error rather than raw HTTP.
    Changed
    • Migrated to @mcp-abap-adt/adt-clients@^7.0.1 and @mcp-abap-adt/interfaces@^9.0.0 (from ^6.1.0 / ^7.2.0). 7.0.1 also makes where-used resilient on S/4 systems whose /usageReferences/scope sub-resource returns 404 — the library now falls back to an unscoped search with client-side type narrowing, hardening the append detection in GetStructuresList (#128) at the library level. No mcp-abap-adt tool contract changed by the migration.
  51. v8.1.1

    Fixes 1

    Notes
    Fixed
    • GetStructuresList no longer loses append structures on heavily-used objects (#128). The append (where-used) search now scopes to append structures only (enableOnlyTypes: ['TABL/DS']) instead of searching all types: where-used caps the number of returned records, so on a widely-referenced base (e.g. a standard S/4 table) the append records were crowded out by other reference types and dropped, yielding 0 appends. It also resolves the base object_type itself (try structure, fall back to table) so a table base no longer 404s on a structures URI, and it sets appends_unavailable: true when the where-used lookup fails for every object_type — so callers can tell "could not determine appends" apart from "no appends" instead of getting a silent 0.
  52. v8.1.0

    Features 5

    Notes
    Added
    • Per-call master_language parameter on the high-level Create* tools (#105). Optional; lets a caller set the master/original language of a single created object (e.g. "DE", "ZH"), overriding the session default. Threaded into the ADT client as config.masterLanguage. Covers class, program, interface, ddl, domain, structure, table, table type, data element, function group, service definition, service binding, behavior definition, metadata extension, package. When omitted, resolution falls back to the session language (SAP_LANGUAGE) then EN.
    • x-sap-language HTTP/SSE request header (#105). Per-request/per-session master-language override for created objects (precedence: tool parameter → x-sap-language header → SAP_LANGUAGE → EN). Carried in a request-scoped context (AsyncLocalStorage), not the process-global system-context cache, so it cannot leak across requests, sessions, or connection modes.
    • Compact facade create/update/delete now expose every routed object type's required arguments. Previously many type-specific args (e.g. table_name, structure_name, service_definition_name, ddl_code, lock_handle, implementation_code, properties, container_class, test_class, cds_view_name, …) were absent from the compact schemas, so schema-driven MCP clients could not construct valid calls for most object types. All missing properties have been added as optional properties to compactCreateSchema, compactUpdateSchema, and compactDeleteSchema. Non-breaking (additive optional properties; compact schema required stays ['object_type']; no routed handler changed).
    • Runtime guard compactSchemaCompleteness.test.ts — fails CI if any routed handler's top-level required arg is not present in the corresponding compact schema, preventing future drift.
    • Corrected compact facade descriptions for HandlerCreate: UNIT_TEST now advertises container_class*/test_class* and CDS_UNIT_TEST now advertises class_name*/package_name*/cds_view_name* (previously both incorrectly showed run_id*, which is an update/delete arg).
  53. v8.0.0dated in the atlas

    Breaking 3

    Notes
    Changed (BREAKING)
    • View DDL-source tools renamed to Ddl. Migrated to @mcp-abap-adt/adt-clients@^6.0.0, whose misnamed getView() client became the generic DDL-source client getDdl() (/sap/bc/adt/ddic/ddl/sources/ — CDS views, AMDP table functions, other DDL sources). MCP tools renamed: GetView→GetDdl, ReadView→ReadDdl, CreateView→CreateDdl, UpdateView→UpdateDdl, DeleteView→DeleteDdl, CheckView→CheckDdl, ActivateView→ActivateDdl, and the *ViewLow variants → *DdlLow. The tool argument view_name is now ddl_name.
    • Generic object tools (LockObject/UnlockObject/CheckObject/ValidateObject/DeleteObject): the object_type value "view" is renamed to "ddl" (removed, no alias).
    • Compact facade (HandlerGet/HandlerCreate/… ): the object_type value VIEW is renamed to DDL and its view_name argument to ddl_name (removed, no alias).
  54. v7.2.1

    Fixes 1

    Notes
    Fixed
    • GetStructuresList now returns extension (append) structures in the tree. Calling it with just a structure name (e.g. ZMCP_SHR_STRU) returns a hierarchy containing both the included structure (kind: include) and the appending structure that extend type <this> with … (kind: append). Previously appends were missed: the where-used lookup used the default scope (some object types unselected, so the extension's type was excluded) and a fragile displayName/globalType regex. It now uses getWhereUsedList({ enableAllTypes: true }) and the parsed references. Also removed dead .APPEND source-parsing (ADT never emits .APPEND in a structure's source — appends are separate extend type objects). Verified end-to-end on a real system.
  55. v7.2.0

    Features 1

    Notes
    Added
    • DNS-rebinding protection for the HTTP/SSE transports. --http-allowed-hosts/--sse-allowed-hosts, --http-allowed-origins/--sse-allowed-origins, and --http-enable-dns-protection/--sse-enable-dns-protection (plus the matching MCP_HTTP_*/MCP_SSE_* env vars and the http/sse allowed-hosts/allowed-origins/enable-dns-protection YAML keys) now take effect: when enabled with an allowlist, requests with a non-allowlisted Host/Origin header are rejected with HTTP 403. Transport-aware (http uses http*, sse uses sse*); applied in registerRoutes so both standalone and embedded modes are protected; /mcp/health is ungated. Defaults off (no-op). This is Host/Origin allowlist validation, NOT browser CORS. Values are matched exactly (Host includes port, e.g. localhost:3000). Implemented as own Express middleware rather than the now-deprecated SDK transport options.
  56. v7.1.3

    Fixes 3

    Notes

    Fixes the SSE transport host/port resolution, removes a broken CLI bin entry, and corrects the install documentation (README.md/docs/ ship in files, so the doc fixes reach npm users with this release).

    Fixed
    • SSE transport now honours its own host/port. --transport=sse resolved the final host/port from the HTTP defaults/flags (httpHost/httpPort, so it listened on 3000), ignoring --sse-host/--sse-port and MCP_SSE_HOST/MCP_SSE_PORT. Host/port resolution is now transport-aware: SSE falls back to the SSE defaults (host 127.0.0.1, port 3001) and its --sse-* flags / MCP_SSE_* env vars take effect; the generic --host/--port still win when provided. (src/lib/config/ServerConfigManager.ts.)
    • Removed the dangling mcp-abap-adt-v2 bin entry. package.json/package-lock.json still mapped mcp-abap-adt-v2 → bin/mcp-abap-adt-v2.js, a file removed in an earlier release, so a global install created a broken mcp-abap-adt-v2 symlink. The only binary is mcp-abap-adt. Also fixed a stale mcp-abap-adt-v2 example in tools/show-storage-paths.js help output.
    • Documentation accuracy sweep:
      • Install docs/README used the wrong npm scope @fr0ster/mcp-abap-adt; corrected to @mcp-abap-adt/core (the GitHub repo path and the io.github.fr0ster/… registry id are unchanged).
      • Stale local-pack tarball names / version pins (…-1.1.0/1.2.0.tgz) → version-independent mcp-abap-adt-core-<version>.tgz (so they no longer drift); Node requirement 18 → 22+ (matches engines); removed the non-existent npm run start:legacy / dev:stdio references and the bin/mcp-abap-adt-v2.js run examples.
      • Corrected narrative drift: default transport is stdio (not HTTP); HTTP/SSE host default is 127.0.0.1 (not 0.0.0.0); removed non-existent --http/--sse CLI shortcuts; refreshed handler-group tool counts and the src/handlers/ directory list; the system handler group rides with readonly (it is not always included).
      • Regenerated docs/user-guide/AVAILABLE_TOOLS*.md from the current TOOL_DEFINITIONs (function-include tools, GetStructuresList, GetIncludesList tree, GetProgFullCode removal, refreshed counts).
      • Removed documentation for the CORS / allowed-hosts / DNS-rebinding-protection options (--http/--sse-allowed-origins/-allowed-hosts/-enable-dns-protection, the matching MCP_* env vars, and the http/sse allowed-origins/allowed-hosts/enable-dns-protection YAML keys): they are parsed but not wired to the servers, so documenting them overstated capability. Actually implementing these is planned for a follow-up release.
  57. v7.1.2

    Fixes 4

    Notes

    Test infrastructure only. The published runtime (dist/) is unchanged from 7.1.1 — no tool, handler, or library behaviour changed for consumers. This release bundles integration-test reliability fixes and a stale-metadata fix.

    Fixed
    • shared:setup resilient to the cloud activation-run timeout. Bulk-activating the ~24 shared objects in one activation run can exceed adt-clients' fixed ~45s request timeout; on a timeout the setup now skips the doomed full-group retries and falls back to batched activation (chunks of 5, with a second pass retrying only the leftovers) instead of failing.
    • Runtime profiling test produces a trace. The runnable class now does measurable CPU work so the profiler arms and a trace is written; a trivial single-statement class finished before tracing engaged, so the trace never resolved (Failed to resolve traceId).
    • Runtime dump test actually creates and reads a dump (was silently skipping or hitting a 400 session error). It activates the division-by-zero class, triggers the dump on a separate connection (so the dumping run's server-side context loss does not poison the connection used to read the dump), fixes dump-id extraction from the feed entry (id field, plural /runtime/dumps/<id> path, URL-encoded, no decode), binds the read dump to the uniquely-named generated class, and fails (instead of skipping) when no dump is produced. params.dump_id remains the explicit opt-out for read-only environments.
    • server.json version was stale at 7.0.3; bumped to match the package version.
  58. v7.1.1

    Fixes 2

    Notes
    Fixed
    • UpdateFunctionInclude now supports an activate parameter (default false) — when true, the include is activated after the source update so the new source becomes the active version, mirroring UpdateFunctionModule. Previously the tool always left the updated source inactive.
    • Integration-test + test-config.yaml.template fixes for the new function-include / structure tools (verified end-to-end on a real system): the FUGR-include lifecycle update source is now a valid declaration (a bare executable statement in an include caused "Statement is not accessible"); the ListFunctionGroupIncludes test no longer asserts the generated L<FUGR>UXX collector is absent (the tool faithfully returns ALL FUGR/I includes — collector filtering is a backup-tool concern); the template seeds the shared ZMCP_SHR_FGRP / Z_MCP_SHR_FM the function-include tests reference.
  59. v7.1.0

    Features 3, Changed 1

    Notes
    Added
    • Function-group include tools. Six new MCP tools expose ADT function-group include operations:
      • Read-only: ReadFunctionInclude (source + metadata), ListFunctionGroupIncludes (a FUGR's includes — TOP + custom), ListFunctionModules (a FUGR's function modules).
      • High-level: CreateFunctionInclude, UpdateFunctionInclude, DeleteFunctionInclude.
      • The list tools surface the new adt-clients getUtils().listFunctionGroupIncludes() / listFunctionModules() (nodestructure drill-down). DeleteFunctionInclude surfaces SAP's refusal message when an include can only be deleted via the Function Builder.
    • Integration tests (FunctionIncludeReadOnlyHandlers, FunctionIncludeHighHandlers) and tests/test-config.yaml.template cases for the new tools.
    • Structure embedding tools (tree output). New read-only GetStructuresList — recursively lists the structures embedded in an ABAP structure (or table) as a TREE. Each node records the embedded structure name, the attribute it is embedded under (named include attr : include X; vs anonymous include X; → null), and kind: include (from the source) or append (an extension found via where-used whose source is extend type <this> with …). Includes come from the DDL/classic source (include X;, .INCLUDE/.APPEND); appends are resolved via where-used (toggle with include_extensions, default true). Cycle-guarded. GetIncludesList reworked from a flat list to a recursive TREE (each include may have child includes; cycle-guarded, depth-capped). Integration test GetStructuresListHandler + tests/test-config.yaml.template case (shared structures ZMCP_SHR_STRU / ZMCP_SHR_STRU_INC).
    Changed
    • Bumped @mcp-abap-adt/adt-clients from ^5.6.0 to ^5.8.0 (adds listFunctionModules/listFunctionGroupIncludes; getFunctionInclude().read() returns source; delete() surfaces server-refused deletions). Clean registry install (no link:true/file:).
  60. v7.0.3

    Changed 1, Other 1

    Notes
    Changed
    • Bumped @mcp-abap-adt/adt-clients from ^5.5.0 to ^5.6.0. Picks up: package create now honours the configurable master language (so CreatePackage stamps the session SAP_LANGUAGE like the other object types — #105), and the behaviorDefinition namespace URL-encoding fix (namespaced /NSP/… behavior definitions can now be read/locked/updated/activated/checked/unlocked/deleted). Clean registry install (no link:true/file: in the lockfile).
    Note
    • Per-call master_language tool parameter on Create* and the inbound X-SAP-Language header override are a planned follow-up; this release wires the session-language default to package as well.
  61. v7.0.2

    Features 1, Changed 1, Other 1

    Notes
    Added
    • Created objects now adopt the logon language as their master/original language. getSystemContext() reads SAP_LANGUAGE into masterLanguage, and createAdtClient() forwards it to the ADT client, so every Create* tool stamps adtcore:masterLanguage/adtcore:language with the session language instead of always EN. When SAP_LANGUAGE is unset the client still defaults to EN. Requires the configured language to be installed on the target system (otherwise SAP normalizes it to the system default). (#105)
    Changed
    • Bumped @mcp-abap-adt/adt-clients from ^5.4.4 to ^5.5.0 for the configurable-master-language support. Clean registry install (no link:true/file: in the lockfile).
    Note
    • A per-call master_language parameter on the high-level Create* tool schemas (to override the session language per object) is planned as a follow-up; the underlying client already supports config.masterLanguage.
  62. v7.0.1

    Fixes 1

    Notes
    Fixed
    • Bumped @mcp-abap-adt/adt-clients from ^5.4.2 to ^5.4.4 to pick up the stateful-lock-chain fix. Object writes (e.g. UpdateClass) no longer fail with HTTP 423 — not locked (invalid lock handle) on older ABAP kernels (e.g. BASIS 7.55) over HTTP: the client now keeps the connection stateful for the whole lock → check → update → unlock chain across all object types, instead of switching back to stateless before the lock-bound PUT. Newer kernels (758/816) already tolerated the stateless write. No server-side API or tool-schema change. (#106)
  63. v7.0.0

    Breaking 1, Changed 1

    Notes
    Changed
    • BREAKING: EmbeddableMcpServer now applies ReadVsGetDedupStrategy by default. Previously the embeddable server defaulted to NoDedupStrategy, so exposing readonly + high together surfaced both Read<X> and Get<X> for the same operation (e.g. ReadProgram + GetProgram), leaving tool selection up to the client. Embedders (e.g. cloud-llm-hub) now get the same single-tool-per-operation view as the standalone launcher. Consumers that relied on both variants being exposed must opt out by passing readOnlyDedupStrategy: new NoDedupStrategy(). No code-level API change — the difference is the set of tools exposed to clients.
  64. v6.11.3dated in the atlas

    Removed 1, Changed 1

    Notes
    Removed
    • GetProgFullCode removed as a redundant read path. A program or function group with includes can already be read through one canonical path — ReadProgram / ReadFunctionGroup (main) → GetIncludesList → GetInclude per include — and an explicit GetInclude is required anyway (includes can live outside any single object's tree). Two overlapping strategies made LLM and RAG-based tool selection non-deterministic. The piecemeal path covers function groups as well (GetIncludesList accepts FUGR), so no capability is lost. (#100)
    Changed
    • Sharpened source-read tool descriptions for clearer LLM / semantic tool selection. ReadProgram now reads as "Read a MAIN ABAP program … NOT for includes — use GetInclude" (dropping the misleading "Create, Update" lead-in on a read-only tool), and GetInclude now reads as "Read ANY single ABAP include source by name, from anywhere in the repository … the correct tool for include names (PROG/I)". GetIncludesList is unchanged. (#100)
  65. v6.11.2

    Fixes 1

    Notes
    Fixed
    • ReadProgram could still return a silent { success: true, source_code: null } for non-main-program names. The 6.11.1 heuristic only fired when both source and metadata came back empty; when the programs endpoint returned metadata for a non-PROG/P object (e.g. an include) the response slipped through to a misleading success: true with source_code: null. ReadProgram now parses adtcore:type from the metadata and rejects anything other than a main program (PROG/P) with a structured { success: false, error: "invalid_object_type", object_type, message }. No tool redirect is emitted — choosing the right tool is the consumer's decision. The tool description now states it works only for main programs (PROG/P). (#91)
  66. v6.11.1

    Fixes 2

    Notes
    Fixed
    • ReadProgram returned a silent { success: true, source_code: null } for include names. LLM agents loading a report's full source call ReadProgram on the include names returned by GetIncludesList, get null, read it as a permission/inactive-object problem rather than "wrong tool", and stall. A readable main program (PROG/P) always returns source; when both source and metadata come back empty the name is an include (PROG/I). ReadProgram now returns a structured { success: false, error: "include_name_passed", suggestion: "GetInclude(\"<name>\")" } so the caller gets an actionable signal at the point of failure. (#91)
    • GetProgFullCode read include source via the wrong content key. handleGetInclude returns { type: 'text', text }, but two call sites read c.data: the recursive collectIncludes helper (so nested includes were never discovered) and the FUGR branch (so every function-group include came back as code: null). All sites now read c.text. (#91)
  67. v6.11.0

    Removed 1, Features 1, Changed 2, Fixes 1

    Notes
    Added
    • LockObjectLow / UnlockObjectLow now support function_module. The schema already advertised function_module with GROUP|FM_NAME name format, but the handlers returned an unsupported-operation error. Both now parse the compound name and delegate to client.getFunctionModule().lock()/unlock(). (#90)
    Changed
    • Tool descriptions aligned with implementation. GetWhereUsed now lists the closed set of supported object_type values, documents the GROUP|FM_NAME format for function modules, clarifies that view = CDS DDL source (classic DDIC views unsupported), and warns about the cost of enable_all_types. DeleteObjectLow and the four compact Handler{Create,Delete,Get,Update} tools annotate PROGRAM as [onprem/legacy only] in their descriptions (no available_in change — the rest of the supported types are still valid on cloud). GetIncludesList spells out the four supported object_type enum values. (#90)
    • server.json version synced to package.json. It had drifted to 6.8.0; now follows the package version.
    Removed
    • Dead filePath / writeResultToFile plumbing. A side-channel filePath argument was accepted (often without being declared in inputSchema) by ten read-only handlers — GetInclude, GetIncludesList, GetSqlQuery, GetEnhancements, GetEnhancementSpot, GetEnhancementImpl, GetPackageContents, GetAbapAST, GetAbapSemanticAnalysis, GetAbapSystemSymbols. The lib comment claimed it sandboxed writes to ./output, but did no validation — any absolute path the process could write to was accepted. The feature was no longer relied on, so it has been removed along with src/lib/writeResultToFile.ts. (#90)
    Fixed
    • tools/generate-tools-docs.js truncated descriptions at the first inner quote. Both the per-property and the top-level extractor used /description\s*:\s*['"]([^'"]+)['"]/, which stops the capture at the first ' or " of any kind. New descriptions added in this release would have rendered as Answers:, form, Supported values: etc. in AVAILABLE_TOOLS*.md. Switched to a quote-balanced pattern with an unescape pass. (#90)
  68. v6.10.0

    Features 1, Changed 2, Other 1

    Notes
    Added
    • SearchSource time budget → partial results. New time_budget_ms argument: when the internal scan deadline is exceeded, the tool returns the hits gathered so far instead of nothing, with a new truncated.by_timeout: true flag. Set it below your client/transport timeout (e.g. 540000 for a 600s client). Addresses the "no partial result after 600s" failure in #89.
    Changed
    • SearchSource default max_hits_per_object raised 1 → 100. The old default of 1 silently capped per-object hits and tripped truncated.by_object_cap on any object with ≥2 matches. (#89)
    • SearchSource tool description clarified: by_object_cap means a per-object hit cap was reached (raise max_hits_per_object), NOT a limit on the number of objects scanned (that is max_objects / by_max_objects). Added concurrency guidance — concurrency is capped at 16 per call; run only ONE SearchSource per destination at a time (parallel calls saturate the SAP scan backend). (#89)
    Notes
    • The remaining #89 item — pushing the substring filter to a server-side ADT source-search endpoint (the only change that makes large-package scans fast rather than just interruptible) — is deferred pending live-system research into the ADT API.
  69. v6.9.2

    Docs 1

    Notes
    Documentation
    • README: document the NTLM hard-reject behavior in the Kerberos auth section (carries the connection 1.9.1 change to the published package).
  70. v6.9.1

    Changed 1

    Notes
    Changed
    • Bump @mcp-abap-adt/connection to ^1.9.1, which adds NTLM hard-reject for Kerberos auth: a Kerberos connection now fails with a clear error if the SAP system offers NTLM (instead of silently swallowing the 401). Only Kerberos/SPNEGO is accepted.
  71. v6.9.0dated in the atlas

    Breaking, Features 1

    Notes
    Added
    • Certificate (mTLS) and Kerberos (SPNEGO) authentication for on-prem HTTP connections. Select via SAP_AUTH_TYPE=certificate or SAP_AUTH_TYPE=kerberos.
      • Certificate: PEM (SAP_CERT_PATH + SAP_CERT_KEY_PATH) or PKCS#12 (SAP_CERT_PFX_PATH + optional SAP_CERT_PASSPHRASE). mTLS — no SAP_USERNAME/SAP_PASSWORD needed.
      • Kerberos: single-leg Negotiate via the optional kerberos native package; SPN from SAP_KERBEROS_SPN (or derived HTTP@<host>, service class via SAP_KERBEROS_SERVICE). Requires a valid TGT (kinit) or keytab on the host.
      • Both bypass the auth-broker (no browser/OAuth flow). Requires @mcp-abap-adt/interfaces@^7.2.0, @mcp-abap-adt/connection@^1.9.0.
    ⚠️ Help wanted — not yet validated on a live system

    These two auth paths pass full unit coverage but have not been exercised against a real SAP system. If you have an on-prem system with client-certificate or Kerberos/SPNEGO SSO, please try SAP_AUTH_TYPE=certificate/kerberos and report results — especially whether Kerberos works with a single-leg Negotiate token or your system requires mutual-auth continuation. Please open an issue with what you see; feedback will confirm or refine these flows.

  72. v6.8.0

    Changed 2, Other 1

    Notes
    Changed
    • SearchSource.packages now accepts ABAP * masks (e.g. Z*, ZFI_*, /NS/Z*) alongside exact dev-class names. Mask resolution reuses the same ADT informationsystem/search path that SearchObject uses for objectType=DEVC. Mask resolution is best-effort and capped at the ADT result window (1000 references); for certainty, pass concrete package names. The tool description carries the no-guarantees disclosure so an LLM caller sees it.
    • SearchSource.scanned.packages now reports the resolved-and-deduplicated starting-package count (post-mask resolution, post-dedup) instead of raw input length. Closes #87.
    Notes
    • + (single-char) wildcard probed against an on-premise system and found unsupported by the ADT informationsystem/search endpoint (despite SAP CP-pattern docs). Dropped from public examples; the resolver still detects + for forward-compat with backends that may honor it.
  73. v6.7.0

    Features 1, Other 3

    Notes
    Added
    • SearchSource (onprem + legacy) — source-text search across ABAP repository objects without invoking SUBMIT AFX_CODE_SCANNER. Scans programs (PROG/P), function groups (FUGR/F, including FMs and FUGR/PU/PD/PT/PY auto-includes), and classes (CLAS/OC, main include only in v1). Supports AND-query, up to three exclusion substrings, per-object hit cap, comment-line skip, subpackage recursion, object-name glob filter, bounded parallelism (default 8, max 16), and a deterministic truncated signal (by_object_cap / by_max_objects). Cloud is explicitly out of scope (no indexed source-search endpoint; see fr0ster/mcp-abap-adt-clients#36). Closes #79.
    Notes
    • Comments are searched by default. Set exclude_comments=true to drop col-1 * and full-line " comment lines (AFX-strict semantics; whitespace-prefixed * is not a comment in ABAP and is never skipped).
    • The version parameter affects PROG and CLAS main include reads only — FUGR subincludes are always read against the active version because the include endpoint exposes no version selector.
    • Class subincludes (CCDEF / CCMAC / CCAU / CCIMP / CCPUBLIC) are not yet enumerable through the MCP surface and are out of scope for v1; tracked as a follow-up.
  74. v6.6.0dated in the atlas

    Deprecated 1, Features 2

    Notes
    Added
    • RuntimeRunClass (onprem + cloud) — execute an ABAP class implementing if_oo_adt_classrun and return its stdout text. Optional profile=true runs through the profiler and additionally surfaces profile.profiler_id / profile.trace_id (same retry tunables as the previous tool).
    • RuntimeRunProgram (onprem only) — execute an ABAP report and return its stdout text. Optional profile=true starts a profiler trace; trace must be located afterwards via RuntimeListProfilerTraceFiles (program execution is fire-and-forget).
    Deprecated
    • RuntimeRunClassWithProfiling and RuntimeRunProgramWithProfiling are now marked deprecated in their tool descriptions. They remain functional. Prefer the unified RuntimeRunClass / RuntimeRunProgram with profile=true. Scheduled for removal in a future major release.
  75. v6.5.3

    Features 1

    Notes
    Added
    • RuntimeRunClassWithProfiling now accepts optional max_trace_attempts, trace_retry_delay_ms, and trace_lookup_uris so callers can tune trace-resolution polling. Defaults remain 5 attempts × 2000 ms; raise on slow systems (e.g. SAP trial cloud) where the trace file is written with extra delay. Closes #82.
  76. v6.5.2

    Fixes 1

    Notes
    Fixed
    • Bump @mcp-abap-adt/adt-clients to ^5.4.2 and @mcp-abap-adt/connection to ^1.8.1 to pick up the stale-CSRF 401 recovery fix (fr0ster/mcp-abap-connection#7, originally reported as #78). On on-prem SAP with basic auth, a cached-but-stale CSRF token caused POST/PUT/DELETE to fail permanently with 401 + login HTML; the connection layer now detects this case, clears the token/cookies, and retries.
  77. v6.5.1

    Docs 1

    Notes
    Documentation
    • README: mention optional readOnlyDedupStrategy in the EmbeddableMcpServer example and link to the dedup strategies section in HANDLERS_MANAGEMENT.md.
  78. v6.5.0

    Features 3, Changed 1, Fixes 1, Other 2

    Notes
    Added
    • EmbeddableMcpServer accepts optional readOnlyDedupStrategy: IReadOnlyDedupStrategy to hide duplicate Read<X> readonly handlers when another group (HighLevel / LowLevel / Compact) contributes an equivalent. Ships two implementations: NoDedupStrategy (default — no behavior change for existing consumers) and ReadVsGetDedupStrategy (hides Read<X> when Get<X> is exposed). Consumers can also plug custom strategies for role-based rules. Exported from @mcp-abap-adt/core/handlers and @mcp-abap-adt/core/server.
    • CLI launcher validates --exposition — compact must be exposed alone, high and low are mutually exclusive. Fails fast at startup with a clear error on misconfiguration.
    • Diagnostic scripts scripts/read-fm.ts and scripts/probe-fm.ts for reproducing function-module endpoint quirks against an arbitrary env file.
    Changed
    • CLI launcher opts into ReadVsGetDedupStrategy by default, so running the shipped server no longer exposes duplicate pairs like ReadFunctionModule + GetFunctionModule when both readonly and high are enabled.
    Fixed
    • ReadFunctionModule / GetFunctionModule: ADT resolves function modules by name regardless of the group segment in the URL, so passing a wrong group silently returned the correct FM's source and echoed the wrong group back in the response. Both handlers now read metadata first, verify the owning group via <adtcore:containerRef/>, and reject mismatches with an explicit error (Function module X belongs to group Y, not Z.). The response now echoes the real group from metadata rather than the input.
    Backward compatibility
    • EmbeddableMcpServer default dedup is NoDedupStrategy — existing consumers see no change in exposed tool sets until they explicitly pass a strategy.
    • Invalid --exposition combinations previously silently produced duplicate-tool errors at runtime or exposed overlapping handlers; they now fail at startup with an explicit message.
  79. v6.4.1

    Fixes 5

    Notes
    Fixed
    • docker/Dockerfile: switch default MCP_TRANSPORT from stdio to http so the image runs as an HTTP MCP server out of the box
    • docker/Dockerfile: correct launcher path (dist/server/v2/launcher.js → dist/server/launcher.js) — previous path did not exist and prevented the container from starting
    • docker/Dockerfile: pass --allow-destination-header so callers can supply SAP connection parameters via request headers (no default SAP connection baked into the image)
    • docker/Dockerfile: fix healthcheck endpoint (/health → /mcp/health) to match the actual route exposed by StreamableHttpServer
    • docker/Dockerfile: use npm run build:fast in the builder stage — the full build runs Biome which requires .gitignore, but .dockerignore excludes it
  80. v6.4.0

    Features 2, Changed 1, Other 1

    Notes
    Added
    • EmbeddableMcpServer accepts a new optional systemType: 'onprem' | 'cloud' | 'legacy' option. When set, it overrides the process-global SAP_SYSTEM_TYPE env var for the available_in tool filter of that server instance only. Enables hosts that serve multiple SAP systems per request (e.g., proxies routing to a mix of on-premise Cloud Connector and cloud-hosted destinations) to register the correct tool set per instance without mutating process.env. (#69)
    • BaseMcpServer constructor accepts the same systemType option for subclasses.
    Changed
    • Filter-skip debug log now reports whether the resolved system type came from the per-instance option or the env var (source=option / source=env).
    Backward compatibility
    • systemType is optional; omitting it preserves the previous behavior (env var fallback, default cloud). No consumer changes required.
  81. v6.3.1

    Changed 1

    Notes
    Changed
    • Bumped @mcp-abap-adt/adt-clients to ^5.2.1 to drop transitive node-rfc dependency in favor of @mcp-abap-adt/sap-rfc-lite
  82. v6.3.0

    Changed 2

    Notes
    Changed
    • Enriched all CRUD tool descriptions (Create, Read, Update, Activate) with structured Operation/Subject context for improved RAG-based tool selection (#66)
    • Unified description format across all 13 object types: Class, Program, Table, View, Domain, DataElement, Interface, FunctionModule, BehaviorDefinition, MetadataExtension, ServiceDefinition, ServiceBinding, Structure
  83. v6.2.2

    Features 2

    Notes
    Added
    • Debug and tracing scripts: debug-profiling, get-dump, list-dumps, list-traces
    • All scripts accept CLI parameters (--class, --user, --id, etc.) and support --help
  84. v6.2.1

    Fixes 1, Docs 2

    Notes
    Fixed
    • SAP_SYSTEM_TYPE was commented out in .env.example — users on on-premise systems silently lost access to Program tools because the default is cloud
    Documentation
    • Added SAP_SYSTEM_TYPE to all .env examples in installation and CLI docs
    • Added SAP_SYSTEM_TYPE to SAP Connection environment variables reference
  85. v6.2.0

    Changed 2

    Notes
    Changed
    • Updated @mcp-abap-adt/connection to ^1.8.0
    • Updated @mcp-abap-adt/adt-clients to ^5.1.0
  86. v6.1.0

    Changed 1

    Notes
    Changed
    • RFC connection is no longer tied to legacy systems — it is now a general-purpose connection transport for any on-prem system with SAP NW RFC SDK (closes #63)
  87. v6.0.0dated in the atlas

    Breaking 2, Changed 2, Fixes 1

    Notes
    Breaking Changes
    • Removed RuntimeListDumps tool — use RuntimeListFeeds(feed_type='dumps') instead, which uses the same ADT feed API as Eclipse and avoids timezone issues
    • Removed datetime and user parameters from RuntimeGetDumpById — dump_id is now the only required parameter; get dump IDs from RuntimeListFeeds first
    Fixed
    • UpdateInterface fails with "Parameter corrNr could not be found" on SAP BTP ABAP Cloud — added missing transportRequest parameter to interface update call (closes #61)
    Changed
    • RuntimeGetDumpById simplified: only accepts dump_id (from RuntimeListFeeds), no more datetime+user search with ±60s window (closes #62)
    • Compact HandlerDumpList now delegates to RuntimeListFeeds instead of removed RuntimeListDumps
  88. v5.2.0

    Features 5, Changed 4

    Notes
    Added
    • ActivateServiceDefinition handler (high + low level) for standalone activation of ABAP service definitions (closes #60)
    • ActivateServiceBinding handler (high + low level) for standalone activation of ABAP service bindings (closes #60)
    • Integration test for ServiceBinding high-level handlers (create with variant, activate, delete)
    • Shared service definition ZMCP_SHR_SRVD01 in shared_dependencies for binding tests
    • Service definitions support in shared:setup and shared:teardown
    Changed
    • CreateServiceBinding: replaced binding_type (ODataV2/ODataV4) with binding_variant using ServiceBindingVariant type — supports ODATA_V2_UI, ODATA_V2_WEB_API, ODATA_V4_UI, ODATA_V4_WEB_API; default changed to ODATA_V4_UI (Fiori Elements) (closes #59, #60)
    • UpdateServiceBinding: replaced service_type with binding_variant for consistent API
    • Upgraded @mcp-abap-adt/interfaces to ^7.0.0, @mcp-abap-adt/adt-clients to ^5.0.0
    • Long test timeout increased from 400s to 600s
  89. v5.1.1

    Changed 1, Fixes 1

    Notes
    Fixed
    • Update handlers must unlock objects in finally block on failure — UpdateBehaviorDefinition and UpdateMetadataExtension now wrap lock/update/unlock in try/finally to prevent stale locks when update fails (closes #57)
    Changed
    • Upgraded @mcp-abap-adt/adt-clients to 4.0.5
  90. v5.1.0

    Features 4

    Notes
    Added
    • 13 high-level Check tools for standalone syntax/semantic validation of ABAP objects (closes #54): CheckBehaviorDefinition, CheckClass, CheckDataElement, CheckDomain, CheckFunctionGroup, CheckFunctionModule, CheckInterface, CheckMetadataExtension, CheckPackage, CheckProgram, CheckStructure, CheckTable, CheckView
    • Normalized response contract for Check tools: shared object_name field, session_id/session_state stripped from output
    • normalizeCheckResponse utility for consistent Check tool response normalization
    • Integration tests for all 13 Check handlers with step-by-step logging
  91. v5.0.11

    Changed 1, Fixes 1

    Notes
    Fixed
    • IAbapConnection.connect() is now part of the interface contract — CSRF token and session cookies are established before any ADT requests, removing unsafe as any cast (relates to #53)
    Changed
    • Upgraded @mcp-abap-adt/interfaces to ^6.1.0, @mcp-abap-adt/connection to ^1.6.0
  92. v5.0.10

    Changed 1

    Notes
    Changed
    • Dropped Node 20 support, minimum is now Node 22
  93. v5.0.9

    Fixes 1

    Notes
    Fixed
    • Skip syntax check when saving without activation (activate=false) — allows uploading source code with unresolved dependencies (e.g., CDS views with circular references) for later mass activation via ActivateObjects (closes #52)
  94. v5.0.8

    Features 4, Fixes 1

    Notes
    Added
    • ActivateObjects tool for group/mass activation of any ABAP object types (closes #49)
    • Per-type Activate tools in high-level group: ActivateDomain, ActivateDataElement, ActivateTable, ActivateStructure, ActivateView, ActivateClass, ActivateInterface, ActivateProgram, ActivateFunctionModule, ActivateFunctionGroup, ActivateBehaviorDefinition, ActivateMetadataExtension
    • Uncommented ActivateObjectLow in low-level group
    • Known Limitations section in architecture docs — parallel write tool calls are not supported
    Fixed
    • Objects not activating after creation — added long polling wait between unlock and activate in 10 high-level handlers (closes #47)
  95. v5.0.6

    Changed 3, Fixes 2

    Notes
    Fixed
    • Stdio mode: log output no longer leaks into stdout (reserved for JSON-RPC protocol) — all logger fallbacks now use stderr or noop (closes #46)
    • CSRF token errors on write operations — resolveSystemContext was making HTTP requests on the handler connection, invalidating cookies/CSRF token (closes #45)
    Changed
    • Breaking: Removed resolveSystemContext and resetSystemContextCache from public API — these were internal functions that mutated connection state. Use getSystemContext() (read-only) and setSystemContext() instead.
    • EmbeddableMcpServer now accepts systemContext option to set system info without HTTP calls
    • BaseMcpServer no longer calls resolveSystemContext on the handler connection — system context is resolved once at startup on a temporary connection
  96. v5.0.4

    Features 1, Changed 1

    Notes
    Added
    • Export resolveSystemContext, getSystemContext, resetSystemContextCache and IAdtSystemContext from @mcp-abap-adt/core/utils for consumers that bypass BaseMcpServer (closes #44)
    Changed
    • Updated @mcp-abap-adt/adt-clients to 4.0.4 — validates responsible person before create to prevent cryptic SAP error
  97. v5.0.3

    Changed 1

    Notes
    Changed
    • Updated @mcp-abap-adt/adt-clients to 4.0.3 — fixes $TMP and similar local package handling
  98. v5.0.2

    Changed 1

    Notes
    Changed
    • Improved tool descriptions for RAG search quality — front-loaded action verbs, added "Answers:" trigger phrases for GetWhereUsed, SearchObject, and all Read* handlers (closes #42)
  99. v5.0.1

    Removed 1

    Notes
    Removed
    • Compact wrappers HandlerFeedList, HandlerSystemMessageList, HandlerGatewayErrorList — duplicated system-level tools, causing LLM tool selection confusion
  100. v5.0.0

    Features 4, Changed 1

    Notes
    Changed
    • Migrated to @mcp-abap-adt/adt-clients 4.0.0 factory API (getProfiler(), getDumps(), getFeeds())
    Added
    • RuntimeListFeeds — unified ADT feed reader (descriptors, variants, dumps, system messages, gateway errors)
    • RuntimeListSystemMessages — SM02 system messages
    • RuntimeGetGatewayErrorLog — SAP Gateway error log (/IWFND/ERROR_LOG) with detail view
    • Compact wrappers: HandlerFeedList, HandlerSystemMessageList, HandlerGatewayErrorList
  101. v4.9.0dated in the atlas

    Removed 1, Changed 1

    Notes
    Changed
    • Merged RuntimeAnalyzeDump into RuntimeGetDumpById — one tool with response_mode parameter (payload, summary, both) instead of two overlapping tools
    Removed
    • RuntimeAnalyzeDump tool (use RuntimeGetDumpById with response_mode: "summary" instead)
  102. v4.8.9

    Changed 1

    Notes
    Changed
    • SearchObject now returns compact TSV (tab-separated) format instead of JSON, further reducing payload size
  103. v4.8.8

    Fixes 1

    Notes
    Fixed
    • SearchObject now returns compact JSON instead of raw XML, reducing payload size significantly (fixes #39)
  104. v4.8.7

    Fixes 1

    Notes
    Fixed
    • GetPackageTree now returns an error for non-existent packages instead of an empty tree (fixes #38)
  105. v4.8.6

    Changed 1, Fixes 2

    Notes
    Fixed
    • Skip SAP auth flow for MCP ping requests (protocol-level check, no SAP connection needed)
    • Serialize concurrent OAuth token acquisition per destination to prevent port conflicts
    Changed
    • Disable x-mcp-destination header processing by default; opt-in via --allow-destination-header flag (HTTP/SSE)
  106. v4.8.5

    Fixes 3

    Notes
    Fixed
    • Pass logger to createAdtClient() in all 150 handlers for proper adt-clients logging
    • Retry on transient SAP errors (SWB_TOOL019) in HighTester after delete+create
    • Update @mcp-abap-adt/adt-clients to 3.14.5
  107. v4.8.4

    Fixes 2

    Notes
    Fixed
    • BDEF low-level test: wrap unlock in try-catch to prevent lock leak on failure
    • shared:setup: retry group activation up to 3 times to resolve circular dependencies
  108. v4.8.3

    Changed 1, Fixes 2

    Notes
    Fixed
    • Domain/DataElement create handlers: use lock + read-modify-write update pattern instead of full chain (fixes intermittent "description is missing" errors)
    • Update @mcp-abap-adt/adt-clients to 3.14.4 (fixes DataElement create XML)
    Changed
    • Add shared:setup prerequisite step to testing documentation
  109. v4.8.2

    Changed 3

    Notes
    Changed
    • Update all dependencies to latest versions (except express)
    • Migrate to TypeScript 6, add rootDir to tsconfig.json
    • Fix test type errors for TypeScript 6 compatibility
  110. v4.8.1

    Changed 1

    Notes
    Changed
    • Add build (tsc) step to pre-commit hook to catch type errors before commit
  111. v4.8.0

    Features 2, Changed 1

    Notes
    Added
    • RuntimeListDumps: new from / to parameters (YYYYMMDDHHMMSS system local time) for server-side time-range filtering. Response is now a structured dumps array with dump_id, datetime, error, title, and user fields — replaces the raw atom feed payload.
    • RuntimeGetDumpById: dump lookup by datetime + user without knowing the dump ID. Pass datetime (ISO or "YYYY-MM-DD HH:MM:SS") and user to resolve the dump automatically using a ±2-minute server-side window and ±60-second client-side match. dump_id is now optional.
    Changed
    • RuntimeGetDumpById: dump_id parameter is no longer required — provide either dump_id or datetime + user.
  112. v4.7.1

    Fixes 1

    Notes
    Fixed
    • Update server.json version to match package version
  113. v4.7.0

    Changed 1

    Notes
    Changed
    • Replace archived node-rfc with @mcp-abap-adt/sap-rfc-lite — a lightweight maintained fork with the same API surface. This change only affects RFC connections to legacy SAP systems (BASIS < 7.50). HTTP connections are not affected. If you experience issues with legacy RFC connections, please open an issue and downgrade to v4.6.0.
  114. v4.6.0

    Features 1

    Notes
    Added
    • HTTPS/TLS support for HTTP and SSE server transports. Configure via CLI (--tls-cert, --tls-key, --tls-ca), environment variables (MCP_TLS_CERT, MCP_TLS_KEY, MCP_TLS_CA), or YAML config (tls: section). Protocol is auto-detected from cert + key presence.
  115. v4.5.2

    Features 1

    Notes
    Added
    • Pre-commit hook via husky + lint-staged — auto-fixes formatting and lint issues before commit so CI stays green.
  116. v4.5.1

    Changed 2

    Notes
    Changed
    • Rewrite ListTransports handler to use AdtClient.getRequest().list() with proper Accept negotiation instead of direct connection.makeAdtRequest(). Update @mcp-abap-adt/adt-clients to 3.13.0.
    • Add integration tests for ListTransports handler.
  117. v4.5.0

    Changed 1, Fixes 1

    Notes
    Fixed
    • Give SAP_AUTH_TYPE priority over SAP_JWT_TOKEN in test helpers — fixes onprem basic-auth tests failing when OS has stale BTP JWT token (#24).
    Changed
    • Update @mcp-abap-adt/adt-clients to 3.12.0 — adds 415 Content-Type negotiation with auto-retry and caching. Accept correction enabled by default (#22, #23).
  118. v4.4.2

    Fixes 1

    Notes
    Fixed
    • Guarantee unlock via try-finally in all update handlers — prevents locked objects when errors occur between lock and unlock (#22). Affected: UpdateProgram, UpdateClass, UpdateInterface, UpdateView, UpdateTable, UpdateStructure, UpdateDomain, UpdateServiceDefinition, UpdateDataElement.
  119. v4.4.1

    Features 1

    Notes
    Improved
    • Rewrite SearchObject tool description with operation-first approach for better RAG discoverability (#21). Description now leads with action verbs (find, search, locate, check exists) instead of object type lists, so embedding vectors match user intent queries like "is there a program named X?".
  120. v4.4.0

    Features 1

    Notes
    Improved
    • Enrich 15 tool descriptions with domain-specific keywords for better vector RAG discoverability (#20). Tools like SearchObject, GetTypeInfo, GetTransaction, GetTableContents, and others now include ABAP object type names (DEVC, CLAS, TABL, DDLS, BDEF, DDLX, etc.) so embedding-based tool discovery returns correct results.
  121. v4.3.2

    Features 1

    Notes
    Improved
    • Suppress ping request logging in StreamableHttpServer and SseServer — ping is now silent unless debug logging is enabled, reducing log noise.
  122. v4.3.1

    Features 1

    Notes
    Improved
    • StreamableHttpServer request logging now includes MCP method, tool name, destination, and completion status. Helps diagnose issues like #19 where health polls generate excessive tools/list noise.
  123. v4.3.0

    Features 2

    Notes
    Added
    • GET /mcp/health endpoint for HTTP and SSE transports — lightweight health check without MCP protocol overhead. Returns status, uptime, version, transport, and activeSessions (SSE only). Resolves #18.
    • Unit tests for health endpoint (both transports).
  124. v4.2.1

    Fixes 1

    Notes
    Fixed
    • Shorten server.json description to fit MCP registry 100-character limit.
  125. v4.2.0

    Features 4, Fixes 6

    Notes
    Added
    • 16 new Read* tools — read source code and metadata for all object types with CRUD handlers: ReadClass, ReadInterface, ReadProgram, ReadTable, ReadStructure, ReadView, ReadDomain, ReadDataElement, ReadFunctionModule, ReadFunctionGroup, ReadPackage, ReadServiceDefinition, ReadServiceBinding, ReadMetadataExtension, ReadBehaviorDefinition, ReadBehaviorImplementation.
    • transport_request parameter for UpdateBehaviorDefinition (low), UpdateMetadataExtension (high).
    • record_changes parameter for CreatePackage (high + low).
    • Transported Object CRUD integration test reproducing GitHub #11 (transport lock issue on legacy systems).
    Fixed
    • SAP_SYSTEM_TYPE now propagated from test config to process.env, so createAdtClient correctly selects AdtClientLegacy on legacy systems.
    • transport_request passed in class update tests for transportable packages.
    • UpdateDataElement now passes transportRequest to adt-clients.
    • BDEF low test resilience: guaranteed BIMPL unlock via try/finally, delay after delete in cleanup, skip create if BDEF already exists (transport re-create limitation).
    • Removed duplicate GetFunction handler (delegates to GetFunctionModule).
    • Auto-detect JWT auth for --env-path, SAP_CLIENT auto-resolve.
  126. v4.1.1

    Fixes 1

    Notes
    Fixed
    • Unit test systemContext.test.ts updated to match SAP_SYSTEM_TYPE env var approach (removed obsolete isModernAdtSystem mock).
  127. v4.1.0

    Features 3, Changed 2, Fixes 2

    Notes
    Added
    • SAP_SYSTEM_TYPE environment variable (cloud | onprem | legacy) to explicitly control SAP system type. Default: cloud.
    • --system-type CLI argument as alternative to the env var.
    • Eager HTTP session initialization — connect() is now called for all connection types (not just RFC), establishing CSRF token and cookies before the first request.
    Changed
    • Removed unreliable auto-detection via /sap/bc/adt/core/discovery. System type is now determined solely by SAP_SYSTEM_TYPE.
    • Migration note: on-premise users must set SAP_SYSTEM_TYPE=onprem in .env to access on-premise-only tools (e.g., Programs).
    Fixed
    • 403 errors on first request to SAP systems (e.g., E96) that require pre-established session cookies.
    • Inconsistent legacy detection across different shell environments, proxy configs, and ICM sessions.
  128. v4.0.0dated in the atlas

    Breaking 3, Features 9, Changed 6, Fixes 7

    Notes
    Added
    • Legacy system support (BASIS < 7.50): automatic detection of legacy systems, AdtClientLegacy factory, RFC connection transport via SADT_REST_RFC_ENDPOINT.
    • RFC connection type: --connection-type=rfc CLI flag and SAP_CONNECTION_TYPE=rfc env var for legacy systems requiring SAP NW RFC SDK.
    • available_in mechanism: each handler's TOOL_DEFINITION declares supported environments (onprem, cloud, legacy). Unsupported tools are hidden from clients at registration time.
    • Legacy available_in on 102 handlers: Class, Interface, View, Program, Function Group/Module, Package (read/update/delete), Include, Unit Test, and common utilities.
    • FunctionGroup/FunctionModule on cloud: previously restricted to on-premise only.
    • Runtime profiling and dumps on cloud: class-based profiling now works on ABAP Cloud.
    • Shared dependencies management: npm run shared:setup / shared:teardown / shared:check for test objects.
    • Test infrastructure: test:init / test:reinit scripts, explicit connection config (env, system_type, connection_type) in test-config.yaml, available_in support in test cases for system-aware skipping.
    • AVAILABLE_TOOLS_LEGACY.md: generated documentation listing 115 legacy-compatible tools.
    Changed
    • BREAKING: Node 18 dropped — minimum Node.js version is now 20.0.0.
    • BREAKING: @mcp-abap-adt/adt-clients upgraded from 2.x to 3.11.2 (major API changes).
    • BREAKING: @mcp-abap-adt/connection upgraded to 1.5.3.
    • CI matrix: Node 20 + 25 on 3 OS for push/PR; Node 20/22/24/25 for release.
    • Guaranteed unlock: force-release DDIC locks, unlock-on-error in CreateDataElement/CreateDomain.
    • TADIR conflict retry: automatic retry on 409 TADIR conflict during create operations.
    Fixed
    • Circular reference in CDS unit test handlers.
    • i18n check handling (adt-clients 3.8.7).
    • MetadataExtension high/low test object conflicts (separate config keys).
    • Function test lock conflicts (separate high/low test objects).
    • Package tests use update() for lock/unlock.
    • Jest 30 deprecated --testPathPattern migrated to --testPathPatterns.
    • SAP_USER → SAP_USERNAME in configuration examples.
  129. v3.2.1

    Fixes 1

    Notes
    Fixed
    • Integration test cleanup: close MCP connections, reuse auth session, random ports.
  130. v3.2.0

    Features 1, Changed 1

    Notes
    Added
    • GetTableContents handler: fully implemented — was a stub returning "temporarily unavailable", now reads table data via ADT Data Preview API with field extraction and SQL query generation.
    Changed
    • parseSqlQueryXml: exported from handleGetSqlQuery for reuse in GetTableContents.
  131. v3.1.0

    Features 1, Fixes 3

    Notes
    Added
    • BehaviorImplementation low-level MCP tools: registered ValidateBehaviorImplementationLow, CreateBehaviorImplementationLow, LockBehaviorImplementationLow in LowLevelHandlersGroup.
    Fixed
    • System context not passed to AdtClient: createAdtClient() now forwards responsible and masterSystem from resolved system context to AdtClient options. Fixes package creation on BTP Cloud failing with "Enter a valid user, not , as the person responsible".
    • ValidatePackageLow schema: added missing super_package property to inputSchema.properties (was in required but not declared — MCP SDK rejected the parameter in hard mode).
    • CreatePackageLow handler: removed incorrect createResult check — AdtPackage.create() returns { errors: [] } without createResult (packages are containers with no response body).
  132. v3.0.0dated in the atlas

    Breaking 1, Changed 1

    Notes
    Changed
    • BREAKING: Separate Create and Update — Create handlers no longer accept source code. Use Update after Create to set source code.
  133. v2.7.5

    Fixes 1

    Notes
    Fixed
    • MCP Registry publish: sync server.json version with npm package version (2.5.2 → 2.7.5).
  134. v2.7.4

    Features 2

    Notes
    Added
    • System context HTTP headers for HTTP/SSE transport:
      • x-sap-master-system and x-sap-responsible headers allow per-request system context override.
      • Priority: HTTP headers → process.env → cloud getSystemInformation() API.
      • Enables on-prem transport request binding for HTTP/SSE connections without .env file.
    • System context cache isolation: cache is reset before each non-stdio request to prevent stale values leaking between requests targeting different SAP systems.
  135. v2.7.3

    Fixes 1

    Notes
    Fixed
    • Release workflow lint gate for v2.7.2:
      • Applied Biome formatting/import ordering fixes in updated hard-mode test infrastructure and integration suites.
      • Kept hard-mode behavior unchanged; this patch is focused on CI/release pipeline stability.
  136. v2.7.2

    Features 1, Fixes 2

    Notes
    Added
    • YAML-driven MCP hard mode test infrastructure:
      • Added environment.integration_hard_mode support for integration tests.
      • Added real MCP client execution path for stdio, sse, and http transports.
      • Added MCP CRUD smoke/matrix scripts in tools/.
    Fixed
    • Real MCP integration gaps in tests:
      • Lambda-based integration suites now support hard mode via real MCP tool calls.
      • LowTester hard mode now bootstraps MCP session (GetSession) so low-level lock/unlock flows receive required session_id.
      • Hard mode test object names now include per-run suffix to prevent object name collisions between runs.
    • System context bootstrap on server startup:
      • Launcher now hydrates on-prem system context env values earlier so real MCP runs consistently resolve context.
  137. v2.7.1

    Features 1

    Notes
    Added
    • SAP_MASTER_SYSTEM and SAP_RESPONSIBLE documented in CLI help (--help) and .env.example.
  138. v2.7.0

    Features 3, Changed 2

    Notes
    Added
    • System context support for on-premise systems (issue #11):
      • masterSystem and responsible are now passed to all ADT create/update XML requests via IAdtSystemContext.
      • On-prem resolution: SAP_MASTER_SYSTEM env var → getSystemInformation() API fallback (cloud).
      • Responsible resolution: SAP_RESPONSIBLE env var → SAP_USERNAME fallback → API.
      • Ensures correct transport request binding on on-premise systems.
    • New .env variables for on-premise configuration:
      • SAP_MASTER_SYSTEM — SAP system ID (the three-character SID). Required for on-prem create/update operations.
      • SAP_RESPONSIBLE — Responsible user (optional, falls back to SAP_USERNAME).
    • Test infrastructure: resolveTestSystemContext() resolves system context from YAML config for integration tests.
    Changed
    • Upgraded @mcp-abap-adt/adt-clients from ^2.0.0 to ^2.2.0.
    • Renamed default_system → default_master_system in test config template.
  139. v2.6.1

    Changed 1, Fixes 1

    Notes
    Fixed
    • UpdateProgram missing transport_request: Added transport_request to schema, interface, and .update() call — same fix as other handlers in 2.6.0.
    Changed
    • Upgraded @mcp-abap-adt/adt-clients to 1.2.2.
  140. v2.6.0

    Features 1, Changed 1, Fixes 1, Docs 1

    Notes
    Added
    • ListTransports tool (read-only):
      • Lists transport requests for the current or specified user.
      • Filters by modifiable status (default: modifiable only).
      • Use before create/update to find the correct transport request.
    Fixed
    • transportRequest forwarding in all high-level handlers (issue #11):
      • Create handlers now forward transportRequest to the internal update (PUT) step: CreateFunctionModule, CreateTable, CreateInterface, CreateView, CreateProgram, CreateDomain, CreateDataElement.
      • Standalone update handlers now forward transportRequest: UpdateStructure, UpdateDomain, UpdateServiceDefinition.
      • Added transport_request to tool schemas where it was missing: UpdateClass, UpdateView, UpdateBehaviorDefinition.
    Changed
    • Upgraded @mcp-abap-adt/adt-clients from 0.3.22 to 1.2.1.
    Documentation
    • Regenerated tools documentation:
      • docs/user-guide/AVAILABLE_TOOLS.md
      • docs/user-guide/AVAILABLE_TOOLS_HIGH.md
      • docs/user-guide/AVAILABLE_TOOLS_LOW.md
      • docs/user-guide/AVAILABLE_TOOLS_COMPACT.md
      • docs/user-guide/AVAILABLE_TOOLS_READONLY.md
  141. v2.5.2

    Features 2, Fixes 3, Docs 1

    Notes
    Fixed
    • --mcp precedence over local .env:
      • Fixed CLI fallback logic so .env from current working directory is no longer auto-loaded when --mcp=<destination> is provided.
      • Startup auth source display now correctly prioritizes service-key: <destination> over .env.
    • JWT auth error diagnostics:
      • Improved HTTP/SSE request-time error reporting for missing JWT tokens by surfacing token provider failure reason in BaseMcpServer.
    • Auth broker runtime behavior:
      • Fixed v2 auth-broker config to respect --unsafe instead of always forcing safe in-memory stores.
    Added
    • Browser OAuth callback port override:
      • Added --browser-auth-port=<port> and MCP_BROWSER_AUTH_PORT to override auth callback server port (useful when default ports are occupied).
    • Inspector runtime dependency safety:
      • Added explicit express-rate-limit dev dependency to avoid missing transitive package issues during dev:http inspector startup.
    Documentation
    • Updated auth and CLI docs for --mcp behavior and browser auth callback port override:
      • README.md
      • docs/user-guide/AUTHENTICATION.md
      • CLI help text via ServerConfigManager.
  142. v2.5.1

    Changed 2, Docs 1

    Notes
    Changed
    • Compact handler descriptions:
      • Reworked compact handler descriptions to be explicit and deterministic for tool routing.
      • Added per-object_type required parameter mapping directly in handler descriptions (TYPE(param*) format).
      • Clarified read vs validate intent to avoid HandlerGet / HandlerValidate confusion.
    • Compact schemas metadata:
      • Added missing parameter descriptions in compact schemas to remove No description in generated tool docs.
    Documentation
    • Regenerated tools documentation:
      • docs/user-guide/AVAILABLE_TOOLS.md
      • docs/user-guide/AVAILABLE_TOOLS_HIGH.md
      • docs/user-guide/AVAILABLE_TOOLS_COMPACT.md
  143. v2.5.0

    Features 2, Changed 1

    Notes
    Added
    • Compact facade CRUD routing:
      • Added compact facade CRUD operations routed by object_type.
      • Added compact router/matrix coverage for supported object/action combinations.
    • Compact lifecycle and test handlers:
      • Added compact handlers for validation, unlock, and unit test workflows.
    Changed
    • Compact routing architecture:
      • Removed legacy HandlerAction path and standardized on dedicated compact handlers.
  144. v2.4.2

    Features 2, Changed 2

    Notes
    Added
    • Service Binding high-level tools:
      • Added CreateServiceBinding, GetServiceBinding, UpdateServiceBinding, DeleteServiceBinding, ListServiceBindingTypes, ValidateServiceBinding.
      • Added serviceBindingPayloadUtils for response parsing/format normalization.
    • Runtime profiling tools:
      • Added RuntimeAnalyzeProfilerTrace, RuntimeRunClassWithProfiling, RuntimeRunProgramWithProfiling.
    Changed
    • System read-only handlers:
      • Migrated GetAllTypes, GetObjectStructure, and GetSqlQuery to AdtClient flow.
    • Lint/type hygiene:
      • Removed explicit any usage in new service binding/runtime handlers and high-level handler registrations.
  145. v2.4.1

    Changed 2, Fixes 2

    Notes
    Fixed
    • Dev HTTP/SSE launch scripts:
      • Fixed dist entry path to dist/server/launcher.js in tools/dev-http-v2.js and tools/dev-sse-v2.js.
      • Fixed dev:http transport argument to --transport=http.
    • HTTP/SSE server startup reliability:
      • Updated standalone server startup flow to listen on listening/error events, preventing false "started" logs followed by silent exits.
      • Kept strong server references in launcher to avoid premature process shutdown.
    Changed
    • Runtime profiling + dumps explorer:
      • Refocused tool workflow to class-based profiling run flow.
      • Improved ABAP user resolution/validation and diagnostics for dumps/traces filtering.
      • Improved object validation/update flow and trace feed handling in interactive tool.
    • Tools documentation:
      • Updated tools/README.md to reflect class-focused profiling flow and ABAP user resolution behavior.
  146. v2.4.0

    Features 2, Changed 2, Docs 2

    Notes
    Added
    • Runtime diagnostics tools:
      • Added runtime profiling tools: RuntimeCreateProfilerTraceParameters, RuntimeListProfilerTraceFiles, RuntimeGetProfilerTraceData.
      • Added runtime dump tools: RuntimeListDumps, RuntimeGetDumpById.
    • Architecture documentation:
      • Added comprehensive architecture document: docs/architecture/ARCHITECTURE.md.
    Changed
    • Runtime payload format:
      • Runtime profiling and dump handlers now return parsed JSON payloads (instead of raw XML strings in payload).
      • Runtime dump read uses dump_id input (RuntimeGetDumpById) instead of URI-based input.
    • Dependencies:
      • Updated @mcp-abap-adt/adt-clients to ^0.3.16.
    Documentation
    • Regenerated tools docs (docs/user-guide/AVAILABLE_TOOLS*.md) with runtime diagnostics tools.
    • Updated README.md feature list with runtime diagnostics capabilities.
  147. v2.3.1

    Fixes 3

    Notes
    Fixed
    • YAML config flag compatibility:
      • Added --conf support as an alias for --config in argument parsing.
      • Fixed YAML template flow: when config file is missing, template generation now exits cleanly instead of continuing into inspection-only mode.
    • YAML env config clarity:
      • Added env-path YAML option to map directly to --env-path.
      • Clarified env as destination name and removed duplicate allowed-origins entry in template.
    • HTTP/SSE connection context resolution:
      • Enforced priority: x-mcp-destination header -> server default destination (--mcp / --env-path) -> 400 error.
      • HTTP default-destination broker initialization now uses the resolved destination key.
  148. v2.3.0

    Changed 3, Docs 1

    Notes
    Changed
    • .env parsing policy:
      • Inline comments are no longer parsed from values.
      • # is treated as a comment only when it starts the line.
      • This avoids accidental truncation of secrets containing # (for example passwords/tokens).
    • Update tool error handling:
      • Added unified ADT/SAP error extraction for update handlers to improve 409 conflict diagnostics.
      • XML exception payloads (exc:exception) are now surfaced as detailed user-facing errors in previously generic update flows.
    • GetTypeInfo structure fallback:
      • Added optional structure lookup fallback (include_structure_fallback, default: true).
      • Fallback is executed only when type lookups return 404 or empty result.
      • Non-404 errors no longer silently fall through to unrelated lookups.
    Documentation
    • Updated .env comments guidance in:
      • README.md
      • docs/user-guide/AUTHENTICATION.md
  149. v2.2.7

    Fixes 1

    Notes
    Fixed
    • CI (Biome): Applied formatting fixes required by npx biome check src --diagnostic-level=error.
      • src/handlers/function_module/high/handleGetFunctionModule.ts
      • src/lib/config/envResolver.ts
  150. v2.2.6

    Changed 3

    Notes
    Changed
    • Function Module handlers: Fixed parameter propagation for function module operations:
      • GetFunctionModule and DeleteFunctionModule now require and pass function_group_name.
      • GetFunction (read-only) now requires and passes function_group.
      • UpdateFunctionModule (high/low) now forwards transport_request to ADT update calls.
    • CLI env resolution: Updated env argument behavior:
      • --env=<name> now resolves to platform sessions path: {sessions}/<name>.env.
      • Added --env-path=<path|file> for explicit .env file usage.
      • MCP_ENV_PATH now maps to explicit env file path behavior (same as --env-path).
    • Errors: Improved function module update error messages for transport-related 400 responses and XML SAP errors.
  151. v2.2.5

    Changed 3

    Notes
    Changed
    • Tools docs generator: Reworked tools/generate-tools-docs.js to scan only handler code in src/handlers/**.
    • Tools docs structure: Updated docs/user-guide/AVAILABLE_TOOLS.md hierarchy to Group (level) → Object (folder) → Tools.
    • Coverage fix: Ensured BDEF and other handler folders are included consistently in generated tools documentation.
  152. v2.2.4

    Changed 3

    Notes
    Changed
    • Discoverability metadata: Updated npm keywords and package metadata to improve MCP search relevance for ABAP, ABAP Cloud, JWT/XSUAA, and service-key flows.
    • Registry description: Shortened server.json description to meet the MCP publisher 100-character constraint.
    • README positioning: Clarified first-screen value proposition around full CRUD, On-Premise + ABAP Cloud support, and auth options.
  153. v2.2.3

    Features 1, Changed 3

    Notes
    Added
    • Docs: Added dedicated Terminology and Authentication guides, with links from README.
    Changed
    • README: Reworked first-screen content and links to emphasize configurator usage and destinations.
    • Docs structure: Moved GitHub configuration README into deployment docs and fixed internal links.
    • Client configuration docs: Linked to the configurator repo and usage guide.
  154. v2.2.2

    Features 3, Changed 4, Fixes 1

    Notes
    Added
    • Client support: Added OpenCode and Copilot configuration support.
    • Claude config: Added .mcp.json project configuration and docs.
    • Configurator options: Added URL, headers, and timeout options in mcp-abap-adt-configure.
    Changed
    • Configurator split: Removed mcp-abap-adt-configure from core. Use @mcp-abap-adt/configurator (repo: mcp-abap-adt-conf) with mcp-conf instead.
    • Codex/Goose handling: Improved transport handling and timeout defaults for non-stdio transports.
    • Default-disabled entries: Updated JSON config handling and docs to keep new entries disabled by default.
    • Streamable HTTP: Refactored server handling for per-request connections.
    Fixed
    • Goose config: Corrected enabled state handling in writeGooseConfig.
  155. v2.2.0

    Features 3, Changed 3

    Notes
    Added
    • MCP Registry metadata: Added server.json and mcpName for registry publishing.
    • Client configurator: Added mcp-abap-adt-configure to add/enable/disable/remove MCP entries across clients.
    • Auto-config docs: Added docs/installation/CLIENT_INSTALLERS.md with usage and common tasks.
    Changed
    • Default logger loading: Lazy-load logger to avoid bundler issues with optional transports.
    • Claude config: Write MCP settings under projects.<full_path>.mcpServers for Claude CLI on Linux.
    • Config defaults: New entries for Cline/Codex/Windsurf/Goose are disabled by default; enable explicitly.
  156. v2.1.7

    Fixes 1

    Notes
    Fixed
    • UpdateLocalTestClass: Added detailed SAP error information for 409 conflict errors to improve troubleshooting (#6)
  157. v2.1.6

    Changed 1, Fixes 1

    Notes
    Fixed
    • GetIncludesList, GetObjectsList, GetObjectsByType, GetEnhancements, GetObjectInfo: Migrate from deprecated fetchNodeStructure stub to AdtClient.getUtils().fetchNodeStructure() - fixes "not implemented" errors
    Changed
    • LowTester logging: Unified to compact format with emojis, matching LambdaTester output style
  158. v2.1.5

    Features 1

    Notes
    Added
    • Startup diagnostics: Display SAP connection configuration at server startup when using --env parameter
      • Shows source file, SAP URL, client, auth type, and UAA URL
      • Tokens and secrets are safely masked (first/last 4 chars for long values, full mask for short)
      • Helps diagnose auth issues by showing which config is actually used
  159. v2.1.4

    Fixes 1

    Notes
    Fixed
    • CreateLocalTestClass: Return full SAP error details on failure (including the original response payload).
  160. v2.1.3dated in the atlas

    Removed 3, Changed 2

    Notes
    Changed
    • Documentation: Restored the stateful ADT flow guide without file-based session persistence references.
    • Available tools: Regenerated docs/user-guide/AVAILABLE_TOOLS.md and restored links across docs.
    Removed
    • Lock/session persistence: Removed filesystem lock registry and session persistence helpers/config from core and tests.
    • CLI commands: adt-lock-object, adt-unlock-object, adt-manage-locks, adt-manage-sessions, and adt-unlock-objects removed from package bins.
    • User guides: Removed usage/scenario docs tied to legacy session flows.
  161. v2.1.2

    Removed 1, Changed 2

    Notes
    Changed
    • Lock registry types: LockState.objectType now uses AdtObjectType with normalization for legacy entries.
    • adt-lock-object: No longer registers lock handles in .locks/active-locks.json; session save only.
    Removed
    • CLI commands: adt-unlock-object, adt-manage-locks, adt-manage-sessions, and adt-unlock-objects removed from package bins.
  162. v2.1.1

    Changed 2

    Notes
    Changed
    • @mcp-abap-adt/auth-broker: bumped to ^0.3.0 - auto-detect service key format, credentials wrapper support, debug logging
    • @mcp-abap-adt/auth-stores: bumped to ^0.3.0
  163. v2.1.0

    Fixes 2

    Notes
    Fixed
    • Updated @mcp-abap-adt/auth-broker so installs now pull 0.2.17 instead of the stale 0.2.14 release that was hiding upstream fixes.
    • Removed the accidental @mcp-abap-adt/core dependency that caused consumers to download the old 1.4.1 tarball.
  164. v2.0.3

    Changed 1, Fixes 1

    Notes
    Changed
    • @mcp-abap-adt/auth-broker: bumped to 0.2.15 so the locked dependency pulls in the latest fixes referenced by the release.
    Fixed
    • CI workflow package install: .github/workflows/ci.yml now looks for mcp-abap-adt-core-*.tgz, matching the actual npm pack output before testing global install.
  165. v2.0.2

    Changed 1, Fixes 1

    Notes
    Changed
    • GetWhereUsed handler: Now uses getWhereUsedList() from adt-clients and returns parsed JSON with structured references instead of raw XML
    Fixed
    • Release workflow: Simplified to use npm registry instead of uploading tgz artifacts
  166. v2.0.1

    Docs 2

    Notes
    Documentation
    • Added glama.ai badge with security/license/quality grades
    • Updated README example to use EmbeddableMcpServer instead of deprecated HandlerExporter.registerOnServer()
  167. v2.0.0

    Breaking 1, Removed 1, Features 1, Changed 1, Other

    Notes
    Added
    • EmbeddableMcpServer: New server class for external integration (e.g., cloud-llm-hub)
      • Accepts injected AbapConnection from consumer
      • Extends BaseMcpServer with proper handler registration
      • Configurable exposition levels: readonly, high, low, system, search
      • Export via @mcp-abap-adt/core/server
    Changed
    • HandlerExporter: Now serves only as registry factory
      • Use createRegistry() to get IHandlersRegistry for custom scenarios
      • getHandlerEntries() and getToolNames() still available for inspection
    Removed
    • BREAKING: HandlerExporter.registerOnServer() method removed
      • Had bug with handler signature (passed context twice)
      • Replace with EmbeddableMcpServer for cleaner architecture
    Migration Guide
    // Before (v1.x - broken)
    import { HandlerExporter } from '@mcp-abap-adt/core/handlers';
    const exporter = new HandlerExporter({ includeReadOnly: true, includeHighLevel: true });
    const mcpServer = new McpServer({ name: 'mcp-abap-adt', version: '1.0.0' });
    exporter.registerOnServer(mcpServer, () => connection, logger);
    
    // After (v2.x)
    import { EmbeddableMcpServer } from '@mcp-abap-adt/core/server';
    const server = new EmbeddableMcpServer({
      connection,
      logger,
      exposition: ['readonly', 'high'],
    });
    await server.connect(transport);
  168. v1.4.3

    Docs 3

    Notes
    Documentation
    • CLI Help: Extended --help output with comprehensive environment variables documentation
      • Added MCP Server Configuration section (MCP_TRANSPORT, MCP_HTTP_HOST, MCP_HTTP_PORT, MCP_SSE_HOST, MCP_SSE_PORT, MCP_ENV_PATH, MCP_UNSAFE, MCP_USE_AUTH_BROKER, MCP_BROWSER)
      • Added Debug Options section (DEBUG_HANDLERS, DEBUG_CONNECTORS, DEBUG_CONNECTION_MANAGER, HANDLER_LOG_SILENT)
      • Extended SAP Connection section with JWT/OAuth2 variables (SAP_REFRESH_TOKEN, SAP_UAA_URL, SAP_UAA_CLIENT_ID, SAP_UAA_CLIENT_SECRET, SAP_LANGUAGE)
    • CLI_OPTIONS.md: Added missing environment variables documentation
      • Added MCP_UNSAFE, MCP_USE_AUTH_BROKER, MCP_BROWSER to General section
      • Added JWT/OAuth2 authentication variables to SAP Connection section
      • New Auth-Broker section with AUTH_BROKER_PATH and debug variables
      • New Debug section with handler and connector logging options
    • INSTALLATION.md: Expanded Environment Variables section with full reference and link to CLI_OPTIONS.md
  169. v1.4.2

    Fixes 1

    Notes
    Fixed
    • Version path: Fixed incorrect path to package.json in --version command
  170. v1.4.1

    Features 4, Changed 2

    Notes
    Changed
    • Package renamed: Changed from @fr0ster/mcp-abap-adt to @mcp-abap-adt/core for npm publishing
    • Package now public: Removed private: true flag to allow npm publishing
    Added
    • Inspection-only mode: Server can now start in stdio mode without connection parameters for tool inspection (used by glama.ai)
    • MockAbapConnection: Added mock connection class for inspection-only mode that returns descriptive error when tools are executed
    • Docker inspection support: Added Dockerfile.inspect and docker-compose.inspect.yml for local testing of inspection mode
    • CLAUDE.md: Added project documentation for Claude Code assistance
  171. v1.3.0

    Features 3, Changed 4

    Notes
    Added
    • Added integration test tests/integration/handleGetFunction.int.test.js for handleGetFunction.
    • Significantly expanded integration test coverage in src/index.test.ts for all main tools (GetProgram, GetClass, GetFunctionGroup, GetFunction, GetTable, GetStructure, GetTableContents, GetPackage, GetInclude, GetTypeInfo, GetInterface, GetTransaction, GetEnhancements, GetSqlQuery, SearchObject).
    • Added new checks for advanced scenarios (e.g., SQL generation, error handling, enhancement XML parsing).
    Changed
    • Updated documentation regarding testing and integration scenarios.
  172. v1.2.9

    Removed 1, Features 1, Changed 3

    Notes
    Changed
    • v1 McpHandlers Refactoring: Rewrote McpHandlers to use HandlerExporter internally. This ensures the legacy embeddable server (v1) stays in sync with all tools available in v2 while reducing code duplication.
    • Shared Schema Utilities: Replaced duplicated jsonSchemaToZod logic in v1 with a central implementation from src/lib/handlers/utils/schemaUtils.ts.
    • Documentation Generator: Updated tools/generate-tools-docs.js category mapping to support the standardized GetBehaviorDefinition naming.
    Removed
    • GetBdef compatibility adapter: Removed the manual adapter for GetBdef in v1 server. All tools now use standard names (e.g., GetBehaviorDefinition).
    Added
    • tsx added to devDependencies for improved TypeScript execution and testing.
  173. v1.2.8

    Features 1, Changed 6, Fixes 4

    Notes
    Added
    • GetPackageContents tool: New read-only tool to retrieve objects inside an ABAP package (moved from legacy GetPackage).
    Changed
    • GetPackage tool: Migrated to high-level API (AdtClient.getPackage().read()). Now returns package metadata and supports version parameter (active/inactive).
    • GetFunction tool: Updated to support version parameter and migrated to new adt-clients signature.
    • High-level handlers: Improved parameter handling and consistency with new adt-clients versioning requirements.
    • Test cleanup documentation updated to clarify afterEach behavior and multi-object cleanup requirements.
    • Test runners (LowTester/HighTester) now emit step-level flow logs for validate/create/lock/update/unlock/activate.
    • Package cleanup can use a fresh connection with optional connection_config for locked session cases.
    Fixed
    • Package create now treats ADT "import" 404 as success when the package is readable.
    • Function group/module low-level create handles transient 400/empty responses with read verification.
    • Table create now validates and creates with explicit descriptions to satisfy ADT requirements.
    • Package low-level tests avoid double-unlock during cleanup.
  174. v1.2.7

    Changed 3, Fixes 3

    Notes
    Changed
    • GetPackageTree now builds the tree from node-structure traversal to preserve package/subpackage hierarchy and ordering.
    • Package tree nodes now include adtType, normalized type, codeFormat, and restoreStatus for parity with backup tooling.
    • Migrated handler usage to AdtClient now that CrudClient/ReadOnlyClient are removed in @mcp-abap-adt/adt-clients.
    Fixed
    • Updated handler typings to match @mcp-abap-adt/adt-clients@0.3.2 (I* config/state interfaces).
    • SearchObject now uses AdtUtils.searchObjects instead of the removed SharedBuilder export.
    • Restored encodeSapObjectName helper in utils to avoid relying on unstable client exports.
  175. v1.2.6

    Fixes 2

    Notes
    Fixed
    • Fixed LICENSE file - corrected copyright attribution (removed incorrect fork author)
    • Fixed TypeScript compilation errors in test files:
      • Updated handlers.test.ts to use high-level handlers instead of removed readonly handlers
      • Removed non-existent getAuthHeaders import from utils.test.ts
      • Fixed sessionHelpers.ts - added type assertions for connection methods not in IAbapConnection interface (getConfig, connect)
      • Fixed ClassCrudClientDirect.test.ts - added type assertions for connect() and reset() methods
      • Fixed BehaviorImplementationLowHandlers.test.ts - removed invalid session_id and session_state parameters from UnlockClassArgs
  176. v1.2.5

    Removed 1, Changed 1, Fixes 3

    Notes
    Removed
    • Removed duplicate readonly handlers that duplicate high-level CRUD operations
      • Deleted handleGetClass.ts (replaced by high-level GetClass)
      • Deleted handleGetDomain.ts (replaced by high-level GetDomain)
      • Deleted handleGetDataElement.ts (replaced by high-level GetDataElement)
      • Deleted handleGetTable.ts (replaced by high-level GetTable)
      • Deleted handleGetStructure.ts (replaced by high-level GetStructure)
      • Deleted handleGetView.ts (replaced by high-level GetView)
      • Deleted handleGetServiceDefinition.ts (replaced by high-level GetServiceDefinition)
      • Deleted handleGetProgram.ts (replaced by high-level GetProgram)
      • Deleted handleGetInterface.ts (replaced by high-level GetInterface)
      • Deleted handleGetFunctionGroup.ts (replaced by high-level GetFunctionGroup)
      • Deleted handleGetBdef.ts (replaced by high-level GetBehaviorDefinition)
    Changed
    • v1 server now uses high-level handlers instead of readonly duplicates
      • GetClass, GetDomain, GetDataElement, GetTable, GetStructure, GetView, GetServiceDefinition, GetProgram, GetInterface, GetFunctionGroup now use high-level handlers internally
      • GetBdef now uses GetBehaviorDefinition high-level handler with parameter adapter for backward compatibility
      • All high-level handlers support version parameter (active/inactive) for better flexibility
    Fixed
    • Fixed TypeScript type errors in handleGetAbapSystemSymbols.ts
      • Added proper JSON parsing for GetClass and GetInterface responses
      • Fixed type assertions for class and interface data
    • Fixed TypeScript type error in utils.ts
      • Added type assertion for makeAdtRequest return type
    • Fixed documentation generator (tools/generate-tools-docs.js)
      • Updated regex pattern to support multi-line imports in handler groups
      • Now correctly detects all handlers including system and search handlers
      • Documentation now includes all 219 tools (76 high-level, 116 low-level, 27 read-only)
  177. v1.2.4

    Features 2, Changed 1, Fixes 2, Dependencies 4

    Notes
    Changed
    • Migrated from ESLint+Prettier to Biome
      • Removed ESLint and Prettier dependencies
      • Added @biomejs/biome for faster linting and formatting
      • Configured Biome with strict rules for production code
      • Relaxed rules for test files (noImplicitAnyLet, noExplicitAny)
      • Updated package.json scripts: lint, lint:check, format
      • Updated npm run build to include Biome check before TypeScript compilation
    Fixed
    • Fixed TypeScript errors in production code:
      • handleGetAbapAST.ts - Fixed regex match types and while loop patterns in 4 methods
      • handleGetObjectInfo.ts - Added type annotation for packageName
      • handleDescribeByList.ts - Added type annotation for parsed
      • handleValidateDataElement.ts - Added type assertion for AxiosResponse
      • handleCreateClass.ts - Added ClassBuilderState type import
      • handleDeleteObject.ts - Changed any to unknown for response
      • handleGetEnhancementSpot.ts - Fixed array types and while loops
      • handleGetProgFullCode.ts - Fixed 3 while loops with regex.exec() pattern
    • All production code now passes strict Biome rules (no implicit any, no assignments in expressions)
    Added
    • Added Biome check to CI workflow (.github/workflows/ci.yml)
    • Added Biome check to Release workflow (.github/workflows/release.yml)
    Dependencies
    • Updated @mcp-abap-adt/adt-clients to ^0.2.6
    • Updated @mcp-abap-adt/adt-interfaces to ^0.2.6
    • Added @biomejs/biome ^2.3.10 as devDependency
    • Removed eslint and prettier dependencies
  178. v1.2.3

    Fixes 1

    Notes
    Fixed
    • Removed unused import of IAbapConnectionExtended from @mcp-abap-adt/interfaces
      • Interface was removed in interfaces package v1.1.0
  179. v1.2.2

    Breaking 1, Removed 7, Features 1, Changed 1, Other

    Notes
    Changed
    • BREAKING: Removed legacy-server.ts
      • Full removal of 3000+ line legacy server with CLI, AuthBroker, .env parsing, etc.
      • v1 module now exports only lightweight EmbeddableMcpServer class
    Added
    • EmbeddableMcpServer - lightweight MCP server for embedding
      • Minimal class (~100 lines) for external HTTP server integration
      • Constructor takes context: HandlerContext with connection and logger
      • Handlers registered immediately in constructor - no run() method needed
      • Access underlying McpServer via .mcpServer getter
      • Configurable exposition levels: readonly, high, low, system
    Removed
    • mcp_abap_adt_server class (was 3000+ lines with CLI, transports, AuthBroker)
    • setAbapConnectionOverride() function
    • ServerOptions interface
    • All CLI argument parsing
    • All transport management (stdio, SSE, streamable-http)
    • AuthBroker integration
    • .env file loading logic
    Migration Guide

    For embedding in external servers (cloud-llm-hub, etc.):

    // Before (1.2.x)
    import { mcp_abap_adt_server } from '@fr0ster/mcp-abap-adt/server/v1';
    const server = new mcp_abap_adt_server({ connection, allowProcessExit: false });
    server.registerHandlers({ connection, logger });
    
    // After (1.2.2)
    import { EmbeddableMcpServer } from '@fr0ster/mcp-abap-adt/server/v1';
    const server = new EmbeddableMcpServer({
      context: { connection, logger },
      exposition: ['readonly', 'high']
    });
    const mcpServer = server.mcpServer; // Use with transport
  180. v1.2.1

    Features 1

    Notes
    Added
    • Subpath export ./utils for internal utilities access
      • @fr0ster/mcp-abap-adt/utils - exports sessionContext, getManagedConnection, and other utilities
      • Required for cloud-llm-hub integration to access session context
  181. v1.2.0

    Breaking 1, Features 3, Changed 2, Other 2

    Notes
    Changed
    • GetWhereUsed now supports any ADT object type (e.g. table/TABL, bdef/BDEF, etc.), not just class, program, include, function, interface, package.
    • Updated documentation in README.md and jsdoc for supported object types and usage examples.
  182. v1.1.32

    Features 1

    Notes
    Added
    • v2 HTTP Server Injection: Added support for external HTTP application injection in v2 servers
      • New IHttpApplication interface for Express-like HTTP applications
      • StreamableHttpServer and SseServer now support app option in constructor
      • New registerRoutes(app, options?) method for embedding into existing Express/CDS/CAP servers
      • Enables integration with cloud-llm-hub and other CAP-based applications
      • Helper methods: getPath(), getSsePath(), getPostPath()
  183. v1.1.31

    Fixes 1

    Notes
    Fixed
    • CLI --version flag: Fixed package.json path resolution for both v1 and v2 servers
      • Path now correctly resolves from dist/server/v1/ and dist/server/v2/ to root package.json
  184. v1.1.30

    Removed 3, Features 6, Changed 3, Fixes 3, Dependencies 6, Docs 6

    Notes
    Added
    • MCP Server v2 Architecture:

      • New modular server architecture with BaseMcpServer, StdioServer, StreamableHttpServer
      • Handler registration system with Dependency Injection pattern
      • HandlerContext for unified connection and logging access in handlers
      • ServerConfigManager for centralized configuration management
      • AuthBrokerConfig for V2 server configuration
      • StdioLogger for minimal logging in stdio transport
      • Transport-specific server implementations (stdio, SSE, HTTP)
    • Headless Browser Mode: Added --browser CLI parameter for SSH and remote sessions

      • --browser=headless: Logs authentication URL and waits for manual callback
      • --browser=none: Logs URL and rejects immediately (for automated tests)
      • --browser=system: Opens system default browser (default)
      • --browser=chrome|edge|firefox: Opens specific browser
      • Environment variable: MCP_BROWSER
      • Ideal for SSH sessions, Docker containers, and CI/CD environments
    • YAML Configuration Support:

      • Load server configuration from YAML files
      • Enhanced command-line transport handling
      • Improved configuration validation
    • Structured Logging:

      • Handler logging with configurable options and integration
      • Structured logging for handler functions
      • Logger cleanup and enhanced handler implementations
    • Testing Improvements:

      • Jest configuration and enhanced testing framework
      • Unified test refactoring roadmap with base tester classes
      • Integration tests for low-level view handlers
      • Read-only handler tests for WhereUsed analysis
      • AuthBrokerFactory for test configuration
    • Infrastructure & Handlers:

      • Added tabletypes to integrated terminal profiles
      • Handler migration to infrastructure module
      • Basic auth support in MCP destination handling
    Changed
    • Token Refresh Architecture (Breaking):

      • Removed legacy createDestinationAwareConnection() wrapper (~150 lines)
      • Token refresh now handled internally by JwtAbapConnection via DI ITokenRefresher
      • getConnectionForSession() creates tokenRefresher from AuthBroker and passes to createAbapConnection()
      • Removed all reset() calls from 23+ handler files (no longer needed)
      • Removed unused disposeConnection() and getAuthHeaders() functions
      • Simplified HandlerContext to use IAbapConnection instead of deprecated IAbapConnectionExtended
    • Handler Refactoring:

      • All handlers updated to utilize HandlerContext for connection and logging
      • Standardized import statements across handlers
      • Enhanced error handling and logging across all handlers
      • Replaced direct connection creation with managed connection
    • Configuration Module:

      • Extracted configuration logic into dedicated module
      • Improved YAML configuration loading and command line parsing
      • Enhanced connection handling and configuration validation
    Fixed
    • Network error handling in createDestinationAwareConnection
    • Handler results properly awaited and errors handled in BaseMcpServer
    • Basic auth support in MCP destination handling
    Removed
    • Legacy test files and deprecated test scripts (85 files)
    • Obsolete v2 components from earlier iterations
    • Deprecated tools: update-crudclient-api.js, update-handlers-with-tool-definitions.js, etc.
    Documentation
    • Comprehensive documentation for various components and usage scenarios
    • MCP Server architecture and implementation roadmap
    • Handler refactoring roadmap
    • YAML configuration support documentation
    • Issue roadmap system for structured issue tracking
    • Updated development guidelines
    Dependencies
    • @mcp-abap-adt/interfaces: ^0.2.3 → ^0.2.6
      • Removed deprecated IAbapConnectionExtended interface
      • Added ITokenRefresher interface for DI
    • @mcp-abap-adt/connection: ^0.2.3 → ^0.2.5
      • createAbapConnection() now accepts 4th parameter tokenRefresher
      • JwtAbapConnection handles token refresh internally
    • @mcp-abap-adt/auth-providers: ^0.2.1 → ^0.2.3 (headless mode, cross-platform fixes)
    • @mcp-abap-adt/auth-broker: ^0.2.4 → ^0.2.9
      • Added createTokenRefresher() factory method
      • Headless mode passthrough
    • @mcp-abap-adt/auth-stores: ^0.2.5 → ^0.2.8 (EnvFileSessionStore with JWT persistence)
    • @mcp-abap-adt/clients: ^0.2.4 → ^0.2.5
  185. v1.1.29

    Features 1, Docs 1

    Notes
    Added
    • Parameter Passing Unit Tests Roadmap: Added comprehensive roadmap for unit tests to verify parameter passing
      • Created doc/roadmaps/parameter_passing_unit_tests_roadmap.md
      • Detailed test strategy for verifying all parameters pass correctly from MCP handlers through CrudClient to Builder classes and finally to low-level functions
      • Test implementation plan with 4 phases covering infrastructure, individual object types, and integration tests
      • Test examples for ViewBuilder, CrudClient, and low-level functions
      • Coverage goals (100% for parameter passing) and success criteria
      • Addresses issue where parameters like transportRequest can be lost during the call chain
    Documentation
    • Roadmap Documentation: Added roadmap for future unit test development
      • Comprehensive plan for preventing parameter loss regression
      • Test file structure and organization guidelines
      • Maintenance and CI/CD integration recommendations
  186. v1.1.28

    Changed 2, Other 2

    Notes
    Changed
    • Header Constants Migration: Replaced all hardcoded header strings with constants from @mcp-abap-adt/interfaces
      • All header references now use constants (e.g., HEADER_SAP_URL, HEADER_SAP_AUTH_TYPE, HEADER_MCP_DESTINATION, etc.)
      • Improved type safety and consistency across packages
      • Updated error messages and hints to use header constants
    • Updated dependencies:
      • @mcp-abap-adt/header-validator: ^0.1.6 → ^0.1.7
        • Added comprehensive header sets documentation
        • Enhanced validation for Basic Auth and UAA refresh token sets
        • All header references migrated to constants from @mcp-abap-adt/interfaces
      • @mcp-abap-adt/interfaces: ^0.1.1 → ^0.1.2
        • Added HTTP header constants for all headers used across packages
        • Added header groups and authentication type constants
    Technical
    • Code Quality: Improved maintainability by centralizing header name definitions
    • Type Safety: Better compile-time checking for header names
  187. v1.1.27

    Changed 1

    Notes
    Changed
    • Updated dependencies:
      • @mcp-abap-adt/auth-broker: ^0.1.9 → ^0.1.10
        • Added constructor validation for stores and tokenProvider parameters
        • Ensures required dependencies are present upon AuthBroker instantiation
  188. v1.1.26

    Features 2, Changed 1, Fixes 1

    Notes
    Added
    • Comprehensive usage documentation – Added complete MCP function call documentation:
      • Created doc/user-guide/usage/ directory with structured documentation
      • Simple Objects (22 files): High-level and low-level handlers for 11 object types:
        • Classes, Interfaces, Programs, Function Groups, Function Modules
        • Tables, Structures, CDS Views, Domains, Data Elements, Service Definitions
        • Each object has separate -high.md and -low.md files with exact MCP call examples
      • RAP Business Objects documentation:
        • Complete workflow for creating RAP BO with all components
        • Deferred group activation guide
        • Step-by-step examples with exact MCP function calls
      • All documentation uses compact, laconic format with only exact MCP calls and parameters
      • No unnecessary descriptions, maximum clarity
    • Test syntax checking – Added npm run test:check command:
      • Performs TypeScript syntax check on test files without running tests
      • Uses tsc --noEmit -p tsconfig.test.json
      • Helps catch type errors before running tests
    Changed
    • Updated dependencies to latest versions:
      • @mcp-abap-adt/adt-clients: ^0.1.35 → ^0.1.36
      • @mcp-abap-adt/auth-broker: ^0.1.8 → ^0.1.9
      • @mcp-abap-adt/auth-providers: ^0.1.1 → ^0.1.2
      • @mcp-abap-adt/auth-stores: ^0.1.3 → ^0.1.4
      • @mcp-abap-adt/connection: ^0.1.14 → ^0.1.15
      • @mcp-abap-adt/header-validator: ^0.1.4 → ^0.1.6
      • @mcp-abap-adt/interfaces: ^0.1.1 (unchanged)
      • @mcp-abap-adt/logger: ^0.1.0 → ^0.1.1
    Fixed
    • TypeScript type errors in TableLowHandlers.test.ts – Fixed type inference issues:
      • Corrected lockSessionForCleanup variable declaration and usage
      • Fixed type narrowing in catch and finally blocks
  189. v1.1.25

    Features 1, Changed 1, Fixes 2, Docs 4

    Notes
    Added
    • --mcp=<destination> parameter – Default MCP destination name for auth-broker:
      • Allows using auth-broker (service keys) with stdio and SSE transports
      • When specified, this destination is used when x-mcp-destination header is not provided in requests
      • For stdio transport: Server initializes auth-broker with the specified destination at startup
      • For SSE transport: Server uses the specified destination as fallback when header is missing
      • For HTTP transport: Server uses the specified destination as fallback when header is missing
      • Example: mcp-abap-adt --transport=stdio --mcp=TRIAL
      • This enables auth-broker usage with stdio and SSE transports, which previously required .env file configuration
    Changed
    • Auth-broker support for stdio and SSE transports – Extended getOrCreateAuthBroker() to support stdio and SSE transports:
      • Previously, auth-broker was only available for HTTP/streamable-http transport
      • Now works with all transport types when --mcp parameter is specified
      • For stdio: Auth-broker is initialized at server startup with the destination from --mcp parameter
      • For SSE: Auth-broker works the same way as HTTP transport (via headers or --mcp fallback)
    Fixed
    • --mcp parameter now skips .env file loading – When --mcp is specified:
      • .env file is not automatically loaded (even if it exists in current directory)
      • .env file is not considered mandatory for stdio and SSE transports
      • Auth-broker configuration takes precedence over .env file
      • This ensures that --mcp parameter works correctly without conflicts from .env file settings
    • SSE transport with --mcp parameter – Fixed issue where SSE transport was using .env configuration instead of auth-broker when --mcp was specified:
      • Server now initializes auth-broker at startup for SSE transport when --mcp is provided
      • applyAuthHeaders() now handles empty headers correctly when defaultMcpDestination is set
      • Configuration from auth-broker takes precedence over .env file
    Documentation
    • Updated CLI_OPTIONS.md with --mcp parameter documentation and examples
    • Updated CLIENT_CONFIGURATION.md with information about using --mcp parameter for stdio and SSE transports
    • Added examples for Cline configuration with stdio transport and --mcp parameter
    • Updated help text in both src/index.ts and bin/mcp-abap-adt.js with --mcp parameter information
  190. v1.1.24

    Features 1, Changed 1, Dependencies 1

    Notes
    Changed
    • Safe session storage by default – Session data is now stored in-memory using SafeSessionStore by default:
      • Session tokens are no longer persisted to disk by default (secure by default)
      • Session data is lost after server restart (requires re-authentication)
      • Use --unsafe flag to enable file-based session storage (legacy behavior)
      • File-based storage (UnixFileSessionStore/WindowsFileSessionStore) is only used when --unsafe is specified
    Added
    • --unsafe flag – Enables file-based session storage (persists tokens to disk):
      • When specified, session data is saved to platform-specific locations:
        • Unix: ~/.config/mcp-abap-adt/sessions/{destination}.env
        • Windows: %USERPROFILE%\Documents\mcp-abap-adt\sessions\{destination}.env
      • Can be set via environment variable: MCP_UNSAFE=true
      • Use this flag if you need session persistence across server restarts
    Dependencies
    • Updated @mcp-abap-adt/auth-broker to ^0.1.5:
      • Benefits from new SafeSessionStore implementation (in-memory, secure)
      • Updated to use new ISessionStore interface (replaces SessionStore)
  191. v1.1.23

    Fixes 1

    Notes
    Fixed
    • Help text correction – fixed incorrect help message for x-mcp-destination header:
      • Removed incorrect statement that x-mcp-destination requires x-sap-url header
      • Updated help text to correctly state that URL is automatically derived from service key (same as x-sap-destination)
      • Help text now accurately reflects that x-sap-url is not required and will be ignored if provided
  192. v1.1.22

    Features 2, Dependencies 2

    Notes
    Dependencies
    • Updated @mcp-abap-adt/adt-clients to ^0.1.33:
      • Benefits from optimized CSRF token endpoint (/sap/bc/adt/core/discovery)
      • Faster connection initialization for all CRUD operations
    • Updated @mcp-abap-adt/auth-broker to ^0.1.4:
      • Benefits from optimized CSRF token endpoint in connection layer
      • Faster authentication flows when managing JWT tokens
    Added
    • ABAP Unit class test tools: Added [low-level] handlers to cover the new CrudClient APIs for class test includes and ABAP Unit orchestration:
      • LockClassTestClassesLow, UpdateClassTestClassesLow, UnlockClassTestClassesLow, ActivateClassTestClassesLow
      • RunClassUnitTestsLow, GetClassUnitTestStatusLow, GetClassUnitTestResultLow
      • All new tools live in class/low/ and are registered with the server and documentation generator.
    • Package creation options: CreatePackage (high- and low-level) now accepts optional package_type, software_component, transport_layer, application_component, and responsible parameters and forwards them directly to CrudClient, enabling Cloud systems to pass values such as ZLOCAL while keeping the transport layer empty.
  193. v1.1.21

    Features 4, Changed 2, Fixes 1, Dependencies 2

    Notes
    Fixed
    • x-mcp-destination validation – fixed issue where x-mcp-destination header was incorrectly requiring x-sap-url:
      • x-mcp-destination now works identically to x-sap-destination - URL is automatically derived from service key
      • x-sap-url is now optional for x-mcp-destination (and will be ignored with a warning if provided)
      • Fixed issue on Windows where x-mcp-destination was being ignored
      • Updated header validator to check headers in both lowercase and original case for better compatibility
    Added
    • Custom auth-broker path – added --auth-broker-path command-line option:

      • Allows specifying custom base path for service keys and sessions
      • Automatically creates service-keys and sessions subdirectories in the specified path
      • Example: --auth-broker-path=~/prj/tmp/ uses ~/prj/tmp/service-keys/ and ~/prj/tmp/sessions/
      • Directories are created automatically if they don't exist
      • Can be used together with --auth-broker flag
    • Automatic directory creation – service keys and sessions directories are now created automatically:

      • Directories are created at server startup if they don't exist
      • Prevents errors when saving sessions for the first time
      • Works for both default platform paths and custom --auth-broker-path
    • Enhanced startup information – improved auth-broker path display at server startup:

      • Shows all search paths for service keys (in order of priority)
      • Shows all save paths for sessions
      • Formatted with clear visual separators for better readability
      • Only displayed when --auth-broker flag is used
    • Diagnostic logging – added platform-aware logging for better debugging:

      • Logs platform information when processing authentication headers
      • Logs all header keys that start with x-sap or x-mcp
      • Logs search paths when creating AuthBroker instances
      • Helps diagnose issues on different platforms (especially Windows)
    Changed
    • Header validation logic – improved validation order and case handling:

      • x-mcp-destination is now checked immediately after x-sap-destination, regardless of x-sap-url presence
      • Added case-insensitive header checking for better compatibility
      • Both x-sap-destination and x-mcp-destination now check headers in both lowercase and original case
    • Documentation updates – comprehensive documentation improvements:

      • Added --auth-broker-path parameter documentation in help text
      • Updated CLIENT_CONFIGURATION.md with custom path examples
      • Updated INSTALLATION.md with new command-line option
      • Updated SERVICE_KEY_SETUP.md with custom path usage examples
      • Added Cline configuration example in help text
    Dependencies
    • Updated @mcp-abap-adt/auth-broker to ^0.1.3 – upgraded to latest version
    • Updated @mcp-abap-adt/header-validator to ^0.1.3 – upgraded to latest version
  194. v1.1.20

    Changed 1

    Notes
    Changed
    • Help documentation improvements – updated help messages with detailed instructions for saving service keys:
      • Added platform-specific instructions for Linux, macOS, and Windows
      • Replaced example JSON structure with instructions to copy service key from SAP BTP
      • Fixed backslash escaping in Windows PowerShell commands
      • Updated both launcher help (bin/mcp-abap-adt.js) and server help (src/index.ts)
      • Updated documentation files: SERVICE_KEY_SETUP.md and CLIENT_CONFIGURATION.md
  195. v1.1.19

    Features 2, Changed 5, Fixes 3

    Notes
    Changed
    • Handler refactoring – migrated handlers to use CrudClient and SharedBuilder from @mcp-abap-adt/adt-clients:

      • handleSearchObject now uses SharedBuilder.search() instead of manual URL construction
      • Eliminates redundant URL concatenation and potential duplication errors
      • Better code reuse and consistency across handlers
    • URL handling simplification – removed unnecessary URL cleaning logic:

      • URLs from .env files and service keys are expected to be clean
      • Removed aggressive URL cleaning that was causing issues
      • Only basic trimming is performed where needed
    • Transport-specific auth-broker handling – auth-broker is now ignored for stdio and sse transports:

      • AuthBroker initialization is skipped for non-HTTP transports
      • Prevents unnecessary errors and resource usage
      • .env file is used directly for stdio and sse transports
    Fixed
    • URL duplication in stdio transport – fixed incorrect URL formation when using stdio transport:
      • Resolved getaddrinfo ENOTFOUND ...comhttps errors
      • Proper URL handling in connection initialization
      • Correct .env file parsing for stdio mode
    Added
    • Lazy AuthBroker initialization – AuthBroker instances are now created on-demand per destination:

      • AuthBroker is only created when a destination is actually needed (not at server startup)
      • Each destination gets its own AuthBroker instance, cached in a map for reuse
      • Reduces memory usage and startup time when auth-broker is not needed
      • Default AuthBroker instance for non-destination requests
    • Improved method filtering – only tools/call requires SAP configuration:

      • All other MCP methods (tools/list, tools/get, initialize, ping, notifications/initialized, etc.) work without SAP config
      • Prevents unnecessary authentication errors for metadata requests
      • Better separation between metadata operations and actual tool execution
    Changed
    • AuthBroker instance management – switched from single instance to per-destination map:

      • authBrokers map stores AuthBroker instances keyed by destination name
      • defaultAuthBroker for requests without specific destination
      • Lazy initialization via getOrCreateAuthBroker() method
      • Better isolation between different destinations
    • Platform store imports – fixed ES module compatibility:

      • Replaced require() with static imports in getPlatformStores()
      • Fixes "UnixFileSessionStore is not a constructor" error
      • Proper ES module support for platform-specific stores
    Fixed
    • ES module compatibility – fixed "UnixFileSessionStore is not a constructor" error:

      • Changed getPlatformStores() to use static imports instead of require()
      • Properly handles ES module exports for Unix/Windows store implementations
      • AuthBroker now correctly initializes on Unix systems
    • Request processing logic – improved handling of requests that don't require SAP connection:

      • Only tools/call method triggers SAP configuration validation
      • All other methods bypass authentication checks
      • Prevents false errors for metadata and initialization requests
  196. v1.1.17

    Features 3, Changed 2, Fixes 2, Dependencies 1

    Notes
    Added
    • Cross-platform session storage – optional persistent session management:

      • Session storage now disabled by default (stateless mode)
      • Enable via MCP_ENABLE_SESSION_STORAGE=true environment variable
      • Custom session directory via MCP_SESSION_DIR=/path/to/sessions
      • Platform-specific defaults: Windows temp dir, /tmp on Linux, /var/folders on macOS
      • Prevents ENOENT errors when running from different working directories
    • Cline configuration documentation – comprehensive setup guide:

      • Created doc/installation/CLINE_CONFIGURATION.md with detailed configuration for all transport types
      • Added example configs in doc/installation/examples/ for NPX, global install, and local development
      • Platform-specific notes for macOS, Linux, and Windows
      • Session storage configuration examples and best practices
      • Troubleshooting guide for common connection issues
    • Cross-platform fixes documentation – created doc/development/CROSS_PLATFORM_FIXES.md:

      • Documented dotenv removal and stdio transport fixes
      • Cross-platform path handling with os.tmpdir() and path.join()
      • Platform-specific session storage locations
      • Testing instructions for all three transports (stdio, HTTP, SSE)
    Changed
    • Session storage architecture – improved reliability and cross-platform support:

      • Moved from relative .sessions path to absolute paths using os.tmpdir()
      • Session storage now optional and controlled by environment variables
      • Removed FileSessionStorage usage from individual handlers (e.g., handleDeletePackage)
      • All session management now centralized in connection manager (utils.ts)
    • Environment variable documentation – comprehensive env var reference:

      • Added table of all available environment variables in Cline configuration docs
      • Documented MCP_ENABLE_SESSION_STORAGE and MCP_SESSION_DIR usage
      • Platform-specific default values and examples
      • Guidance on when to enable/disable session storage
    Fixed
    • Session directory creation errors – fixed ENOENT: no such file or directory, mkdir '/.sessions':

      • Replaced relative .sessions path with absolute paths
      • Session directory now uses OS-specific temp directory by default
      • Works correctly when running as global npm package or via npx
      • No longer depends on current working directory
    • Handler session management – simplified connection handling:

      • Removed custom FileSessionStorage creation in handleDeletePackage
      • Deprecated force_new_connection parameter (now ignored)
      • All connections use centralized session management
      • Restart server to force new session instead of per-handler sessions
    Dependencies
    • No dependency changes in this release
  197. v1.1.16

    Removed 1, Changed 3, Fixes 1, Dependencies 2

    Notes
    Removed
    • dotenv dependency – completely removed dotenv package from dependencies:
      • Eliminated dotenv as a dependency to prevent stdout pollution in stdio mode
      • Resolved "[dotenv@17."... is not valid JSON error that was breaking stdio transport
      • Server now uses manual .env file parsing for all transport modes
    Changed
    • .env file parsing – replaced dotenv library with manual parsing:

      • Manual .env file parsing for all transport modes (stdio, http, sse)
      • Supports both Unix (\n) and Windows (\r\n) line endings
      • Handles comments, quoted values, and empty lines correctly
      • Cross-platform compatible (works on Linux, Windows, macOS)
      • No external dependencies for environment variable loading
    • stdio mode auto-detection – improved stdio mode detection:

      • Auto-detects stdio mode when stdin is not a TTY (piped by MCP Inspector or other tools)
      • Checks !process.stdin.isTTY in addition to command-line arguments and environment variables
      • Allows seamless connection from MCP Inspector and other stdio-based clients without explicit --transport=stdio flag
    • stderr handling in stdio mode – restored stderr for error logging:

      • Following reference implementation pattern, stderr is now available for error logging in stdio mode
      • stdout remains reserved exclusively for MCP JSON-RPC protocol
      • console.error() and console.warn() now work correctly in stdio mode (write to stderr)
    Fixed
    • stdio transport connection – fixed issue where server was not connecting properly in stdio mode:
      • Server now correctly detects stdio mode when launched by MCP Inspector
      • Tools are properly registered and available when connecting via stdio
      • Resolved connection issues that prevented tools from being listed
    Dependencies
    • Updated @mcp-abap-adt/connection to ^0.1.12:
      • Connection package no longer reads .env files or depends on dotenv
      • Consumers must pass SapConfig directly to connection constructors
    • Updated @mcp-abap-adt/adt-clients to ^0.1.27:
      • Updated to work with connection@0.1.12
      • Fixed example scripts to use manual .env parsing
  198. v1.1.15

    Fixes 1

    Notes
    Fixed
    • Git tag management – fixed issues with release tag creation and management:
      • Corrected tag placement on the latest commit with CHANGELOG updates
      • Ensured proper tag synchronization between local and remote repositories
      • Fixed tag deletion and recreation workflow for release management
  199. v1.1.14

    Features 1, Changed 2, Fixes 1

    Notes
    Added
    • Command-line flags --version/-v and --help/-h – added support for standard command-line flags:
      • --version or -v – displays package version and exits
      • --help or -h – displays help message and exits
      • Flags are processed before any other initialization, allowing quick version/help checks
    Changed
    • .env file search logic – improved .env file discovery and loading:

      • Search location: Now searches for .env file only in the current working directory (where the command is executed)
      • No recursive search: Removed fallback searches in parent directories or project root
      • Explicit path support: Still supports --env=/path/to/.env or MCP_ENV_PATH environment variable for custom locations
      • Cross-platform paths: Uses path.resolve() and path.join() for proper path handling on Windows, Linux, and macOS
    • Transport mode and .env file relationship – refined logic for transport mode selection and .env file requirements:

      • Default behavior: If transport is not explicitly specified and no .env file is found, server starts in HTTP mode (no .env required)
      • Explicit transport with .env: If transport is explicitly set to stdio or sse but .env file is missing, server exits with clear error message
      • Explicit transport without .env: If transport is explicitly set to http or streamable-http, server starts without .env file (as expected)
      • Error messages: Improved error messages that clearly indicate what transport mode requires .env file and how to specify custom .env location
    Fixed
    • Cross-platform compatibility – ensured all file path operations work correctly on Windows, Linux, and macOS:
      • All path operations use Node.js path module methods (path.join(), path.resolve(), path.isAbsolute())
      • Proper handling of Windows path separators (\) vs Unix path separators (/)
      • Version reading from package.json uses cross-platform path resolution
  200. v1.1.13

    Changed 1, Fixes 1, Dependencies 1

    Notes
    Fixed
    • Handler parameter schemas alignment with CrudClient – comprehensive fix to ensure all handler input schemas match actual CrudClient method parameters:
      • Data Element handlers:
        • Added type_name parameter to handleCreateDataElement schema (was missing but used in code)
        • Added search_help, search_help_parameter, set_get_parameter to handleUpdateDataElement schema (now properly passed to builder)
        • Updated field label descriptions to clarify they are applied during update step after creation
      • Domain handlers:
        • Removed unsupported parameters from handleCreateDomain low-level handler: domain_type, application, master_system, responsible
      • Program handlers:
        • Removed master_system and responsible from both high and low-level handlers (these are set automatically by system)
      • Class handlers:
        • Removed master_system and responsible from high-level handler schema (set automatically by system)
        • Removed master_system and responsible from low-level handler schema and code
      • Interface handlers:
        • Removed master_system and responsible from both high and low-level handlers (set automatically by system)
      • Structure, View, Table handlers:
        • Removed master_system and responsible from low-level create handlers (set automatically by system)
      • Behavior Definition, Metadata Extension handlers:
        • Removed master_system and responsible from low-level create handlers (set automatically by system)
      • Impact: All handler schemas now accurately reflect what parameters are actually supported and used by CrudClient methods, preventing confusion and ensuring correct API usage
    Changed
    • Data Element handlers – search help support – enhanced data element handlers to support search help configuration:
      • handleCreateDataElement now accepts search_help, search_help_parameter, set_get_parameter parameters
      • handleUpdateDataElement now accepts search_help, search_help_parameter, set_get_parameter parameters
      • These parameters are properly passed through to DataElementBuilder and included in update operations
      • Enables full configuration of search help and parameter settings for data elements
    Dependencies
    • Updated @mcp-abap-adt/adt-clients to ^0.1.26 – upgraded to latest version which includes:
      • Added searchHelp, searchHelpParameter, setGetParameter to DataElementBuilderConfig
      • Updated DataElementBuilder.update() to pass search help parameters to UpdateDataElementParams
      • Full support for search help configuration in data element operations
  201. v1.1.11

    Changed 1, Fixes 1, Dependencies 1, Docs 1, Other 1

    Notes
    Fixed
    • DomainHighHandlers - Update workflow detection bug – fixed critical issue where domain update operations were incorrectly using CREATE workflow instead of UPDATE workflow:
      • Root cause: DomainBuilder.update() method determines which workflow to use (CREATE vs UPDATE) by checking if this.state.createResult exists. When CrudClient.getDomainBuilder() reuses a builder instance from a previous createDomain() call, the builder still has createResult in its state, causing update() to incorrectly use CREATE workflow (upload() function) instead of UPDATE workflow (updateDomain() function).
      • Solution: Modified handleUpdateDomain to pass packageName directly to lockDomain() call, ensuring the builder is created with correct configuration from the start. This prevents builder reuse issues and ensures proper workflow detection.
      • Additional fix: Removed redundant validation step from handleUpdateDomain handler. The validation was checking if domain exists, but lockDomain() will fail if domain doesn't exist anyway, making the validation step unnecessary and potentially causing confusion.
      • Impact: Domain update operations now correctly use UPDATE workflow for existing domains, preventing "Domain already exists" errors that were occurring when the system tried to create an already-existing domain.
    Changed
    • DomainHighHandlers test logging – completely refactored test logging system for better information density and clarity:
      • Removed verbose debugLog() system – eliminated all debugLog() calls that were cluttering test output with verbose JSON structures and nested data objects
      • Introduced concise logging – replaced with simple, informative console.log() statements that provide clear, actionable information:
        • Clear operation names (e.g., "High Create: Creating domain...", "High Update: Updating domain...")
        • Success/failure status with object names
        • Skip reasons when tests are skipped
        • Visual infographics (emojis) for quick visual scanning of test output
      • Simplified error handling – removed redundant try-catch blocks and verbose error logging, now relies on Jest's native error handling which provides better stack traces
      • More informative output – each log statement now provides essential information (operation type, object name, status) without JSON noise
      • Consistent with other tests – matches logging patterns used in BehaviorDefinitionHighHandlers, PackageHighHandlers, and other high-level handler tests
      • Better maintainability – removed ~50 lines of verbose debug logging code, making tests easier to read and maintain
      • Improved debugging – concise logs make it easier to identify which operation failed and why, without scrolling through JSON dumps
    Dependencies
    • Updated @mcp-abap-adt/adt-clients to ^0.1.25 – upgraded to latest version which includes:
      • Improved DomainBuilder.update() workflow detection logic
      • Better handling of CREATE vs UPDATE workflow selection based on builder state
      • Enhanced error messages for domain operations
    Documentation
    • Test Issues Roadmap – comprehensive update to development roadmap:
      • Issue #10 (DomainHighHandlers) – changed status from "Expected Skip" to "Fixed" with detailed explanation
      • Updated statistics: 8 fixed issues (was 7), 1 expected skip (was 3)
      • Added detailed problem analysis: Documented root cause, solution approach, and code changes
      • Improved issue tracking: Better categorization of issues vs expected skips vs cloud limitations
    Development Process
    • Issue Roadmap System – introduced systematic problem analysis and resolution tracking:
      • New workflow: Test failures are now systematically analyzed and documented in doc/development/TEST_ISSUES_ROADMAP_*.md
      • Issue classification: Problems are categorized as Simple (⚡) or Complex (🔍), with clear distinction between real issues and expected skips
      • Problem analysis: Each issue includes detailed error messages, root cause analysis, solution approach, and code references
      • Progress tracking: Roadmap maintains statistics on fixed vs pending vs expected issues, providing clear visibility into test suite health
      • Solution documentation: All fixes are documented with code references, making it easier to understand what was changed and why
      • Continuous improvement: Roadmap is updated after each test run, ensuring it reflects current state and helps prioritize future work
      • Benefits: Enables faster problem resolution, prevents duplicate work, and provides historical context for similar issues
  202. v1.1.10

    Features 2, Changed 2, Fixes 1

    Notes
    Added
    • Client Connection Isolation: Implemented per-session connection isolation to prevent data mixing between different clients

      • Each client session now maintains its own isolated SAP connection based on sessionId and configuration hash
      • Connections are cached per unique combination of sessionId + sapUrl + authentication parameters
      • Uses AsyncLocalStorage to pass session context to handlers, ensuring each request uses the correct connection
      • Prevents race conditions where concurrent requests from different clients could overwrite each other's connection settings
      • Backward compatible: falls back to global connection cache for non-HTTP transports (stdio)
    • Non-Local Connection Restrictions: Added security restrictions for non-local connections

      • SSE Transport: Always restricted to localhost connections only (127.0.0.1, ::1, localhost)
      • HTTP Transport: Non-local connections are restricted when:
        • .env file exists (was found at startup)
        • AND request does not include SAP connection headers (x-sap-url, x-sap-auth-type)
      • Non-local connections with SAP headers are allowed (enables multi-tenant scenarios)
      • Local connections are always allowed regardless of .env file presence
      • Clear error messages guide users when connections are rejected
    Changed
    • Connection Management: Refactored getManagedConnection() to support session-based connection caching

      • Added generateConnectionCacheKey() function to create unique cache keys from sessionId and config signature
      • Implemented getConnectionForSession() to retrieve or create session-specific connections
      • Added automatic cleanup of old connection cache entries (older than 1 hour)
      • Each session uses unique sessionId for its AbapConnection to prevent session mixing
    • Session Tracking: Enhanced streamableHttpSessions to store SAP configuration per session

      • Added sapConfig field to session objects to track configuration per client
      • applyAuthHeaders() now stores configuration in session object
      • Session cleanup automatically removes associated connection from cache
    Security
    • Connection Isolation: Prevents one client from receiving another client's data when multiple clients connect to different SAP systems
    • Access Control: Restricts non-local access when .env file is present, requiring explicit SAP headers for remote connections
  203. v1.1.10

    Security 2, Features 2, Changed 2

    Notes
    Added
    • Client Connection Isolation: Implemented per-session connection isolation to prevent data mixing between different clients

      • Each client session now maintains its own isolated SAP connection based on sessionId and configuration hash
      • Connections are cached per unique combination of sessionId + sapUrl + authentication parameters
      • Uses AsyncLocalStorage to pass session context to handlers, ensuring each request uses the correct connection
      • Prevents race conditions where concurrent requests from different clients could overwrite each other's connection settings
      • Backward compatible: falls back to global connection cache for non-HTTP transports (stdio)
    • Non-Local Connection Restrictions: Added security restrictions for non-local connections

      • SSE Transport: Always restricted to localhost connections only (127.0.0.1, ::1, localhost)
      • HTTP Transport: Non-local connections are restricted when:
        • .env file exists (was found at startup)
        • AND request does not include SAP connection headers (x-sap-url, x-sap-auth-type)
      • Non-local connections with SAP headers are allowed (enables multi-tenant scenarios)
      • Local connections are always allowed regardless of .env file presence
      • Clear error messages guide users when connections are rejected
    Changed
    • Connection Management: Refactored getManagedConnection() to support session-based connection caching

      • Added generateConnectionCacheKey() function to create unique cache keys from sessionId and config signature
      • Implemented getConnectionForSession() to retrieve or create session-specific connections
      • Added automatic cleanup of old connection cache entries (older than 1 hour)
      • Each session uses unique sessionId for its AbapConnection to prevent session mixing
    • Session Tracking: Enhanced streamableHttpSessions to store SAP configuration per session

      • Added sapConfig field to session objects to track configuration per client
      • applyAuthHeaders() now stores configuration in session object
      • Session cleanup automatically removes associated connection from cache
    Security
    • Connection Isolation: Prevents one client from receiving another client's data when multiple clients connect to different SAP systems
    • Access Control: Restricts non-local access when .env file is present, requiring explicit SAP headers for remote connections
  204. v1.1.9

    Features 4, Changed 5, Docs 2

    Notes
    Added
    • DDLX (Metadata Extension) Management Tools:

      • CreateMetadataExtension: Create new ABAP Metadata Extensions (DDLX) with automatic activation
      • UpdateMetadataExtension: Update source code of existing Metadata Extensions
      • Full CRUD workflow: Create → Lock → Check → Unlock → Activate
      • Support for transport request validation
      • Integrated with CrudClient API for consistent behavior
    • BDEF (Behavior Definition) Management Tools:

      • CreateBehaviorDefinition: Create new ABAP Behavior Definitions (BDEF) with support for Managed, Unmanaged, Abstract, and Projection implementation types
      • UpdateBehaviorDefinition: Update source code of existing Behavior Definitions
      • Full CRUD workflow: Create → Lock → Check → Unlock → Activate
      • Support for root entity specification and implementation type selection
      • Transport request validation integrated
    • Full Low-Level CRUD Coverage:

      • Added dedicated create, lock, unlock, check, validate, delete, and update handlers for classes, programs, interfaces, function groups/modules, domains, data elements, packages, tables, views, structures, transports, and more to cover every CrudClient method.
      • Each handler now has a consistent naming scheme (CreateX, UpdateX, etc.) to match the high-level counterparts.
    • System Management Tools:

      • GetInactiveObjects: Retrieve list of inactive ABAP objects (objects that have been modified but not activated)
      • Provides count and detailed list of objects waiting for activation
      • Useful for monitoring development progress and identifying objects requiring attention
    Changed
    • Handler Organization Refactoring: All handlers reorganized into categorized subdirectories with explicit high/, low/, and readonly/ layers for better maintainability. Each low-level handler description now starts with [low-level], and every read handler was moved to the new readonly/ folders and tagged [read-only]. This improves discoverability, enforces single-responsibility boundaries, and makes it clear which tools mutate SAP data.

      • bdef/ - Behavior Definition handlers (GetBdef, CreateBehaviorDefinition, UpdateBehaviorDefinition)
      • class/ - Class handlers (GetClass, CreateClass, UpdateClass, ValidateClass, CheckClass)
      • common/ - Common handlers (ActivateObject, DeleteObject, CheckObject, LockObject, UnlockObject, ValidateObject)
      • data_element/ - Data Element handlers (GetDataElement, CreateDataElement, UpdateDataElement)
      • ddlx/ - Metadata Extension handlers (CreateMetadataExtension, UpdateMetadataExtension)
      • domain/ - Domain handlers (GetDomain, CreateDomain, UpdateDomain)
      • enhancement/ - Enhancement handlers (GetEnhancements, GetEnhancementImpl, GetEnhancementSpot)
      • function/ - Function handlers (GetFunction, GetFunctionGroup, CreateFunctionGroup, CreateFunctionModule, UpdateFunctionModule, ValidateFunctionModule, CheckFunctionModule)
      • include/ - Include handlers (GetInclude, GetIncludesList)
      • interface/ - Interface handlers (GetInterface, CreateInterface, UpdateInterface)
      • package/ - Package handlers (GetPackage, CreatePackage)
      • program/ - Program handlers (GetProgram, CreateProgram, UpdateProgram)
      • search/ - Search handlers (SearchObject, GetObjectsByType, GetObjectsList)
      • structure/ - Structure handlers (GetStructure, CreateStructure)
      • system/ - System handlers (GetTypeInfo, GetTransaction, GetSqlQuery, GetWhereUsed, GetObjectInfo, GetSession, GetAbapAST, GetAbapSemanticAnalysis, GetAbapSystemSymbols, GetAllTypes, GetObjectStructure, GetObjectNodeFromCache, GetInactiveObjects)
      • table/ - Table handlers (GetTable, GetTableContents, CreateTable, ValidateTable, CheckTable)
      • transport/ - Transport handlers (GetTransport, CreateTransport)
      • view/ - View handlers (GetView, CreateView, UpdateView)
      • This reorganization improves code navigation, reduces merge conflicts, and makes the codebase more maintainable
    • Dependencies Updated:

      • @mcp-abap-adt/adt-clients: ^0.1.9 → ^0.1.12
      • @mcp-abap-adt/connection: ^0.1.4 → ^0.1.9
      • Updated to leverage new CrudClient methods for DDLX and BDEF operations
    • Index and Tools Registry Updates:

      • src/index.ts: Updated all handler imports to reflect new subdirectory structure
      • src/lib/toolsRegistry.ts: Updated tool definitions imports to match new handler locations
      • All 81 handler files moved and imports updated consistently
    • Tooling Improvements:

      • tools/generate-tools-docs.js now outputs a navigation tree that mirrors the document structure, separates high-/low-/read-only tools, and adds summary counts for each level.
      • doc/user-guide/AVAILABLE_TOOLS.md regenerated to include the new navigation, level-specific anchors, and the [read-only] marker.
      • Test suite, configs, and helper scripts updated to follow the new directory layout and renamed high-level handlers (UpdateClass, UpdateProgram, etc.).
    • Low-Level Tool Names:

      • Every low-level handler now advertises a unique MCP tool name suffixed with Low (e.g., CreateClassLow, UpdateDomainLow, LockPackageLow). This prevents Tool <name> is already registered errors when both low/high versions exist and keeps the server compatible with clients that look up tools by name.
    Documentation
    • Added implementation_plan.md: Plan for future refactoring of read handlers to src/readers directory
    • Removed roadmap.md: Work items are complete, so the standalone roadmap is no longer required
  205. v1.1.8

    Notes

    Documentation for this tag is intentionally minimal; see the Git tag v1.1.8 for its exact contents.

  206. v1.1.6

    Docs 1

    Notes
    Documentation
    • Comprehensive CLI documentation: Added detailed documentation for all command line options and configuration
      • Updated README.md with global installation guide and CLI usage examples
      • Enhanced INSTALLATION.md with complete CLI options reference
      • Created new CLI_OPTIONS.md with comprehensive command line reference
      • Documented environment file priority and auto-discovery behavior
      • Added troubleshooting section for common CLI issues
  207. v1.1.5

    Features 1, Changed 1, Fixes 4

    Notes
    Added
    • --help flag: Added comprehensive help message showing all CLI options, environment variables, and usage examples
      • Shows transport options (stdio, http, sse)
      • Lists all HTTP and SSE configuration options
      • Documents SAP connection environment variables
      • Includes practical usage examples
    Fixed
    • Global installation .env loading: Fixed .env file discovery for globally installed packages
      • Now correctly prioritizes process.cwd()/.env (user's working directory) over package directory
      • Works correctly when installed via npm install -g
      • Better error messages showing current directory and suggesting --env flag
    • --env argument parsing: Fixed custom .env file path support
      • Both --env=/path/to/.env and --env /path/to/.env formats now work correctly
      • Relative paths are resolved from current working directory
      • Clear feedback messages showing which .env file is being used
    • Runtime dependencies: Moved dotenv from devDependencies to dependencies
      • Fixes "Cannot find module 'dotenv'" error when installed globally
      • Ensures all runtime dependencies are available in production
    Changed
    • All handlers refactored to use CrudClient – replaced Builder pattern with unified CrudClient API
      • 18 handlers converted: Create (Program, DataElement, Domain, FunctionGroup, FunctionModule, Package, Structure, Table, Transport, View), Update (ClassSource, DataElement, InterfaceSource), Check (Class, FunctionModule, Table, Object), Validate (Class, Object), Lock/Unlock (Object)
      • Removed direct Builder instantiation from all handlers
      • All CRUD operations now use CrudClient methods (createProgram, lockClass, updateInterface, etc.)
      • Session ID no longer exposed - internal to CrudClient implementation
      • Simplified error handling with try-catch around lock/update/unlock sequences
    Fixed
    • utils.ts deprecated methods – added TODO comments for missing ReadOnlyClient methods
      • fetchNodeStructure() - marked for future implementation
      • getSystemInformation() - marked for future implementation
      • Both throw errors with clear TODO messages instead of attempting non-existent method calls
  208. v1.1.3

    Features 1, Changed 1

    Notes
    Added
    • Parameter Optionality Sync Tool: Created tools/sync-optional-from-interfaces.js to extract optional parameter information from TypeScript interfaces
      • Extracts required vs optional fields from @mcp-abap-adt/adt-clients builder interfaces
      • Provides single source of truth for parameter optionality
      • Prevents drift between TypeScript interfaces and MCP tool definitions
      • Documentation added in doc/development/SYNC_OPTIONAL_PARAMS.md
    Changed
    • CreateDomain Handler: Synced parameter optionality with DomainBuilderConfig interface
      • Only domain_name is required (was incorrectly requiring package_name)
      • Marked all optional parameters with (optional) prefix in descriptions
      • Ensures consistency with TypeScript interface definitions
  209. v1.1.2

    Features 1, Fixes 1

    Notes
    Fixed
    • STDIO Transport: Fixed STDIO transport broken by mixing old Server and new McpServer APIs
      • Removed legacy Server class and setupHandlers() method
      • Unified all transports (STDIO, HTTP, SSE) to use single McpServer instance
      • Fixed STDIO connection to use mcpServer.server.connect(transport) pattern
      • Removed unused imports: Server, CallToolRequestSchema, ListToolsRequestSchema
    Added
    • SSE Development Tool: Created tools/dev-sse.js for proper SSE server development workflow
      • Launches SSE server on port 3001 (default)
      • Automatically starts MCP Inspector with correct SSE endpoint (http://localhost:3001/sse)
      • Mirrors dev-http.js functionality for consistent development experience
      • Updated npm run dev:sse to use new development script
  210. v1.4.0

    Features 6

    Notes
    Added
    • All MCP handler functions now support an optional filePath parameter. If provided, the handler will save the result to the specified file path (any absolute or relative path, with any extension).
    • All CLI test scripts now accept the --filePath=... argument to save the result to a file.
    • The result written to file is exactly what is returned to the user (no extra formatting or caching).
    • Improved error logging and debug output for file writing operations.
    • Updated documentation and usage examples in README.md to reflect new file output support.
    • Implemented utility writeResultToFile for safe file writing with directory restriction and logging.
  211. v1.3.0

    Features 3, Changed 1

    Notes
    Added
    • Added integration test tests/integration/handleGetFunction.int.test.js for handleGetFunction.
    • Significantly expanded integration test coverage in src/index.test.ts for all main tools (GetProgram, GetClass, GetFunctionGroup, GetFunction, GetTable, GetStructure, GetTableContents, GetPackage, GetInclude, GetTypeInfo, GetInterface, GetTransaction, GetEnhancements, GetSqlQuery, SearchObject).
    • Added new checks for advanced scenarios (e.g., SQL generation, error handling, enhancement XML parsing).
    Changed
    • Updated documentation regarding testing and integration scenarios.
  212. v1.2.0

    Changed 2

    Notes
    Changed
    • GetWhereUsed now supports any ADT object type (e.g. table/TABL, bdef/BDEF, etc.), not just class, program, include, function, interface, package.
    • Updated documentation in README.md and jsdoc for supported object types and usage examples.
  213. v1.1.0

    Features 1

    Notes
    Added
    • New GetTransaction tool to retrieve ABAP transaction details.
      • Allows fetching transaction details using the ADT endpoint /sap/bc/adt/repository/informationsystem/objectproperties/values.
      • Added documentation in README.md.
  214. v0.1.2

    Changed 4

    Notes
    Changed
    • Added Jest Test Script index.test.ts available through npm test
    • Enhanced makeAdtRequest method to support:
      • Custom headers through an optional parameter
      • Query parameters through an optional params parameter
    • Improved handleGetPackage method to use ADT's nodeContent API
      • Now uses POST request with proper XML payload
      • Added specific content type headers for nodeContent endpoint
      • Added filtering to return only objects with URI
    • Improved CSRF token handling in utils.ts
      • Added automatic CSRF token fetching for POST/PUT requests
      • Enhanced token extraction to work with error responses
      • Added cookie management for better session handling
      • Implemented singleton axios instance for consistent state
      • Added proper cleanup for test environments
  215. v0.1.1

    Features 1

    Notes
    Added
    • New GetInterface tool to retrieve ABAP interface source code
      • Allows fetching source code of ABAP interfaces using the ADT endpoint /sap/bc/adt/oo/interfaces/
      • Similar functionality to GetClass but for interfaces
      • Added documentation in README.md
  216. v0.1.0

    Features 4

    Notes
    Added
    • Initial release of the MCP ABAP ADT server
    • Basic ABAP object retrieval functionality
    • Support for programs, classes, function modules, and more
    • Documentation and setup instructions
Agent access to your code
  1. @abap2ui5/mcp-serverMCP server for the abap2UI5 check, deploy and run loopfrom ABAP
  2. arc-1-bopf-enharc-1 extension for reading and maintaining BOPF enhancementsfrom ABAP
  3. HammerNative macOS code editor for ABAP and CDSfrom ABAP
  4. arc-1-lspEdition of ARC-1 that runs ADT calls through adt-lsfrom ABAP
  5. arc-1MCP server over the ADT REST API for ABAP objectsfrom ABAP
  6. arc1-abap-bridgeVS Code bridge between arc-1 and ADT for VS Codefrom ABAP
  7. arc1-adt-abap-mcp-extEclipse add-on enabling the ADT MCP server with read-only toolsfrom ABAP
  8. capforgeMCP server scaffolding CAP and CDS checked by cds compilefrom CAP
  9. ABAP Accelerator MCP ServerAWS's ABAP MCP server for Amazon Q Developer and Kirofrom ABAP
  10. Modernization Agents for SAP on AWSAWS agents for ABAP, PI/PO and BW modernisation workfrom ABAP
  11. @mcp-abap-adt/coreABAP ADT MCP server published as an npm scope and pluginfrom ABAP
  12. @cap-js/mcp-serverMCP server for CAP development: model and docs searchfrom CAP
  13. chandrashekhar-mahajan/abap-mcp-serverABAP MCP server with ADT tools and five authentication strategiesfrom ABAP
  14. dassian-adtFork of mcp-abap-abap-adt-api with 25 tools and session recoveryfrom ABAP
  15. erpl-adtC++ binary speaking the ADT REST API as a CLI and MCP serverfrom ABAP
  16. bw-modeling-mcpMCP server for BW/4HANA modelling objectsfrom BTP/BAIP
  17. EPOD ADT MCP ServerMCP server for ABAP on the ADT session, in Eclipse or standalonefrom ABAP
  18. ZARC1_EXTENSION_FORMSarc-1 extension for SAPscript and Smart Forms objectsfrom ABAP
  19. fgalastri/MCP_ABAPValidates an ABAP class before it is deployed by converting the global class to a local one and syntax-checking that — plus CDS DDL and table metadata retrieval.from ABAP
  20. abapsmithABAP MCP server over ADT with a local journal that can undo writesfrom ABAP
  21. sap-ai-dev-toolkitMCP proxy pinning an agent's ABAP tools to one SAP systemfrom ABAP
  22. aibap.mcpGo MCP server that writes, activates and runs ABAP classes via ADTfrom ABAP
  23. BellaEclipse plugin bringing Claude into ABAP Development Toolsfrom ABAP
  24. Maherd18/sap-mcp-serverPolicy-checked MCP server for ABAP development over ADTfrom ABAP
  25. sapdumpmcpMCP server reading ABAP short dumps over ADTfrom ABAP
  26. ABAP remote filesystemVS Code extension editing ABAP systems over ADT as a filesystemfrom ABAP
  27. mcp-abap-abap-adt-apiWrite-oriented MCP server over abap-adt-apifrom ABAP
  28. mcp-abap-adtRead-oriented MCP server fetching ABAP programs, classes, tablesfrom ABAP
  29. SAP MCP Connection ManagerWindows desktop manager handing SAP connections to assistants over MCPfrom ABAP
  30. ABAPilotMCP connector for ECC and on-premise S/4HANA with a licensed SAP sidefrom ABAP
  31. vibing-steampunkADT-to-MCP bridge in Go reaching past what ADT exposesfrom ABAP
  32. ADT MCP ServerLocal HTTP MCP server started from ABAP Development Tools for VS Codefrom ABAP
  33. ABAP Development Tools for VS CodeSAP's ABAP tools in VS Code for editing, debugging, ATC and transportsfrom ABAP
  34. SAP Joule for Developers, ABAP AI capabilitiesSAP's AI for ABAP development in ADT for Eclipse and VS Codefrom ABAP
  35. @sap/mdk-mcp-serverMCP server with five tools for Mobile Development Kit workfrom BTP/BAIP
  36. @sap-ux/fiori-mcp-serverMCP server for creating and editing Fiori applicationsfrom UI5
  37. ABAPDocMCPMCP server turning a transport request into a WRICEF specificationfrom ABAP
  38. claude-sap-kitClaude Code plugin adding Custom_* tools to arc-1from ABAP
  39. Skybuffer AI MCP serverABAP add-on serving business data and transactions as MCP toolsfrom ABAP
  40. @ui5/mcp-serverMCP server for UI5 development with ten toolsfrom UI5
  41. abap-adt-cliCLI that pulls and pushes ABAP packages as local files over ADTfrom ABAP
  42. workskong/mcp-abap-adtLight MCP adapter for ABAP over ADT, documented in Koreanfrom ABAP
  43. KlystronMCP server in ABAP inside the application server, with agent skillsfrom ABAP
  44. pyrfc-mcpMCP server calling RFC-enabled function modules behind an allowlistfrom ABAP
  45. ABAP-AI-CODE-TOOLSabapGit package adding create, modify and delete tools to ABAP AI Codefrom ABAP
  46. ABAP AI CodeAgentic coding assistant in ABAP that runs as an SAP GUI reportfrom ABAP
  47. VERTEXADT extension for Eclipse and VS Code showing three SAP GUI toolsfrom ABAP

Details

Sources3
sap-ai-mcp-serversdiscovered herechecked
github.com/fr0ster/mcp-abap-adttree, pushes, contributors, CIchecked
README.mdquoted, and what the entry declareschecked

Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.

Compatibility and provenance
runtimesnode
discovered viasap-ai-mcp-servers
first seen in the atlas
ecosystem and domainabap, ai-and-agents
Flavors2
Standard ABAP
“- Works with **On-Premise (ECC/S/4HANA)** and **ABAP Cloud (BTP)** systems” — fr0ster/mcp-abap-adt — READMEchecked
ABAP Cloud
“- Works with **On-Premise (ECC/S/4HANA)** and **ABAP Cloud (BTP)** systems” — fr0ster/mcp-abap-adt — READMEchecked
Contributors4

From the repository’s contributor list, .

History in the atlas6
  1. the ABAP platform and the BTP ABAP environment, through ADT, from the README's first sentence and its list of the systems it works with
  2. legacy systems not supported at present; Apache-2.0 for the repository and the library, AGPL-3.0-only for the standalone server
  3. inspired by mario-andreschak/mcp-abap-adt
  4. corrected against the README
  5. Triaged out of data/crawled/mcp-candidates.json, the sap-ai-mcp-servers sweep. Written from the repository's own README; the catalogue is credited as where it was found and its words are not reused.
  6. First seen.
For your agent
Ref
artifact:fr0ster-mcp-abap-adt
JSON
/artifacts/fr0ster-mcp-abap-adt.json
MCP
Connecting an agent