Generic, reusable TypeScript SDK over SAP's headless adt-ls — the
adt-lsclanguage server shipped inside the officialsapse.adt-vscodeextension. It hides the painful setup (discovery, JVM, named-pipe + LSP handshake, reentrance logon, TLS/truststore, session resilience) so that driving adt-ls is a few lines of code.
Status: published on npm — functionally complete & live-proven. The full SAP authoring
lifecycle (search → create → update → read → activate → run-tests → delete), plus code
intelligence, quality gates, ABAP formatting, transport, and OData service info, all run
end-to-end through createAdtLs() against a real S/4HANA system (adt-ls
1.0.0.202605281240). Runs under Node ≥ 20 and Bun (both verified live).
npm install @marianfoo/adt-ls
You bring adt-ls (SAP Developer License — not redistributable): install the
sapse.adt-vscode extension (VS Code / Cursor) and the library auto-discovers it, or
vendor the per-platform VSIX for CI. New here → docs/setup.md:
which platform build to download, CI vendoring, and connecting with auth.
import { createAdtLs, basic } from '@marianfoo/adt-ls';
const adt = await createAdtLs({
connection: { systemUrl: 'https://my-s4:50001', selfSigned: true, client: '001' },
auth: basic('MARIAN', process.env.SAP_PW!), // or bearer(token) / interactive({ openUrl })
});
const hits = await adt.repository.search('CL_ABAP*', { types: ['CLAS/OC'] });
const src = await adt.source.read({ name: 'ZCL_FOO', objectType: 'CLAS/OC' });
await adt.lifecycle.create({ objectType: 'CLAS/OC', name: 'ZCL_BAR', packageName: '$TMP', description: 'demo' });
await adt.lifecycle.activate({ name: 'ZCL_BAR', objectType: 'CLAS/OC' });
await adt.dispose();
Consumers that drive adt-ls themselves — e.g. proxying its MCP endpoint to external
agents — can skip createAdtLs() and use the primitives directly (this is what
abapify/openadt adopts):
import { resolveAdtLsPath, AdtLsDriver, startMcpServer } from '@marianfoo/adt-ls';
const driver = new AdtLsDriver(resolveAdtLsPath(), {
extraArgs: ['-consoleLog', `-Djco.middleware.snc_lib=${sncLib}`], // SNC/JCo JVM flags
});
await driver.start(); // discovery + spawn + LSP initialize (short pipe; macOS-safe)
// register your own logon handlers: driver.setRequestHandler('adtLs/destinations/requestBrowserBasedLogon', …)
const { port, token } = await startMcpServer(driver, { port: 2240, token: myToken });
// → proxy http://localhost:${port}/mcp (Authorization: Bearer ${token}) however you like
await driver.dispose();
One namespaced client over both adt-ls channels (LSP + its own MCP) — the split is hidden:
repository — object search, file read/write/delete, inactive-object list, name→URI resolver.source / lifecycle — read; create, update, activate (native — per-phase diagnostics, forceActivation), run unit tests, delete; RAP generators; creatable-type catalog + creation-form (legal values per field) + validation.navigation — document symbols, definition/declaration, references, type hierarchy, hover, completion (with resolve → method signatures + ABAP-Doc), syntax check, semantic tokens, and ABAP Pretty-Printer formatting.quality — ATC static analysis + ABAP Unit code coverage.services — run a console app, service-binding details/publish, and live OData service info (URL + entity sets).transport — find / create / assign / list, lock status, and the transport decision oracle (check).raw — escape hatches to any adt-ls MCP tool or LSP method.What maps to which adt-ls call: the capability matrix. What's reachable headless vs. not (with live evidence): the capability survey.
main.npm run docs:api).Two first-party projects already drive headless adt-ls and reimplement the same
fragile, reverse-engineered plumbing: abapify/openadt (its
@openadt/sap-adt-mcp-launcher) and arc-1-lsp (src/adt-ls/*). Both encode the
identical landmines (the userAgentInfos NPE, HTTPS-only + hostname verification,
silent session death, the reentrance-ticket dance). The fragmentation that actually
hurt here is first-party duplication, and the cure is one shared library.
The thesis: adt-ls is the correct path (SAP-maintained CSRF/locking/activation/XML), but its setup is so much harder than calling ADT REST directly that people avoid it. This library makes adt-ls as easy to use as a plain API, so the easy choice is also the right one.
adt-ls ONLY. No direct ADT/SAP HTTP, no SAP ADT SDK sidecar, no MCP server, no Cloud-Connector/BTP bridge inside the library. What adt-ls can't do headless is out of scope. See ADR-0001.
1.0.0.202605281240)Proven hands-on against the freshly-downloaded 1.0.0 VSIX and the live a4h system:
initialize (with the userAgentInfos workaround) → ADTLS 1.0.0.202605281240.keytool.create → update → read → activate → run-tests → delete GREEN against a4h — exercising auth (reentrance + TLS proxy), the LSP channel, the MCP channel, and the resilience layer end-to-end.→ No design or protocol blockers remain.
src/adt-ls/* entirely; keeps its own MCP server, BTP/Cloud-Connector
bridge (via the connection.forwardProxy hook), authz, and write-safety as thin wrappers over the lib.