@mcp-abap-adt/connection
v9.1.0
Published
ABAP connection layer for MCP ABAP ADT server
Maintainers
Readme
@mcp-abap-adt/connection
ABAP connection layer for MCP ABAP ADT server. Provides a unified interface for connecting to SAP ABAP systems via ADT (ABAP Development Tools) protocol, supporting both on-premise (Basic Auth) and cloud (JWT/OAuth2) authentication methods.
Key Features
- 🔐 Multiple Authentication Methods:
- Basic Auth for on-premise SAP systems
- JWT/OAuth2 for SAP BTP ABAP Environment
- SAML session cookies for pre-authenticated enterprise flows
- 🔄 Token Management:
- Token refresh is handled by
@mcp-abap-adt/auth-brokerpackage - Connection package focuses on HTTP communication only
- Token refresh is handled by
- 💾 Session Management:
- Session headers management (cookies, CSRF tokens)
- Session state persistence is handled by
@mcp-abap-adt/auth-brokerpackage
- 🏗️ Clean Architecture:
- One connector per SYSTEM (
AdtOnPremConnector,AdtCloudConnector), handed an auth provider - Authentication is a parameter, not a subclass — nothing about the system is inferred from it
- Proper separation of concerns - no JWT logic in base class
- One connector per SYSTEM (
- 🔌 Realtime Transport Scaffold:
- Generic
GenericWebSocketTransportwith pluggable WS factory - Reusable for debugger/traces and other event-driven flows
- Generic
- 📝 Custom Logging: Pluggable logger interface for integration with any logging system
- 🛠️ CLI Tool: See JWT Auth Tools for obtaining SAP BTP tokens
- 📦 TypeScript: Full TypeScript support with type definitions included
- ⚡ Timeout Management: Configurable timeouts for different operation types
Architecture
The package uses a clean separation of concerns:
AbstractAbapConnection(abstract, internal only):- Common HTTP request logic
- Session lifecycle:
connect()/disconnect()/flushGoodbye(), admission, lock windows, teardown draining - Capability atoms a consumer narrows to, rather than casting to a connector class:
ISessionLifecycleAware,ICriticalSection,IRequestProfiling - Session management (cookies, CSRF tokens)
- CSRF token fetching with retry
- Auth-agnostic - knows nothing about Basic or JWT
AdtOnPremConnector(concrete, exported):- An on-prem system: the session arrives with the establishing call, and the platform's ICF logoff is how it is given back
- Takes an auth provider — basic, SAML, certificate, a bearer token, whatever you hold
AdtCloudConnector(concrete, exported):- An ABAP Cloud system: a session is a resource, opened at
/sap/bc/adt/core/http/sessionsand given back byDELETEon the address it publishes - Takes an auth provider, same as above
Which one you take is how you say where you are dialling. Nothing is probed: the session resource answers on on-prem too, and its
DELETEthere leaves the session open while the logoff removes it — so asking the server would pick the mechanism that releases nothing.- An ABAP Cloud system: a session is a resource, opened at
Auth providers (
BasicAuthProvider,TokenAuthProvider,SamlAuthProvider,CertificateAuthProvider):- What a connection authenticates with, passed in
- A token provider renews on its own, and the connector asks it per request — which is how a token that expired between two requests is replaced with nobody deciding to replace it
- A
401surfaces. Whether a refusal meant "the token is stale" or "these credentials are refused" is a judgement made with what you know, so the connector does not answer it for you. A credential that can be told to get a new one says so throughIRenewableCredential, which you narrow to
Transports (
OnPremHttpTransport,CloudHttpTransport,RfcTransport):- What a request travels over, and everything that is true of that wire.
HttpTransportkeeps the cookie jar, the CSRF token and the affinity headers;RfcTransporttranslates intoSADT_REST_RFC_ENDPOINTand keeps a conversation that IS the session - On-prem is where this is a real choice; ABAP Cloud has one wire and its connector takes no such parameter
rfcConversationFrom(config)builds whatRfcTransportneeds, derivingashostandsysnrand loading the SDK only when a conversation opens
- What a request travels over, and everything that is true of that wire.
GenericWebSocketTransport(concrete, exported):- Transport abstraction for realtime WS message flows
- Pluggable factory, envelope-based send/receive
- Intended for higher-level debugger/trace session orchestration
Responsibilities and Design Principles
Core Development Principle
Interface-Only Communication: This package follows a fundamental development principle: all interactions with external dependencies happen ONLY through interfaces. The code knows NOTHING beyond what is defined in the interfaces.
This means:
- Does not know about concrete implementation classes from other packages
- Does not know about internal data structures or methods not defined in interfaces
- Does not make assumptions about implementation behavior beyond interface contracts
- Does not access properties or methods not explicitly defined in interfaces
This principle ensures:
- Loose coupling: Connection classes are decoupled from concrete implementations in other packages
- Flexibility: New implementations can be added without modifying connection classes
- Testability: Easy to mock dependencies for testing
- Maintainability: Changes to implementations don't affect connection classes
Package Responsibilities
This package is responsible for:
- HTTP communication with SAP systems: Makes HTTP requests to SAP ABAP systems via ADT protocol
- Authentication handling: Supports Basic Auth and JWT/OAuth2 authentication methods
- Session management: Manages cookies, CSRF tokens, and session state
- Error handling: Handles HTTP errors and connection issues
What This Package Does
- Provides connection abstraction:
AbapConnectioninterface for interacting with SAP systems - Handles HTTP requests: Makes requests to SAP ADT endpoints with proper headers and authentication
- Manages sessions: Handles cookies, CSRF tokens, and session state persistence
What This Package Does NOT Do
- Does NOT obtain tokens: Token acquisition is handled by
@mcp-abap-adt/auth-providersand@mcp-abap-adt/auth-broker - Does NOT store tokens: Token storage is handled by
@mcp-abap-adt/auth-stores - Does NOT refresh tokens: Token refresh is handled by
@mcp-abap-adt/auth-broker - Does NOT orchestrate authentication: Token lifecycle management is handled by
@mcp-abap-adt/auth-broker - Does NOT know about destinations: Destination-based authentication is handled by consumers
- Does NOT handle OAuth2 flows: OAuth2 flows are handled by token providers
External Dependencies
This package interacts with external packages ONLY through interfaces:
- Logger interface: Uses
ILoggerinterface for logging - does not know about concrete logger implementation - No direct dependencies on auth packages: All token-related operations are handled through configuration (
SapConfig) passed by consumers
The contracts themselves come from four packages, and are the only runtime
dependencies of this one besides axios, commander and open:
| Package | What this package takes from it |
|---|---|
| @mcp-abap-adt/interfaces-adt | IAbapConnection, ISapConfig, ITokenRefresher, the capability atoms, ADT_SESSION_ERROR |
| @mcp-abap-adt/interfaces-auth | IAuthProvider, IRenewableCredential, ICertificateMaterial |
| @mcp-abap-adt/interfaces-network | ITimeoutConfig, NETWORK_ERROR_CODES, the WebSocket contracts |
| @mcp-abap-adt/interfaces-utils | ILogger |
Not @mcp-abap-adt/interfaces. That name is now an umbrella of deprecated
re-exports, and this package no longer depends on it — see
Migration to 9.0.0.
Documentation
- 📦 Installation Guide - Setup and installation instructions
- 📚 Usage Guide - Detailed usage examples and API documentation
- 🚚 Migration to 9.0.0 - the contracts split out of
@mcp-abap-adt/interfaces; which package each one moved to - 🚚 Migration to 7.0.0 and 8.0.0 - request headers leave the stateful branch, the contracts floor moves, and
flushGoodbye() - 🚚 Migration to 6.0.0 - the factory and the per-credential classes are removed; RFC is a transport, not a class
- 🚚 Migration to 4.0.0 - a 401 refreshes the token, a 403 reaches you with the server's message; the synthesised "JWT token has expired" is gone
- 🚚 Migration: the explicit session lifecycle -
connect()is now required; start here if you are coming from 1.x - 💡 Examples - Working code examples
Features
- 🔐 Multiple Authentication Methods: Basic Auth for on-premise systems, JWT/OAuth2 for SAP BTP ABAP Environment
- 💾 Session Management: Session headers management (cookies, CSRF tokens) for HTTP communication
- 📝 Custom Logging: Pluggable logger interface for integration with any logging system (optional)
- 📦 TypeScript: Full TypeScript support with type definitions included
- ⚡ Timeout Management: Configurable timeouts for different operation types
- 🌐 Network Error Detection: Automatic detection and proper handling of network-level errors (connection refused, timeout, DNS failures)
Installation
npm install @mcp-abap-adt/connectionThat is everything you need to use the connectors. If your own code names a
contract type — IAuthProvider for a credential you write, ISapConfig for a
config you build — install the package it lives in as well, because this one no
longer brings them along:
npm install @mcp-abap-adt/interfaces-adt @mcp-abap-adt/interfaces-authFor detailed installation instructions, see Installation Guide.
Quick Start
Basic Usage (On-Premise)
import {
AdtOnPremConnector,
BasicAuthProvider,
OnPremHttpTransport,
SapConfig,
getTimeout,
} from "@mcp-abap-adt/connection";
const config: SapConfig = {
url: "https://your-sap-system.com",
client: "100",
authType: "basic",
username: "your-username",
password: "your-password",
};
// Create a simple logger
const logger = {
info: (msg: string, meta?: any) => console.log(msg, meta),
error: (msg: string, meta?: any) => console.error(msg, meta),
warn: (msg: string, meta?: any) => console.warn(msg, meta),
debug: (msg: string, meta?: any) => console.debug(msg, meta),
};
// Which system you are dialling is the class you take; which credential it
// authenticates with is the object you hand it. Neither is detected.
const connection = new AdtOnPremConnector(
config,
new BasicAuthProvider(config.username!, config.password!),
new OnPremHttpTransport(() => ({}), logger, {
client: config.client,
baseUrl: config.url,
}),
logger,
);
await connection.connect(); // required before any request
// Make ADT request
const response = await connection.makeAdtRequest({
method: "GET",
url: "/sap/bc/adt/programs/programs/your-program",
timeout: getTimeout("default"),
});Cloud Usage (JWT/OAuth2)
import {
AdtCloudConnector,
CloudHttpTransport,
SapConfig,
TokenAuthProvider,
getTimeout,
} from "@mcp-abap-adt/connection";
// JWT configuration
const config: SapConfig = {
url: "https://your-instance.abap.cloud.sap",
client: "100", // Optional
authType: "jwt",
jwtToken: "your-jwt-token-here", // Obtained via OAuth2 flow
};
const logger = {
info: (msg: string, meta?: any) => console.log(msg, meta),
error: (msg: string, meta?: any) => console.error(msg, meta),
warn: (msg: string, meta?: any) => console.warn(msg, meta),
debug: (msg: string, meta?: any) => console.debug(msg, meta),
};
// Logger is optional - if not provided, no logging output.
// A bare string is a token with nothing behind it. Hand `TokenAuthProvider` an
// `ITokenRefresher` instead and it checks expiry and renews on its own, which
// is what you want in anything long-lived.
const connection = new AdtCloudConnector(
config,
new TokenAuthProvider(config.jwtToken!),
new CloudHttpTransport(() => ({}), logger, {
client: config.client,
baseUrl: config.url,
}),
logger,
);
await connection.connect();
// Note: obtaining and refreshing tokens is @mcp-abap-adt/auth-broker's job
const response = await connection.makeAdtRequest({
method: "GET",
url: "/sap/bc/adt/programs/programs/your-program",
timeout: getTimeout("default"),
});On-Premise over RFC
The same ADT calls, over SADT_REST_RFC_ENDPOINT — the function module Eclipse
ADT itself uses through JCo — instead of over HTTP. Worth taking on a system
where stateful HTTP sessions are not usable: an RFC conversation is one ABAP
session for its whole lifetime, which is the way past 423 invalid lock handle
on BASIS < 7.50.
Needs the SAP NW RFC SDK on the machine and npm install @mcp-abap-adt/sap-rfc-lite.
import {
AdtOnPremConnector,
BasicAuthProvider,
RfcTransport,
rfcConversationFrom,
} from "@mcp-abap-adt/connection";
const connection = new AdtOnPremConnector(
config,
new BasicAuthProvider(config.username!, config.password!),
new RfcTransport(rfcConversationFrom(config), logger),
logger,
);
await connection.connect();
// Everything above the wire is the same: makeAdtRequest, setSessionType,
// disconnect. What differs is where the session lives — see below.Where to look for it. An HTTP session is an ICF session and appears in
SM05. An RFC conversation is a gateway client: it appears in SMGW → Logged
on Clients as NWRFC, and never in SM05, because there is no ICM in that
path. Looking for one in the other monitor and finding nothing is not a fault.
There is no cloud equivalent: ABAP Cloud has one wire, and AdtCloudConnector
takes no transport parameter at all.
SSO Usage (SAML Session Cookies)
import {
AdtOnPremConnector,
OnPremHttpTransport,
SamlAuthProvider,
SapConfig,
getTimeout,
} from "@mcp-abap-adt/connection";
const config: SapConfig = {
url: "https://your-sap-system.com",
authType: "saml",
sessionCookies: "MYSAPSSO2=...; SAP_SESSIONID=...",
};
// The cookies ARE the credential here — there is no Authorization header at all.
const connection = new AdtOnPremConnector(
config,
new SamlAuthProvider(config.sessionCookies!),
new OnPremHttpTransport(() => ({}), logger, {
client: config.client,
baseUrl: config.url,
}),
logger,
);
await connection.connect();
const response = await connection.makeAdtRequest({
method: "GET",
url: "/sap/bc/adt/programs/programs/your-program",
timeout: getTimeout("default"),
});Cloud Usage with Automatic Token Refresh
Give TokenAuthProvider an ITokenRefresher and the provider replaces an
expired token on its own — it is asked per request and checks expiry before
answering, so nobody decides to renew. A token the source still believes in and
the server refuses is the other half, and that one surfaces:
import {
AdtCloudConnector,
CloudHttpTransport,
SapConfig,
TokenAuthProvider,
getTimeout,
} from "@mcp-abap-adt/connection";
import type { ITokenRefresher } from "@mcp-abap-adt/interfaces-adt";
// Token refresher provides token acquisition and refresh
// (created by @mcp-abap-adt/auth-broker or custom implementation)
const currentAccessToken = 'the access token you already hold';
const exchangeRefreshToken = async () => 'a freshly exchanged access token';
const tokenRefresher: ITokenRefresher = {
getToken: async () => currentAccessToken, // the one you hold
refreshToken: async () => exchangeRefreshToken(), // a new one, and cache it
};
const config: SapConfig = {
url: "https://your-instance.abap.cloud.sap",
authType: "jwt",
};
// The connector says which SYSTEM this is; the provider says how to
// authenticate. Neither decides the other.
const connection = new AdtCloudConnector(
config,
new TokenAuthProvider(tokenRefresher),
new CloudHttpTransport(() => ({}), logger, {
client: config.client,
baseUrl: config.url,
}),
logger,
);
await connection.connect();
// On a 401 nothing here decides to get a new credential: the refusal reaches
// you. Whether it meant "stale" is a judgement made with what you know, and
// `renew()` is the seam you make it with. The session is untouched — a refused
// reaches you. A refresh replaces the SAP session, so if a lock window is open
// the request fails with ADT_SESSION_REPLACED rather than continuing on a
// session your lock is not in.
const response = await connection.makeAdtRequest({
method: "GET",
url: "/sap/bc/adt/programs/programs/your-program",
timeout: getTimeout("default"),
});A 403 is never treated as an expired token. It means the server
authenticated the caller and refused the action anyway, so no credential can
change the answer. It propagates unchanged — error.response.status and the
server's message, which usually names the authorization object — rather than
being reported as an expired token.
Earlier versions reported both 401 and 403 as
JWT token has expired. Please re-authenticate. and discarded the original
error. Code matching on that message must branch on error.response.status
instead — which it can now do, since the status is no longer thrown away.
See MIGRATION-4.0.md.
Stateful Sessions
For operations that require session state (e.g., object modifications), you can enable stateful sessions:
import {
AdtOnPremConnector,
BasicAuthProvider,
OnPremHttpTransport,
getTimeout,
} from "@mcp-abap-adt/connection";
const connection = new AdtOnPremConnector(
config,
new BasicAuthProvider(config.username!, config.password!),
new OnPremHttpTransport(() => ({}), logger, {
client: config.client,
baseUrl: config.url,
}),
logger,
);
await connection.connect();
// Enable stateful session mode (adds x-sap-adt-sessiontype: stateful header)
connection.setSessionType("stateful");
// Make requests - SAP will maintain session state
await connection.makeAdtRequest({
method: "POST",
url: "/sap/bc/adt/objects/domains",
data: { /* domain data */ },
timeout: getTimeout("default"),
});
// Note: Session state persistence is handled by @mcp-abap-adt/auth-broker packageCustom Logger
import { AdtOnPremConnector, BasicAuthProvider, ILogger, OnPremHttpTransport } from "@mcp-abap-adt/connection";
class MyLogger implements ILogger {
info(message: string, meta?: any): void {
// Your logging implementation
}
error(message: string, meta?: any): void {
// Your logging implementation
}
warn(message: string, meta?: any): void {
// Your logging implementation
}
debug(message: string, meta?: any): void {
// Your logging implementation
}
csrfToken(action: "fetch" | "retry" | "success" | "error", message: string, meta?: any): void {
// CSRF token specific logging
}
tlsConfig(rejectUnauthorized: boolean): void {
// TLS configuration logging
}
}
const logger = new MyLogger();
const connection = new AdtOnPremConnector(
config,
new BasicAuthProvider(config.username!, config.password!),
new OnPremHttpTransport(() => ({}), logger, {
client: config.client,
baseUrl: config.url,
}),
logger,
);CLI Tool
The package includes a CLI tool for authenticating with SAP BTP using service keys:
Installation Options
- Local project install
npm install @mcp-abap-adt/connection --save-dev npx sap-abap-auth auth -k path/to/service-key.json - Global install
npm install -g @mcp-abap-adt/connection sap-abap-auth auth -k path/to/service-key.json - On-demand (npx)
npx @mcp-abap-adt/connection sap-abap-auth auth -k path/to/service-key.json
Usage
# Show help
sap-abap-auth --help
# Authenticate with service key
sap-abap-auth auth -k service-key.json
# Specify browser
sap-abap-auth auth -k service-key.json --browser chrome
# Custom output file
sap-abap-auth auth -k service-key.json --output .env.productionOptions
-k, --key <path>- Path to service key JSON file (required)-b, --browser <name>- Browser to open (chrome, edge, firefox, system, none)-o, --output <path>- Path to output .env file (default: .env)-h, --help- Show help message
Using via npx (without global install)
If @mcp-abap-adt/connection is listed as a dependency in your project, you can invoke the CLI directly:
npx sap-abap-auth auth -k service-key.jsonThis works even when you do not install the package globally. For one-off usage, you can also run:
npx @mcp-abap-adt/connection sap-abap-auth auth -k service-key.jsonThis will download the package on demand and execute the CLI.
API Reference
Types
SapConfig
Configuration for SAP ABAP connection.
type SapConfig = {
url: string;
client?: string;
authType: "basic" | "jwt" | "saml";
// For basic auth
username?: string;
password?: string;
// For JWT auth
jwtToken?: string;
// For SAML session cookies
sessionCookies?: string;
};AbapConnection
Main interface for ABAP connections.
import { AbapRequestOptions } from '@mcp-abap-adt/connection';
import type { AxiosResponse } from 'axios';
// The shared contract (IAbapConnection), what every connection provides:
interface AbapConnection {
connect(): Promise<void>; // REQUIRED before any request; rejects on failure
makeAdtRequest(options: AbapRequestOptions): Promise<AxiosResponse>;
getBaseUrl(): Promise<string>;
setSessionType(type: "stateless" | "stateful"): void; // Switch session type
getSessionId(): string | null; // Client-side conversation id
}The connectors carry the rest of the session lifecycle. It is on the shared
contract as a capability atom in @mcp-abap-adt/interfaces-adt rather than as
methods on IAbapConnection, so a consumer that only carries requests is
unaffected by its existence. Note that a connection over RFC has the whole of it
— what an RFC conversation has none of is a session RESOURCE to open and close
by address, which is an empty mechanism, not an absent lifecycle:
// ISessionLifecycleAware
disconnect(): Promise<void>; // never throws, and waits for nothing
isConnected(): boolean;
getSessionIdentity(): string | null; // WHICH SAP session; null is not "disconnected"Import those names from @mcp-abap-adt/interfaces-adt, not from this package: a
contract type re-exported under a second name is a contract type that can drift.
The connection does not track locks. Deciding when to disconnect, and
preparing for it, belongs to the caller; pairing every LOCK with its UNLOCK
belongs to @mcp-abap-adt/adt-clients, which holds the handles. What this layer
owns is not being interrupted by a timeout mid-operation — see
beginCriticalSection() below.
See docs/USAGE.md — Session Lifecycle.
Session Management:
setSessionType(type): Programmatically switch between stateful and stateless modes (on the contract)getSessionId(): Returns the client-side conversation id, an auto-generated UUID (on the contract)getSessionMode(): Returns current session mode (HTTP classes only)
ILogger
Logger interface for custom logging implementations.
interface ILogger {
info(message: string, meta?: any): void;
error(message: string, meta?: any): void;
warn(message: string, meta?: any): void;
debug(message: string, meta?: any): void;
csrfToken?(action: "fetch" | "retry" | "success" | "error", message: string, meta?: any): void;
tlsConfig?(rejectUnauthorized: boolean): void;
}Functions
rfcConversationFrom(config)
What RfcTransport is constructed with. Derives ashost from the url and
sysnr from the HTTP port by the SAP convention that 80XX is the ICM port for
system XX, which SAP_SYSNR overrides for a port that follows no convention.
The SAP NW RFC SDK is loaded when a conversation opens, not when this is called,
so a machine without it fails at connect() with a message saying what to
install rather than at construction.
function rfcConversationFrom(config: SapConfig): () => IRfcConversation;
function rfcParamsFrom(config: SapConfig): RfcConnectionParams;import {
AdtOnPremConnector,
BasicAuthProvider,
RfcTransport,
rfcConversationFrom,
} from "@mcp-abap-adt/connection";
const connection = new AdtOnPremConnector(
config,
new BasicAuthProvider(config.username!, config.password!),
new RfcTransport(rfcConversationFrom(config), logger),
logger,
);A third argument turns on the wire log, which is off by default:
new RfcTransport(rfcConversationFrom(config), logger, { logWire: true });It adds three debug lines per request — header fields, request body, response
body — which is how you see that a payload was mis-serialised before it reached
SADT_REST_RFC_ENDPOINT. Credential header values are replaced with
[redacted] and bodies are clipped at maxLoggedBodyChars (2000). The bodies
themselves are not redacted, so read a captured log before pasting it into an
issue. HttpTransport never logs bodies, so this is the one wire whose debug
channel can be asked for the payload.
CSRF_CONFIG and CSRF_ERROR_MESSAGES
New in 0.1.13+: Exported constants for consistent CSRF token handling across different connection implementations.
import { CSRF_CONFIG, CSRF_ERROR_MESSAGES } from '@mcp-abap-adt/connection';
// CSRF_CONFIG contains:
// - RETRY_COUNT: number (default: 3)
// - RETRY_DELAY: number (default: 1000ms)
// - ENDPOINT: string (default: '/sap/bc/adt/core/discovery')
// - REQUIRED_HEADERS: { 'x-csrf-token': 'fetch', 'Accept': 'application/atomsvc+xml' }
// CSRF_ERROR_MESSAGES contains:
// - FETCH_FAILED(attempts: number, cause: string): string
// - NOT_IN_HEADERS: string
// - REQUIRED_FOR_MUTATION: stringUse case: When implementing custom connection classes (e.g., Cloud SDK-based), you can use these constants to ensure consistent CSRF token handling:
import { CSRF_CONFIG, CSRF_ERROR_MESSAGES } from '@mcp-abap-adt/connection';
// Whatever HTTP client your own connection class is built on.
const yourHttpClient = {
get: async (url: string, config: { headers: Record<string, string> }) =>
({ headers: {} as Record<string, string> }),
};
async function fetchCsrfToken(baseUrl: string): Promise<string> {
const csrfUrl = `${baseUrl}${CSRF_CONFIG.ENDPOINT}`;
for (let attempt = 0; attempt <= CSRF_CONFIG.RETRY_COUNT; attempt++) {
try {
const response = await yourHttpClient.get(csrfUrl, {
headers: CSRF_CONFIG.REQUIRED_HEADERS
});
const token = response.headers['x-csrf-token'];
if (!token) {
if (attempt < CSRF_CONFIG.RETRY_COUNT) {
await new Promise(resolve => setTimeout(resolve, CSRF_CONFIG.RETRY_DELAY));
continue;
}
throw new Error(CSRF_ERROR_MESSAGES.NOT_IN_HEADERS);
}
return token;
} catch (error) {
if (attempt >= CSRF_CONFIG.RETRY_COUNT) {
throw new Error(
CSRF_ERROR_MESSAGES.FETCH_FAILED(
CSRF_CONFIG.RETRY_COUNT + 1,
error instanceof Error ? error.message : String(error)
)
);
}
await new Promise(resolve => setTimeout(resolve, CSRF_CONFIG.RETRY_DELAY));
}
}
// Unreachable: the last attempt either returns or throws above. Stated so the
// function has a return type the compiler can agree with.
throw new Error(CSRF_ERROR_MESSAGES.NOT_IN_HEADERS);
}Requirements
- Node.js >= 18.0.0
- Access to SAP ABAP system (on-premise or BTP)
Changelog
See CHANGELOG.md for detailed version history and breaking changes.
Version history: CHANGELOG.md
- Removed token refresh functionality (handled by
@mcp-abap-adt/auth-broker) - Removed session storage functionality (handled by
@mcp-abap-adt/auth-broker) - Logger is now optional
- See CHANGELOG.md for full details
Documentation
License
GNU Lesser General Public License v3.0 only (LGPL-3.0-only).
Earlier published versions were MIT and stay MIT — a licence change is not
retroactive.
Copyright © 2025–2026 Oleksii Kyslytsia
This library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, version 3.
It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
Both texts ship with the package and both are needed: LICENSE is the
LGPL, COPYING is the GPL it is written on top of, since the LGPL is a
set of additional permissions over the GPL and cannot be read alone.
What this means if you depend on this package. Linking it into your own program — importing it, as every consumer of an npm package does — does not put your program under the LGPL. What the licence asks is that changes to this library stay free, and that your users can replace it with their own build.
Repository
https://github.com/fr0ster/mcp-abap-adt
Related Projects
- mcp-abap-adt - Main MCP server for ABAP ADT
