@mcp-abap-adt/adt-clients-legacy

中文文档
0.4.0发布于 2026/11/02LGPL-3.0-onlyTypeScriptCommonJS

Clients for legacy SAP ABAP systems (7.40), built on @mcp-abap-adt/adt-clients

原始使用文档
文档目录24 节
  1. Install and construct
  2. How to read the tables
  3. What a consumer must know
  4. Headers are the caller's
  5. Empty 200 is not success
  6. The rest
  7. The members, per handler
  8. Class — getClass()
  9. Class includes — getLocalTestClass() , getLocalTypes() , ge…
  10. Program — getProgram()
  11. Interface — getInterface()
  12. Function group — getFunctionGroup()
  13. Function module — getFunctionModule()
  14. Function include — getFunctionInclude()
  15. Include — getInclude()
  16. DDL source — getDdl()
  17. Message class — getMessageClass() , and a message — getMess…
  18. Package — getPackage()
  19. Transport request — getRequest()
  20. Utilities — getUtils()
  21. Executors — AdtExecutorLegacy
  22. Types 7.40 does not have
  23. Development
  24. License

@mcp-abap-adt/adt-clients-legacy

Clients for legacy SAP ABAP systems (SAP_BASIS 7.40, measured on 7.40 SP04), built on @mcp-abap-adt/adt-clients. AdtClientLegacy and AdtExecutorLegacy extend that package's AdtClient and AdtExecutor and only override: each member sends the request measured accepted on SAP_BASIS 7.40 SP04, or keeps an unmeasured request under a collection 7.40 advertises, or answers a refusal without sending a request.

The basis of every claim in this package is SAP_BASIS 7.40 SP04 over RFC, the 7.40 test system. Nothing here is inferred for another release. The probe records behind each claim, and what is not measured, are in docs/LEGACY.md.

Install and construct

npm install @mcp-abap-adt/adt-clients-legacy @mcp-abap-adt/adt-clients

@mcp-abap-adt/adt-clients is a peer dependency (^38.0.0): the consumer installs it, and the legacy classes extend that one copy, so AdtClientLegacy is an AdtClient of the consumer's own version. npm warns when the installed version is outside the range.

import { AdtClientLegacy, AdtExecutorLegacy } from '@mcp-abap-adt/adt-clients-legacy';

const client = new AdtClientLegacy(connection, logger);       // connection: an RFC connection
const executor = new AdtExecutorLegacy(connection, logger);

const program = client.getProgram();                          // the getters 7.40's table serves
  • The consumer declares the system type. Nothing here detects a legacy system and no factory chooses a legacy class: createAdtClient(connection, { variant }) of @mcp-abap-adt/adt-clients answers a modern client ('onprem' or 'cloud') only.
  • Typed by 7.40's ADT type table (since 0.2.0). AdtClientLegacy is AdtClient<'basis740'> of adt-clients 38, typed by ADT_TYPES_BASIS_740 of @mcp-abap-adt/interfaces-adt: a getter or member that table does not list does not compile (client.getTable(), getClass().checkDeletion()), and every override keeps its base's gate. It is not assignable to a modern AdtClient. The refusals below stay, for callers outside the type system. AdtExecutorLegacy is AdtExecutor<'basis740'> the same way. Every code a member takes is a code of that table ('CLAS/OC', 'FUGR/FF', …): no friendly name, bare R3TR type or lower-case spelling.
  • RFC only. The legacy client is supported, and was measured, over RFC; HTTP to a 7.40 system is not a supported wire (no logon without a workaround, a write under a lock answers 423, some reads disagree with RFC; the records are in LEGACY.md). The package does not detect the wire and has no branch for it.
  • The package depends on @mcp-abap-adt/adt-clients ^38.0.0. It also exports AdtRequestLegacy.
  • Each getter answers a class (since 0.2.0): its legacy class (getProgram(): AdtProgramLegacy<…>, getInclude(): AdtIncludeLegacy<…>, …) or, for a type 7.40 does not have, the base class answering refusals. Each is assignable to the AdtClient getter it overrides. A consumer that names the type of the client it accepts composes that contract from @mcp-abap-adt/interfaces-adt alone (the atoms, the result-set contracts and the config types), as for AdtClient; the compiler checks there that AdtClientLegacy satisfies it. The result-set contracts (IClassResults, …) are imported from @mcp-abap-adt/interfaces-adt.

