npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mcp-abap-adt/connection

v9.1.0

Published

ABAP connection layer for MCP ABAP ADT server

Readme

@mcp-abap-adt/connection

Stand With Ukraine

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-broker package
    • Connection package focuses on HTTP communication only
  • 💾 Session Management:
    • Session headers management (cookies, CSRF tokens)
    • Session state persistence is handled by @mcp-abap-adt/auth-broker package
  • 🏗️ 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
  • 🔌 Realtime Transport Scaffold:
    • Generic GenericWebSocketTransport with pluggable WS factory
    • Reusable for debugger/traces and other event-driven flows
  • 📝 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/sessions and given back by DELETE on 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 DELETE there leaves the session open while the logoff removes it — so asking the server would pick the mechanism that releases nothing.

  • 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 401 surfaces. 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 through IRenewableCredential, which you narrow to
  • Transports (OnPremHttpTransport, CloudHttpTransport, RfcTransport):

    • What a request travels over, and everything that is true of that wire. HttpTransport keeps the cookie jar, the CSRF token and the affinity headers; RfcTransport translates into SADT_REST_RFC_ENDPOINT and 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 what RfcTransport needs, deriving ashost and sysnr and loading the SDK only when a conversation opens
  • 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:

  1. HTTP communication with SAP systems: Makes HTTP requests to SAP ABAP systems via ADT protocol
  2. Authentication handling: Supports Basic Auth and JWT/OAuth2 authentication methods
  3. Session management: Manages cookies, CSRF tokens, and session state
  4. Error handling: Handles HTTP errors and connection issues

What This Package Does

  • Provides connection abstraction: AbapConnection interface 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-providers and @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 ILogger interface 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

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/connection

That 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-auth

For 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 package

Custom 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.production

Options

  • -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.json

This 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.json

This 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: string

Use 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