ADT 协议技术笔记
August 28, 2026 · View on GitHub
本文档是
@nefevcore/abap-adt-protocol客户端实现的事实依据,交叉验证自:
- 生产级开源客户端:
@mcp-abap-adt/adt-clients、abap-adt-api(MIT)、vscode_abap_remote_fs(MIT)、abapify/adt-cli(MIT)- SAP 官方 BTP REST 文档:ABAP Unit、ATC
- 真实系统 discovery 捕获:fr0ster/mcp-abap-adt docs/adt-discovery.xml
ADT 协议没有官方公开规范(见 SAP Community 讨论),社区以"抓包 + 客户端实现"为事实标准。原始调研材料保存在仓库
.research/目录。
1. 基础
- 所有资源挂
/sap/bc/adt/前缀;对象 URI 命名一律小写 - 子资源模式:
<uri>/source/main(源码)、<uri>/source/main/versions(版本)、<uri>/includes/<段>(类段)、<uri>/transport - URI 有两种形态:对象 URI(
…/oo/classes/zcl_demo)与源码形态(…/oo/classes/zcl_demo/source/main,读/搜索输出常见)。对象级端点(versions、lock、where-used、transport、删除等)只认对象形态,直接对源码形态拼接会得到…/source/main/source/main/versions→ 404;客户端所有对象级方法先经objectBaseUri()剥掉/source/main后缀再拼接 - 旧系统(BASIS ≤ 7.40)仅
/sap/bc/adt/discovery,现代系统/sap/bc/adt/core/discovery
认证
| 方式 | 适用 | 要点 |
|---|---|---|
| Basic | on-prem | Authorization: Basic b64(user:pass) + X-SAP-Client 头或 ?sap-client= 参数;Set-Cookie 建立会话 |
| JWT/OAuth2 | BTP ABAP Cloud | Authorization: Bearer <jwt>,token 过期需刷新 |
CSRF 流程(所有写操作)
GET /sap/bc/adt/core/discovery+ 头x-csrf-token: fetch(Acceptapplication/atomsvc+xml)- 响应头取
x-csrf-token: <token>,同时 Set-Cookie - 写请求带
x-csrf-token+Cookie - 403(含 "CSRF")/ 401(写操作)→ 丢弃 token+cookie 重取重试一次
会话头
sap-adt-connection-id: <uuid>— 所有请求x-sap-adt-sessiontype: stateful— 写链(锁定/激活/写入);7.40 旧系统上会导致锁进会话内存(423),需可关闭sap-adt-request-id— 有状态会话中每请求唯一sap-usercontextcookie 会被服务端按系统默认客户端回写,客户端应强制覆盖为请求 client
2. 关键端点
系统信息
| 方法 | 路径 | 媒体类型 |
|---|---|---|
| GET | /sap/bc/adt/core/discovery | Accept application/atomsvc+xml(CSRF fetch 端点) |
| GET | /sap/bc/adt/discovery | 旧系统回退 |
| GET | /sap/bc/adt/core/http/systeminformation | ...core.http.systeminformation.v1+json |
| GET | /sap/bc/adt/ato/settings | ATO 设置(云环境) |
对象 CRUD
| 方法 | 路径 | 媒体类型 | 用途 |
|---|---|---|---|
| POST | /sap/bc/adt/oo/classes(等类型集合) | CT application/vnd.sap.adt.oo.classes.v4+xml | 创建(?package=ZTEST、?corrNr=) |
| GET | <对象URI> | 对应对象媒体类型 | 读元数据(?version=active|inactive) |
| GET | <对象URI>/source/main | Accept text/plain | 读源码 |
| PUT | <对象URI>/source/main?lockHandle=<h> | CT text/plain; charset=utf-8 | 写源码(先锁定) |
| POST | <对象URI>?_action=LOCK&accessMode=MODIFY | .../lock.result | 锁定,响应 asx:abap → LOCK_HANDLE/CORRNR |
| POST | <对象URI>?_action=UNLOCK&lockHandle=<h> | 同上 | 解锁 |
| POST | <对象URI>?_action=DELETE&deleteOption=deleteAndLocalVersions | 删除 | |
| GET | /sap/bc/adt/repository/nodestructure | ...nodestructure.v1+xml | 包树/结构浏览 |
| POST | /sap/bc/adt/checkruns?reporters=abapCheckRun | CT checkobjects+xml / Accept checkmessages+xml | 语法检查 |
激活
POST /sap/bc/adt/repository/activation?method=activate&preauditRequested=true
CT: application/vnd.sap.adt.activation+xml Accept: application/xml
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
<adtcore:objectReference adtcore:uri="..." adtcore:name="..."/>
</adtcore:objectReferences>
注意:激活错误在 HTTP 200 body 的 <chkl:messages><msg type="E"> 中。
ABAP Unit(异步)
POST /sap/bc/adt/abapunit/runs(on-prem)//sap/bc/adt/api/abapunit/runs(BTP)— CT...abapunit.run.v1+xml,bodyaunit:run+osl:objectSetGET .../runs/{runId}?withLongPolling=true— 轮询状态(status/completed)GET .../results/{runId}— JUnit XML(testsuites/testsuite/testcase+failure/error/skipped)
ABAP Unit(legacy 同步,BASIS < 7.5x,如 D01/NW 7.4x)
老后端没有异步 run API:POST /abapunit/runs 直接 404,只有同步的 testruns 服务
(与官方 Eclipse/VS Code ADT 客户端 com.sap.adt.abapunit bundle 的
AbapUnitRequestContentHandlerV1 走法一致,已在真实 D01 上验证):
POST /sap/bc/adt/abapunit/testruns— CT/Accept 均为application/xml, bodyaunit:runConfiguration(nshttp://www.sap.com/adt/aunit)+adtcore:objectReferences- 响应体即结果:
aunit:runResult→program→testClass→testMethod, 方法无aunit:alert= 通过;severity="critical|fatal"= 失败;title/元素文本为消息。无 runId、无需轮询。 - 对象无测试类时返回空
runResult(0 tests),不是错误。 客户端在POST /abapunit/runs404 时自动回退到该路径(见client.runUnitTestsLegacy)。
ATC(异步)
POST /sap/bc/adt/atc/runs(on-prem)//sap/bc/adt/api/atc/runs(BTP)— CT...atc.run.parameters.v1+xml,bodyatc:runparameters+osl:objectSet(checkVariant属性)GET .../runs/{runId}— 轮询(state/ phases)GET .../results/{displayId}— checkstyle XML(checkstyle/file/error,severity/line/source)
传输
- 列表:
GET /sap/bc/adt/cts/transportrequests?user=...&type=...— Accepttransportorganizertree.v1+xml - 详情:
GET /sap/bc/adt/cts/transportrequests/{tr}—transportorganizer.v1+xml - 动作:
POST /sap/bc/adt/cts/transportrequests/{tr}/release(release/check/import) - 创建:
POST /sap/bc/adt/cts/transportrequests(bodytm:root tm:useraction="newrequest") - 旧系统(< 7.52):
/sap/bc/cts/前缀
搜索
GET /sap/bc/adt/repository/informationsystem/search?operation=quickSearch&query=Z*&maxResults=25[&objectType=CLAS]
Accept: application/xml → <adtcore:objectReferences><adtcore:objectReference adtcore:name=... adtcore:uri=.../>
3. 错误格式
<exc:exception xmlns:exc="http://www.sap.com/adt/xml/exception" exc:type="...">
<exc:message>...</exc:message>
<exc:localizedMessage>...</exc:localizedMessage>
</exc:exception>
- 403 还可能是锁冲突(
ExceptionResourceNoAccess"currently editing")——此时不要清会话 - 412 = ETag 过期(元数据 PUT 后源码需先 GET 刷新再 PUT)
- 423 = 旧系统上 stateful 会话锁问题
4. 对象类型码(ADT registry)
| 对象 | 类型码 | 创建集合 |
|---|---|---|
| 类 | CLAS/OC | /sap/bc/adt/oo/classes |
| 接口 | INTF/OI | /sap/bc/adt/oo/interfaces |
| 程序 | PROG/P | /sap/bc/adt/programs/programs |
| include | PROG/I | |
| 函数组/模块 | FUGR/F / FUGR/FF | /sap/bc/adt/fugr |
| CDS 数据定义 | DDLS/DF | /sap/bc/adt/ddls/sources |
| 访问控制 | DCLS/DL | /sap/bc/adt/dcls/sources |
| 元数据扩展 | DDLX/EX | /sap/bc/adt/ddlx/sources |
| 行为定义 | BDEF/BDO | /sap/bc/adt/bdef/sources |
| 服务定义 | SRVD/SRV | /sap/bc/adt/srvdef/sources |
| 表/结构 | TABL/DT / STRU/DT | `/sap/bc/adt/ddic/tables |
| 消息类 | MSAG/N | /sap/bc/adt/msgclass |
| 包 | DEVC/K | /sap/bc/adt/packages |
5. ABAP Cloud vs 经典差异
| 维度 | 经典 | ABAP Cloud |
|---|---|---|
| 认证 | Basic | JWT(无 Basic) |
| discovery | /discovery → /core/discovery | 必须 /core/discovery |
| 对象 | 全部 | PROG 等经典对象不可用 |
| 单测 | /sap/bc/adt/abapunit/runs;老系统(< 7.5x,如 D01)仅 /sap/bc/adt/abapunit/testruns(同步,见 §3) | /sap/bc/adt/api/abapunit/runs |
| ATC | /sap/bc/adt/atc/runs | /sap/bc/adt/api/atc/runs |
| 传输 | 传统 CTS | 软件组件 + release state 驱动 |
6. 实测补充(S/4HANA S4C 系统验证结论)
在真实 S/4HANA 系统(sap-system: S4C)上端到端验证后的经验值:
operation=quickSearchSource和operation=objectSearch可能返回 HTTP 500(搜索提供者受限时)。客户端会自动降级到quickSearch并通过note字段告知;此时全文搜索不可用、对象搜索仍可用。/sap/bc/adt/repository/nodestructure在加固系统上可能 405。包成员列表优先用搜索 +packageName过滤(?query=*&packageName=ZPACK&maxResults=500),nodestructure 仅作回退。- 系统信息用
/sap/bc/adt/core/http/systeminformation(JSON:systemID/userName/client/language)比 discovery feature 可靠;discovery 的feature元素在真实系统上经常没有。 - 自签名证书常见(
CN=...pvt自签发):strictSSL: false时客户端通过 undici dispatcher 关闭校验(仅该目的地生效)。 - ATC run 启动必须显式
clientWait=false,否则 400("Only 'false' is currently supported as QueryParameter 'ClientWait'")。 - ATC run 状态响应用
status属性(<atc:run status="Running|Completed">+<atc:phase status=...>);完成时<atom:link href="/sap/bc/adt/atc/results/{displayId}">的 displayId 与 runId 不同,必须从链接提取。 - ATC 结果端点拒绝 checkstyle 媒体类型(406),用
application/xml取回;解析时兼容 checkstyle 与未知格式(保留原始 XML)。 - ATC 结果的真实格式是
atcresult命名空间(非 checkstyle):resultList → result → objects → object → findings → finding。finding 属性:priority(1-4)、checkId、checkTitle、messageId、messageTitle、location(#start=行,列)、uri。集合列表也是子元素格式(<atcresult:displayId>等),并带aggregates(numPrio1..4/numFailure)。priority 映射:1→CRITICAL、2→ERROR、3→WARNING、4→INFO。 - ATC 结果集合
GET /atc/results需要createdBy参数(缺省会 400);createdBy=*可跨用户列出全部结果;activeResult=true语义不同(活跃结果)。结果可通过 displayId 直接获取(GET /atc/results/{displayId})。
7. 运行时转储 / 执行器 / $batch / 结构化编辑器
运行时转储(ST22 短转储分析)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /sap/bc/adt/runtime/dumps | Atom feed。$query(and( equals( user, X ) ) 表达式过滤)、from/to(YYYYMMDDHHMMSS 时间范围)、$top/$skip 分页;Accept application/atom+xml;type=feed |
| GET | /sap/bc/adt/runtime/dump/{id} | 结构化 XML(application/vnd.sap.adt.runtime.dump.v1+xml)。注意集合是 dumps(复数)、单条是 dump(单数) |
| GET | /sap/bc/adt/runtime/dump/{id}/summary | HTML 概览(Accept text/html) |
| GET | /sap/bc/adt/runtime/dump/{id}/formatted | 纯文本分析视图(Accept text/plain) |
dump id 是复合键:{YYYYMMDDHHMMSS}{hostname}_{SYSID}_{instance}{user}{client}{seq}。feed entry 的 id 从 <link href=".../runtime/dump/{id}"/> 提取(<id> 元素不总是可用)。
程序 / 类执行器
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /sap/bc/adt/programs/programrun/{NAME} | 运行可执行程序(F8 等价物);控制台输出 text/plain 返回;名称大写 |
| POST | /sap/bc/adt/oo/classrun/{NAME} | 运行实现 if_oo_adt_classrun 的类(main( ) 执行,out->write( ) 输出以 text/plain 返回);未实现该接口 → 400 |
两者都是状态变更 POST(需 CSRF);报文无 body。
协议级 $batch
POST /sap/bc/adt/$batch
Content-Type: multipart/mixed; boundary=batch_<uuid>
Accept: multipart/mixed
--batch_<uuid>
Content-Type: application/http
content-transfer-encoding: binary
GET /sap/bc/adt/oo/classes/zcl_demo/source/main?sap-client=100 HTTP/1.1
Accept: text/plain
--batch_<uuid>
...
--batch_<uuid>--
- 每部分是
application/http内嵌完整 HTTP 请求(请求行 + 头 + 空行 + body) - 响应是
multipart/mixed,逐部分内嵌 HTTP 响应(HTTP/1.1 200 OK+ 头 + body),顺序与请求一致 - CSRF 只需在外层 POST 校验一次;
sap-client/sap-language应附加到每个内层路径 - 参考实现:
@mcp-abap-adt/adt-clients的batch/buildBatchPayload(boundary 随机生成、requestId 32 位);语义同 OData $batch 但挂在整个 ADT 命名空间下
结构化编辑器(MSAG / DOMA / DTEL / TTYP)
这些 DDIC 对象没有 /source/main,内容是元数据 XML;写入走 read-modify-write(lock → GET 原文 → 只补丁显式字段 → PUT ?lockHandle= → unlock),SAP 管理属性全量保留:
| 类型 | 端点 | 媒体类型 | 结构要点 |
|---|---|---|---|
| MSAG | /sap/bc/adt/messageclass/{name} | application/vnd.sap.adt.mc.messageclass+xml | 根 mc:messageClass(ns http://www.sap.com/adt/MessageClass);消息是 mc:messages 元素属性(mc:msgno/mc:msgtext/mc:selfexplainatory——SAP 拼写就是没有 x);删除走 mc:deletedmessages |
| DOMA | /sap/bc/adt/ddic/domains/{name} | application/vnd.sap.adt.domains.v2+xml | doma:content → doma:typeInformation(datatype/length/decimals)+ doma:outputInformation(conversionExit/signExists/lowercase)+ doma:fixValues → doma:fixValue(doma:low/doma:text 子元素) |
| DTEL | /sap/bc/adt/ddic/dataelements/{name} | application/vnd.sap.adt.dataelements.v2+xml | dtel:typeKind(domain/builtin)+ dtel:typeName 或 dtel:dataType+dtel:dataTypeLength(补零到 6 位);dtel:labels → `dtel:label type="shortText |
| TTYP | /sap/bc/adt/ddic/tabletypes/{name} | application/vnd.sap.adt.tabletypes.v2+xml | ttyp:typeKind/ttyp:typeName/ttyp:accessType;主键 ttyp:key → ttyp:definition/ttyp:kind |
TABL/STRU 不在结构化编辑器范围:现代系统上它们有 DDL 源(/ddic/tables/{name}/source/main,PUT DDL 文本),走普通源码读写路径。
8. 真实后端实战记录(impc-dev / D01 / client 110)
一次完整的真实系统实战反馈及其在工具链中的处理方式。原则:合理的后端报错保持原样透传(只加提示),行为缺陷在客户端修复。
后端不支持(合理报错,工具层给出定向提示)
| 现象 | 处理 |
|---|---|
POST /sap/bc/adt/$batch → 404(未部署 $batch 服务) | adt_batch 捕获 404/405,明确提示"该后端未部署 $batch 服务,请逐个调用工具" |
对象元数据不含锁状态 → adt_lock_info 返回 locked=null | 保留 null + 强化提示:此类后端无法探测并发编辑者,应依赖写后持久性验证(见下) |
自由 SQL 的 SELECT 列表带 mandt → 400(解析器拒绝跨 client 字段) | adt_data_preview 描述明示禁止选 mandt 列;400 时给出定向提示 |
行为缺陷(客户端修复)
| 现象 | 根因 | 修复 |
|---|---|---|
include 用 name+type=PROG 读取 404 | 按约定拼 URI 到 /programs/programs/,而 include 在 /programs/includes/ | resolveObject 对 PROG/P 与 PROG/I 先做精确名搜索,用命中对象的真实 URI/type;搜索不可用回落约定 URI |
adt_list_transports(status=modifiable) 漏报未释放请求(D01K966363 状态 D 却返回 0 条) | 语义词 modifiable 被原样发给后端,后端按 CTO 字母码精确匹配 → 匹配 0 行 | 客户端把 modifiable→D、released→R 再发;后端 400 拒绝 status 参数时自动去参重试,客户端侧过滤兜底 |
adt_list_atc_runs 带任何过滤参数 400 | 子集实现的 ATC results 服务只接受无参数查询 | 400 时自动回退无参数查询重试 |
| ATC 结果 P1–P4/耗时全 0 | 单结果体不带 <aggregates> 节点、无运行时 | 无 aggregates 时按 finding 的 priority 属性推导 P1–P4;adt_run_atc 的 durationMs 改为客户端计时 |
| ATC finding 全挂主程序名、行号却是 include 的 | 后端把程序所有 finding 嵌在主程序 object 下,位置在 finding 自己的 location URI 里 | 解析出 locationUri(#start= 前的 URI 部分),工具输出为每条 finding 的 uri 字段,渲染时标注 (in <include>) |
adt_get_transport 传任务号返回父请求 | 真实 CTO 行为:任务号解析到父请求(版本历史记录的是任务级号) | 保留该行为,输出 requestedNumber + note 显式标出任务→父请求映射 |
adt_system_info release 为空 | release 藏在备用 feature 键里 | 依次探测 JSON release、release、SAP_SYSTEM_RELEASE、SAP_BASIS_RELEASE、SAP_SYSTEM_RELEASE_ID(注意用 ` |
| 编辑报成功但源码被静默打回原样(本次最大事故) | 同一开发账号的另一个会话持有旧缓冲区,在写入解锁之后整缓冲保存覆盖;写前 OCC 哈希校验只保护写前窗口,感知不到写后覆盖;版本库还留下 99999 临时版本号加剧误判 | 三个写工具(edit/write/push)在解锁后回读验证:与所写内容做容错比对(CRLF/行尾空白),不一致 → persisted:false + 醒目告警(重读重做、勿激活);读失败 → persisted:undefined + 提示回读确认。回读结果同时刷新本地快照(OCC 基准 = 真实服务端状态) |
标准操作顺序(沉淀)
adt_permissions → adt_search(拿全对象 URI)→ adt_read_object(建快照)→ adt_edit_object → 立即回读确认(persisted 标志 / adt_read_object)→ adt_activate(主程序 + include 一次传齐)→ adt_version_diff(saved vs active) 验证无遗留 → adt_run_atc + 无参数 adt_list_atc_runs。
多人/多会话共用同一开发账号时,"编辑成功"之后的那次回读,比激活成功更值得信赖。
9. 待验证项(勿在生产依赖)
?includeSupportPackageCompatibility参数x-sap-login-with头- 通用
?feature=参数 /sap/bc/adt/repository/package_service/unit/discovery端点(不存在于任何来源)
参考
- 完整调研材料:本仓库
.research/(含社区仓库克隆、SAP 文档摘录、端点清单) - mcp-abap-adt(MIT)— 端点/媒体类型最全的参考实现
- vscode_abap_remote_fs(MIT)— ADT 通信层 + 调试器
- abap-adt-api(MIT)— 经典 TS 客户端
- ADT Debugger API 调研
- ADT 原生功能清单