The package does not engineer around a consumer who ignores what follows: it states what 7.40 SP04 does and passes the request through.

How to read the tables

WordMeans
sendsThe member sends the request shown; measured accepted on 7.40 SP04 (the record is named in LEGACY.md).
refusedThe member answers the library's refusal (ok: false, origin: 'refusal', code: UNSUPPORTED_OPERATION) without a request; it is answered, never thrown. A consumer branches on the code; the message is for people.
kept, not measuredThe request is the modern one, under a collection 7.40 advertises; no run has measured it. If 7.40 does not serve it, the answer is SAP's own 404, which the strict default reads as a failure, never as success.

Where a row says "sends" and its record is a read of a system object (a system program, class, function module or include) rather than of an object a cycle created, LEGACY.md says so; the cycle suites (src/__tests__/integration/legacy/cycles/) measure the read of a created object and T6-20261027 ran them whole on 7.40 SP04 (2026-10-27): the reads of created objects, and the free-name validate of a function group and module are measured there; the DDL source's readTransport 404 by one raw read in the same record (t6-ddl-readTransport).

What a consumer must know

Headers are the caller's

The library passes a request's Accept / Content-Type through unchanged (options.headers, options.accept) and chooses none for 7.40. The measured pairs:

MemberPair 7.40 SP04 accepts
Include createonly application/vnd.sap.adt.programs.includes+xml, both ways. The v2 pair answers 400 ExceptionContentHandlerNotFound
Class readMetadata (getClass() and getUtils().readObjectMetadata('CLAS/OC', …))only Accept: application/vnd.sap.adt.oo.classes+xml. Every versioned type, a list naming one (v4, unversioned), and application/xml answer 400 ExceptionContentHandlerNotFound — not the 406 a modern system answers
Function include createapplication/vnd.sap.adt.functions.fincludes+xml both ways; v2 answers 400
Message class createapplication/xml both ways (201)
Function group update (its source, source/main)Content-Type: text/plain; charset=utf-8, Accept: text/plain (200); its read sends Accept: text/plain (200 text/plain)
Every other typethe library's default pairs work (class create: application/vnd.sap.adt.oo.classes+xml; program, function group and module creates: the unversioned types; interface create: the v5 pair; DDL create: application/vnd.sap.adt.ddlSource+xml; a source write: text/plain)

The measured pairs are passed by the tests as a consumer would pass them (integrationHeadersOf).

Empty 200 is not success

  • A message class that does not exist reads 200 with an empty body, not 404 — and still does after its delete. Check the body.
  • Over HTTP, families 7.40 does not have answer an empty 200 too (over RFC they answer 404). The legacy classes refuse those families, so none of them reads an empty 200 as success. A consumer calling getUtils().readObjectMetadata must check the body.

The rest

  • Local class includes' check and validate need the source in the config (getLocalTestClass(), getLocalTypes(), getLocalDefinitions(), getLocalMacros()). Without it the library throws before it sends. With it both are a check run (POST /checkruns, 200).

  • A function module's source on 7.40: a parameter comment block is refused (500 ExceptionResourceSaveFailure, "Parameter comment blocks are not allowed"); a plain FUNCTION … ENDFUNCTION body is accepted.

  • validate of an existing name answers 400 (InvalidClifName, InvalidProgramName, InvalidMcName, "already exists") for a class, interface, program, include and message class. That is the answer for an existing object, not a failure of the request; a name that is free answers 200. For a function group and a function module, validate answers 200 (measured for an existing name).

  • delete is yours to lock. 7.40 has no /sap/bc/adt/deletion/*, so there is no checkDeletion (every one is refused), and delete is one request: DELETE <object URI>?lockHandle=<handle> (plus &corrNr= when the config's transportRequest is given), stateless. The handle is from your own lock(); the member does not lock, unlock or retry. Without a handle the request still goes and SAP's 400 reaches you.

    const include = client.getInclude();
    const config = { includeName: 'ZINCL' };
    const locked = await include.lock(config);                   // stateful
    if (!locked.ok) throw new Error(locked.getError().message);
    const lockHandle = locked.getResult().value;
    try {
      const deleted = await include.delete(config, { headers, lockHandle });
      if (!deleted.ok) throw new Error(deleted.getError().message);
    } finally {
      await include.unlock(config, lockHandle);
    }
    

    A delete with corrNr in a transportable package is measured (the 2026-10-26 live run, LegacyTransportable): lock, DELETE ?lockHandle=&corrNr=, unlock, then 404 on read.

  • What ADT does not serve on 7.40 is the user's, one level up. Where-used outside RIS, DDIC sources, transport changes, and the like are not ADT there: a consumer that needs them goes through the legacy MCP server, whose LLM writes ABAP (classes, reports) that does it. This package refuses those members without a request, so no tool can only fail.

The members, per handler

The records B2, B3 (earlier runs) and B4 (the 2026-10-26 cycles) are listed in LEGACY.md. Every handler is the getter AdtClient has for it, fixed to 'basis740'. A member marked refused whose operation ADT_TYPES_BASIS_740 does not list also does not compile; the refusal answers a caller outside the type system.

Class — getClass()

MemberOn 7.40 SP04
createsends POST /oo/classes, the unversioned pair
lock, unlocksends (?_action=LOCK / UNLOCK, stateful)
updatesends the stateless PUT …/source/main?lockHandle=, text/plain
activate, checksends
validatesends POST /oo/validation/objectname: free 200, existing 400
read, readMetadatasends (read of a system class); readMetadata only with the unversioned Accept (above)
getVersions, getVersionSourcesends GET …/includes/<main|…>/versions, then GET <contentUri>
deletesends one DELETE ?lockHandle= under your lock
readTransportrefused — GET …/transport answered 404
checkDeletionrefused — no deletion API
updateMetadatarefused — not measured on 7.40 (7.40's table lists none)
activateTestClasses, readEnhancementElements, readEnhancementOptionskept, not measured

Class includes — getLocalTestClass(), getLocalTypes(), getLocalDefinitions(), getLocalMacros()

MemberOn 7.40 SP04
readsends (GET …/includes/<name>, a system class)
updatesends the include PUT under the class's lock
check, validatesends a check run — needs source in the config
getVersions, getVersionSourcesends includes/<name>/versions, GET <contentUri>
deletekept: an update with empty source; the write is the include PUT
lock, unlock, activate, readMetadatasends — of the container class, as getClass()'s
readTransportrefused — the container class's GET …/transport answered 404 (B4-cycles-20261026-2019 c6-readTransport)

Program — getProgram()

MemberOn 7.40 SP04
createsends POST /programs/programs, the unversioned pair
lock, unlock, update, activate, checksends
validatesends /programs/validation: free 200, existing 400
read, readMetadatasends (a system program)
getVersions, getVersionSourcesends
deletesends one DELETE ?lockHandle= under your lock
readTransportrefused — 404
checkDeletionrefused
updateMetadatarefused — not measured on 7.40 (7.40's table lists none)
readEnhancementElements, readEnhancementOptionskept, not measured

Interface — getInterface()

MemberOn 7.40 SP04
createsends POST /oo/interfaces, the v5 pair (201)
lock, unlock, update, activate, checksends
validatesends: free 200, existing 400
read, readMetadata, getVersions, getVersionSourcesends (getVersions: source/main/versions)
deletesends one DELETE ?lockHandle= under your lock
readTransportrefused — 404
checkDeletionrefused
updateMetadatarefused — not measured on 7.40 (7.40's table lists none)

Function group — getFunctionGroup()

MemberOn 7.40 SP04
create, lock, unlock, checksends
activatesends the group alone, one reference — the modern client (adt-clients 38) adds its main program SAPL<group> (FUGR/I), measured on S/4HANA 2025 and ABAP cloud only
validatesends /functions/validation (200)
readMetadatasends (a system group)
read, update (the group's source, source/main — the main program SAPL<group>)sends, inherited from adt-clients: read one GET …/source/main (Accept: text/plain, the version as asked), update one PUT …/source/main?lockHandle= of your text under your lock; read → write → activate → read back measured in $TMP
deletesends one DELETE ?lockHandle= under your lock
readTransportrefused — 404
checkDeletionrefused
updateMetadata, readEnhancementElements, readEnhancementOptionskept, not measured

A function group has no version members.

Function module — getFunctionModule()

MemberOn 7.40 SP04
createsends POST …/fmodules (201)
lock, unlock, update, activate, checksends — the source is a plain FUNCTION … ENDFUNCTION body (a parameter comment block is refused, above)
read, getVersions, getVersionSourcesends
readMetadatasends (a system module)
validatesends (200)
deletesends one DELETE ?lockHandle= under your lock
readTransportrefused — 404
checkDeletionrefused
updateMetadatarefused — not measured on 7.40 (7.40's table lists none)

Its members take the group (functionGroupName), required by type.

Function include — getFunctionInclude()

MemberOn 7.40 SP04
createsends — pass the v1 pair application/vnd.sap.adt.functions.fincludes+xml
lock, unlock, update, activatesends
readsends (a system include)
readMetadata, updateMetadata, check, getVersions, getVersionSourcekept, not measured
validatekept, not measured: a GET of its function group
delete, checkDeletionrefused — a direct DELETE of it is not measured and is not offered; delete the include through its function group

Include — getInclude()

MemberOn 7.40 SP04
createsends POST /programs/includes — the unversioned pair only
lock, unlock, update, activate, read, readMetadatasends
validatesends: free 200, existing 400 InvalidProgramName
deletesends one DELETE /programs/includes/<name>?lockHandle= under your lock (200; the read afterwards 404)
checkDeletionrefused
updateMetadatarefused — not measured on 7.40 (7.40's table lists none)
readEnhancementElements, readEnhancementOptionskept, not measured

DDL source — getDdl()

MemberOn 7.40 SP04
create, lock, unlock, update, activate, check, read, readMetadatasends (a define view over T000 with its sqlViewName activated)
validatesends: a free name 200
deletesends one DELETE ?lockHandle= under your lock
getVersions, getVersionSourcerefused — GET …/ddic/ddl/sources/<name>/versions answered 404
readTransportrefused — GET …/transport answered 404 for a class, program, interface, function group, function module and (T6-20261027) DDL source
checkDeletion, checkCdsTestDoublesrefused
updateMetadatarefused — not measured on 7.40 (7.40's table lists none)

Message class — getMessageClass(), and a message — getMessageClassMessage()

MemberOn 7.40 SP04
createsends POST /messageclass, application/xml both ways (201)
lock, unlock, readMetadatasends — readMetadata of an absent class is 200, empty
validatesends: free 200, existing 400 InvalidMcName
updateMetadatarefused — the PUT of the class document answered 400 ExceptionInvalidData (7.40 expects another root element); not sent until that document is measured
deletesends one DELETE /messageclass/<name>?lockHandle= under your lock (200; the read afterwards 200, empty)
checkDeletionrefused
message create, read, update, deletesends — a message is a row in its class's document: a read of the class, two locks, the PUT, two unlocks
message readMetadatakept, not measured

Package — getPackage()

Every member refused: the item resource /packages/<name> answers 404 (the collection is advertised, the item is not served) and POST /packages 405. Package contents come through getUtils().fetchNodeStructure('DEVC/K', name), one level per request: the root node answers the subpackages and one node per object type with its NODE_ID, and the objects come with that NODE_ID as nodeId (T6-20261027).

Transport request — getRequest()

MemberOn 7.40 SP04
list, readMetadatasends GET /sap/bc/cts/transportrequests (the user's list; a configUri is refused when given)
createrefused (400)
every other memberrefused — /sap/bc/adt/cts/* is not advertised, and changes to a request are refused

A populated list is measured (the 2026-10-26 live run, LegacyTransportable): after a write under the lock with corrNr, the list and readMetadata name the request.

Utilities — getUtils()

Served: search, fetchNodeStructure, getObjectStructure, discovery, getInactiveObjects (7.40 answers the older adtcore:objectReferences document), readObjectMetadata / readObjectSource for the served types, getTypeStructure. readObjectMetadata, readObjectSource, fetchNodeStructure, getObjectStructure and activateObjectsGroup take 7.40's codes (ADT_TYPES_BASIS_740, and DEVC/K for the structure reads; VIEW/DV too for the group activation, ADT_UTILITY_CODES_BASIS_740) and refuse any other without a request — BDEF/BDO, SRVD/SRV, DDLX/EX, DCLS/DL and ENHO/XHH among them. search filters by 7.40's search names (ADT_SEARCH_TYPES_BASIS_740: DEVC, CLAS, INTF, PROG, FUGR, FUNC, DDLS, MSAG, TABL, ENHO, ENHS), since 0.3.0: 7.40 has no object types list, reads a code as its main type, and answers an alias (STRU, DTAB, REPO, INCL, BADI) or an unknown name with no hits — so any other filter does not compile, and is refused without a request. Refused: where-used (7.40 serves it only through RIS), virtual folders, object types, activation runs, group deletion, data preview, and the DDIC and package types' reads. Member by member: LEGACY.md.

Executors — AdtExecutorLegacy

MemberOn 7.40 SP04
class, program, function group run / runSync; function module runSyncsends one POST /abapunit/testruns with <options>, the finished result
function module runrefused — it names no group; use runSync({ functionGroupName, functionModuleName })
generic runner (getAbapUnitTestRunner()) runSync(uris, { headers })sends one POST /abapunit/testruns with <options> and the caller's headers, the finished result — inherited
generic runner run (object or package target)refused — no /abapunit/runs, and /testruns names objects by URI, with no package set; use runSync with the objects' URIs
every getStatus, getResultrefused — a legacy run answers finished; there is no run id
class run, runWithProfilerrefused — no /oo/classrun (404)
program run, trace type listsrefused until measured — not advertised
trace listRequests, getRequestsByUri, scheduleTracekept, not measured

Types 7.40 does not have

getDomain(), getDataElement(), getStructure(), getTable(), getTableType(), getAppendStructure(), getTransformation(), getScalarFunction(), getScalarFunctionImplementation(), getEnhancement(), getAccessControl(), getServiceDefinition(), getServiceBinding(), getBehaviorDefinition(), getBehaviorImplementation(), getMetadataExtension(), getAuthorizationField(), getFeatureToggle(): ADT_TYPES_BASIS_740 has no such API, so the getter does not compile on AdtClientLegacy (since 0.2.0). Called from outside the type system, it hands out a handler and every member of it is refused.

Development

Unit tests need no SAP system: npm test (here, or at the repository root). Live checks need a 7.40 system and its own overlay: see docs/LIVE_TESTING.md.

  • npm run check:config checks an overlay that holds both the root package's keys and the legacy keys (environment.is_legacy, legacy_env.*, legacy_740.*). The root package's check:config knows the modern overlays' keys only and refuses an overlay that has legacy keys.
  • npm run header:discovery runs the root package's error corpus with the legacy clients probed beside the modern ones; it needs the root package built first (npm run build at the repository root).

License

LGPL-3.0-only

版本与发布记录

@mcp-abap-adt/adt-clients-legacy 共发布 5 个版本,首次发布于 2026/10/27;过去 12 个月发布了 5 个版本。

版本发布日期距今
0.4.0latest2026/11/021 天前
0.3.02026/10/313 天前
0.2.02026/10/304 天前
0.1.02026/10/277 天前
0.0.0-stage2026/10/277 天前

关于 @mcp-abap-adt/adt-clients-legacy 的常见问题

@mcp-abap-adt/adt-clients-legacy 是什么?

旧SAP ABAP系统(7.40)的客户端,基于@mcp-abap-adt/adt-clients

如何安装 @mcp-abap-adt/adt-clients-legacy?

在项目目录运行 npm install @mcp-abap-adt/adt-clients-legacy,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@mcp-abap-adt/adt-clients(^40.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@mcp-abap-adt/adt-clients-legacy 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@mcp-abap-adt/adt-clients-legacy 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@mcp-abap-adt/adt-clients-legacy 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@mcp-abap-adt/adt-clients-legacy 可以商用吗?

@mcp-abap-adt/adt-clients-legacy 使用 LGPL-3.0-only 许可。弱 Copyleft:作为库被调用时通常不要求开源你自己的代码,但修改库本身需以 LGPL 开源;前端打包合并后的适用边界存在不同理解。以上为概括,不构成法律意见,请以许可原文为准。

@mcp-abap-adt/adt-clients-legacy 还在维护吗?

最新版本 0.4.0 发布于 2026/11/02(1 天前);至今共发布 5 个版本,其中过去 12 个月发布了 5 个;首次发布于 2026/10/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。