arc-1-lsp 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +30 -0
- package/README.md +295 -0
- package/dist/adt-ls/cert.js +121 -0
- package/dist/adt-ls/cert.js.map +1 -0
- package/dist/adt-ls/destinations.js +116 -0
- package/dist/adt-ls/destinations.js.map +1 -0
- package/dist/adt-ls/discovery.js +59 -0
- package/dist/adt-ls/discovery.js.map +1 -0
- package/dist/adt-ls/driver.js +150 -0
- package/dist/adt-ls/driver.js.map +1 -0
- package/dist/adt-ls/lifecycle.js +96 -0
- package/dist/adt-ls/lifecycle.js.map +1 -0
- package/dist/adt-ls/mcp-federation.js +67 -0
- package/dist/adt-ls/mcp-federation.js.map +1 -0
- package/dist/adt-ls/mcp-lifecycle.js +10 -0
- package/dist/adt-ls/mcp-lifecycle.js.map +1 -0
- package/dist/adt-ls/repository.js +59 -0
- package/dist/adt-ls/repository.js.map +1 -0
- package/dist/adt-ls/session-retry.js +79 -0
- package/dist/adt-ls/session-retry.js.map +1 -0
- package/dist/adt-ls/tls-reverse-proxy.js +80 -0
- package/dist/adt-ls/tls-reverse-proxy.js.map +1 -0
- package/dist/btp/bridge.js +75 -0
- package/dist/btp/bridge.js.map +1 -0
- package/dist/btp/connectivity.js +27 -0
- package/dist/btp/connectivity.js.map +1 -0
- package/dist/btp/destination.js +22 -0
- package/dist/btp/destination.js.map +1 -0
- package/dist/btp/token.js +23 -0
- package/dist/btp/token.js.map +1 -0
- package/dist/btp/types.js +7 -0
- package/dist/btp/types.js.map +1 -0
- package/dist/btp/vcap.js +58 -0
- package/dist/btp/vcap.js.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/server/auth.js +26 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/config.js +53 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/engine.js +239 -0
- package/dist/server/engine.js.map +1 -0
- package/dist/server/http.js +52 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/logger.js +14 -0
- package/dist/server/logger.js.map +1 -0
- package/dist/server/safety.js +25 -0
- package/dist/server/safety.js.map +1 -0
- package/dist/server/server.js +186 -0
- package/dist/server/server.js.map +1 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marian Zeis and contributors
|
|
4
|
+
|
|
5
|
+
Portions of this software are derived from ARC-1 (https://github.com/marianfoo/arc-1),
|
|
6
|
+
MIT License, Copyright (c) 2025-2026 Alice Vinogradova and contributors — specifically
|
|
7
|
+
the MCP server shell, configuration, authorization/scope model, audit, logging, and
|
|
8
|
+
Zod schema patterns that arc-1-lsp reuses.
|
|
9
|
+
|
|
10
|
+
This project does NOT include or redistribute SAP's `adt-ls` language server or any
|
|
11
|
+
other SAP binaries; those remain under SAP's own license and must be brought by the
|
|
12
|
+
user (see docs/adr/0002-byo-adt-ls-no-redistribution.md).
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# arc-1-lsp
|
|
2
|
+
|
|
3
|
+
An edition of **ARC-1** — a Model Context Protocol (MCP) server for SAP ABAP
|
|
4
|
+
development — that delegates **all** ABAP/ADT interaction to SAP's own embedded
|
|
5
|
+
**`adt-ls`** language server instead of a hand-rolled ADT HTTP client. arc-1-lsp
|
|
6
|
+
owns the MCP front-end, auth/scopes, write-safety, and orchestration; `adt-ls`
|
|
7
|
+
owns CSRF, locking, XML, activation, transport — everything system-specific.
|
|
8
|
+
|
|
9
|
+
> **Status:** working — connects headless to a SAP system, exposes 16 MCP tools
|
|
10
|
+
> (reads + a full create→edit→activate→test→delete authoring loop), runs locally
|
|
11
|
+
> over stdio or as a Docker app on SAP BTP Cloud Foundry. Single-tenant / one
|
|
12
|
+
> technical user today; per-user principal propagation is on the roadmap.
|
|
13
|
+
|
|
14
|
+
## Where `adt-ls` comes from
|
|
15
|
+
|
|
16
|
+
`adt-ls` is **SAP's** language server: the headless core of the Eclipse-based
|
|
17
|
+
**ABAP Development Tools (ADT)**, shipped inside the official
|
|
18
|
+
[**ABAP Development Tools for VS Code**](https://marketplace.visualstudio.com/items?itemName=SAPSE.adt-vscode)
|
|
19
|
+
extension (`sapse.adt-vscode`). It speaks a private LSP namespace (`adtLs/*` —
|
|
20
|
+
destinations, logon, filesystem, activation, transport, unit tests) and embeds an
|
|
21
|
+
**experimental MCP server** (object creation, activation, generators, …).
|
|
22
|
+
|
|
23
|
+
arc-1-lsp does not reimplement any of that — it **discovers, spawns, and drives**
|
|
24
|
+
the developer-provided `adt-ls` headless (no Eclipse, no VS Code, no browser). The
|
|
25
|
+
`adt-ls` binary is under SAP's Developer License and is **never bundled or
|
|
26
|
+
redistributed** — you bring your own (see [Prerequisites](#prerequisites) and
|
|
27
|
+
[ADR-0002](docs/adr/0002-byo-adt-ls-no-redistribution.md)).
|
|
28
|
+
|
|
29
|
+
## arc-1-lsp vs. main ARC-1 — which should I use?
|
|
30
|
+
|
|
31
|
+
Both are MCP servers for SAP ABAP and share the same tool shape. They differ in
|
|
32
|
+
*how* they talk to SAP, and therefore in what they can do.
|
|
33
|
+
|
|
34
|
+
| | **[ARC-1](https://github.com/marianfoo/arc-1)** (main) | **arc-1-lsp** (this repo) |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| ADT protocol | Hand-rolled (CSRF, locking, XML, version quirks) | Delegated to SAP's `adt-ls` |
|
|
37
|
+
| System-specific code to maintain | ~29 ADT modules | ~zero (it's SAP's job) |
|
|
38
|
+
| Object-type coverage | **All** — classic *and* modern (programs, tables, function groups, domains, CDS, classes, RAP, …) | **Modern ABAP-Cloud types only** (class, interface, CDS, behavior def, service def/binding, …) |
|
|
39
|
+
| Free SQL / data preview | ✅ | ❌ (absent in adt-ls) |
|
|
40
|
+
| Navigation / where-used, ATC/lint | ✅ | ❌ (not reachable headless) |
|
|
41
|
+
| Git (gCTS / abapGit) | ✅ | ❌ (absent in adt-ls) |
|
|
42
|
+
| Maturity | Production, multi-user, write-capable | Working; reads + authoring loop; single technical user |
|
|
43
|
+
|
|
44
|
+
**Use main ARC-1** for the broadest coverage (classic objects, SQL, ATC, git,
|
|
45
|
+
where-used) and production multi-user deployments. **Use arc-1-lsp** when you want
|
|
46
|
+
SAP itself to own the ADT protocol — less code to maintain, and behavior that
|
|
47
|
+
tracks ADT exactly — and your work is on modern ABAP-Cloud objects.
|
|
48
|
+
|
|
49
|
+
The honest, line-by-line map of what is and isn't wired (and *why*) lives in
|
|
50
|
+
[`docs/arc-1-feature-parity.md`](docs/arc-1-feature-parity.md); the live-verified
|
|
51
|
+
capability boundary of `adt-ls` itself is in
|
|
52
|
+
[`docs/adt-ls-reference.md`](docs/adt-ls-reference.md).
|
|
53
|
+
|
|
54
|
+
## What works today
|
|
55
|
+
|
|
56
|
+
**16 MCP tools.** Reads work read-only; the authoring loop is gated behind
|
|
57
|
+
`ARC1_ALLOW_WRITES` + a package allowlist.
|
|
58
|
+
|
|
59
|
+
- **Reads (11):** `health`, `list_destinations`, `list_creatable_objects`,
|
|
60
|
+
`search_objects`, `list_inactive_objects`, `list_users`, `list_generators`,
|
|
61
|
+
`get_generator_schema`, `get_object_type_details`, `get_service_binding`,
|
|
62
|
+
`read_source`.
|
|
63
|
+
- **Authoring loop (5, write-gated):** `create_object`, `update_source`,
|
|
64
|
+
`activate_object`, `run_unit_tests`, `delete_object` — a full
|
|
65
|
+
create → edit → activate → test → delete cycle, by object name, for modern
|
|
66
|
+
ABAP-Cloud types. `activate_object` returns ranged syntax diagnostics so an
|
|
67
|
+
agent can self-correct.
|
|
68
|
+
|
|
69
|
+
**Out of scope here (use main ARC-1):** classic object types (program/table/
|
|
70
|
+
function group/domain/…), free SQL, navigation/where-used, ATC/lint, transport
|
|
71
|
+
*writes*, and git. These are honest limits of `adt-ls`'s headless surface, not
|
|
72
|
+
missing features — details in [`docs/arc-1-feature-parity.md`](docs/arc-1-feature-parity.md).
|
|
73
|
+
|
|
74
|
+
The SAP session behind `adt-ls` self-heals: if it expires (idle timeout →
|
|
75
|
+
"logged off"), arc-1-lsp transparently re-logs on and retries the call once.
|
|
76
|
+
|
|
77
|
+
## Architecture
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
agent (Claude / Copilot / Cursor / …)
|
|
81
|
+
│ MCP (stdio | http-streamable)
|
|
82
|
+
▼
|
|
83
|
+
arc-1-lsp (Node/TS — discovers, spawns & supervises adt-ls; auth + scopes; owns SAP logon)
|
|
84
|
+
├─ LSP over pipe ───────────▶ adt-ls (headless, BYO) ← bootstrap, destinations, logon, filesystem, activation
|
|
85
|
+
└─ HTTP localhost ─────────▶ adt-ls's own /mcp ← federated tools
|
|
86
|
+
│ HTTPS
|
|
87
|
+
▼
|
|
88
|
+
TLS reverse proxy (CN=localhost, in arc-1-lsp)
|
|
89
|
+
│ DIRECT ───────────────▶ SAP ABAP (internet-reachable)
|
|
90
|
+
└ CC ─▶ connectivity bridge ─▶ BTP Connectivity ─▶ Cloud Connector ─▶ SAP ABAP
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`adt-ls` requires an **HTTPS** backend and validates its hostname; SAP's default
|
|
94
|
+
self-signed cert (`CN=*.dummy.nodomain`) fails that. So arc-1-lsp runs a local
|
|
95
|
+
**TLS-terminating reverse proxy** (cert `CN=localhost`, trusted via a truststore
|
|
96
|
+
built from `adt-ls`'s own JRE) and re-originates to the real backend — directly,
|
|
97
|
+
or through the connectivity bridge on BTP. Logon is **headless reentrance-ticket**
|
|
98
|
+
emulation (no browser). Full recipe + decisions:
|
|
99
|
+
[`docs/adt-ls-headless-notes.md`](docs/adt-ls-headless-notes.md) +
|
|
100
|
+
[`docs/adr/`](docs/adr/README.md).
|
|
101
|
+
|
|
102
|
+
## Prerequisites
|
|
103
|
+
|
|
104
|
+
1. **Node.js 22+**.
|
|
105
|
+
2. **A developer-provided `adt-ls`** (BYO — never redistributed). Install the
|
|
106
|
+
official **ABAP Development Tools for VS Code** extension (`sapse.adt-vscode`,
|
|
107
|
+
which accepts SAP's Developer License) and arc-1-lsp finds its `adt-ls`
|
|
108
|
+
automatically. Discovery order:
|
|
109
|
+
1. `ARC1_ADT_LS_PATH` (explicit path)
|
|
110
|
+
2. `vendor/adt-ls/<platform>/…` (build-time injection, for containers)
|
|
111
|
+
3. the newest installed `sapse.adt-vscode-*` VS Code extension
|
|
112
|
+
3. **A reachable SAP ABAP system** to connect to (optional — the server also
|
|
113
|
+
starts disconnected and still serves `health`/`tools`). Runtime cert/proxy
|
|
114
|
+
deps: `openssl` + `keytool` (the latter ships inside `adt-ls`'s JRE). See
|
|
115
|
+
[`docs/native-deps.md`](docs/native-deps.md).
|
|
116
|
+
|
|
117
|
+
## Install & run
|
|
118
|
+
|
|
119
|
+
### From source (stdio)
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm install
|
|
123
|
+
npm run build
|
|
124
|
+
node dist/index.js # or: npm run dev (tsx, no build)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Point an MCP client at the process over **stdio**. With no SAP vars set it starts
|
|
128
|
+
disconnected (handy for inspecting the tool list); set `ARC1_SAP_*` to auto-connect
|
|
129
|
+
(see [Connect a SAP system](#connect-a-sap-system)).
|
|
130
|
+
|
|
131
|
+
### As a CLI (npm)
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm install -g arc1-lsp
|
|
135
|
+
arc1-lsp # stdio MCP server (honors the same env/flags)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The npm package ships the Node wrapper only — it still discovers your BYO `adt-ls`
|
|
139
|
+
(it does **not** contain any SAP binary).
|
|
140
|
+
|
|
141
|
+
### Docker / http-streamable
|
|
142
|
+
|
|
143
|
+
The container bundles a **build-time-injected** linux `adt-ls` and serves MCP over
|
|
144
|
+
http-streamable behind an API key — this is the artifact deployed to BTP CF.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# stage the linux adt-ls (admin provides the licensed VSIX → vendor/)
|
|
148
|
+
node scripts/extract-adt-ls.mjs
|
|
149
|
+
# build the linux/amd64 image (host-builds dist; only prod deps + adt-ls are amd64)
|
|
150
|
+
IMAGE=arc-1-lsp:dev bash scripts/docker-build.sh
|
|
151
|
+
# run
|
|
152
|
+
docker run -e ARC1_API_KEYS=devkey -p 8080:8080 arc-1-lsp:dev
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`GET /healthz` (no auth) for health checks; `POST /mcp` with
|
|
156
|
+
`Authorization: Bearer <key>` for MCP.
|
|
157
|
+
|
|
158
|
+
## Connect a SAP system
|
|
159
|
+
|
|
160
|
+
Set the `ARC1_SAP_*` vars (or `--sap-*` flags) and arc-1-lsp logs on at startup.
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
ARC1_SAP_HOST=a4h.example.com ARC1_SAP_PORT=50001 \
|
|
164
|
+
ARC1_SAP_USER=DEVELOPER ARC1_SAP_PASSWORD=… ARC1_SAP_DESTINATION=A4H \
|
|
165
|
+
node dist/index.js
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`health` then reports `connectedDestination`, and `list_creatable_objects` returns
|
|
169
|
+
the system's object catalog. Two connection modes:
|
|
170
|
+
|
|
171
|
+
- **DIRECT** (default) — the reverse proxy connects straight to an
|
|
172
|
+
internet-reachable backend. All four of `HOST`/`PORT`/`USER`/`PASSWORD` must be set.
|
|
173
|
+
- **CC** (on-prem via Cloud Connector, on BTP) — bind the `connectivity` +
|
|
174
|
+
`destination` services and set only `ARC1_SAP_DESTINATION <btp-destination-name>`;
|
|
175
|
+
the engine resolves it and routes through the connectivity bridge automatically.
|
|
176
|
+
|
|
177
|
+
### Connect an MCP client
|
|
178
|
+
|
|
179
|
+
```jsonc
|
|
180
|
+
// Claude Code (HTTP):
|
|
181
|
+
// claude mcp add --transport http arc1lsp https://<host>/mcp \
|
|
182
|
+
// --header "Authorization: Bearer <api-key>"
|
|
183
|
+
//
|
|
184
|
+
// Cursor / Claude Desktop / VS Code — mcp.json:
|
|
185
|
+
{
|
|
186
|
+
"mcpServers": {
|
|
187
|
+
"arc1lsp": {
|
|
188
|
+
"url": "https://<host>/mcp",
|
|
189
|
+
"headers": { "Authorization": "Bearer <api-key>" }
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
For local stdio, point the client at the `node dist/index.js` (or `arc1-lsp`)
|
|
196
|
+
process instead of a URL. GUI inspector: `npx @modelcontextprotocol/inspector`.
|
|
197
|
+
|
|
198
|
+
## Configuration (precedence: CLI flag > env var > default)
|
|
199
|
+
|
|
200
|
+
| Env / flag | Default | Meaning |
|
|
201
|
+
|------------|---------|---------|
|
|
202
|
+
| `ARC1_ADT_LS_PATH` / `--adt-ls-path` | (discovered) | explicit `adt-ls` binary |
|
|
203
|
+
| `ARC1_ADT_LS_MCP_PORT` / `--adt-ls-mcp-port` | `2240` | port for `adt-ls`'s own MCP server |
|
|
204
|
+
| `ARC1_ADT_LS_MCP_TOKEN` / `--adt-ls-mcp-token` | (generated) | bearer for `adt-ls`'s MCP server |
|
|
205
|
+
| `ARC1_TRANSPORT` / `--transport` | `stdio` | `stdio` \| `http-streamable` |
|
|
206
|
+
| `ARC1_PORT` / `--port` | `8080` | HTTP port (http-streamable; CF `$PORT` honored) |
|
|
207
|
+
| `ARC1_API_KEYS` / `--api-keys` | (none) | edge auth: `key[:label][,key2…]`; empty disables auth (local only) |
|
|
208
|
+
| `ARC1_ALLOW_WRITES` / `--allow-writes` | `false` | enable mutating tools (create/update/activate/delete) |
|
|
209
|
+
| `ARC1_ALLOWED_PACKAGES` / `--allowed-packages` | `$TMP` | packages writes may target — exact / `PREFIX*` / `*` |
|
|
210
|
+
| `ARC1_LOG_LEVEL` | `info` | `debug`\|`info`\|`warn`\|`error` (stderr only) |
|
|
211
|
+
| **SAP connection — DIRECT mode** (internet-reachable backend) | | |
|
|
212
|
+
| `ARC1_SAP_HOST` / `--sap-host` | — | backend host |
|
|
213
|
+
| `ARC1_SAP_PORT` / `--sap-port` | — | backend **HTTPS** port |
|
|
214
|
+
| `ARC1_SAP_USER` / `--sap-user` | — | SAP user (the reentrance ticket is fetched with these creds) |
|
|
215
|
+
| `ARC1_SAP_PASSWORD` / `--sap-password` | — | SAP password (set via env / `cf set-env`, never committed) |
|
|
216
|
+
| `ARC1_SAP_DESTINATION` / `--sap-destination` | `SAP` | `adt-ls` destination id (DIRECT) **or** BTP destination name (CC) |
|
|
217
|
+
| `ARC1_SAP_CLIENT` / `--sap-client` | `001` | SAP client |
|
|
218
|
+
| `ARC1_SAP_LANGUAGE` / `--sap-language` | `EN` | SAP logon language |
|
|
219
|
+
| `ARC1_SAP_INSECURE` / `--sap-insecure` | `true` | accept the backend's self-signed cert (the proxy's own TLS is trusted separately) |
|
|
220
|
+
| **SAP connection — CC mode** (on-prem via Cloud Connector) | | |
|
|
221
|
+
| `ARC1_SAP_DESTINATION` | — | BTP Destination Service name; resolved when `connectivity` is bound |
|
|
222
|
+
|
|
223
|
+
> Config is read from CLI flags and the process environment only (no `.env`
|
|
224
|
+
> auto-loading) — export the vars in your shell or set them via `cf set-env`.
|
|
225
|
+
|
|
226
|
+
## Deploy to BTP Cloud Foundry
|
|
227
|
+
|
|
228
|
+
The image deploys to CF as a docker app (see `manifest.yml`). Secrets stay out of
|
|
229
|
+
git — the API key, SAP creds, and the registry pull token are passed at deploy time:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
docker push ghcr.io/<owner>/arc-1-lsp:0.0.1
|
|
233
|
+
# secrets via cf set-env (never committed):
|
|
234
|
+
cf set-env arc-1-lsp ARC1_API_KEYS "$(openssl rand -hex 16)"
|
|
235
|
+
cf set-env arc-1-lsp ARC1_SAP_HOST <host>
|
|
236
|
+
cf set-env arc-1-lsp ARC1_SAP_PORT 50001
|
|
237
|
+
cf set-env arc-1-lsp ARC1_SAP_USER <user>
|
|
238
|
+
cf set-env arc-1-lsp ARC1_SAP_PASSWORD <secret>
|
|
239
|
+
cf set-env arc-1-lsp ARC1_SAP_DESTINATION <id>
|
|
240
|
+
cf set-env arc-1-lsp ARC1_ALLOW_WRITES true # optional, to enable the authoring loop
|
|
241
|
+
cf set-env arc-1-lsp ARC1_ALLOWED_PACKAGES '$TMP' # scope writes
|
|
242
|
+
# re-push (stop first if the org memory quota is tight — avoids a transient 2×2G):
|
|
243
|
+
cf stop arc-1-lsp
|
|
244
|
+
CF_DOCKER_PASSWORD=$(gh auth token) cf push arc-1-lsp -f manifest.yml
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`cf logs` shows `engine: connected destination …`; the MCP `health` tool reports
|
|
248
|
+
`connectedDestination`. `cf push` preserves `cf set-env` vars not listed in the
|
|
249
|
+
manifest. If the ghcr image is private, CF pulls it with `CF_DOCKER_PASSWORD`;
|
|
250
|
+
make the package public to drop that.
|
|
251
|
+
|
|
252
|
+
## Test a running instance (local or CF)
|
|
253
|
+
|
|
254
|
+
The `/mcp` endpoint is **stateless** StreamableHTTP — a bare `tools/call` works, no
|
|
255
|
+
session handshake.
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
ARC1_URL=https://<host>/mcp ARC1_KEY=<api-key> bash scripts/smoke-remote.sh
|
|
259
|
+
# → /healthz ok · health {connectedDestination} · tools/list · list_creatable_objects
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Documentation
|
|
263
|
+
|
|
264
|
+
| Doc | What it covers |
|
|
265
|
+
|-----|----------------|
|
|
266
|
+
| [`docs/adt-ls-reference.md`](docs/adt-ls-reference.md) | **The authoritative, live-verified `adt-ls` capability map** — URI model, the `getLsUri` name→URI resolver, the method/tool matrix, the object-type boundary, the proven lifecycle, session self-heal, gotchas |
|
|
267
|
+
| [`docs/arc-1-feature-parity.md`](docs/arc-1-feature-parity.md) | arc-1 vs arc-1-lsp coverage, per-capability "implemented? why / why not" |
|
|
268
|
+
| [`docs/adt-ls-headless-notes.md`](docs/adt-ls-headless-notes.md) | The reverse-engineered headless connection recipe (initialize, reentrance-ticket logon, TLS/truststore) |
|
|
269
|
+
| [`docs/adr/`](docs/adr/README.md) | Architecture Decision Records — each decision, its context, and **when to revisit** it |
|
|
270
|
+
| [`docs/assumptions-and-future-changes.md`](docs/assumptions-and-future-changes.md) | The watch-list: what to re-verify against new `adt-ls` releases, and what would let us delete complexity |
|
|
271
|
+
| [`docs/native-deps.md`](docs/native-deps.md) | System libraries `adt-ls` needs in a slim container |
|
|
272
|
+
| [`docs/journey.md`](docs/journey.md) | The chronological story, including dead-ends (so they aren't re-walked) |
|
|
273
|
+
|
|
274
|
+
Working on the code with Claude Code? [`CLAUDE.md`](CLAUDE.md) is the contributor
|
|
275
|
+
guide (design principles, codebase map, conventions).
|
|
276
|
+
|
|
277
|
+
## Status & roadmap
|
|
278
|
+
|
|
279
|
+
✅ foundation → ✅ containerize → ✅ deploy to BTP CF → ✅ headless connect
|
|
280
|
+
(DIRECT) → ✅ read + authoring-loop tools (16) → ✅ session self-heal.
|
|
281
|
+
|
|
282
|
+
**Next:** CC-mode deploy (code ready; needs a running Cloud Connector + bound
|
|
283
|
+
`connectivity`/`destination`), then **per-user principal propagation** (one
|
|
284
|
+
`adt-ls` session per user via the BTP Destination Service). Roadmap detail in
|
|
285
|
+
[`docs/plans/`](docs/plans/) and [`docs/assumptions-and-future-changes.md`](docs/assumptions-and-future-changes.md).
|
|
286
|
+
|
|
287
|
+
## License & credits
|
|
288
|
+
|
|
289
|
+
[MIT](LICENSE) © 2026 Marian Zeis and contributors.
|
|
290
|
+
|
|
291
|
+
Built on the shell of **[ARC-1](https://github.com/marianfoo/arc-1)** (MIT,
|
|
292
|
+
© Alice Vinogradova and contributors) — arc-1-lsp reuses its MCP server,
|
|
293
|
+
configuration, authorization model, audit, and logging patterns. SAP's `adt-ls`
|
|
294
|
+
is **not** included or redistributed (SAP Developer License) — bring your own; see
|
|
295
|
+
[ADR-0002](docs/adr/0002-byo-adt-ls-no-redistribution.md).
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TLS material for the headless adt-ls connection (ADR-0006).
|
|
3
|
+
*
|
|
4
|
+
* Two artifacts, both ephemeral (built at startup into a temp dir, never
|
|
5
|
+
* persisted):
|
|
6
|
+
* 1. a `CN=localhost` keypair+cert for the TLS reverse proxy
|
|
7
|
+
* (`tls-reverse-proxy.ts`), and
|
|
8
|
+
* 2. a JVM truststore = a copy of adt-ls's own JRE `cacerts` (so all public CAs
|
|
9
|
+
* still validate — needed for BTP later) + that localhost cert, so the JVM
|
|
10
|
+
* trusts the proxy. Injected into adt-ls via `JAVA_TOOL_OPTIONS`
|
|
11
|
+
* (launcher-agnostic; no `-vmargs` parsing risk).
|
|
12
|
+
*
|
|
13
|
+
* `keytool` is required and always ships with adt-ls's JRE; `openssl` is required
|
|
14
|
+
* for cert generation (present on macOS/Linux + the container image).
|
|
15
|
+
*/
|
|
16
|
+
import { execFile } from 'node:child_process';
|
|
17
|
+
import { promises as fsp } from 'node:fs';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { promisify } from 'node:util';
|
|
21
|
+
const execFileP = promisify(execFile);
|
|
22
|
+
export const TRUSTSTORE_PASSWORD = 'changeit'; // JRE cacerts default; truststore holds only public certs
|
|
23
|
+
export const PROXY_CERT_ALIAS = 'arc1-proxy-localhost';
|
|
24
|
+
/**
|
|
25
|
+
* Locate the bundled SAP Machine JRE's `keytool` + `cacerts` relative to the
|
|
26
|
+
* adt-ls binary. Layouts differ by platform:
|
|
27
|
+
* - linux/win: binDir/plugins/com.sap.adt.jvm.<ver>/jre/...
|
|
28
|
+
* - macOS: binDir/../Eclipse/plugins/com.sap.adt.jvm.<ver>/jre/...
|
|
29
|
+
*/
|
|
30
|
+
export function resolveJreTools(adtLsBin) {
|
|
31
|
+
const binDir = path.dirname(adtLsBin);
|
|
32
|
+
const pluginRoots = [
|
|
33
|
+
path.join(binDir, 'plugins'),
|
|
34
|
+
path.join(binDir, '..', 'Eclipse', 'plugins'),
|
|
35
|
+
path.join(binDir, '..', '..', 'Eclipse', 'plugins'),
|
|
36
|
+
];
|
|
37
|
+
const keytoolName = process.platform === 'win32' ? 'keytool.exe' : 'keytool';
|
|
38
|
+
for (const plugins of pluginRoots) {
|
|
39
|
+
if (!fs.existsSync(plugins))
|
|
40
|
+
continue;
|
|
41
|
+
const jvm = fs.readdirSync(plugins).find((d) => d.startsWith('com.sap.adt.jvm.'));
|
|
42
|
+
if (!jvm)
|
|
43
|
+
continue;
|
|
44
|
+
const jreLib = path.join(plugins, jvm, 'jre');
|
|
45
|
+
const keytool = path.join(jreLib, 'bin', keytoolName);
|
|
46
|
+
const cacerts = path.join(jreLib, 'lib', 'security', 'cacerts');
|
|
47
|
+
if (fs.existsSync(keytool))
|
|
48
|
+
return { keytool, cacerts };
|
|
49
|
+
}
|
|
50
|
+
throw new Error(`Could not locate the adt-ls JRE keytool relative to ${adtLsBin}. Looked under: ${pluginRoots.join(', ')}`);
|
|
51
|
+
}
|
|
52
|
+
/** Generate an ephemeral `CN=localhost` self-signed keypair+cert via openssl. */
|
|
53
|
+
export async function generateLocalhostCert(dir) {
|
|
54
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
55
|
+
const keyPath = path.join(dir, 'proxy-key.pem');
|
|
56
|
+
const certPath = path.join(dir, 'proxy-cert.pem');
|
|
57
|
+
// CN=localhost (no SAN): adt-ls's verifier falls back to CN when no SAN is
|
|
58
|
+
// present, and the proxy is only ever reached as `localhost`. Avoiding -addext
|
|
59
|
+
// keeps this portable across OpenSSL and macOS LibreSSL.
|
|
60
|
+
await execFileP('openssl', [
|
|
61
|
+
'req',
|
|
62
|
+
'-x509',
|
|
63
|
+
'-newkey',
|
|
64
|
+
'rsa:2048',
|
|
65
|
+
'-nodes',
|
|
66
|
+
'-keyout',
|
|
67
|
+
keyPath,
|
|
68
|
+
'-out',
|
|
69
|
+
certPath,
|
|
70
|
+
'-days',
|
|
71
|
+
'825',
|
|
72
|
+
'-subj',
|
|
73
|
+
'/CN=localhost',
|
|
74
|
+
]);
|
|
75
|
+
const [keyPem, certPem] = await Promise.all([fsp.readFile(keyPath, 'utf8'), fsp.readFile(certPath, 'utf8')]);
|
|
76
|
+
return { keyPath, certPath, keyPem, certPem };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build a JVM truststore: copy the JRE cacerts (preserving public CAs) and import
|
|
80
|
+
* the proxy cert under PROXY_CERT_ALIAS. Returns the truststore path.
|
|
81
|
+
*/
|
|
82
|
+
export async function buildTruststore(opts) {
|
|
83
|
+
const password = opts.password ?? TRUSTSTORE_PASSWORD;
|
|
84
|
+
await fsp.mkdir(path.dirname(opts.outPath), { recursive: true });
|
|
85
|
+
await fsp.copyFile(opts.cacerts, opts.outPath);
|
|
86
|
+
await execFileP(opts.keytool, [
|
|
87
|
+
'-importcert',
|
|
88
|
+
'-noprompt',
|
|
89
|
+
'-keystore',
|
|
90
|
+
opts.outPath,
|
|
91
|
+
'-storepass',
|
|
92
|
+
password,
|
|
93
|
+
'-alias',
|
|
94
|
+
PROXY_CERT_ALIAS,
|
|
95
|
+
'-file',
|
|
96
|
+
opts.certPath,
|
|
97
|
+
]);
|
|
98
|
+
return opts.outPath;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* One-shot: generate the localhost proxy cert + build the truststore from adt-ls's
|
|
102
|
+
* own JRE, into `workDir`. Returns everything the engine needs to start the proxy
|
|
103
|
+
* and spawn adt-ls trusting it.
|
|
104
|
+
*/
|
|
105
|
+
export async function prepareAdtLsTls(opts) {
|
|
106
|
+
const { keytool, cacerts } = resolveJreTools(opts.adtLsBin);
|
|
107
|
+
const cert = await generateLocalhostCert(opts.workDir);
|
|
108
|
+
const trustStorePath = await buildTruststore({
|
|
109
|
+
keytool,
|
|
110
|
+
cacerts,
|
|
111
|
+
certPath: cert.certPath,
|
|
112
|
+
outPath: path.join(opts.workDir, 'truststore.p12'),
|
|
113
|
+
});
|
|
114
|
+
const javaToolOptions = [
|
|
115
|
+
`-Djavax.net.ssl.trustStore=${trustStorePath}`,
|
|
116
|
+
`-Djavax.net.ssl.trustStorePassword=${TRUSTSTORE_PASSWORD}`,
|
|
117
|
+
'-Djavax.net.ssl.trustStoreType=PKCS12',
|
|
118
|
+
].join(' ');
|
|
119
|
+
return { proxyKeyPem: cert.keyPem, proxyCertPem: cert.certPem, trustStorePath, javaToolOptions };
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=cert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cert.js","sourceRoot":"","sources":["../../src/adt-ls/cert.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adt-ls destination + headless reentrance-ticket logon (ADR-0006).
|
|
3
|
+
*
|
|
4
|
+
* Proven end-to-end against a4h → `logonState:"connected"`. The recipe (see
|
|
5
|
+
* `docs/adt-ls-headless-notes.md`):
|
|
6
|
+
* 1. initializeService with an ISOLATED store path (never the global
|
|
7
|
+
* ~/.adtls/destinations.json, which is shared with the user's IDEs).
|
|
8
|
+
* 2. create with protocol:"http" (URL scheme lives in systemUrl) and
|
|
9
|
+
* authenticationKind:"reentranceTicket" (NOT basicAuth — that fails session
|
|
10
|
+
* dispatch: "password must not be null"). systemUrl points at the local TLS
|
|
11
|
+
* reverse proxy (https://localhost:<port>).
|
|
12
|
+
* 3. ensureLoggedOn triggers the server→client request
|
|
13
|
+
* `adtLs/destinations/requestBrowserBasedLogon`; our handler emulates the
|
|
14
|
+
* browser: GET logonUrl with real creds → 307 + reentrance-ticket → deliver
|
|
15
|
+
* to adt-ls's 127.0.0.1 listener → return TRUE IMMEDIATELY (fire-and-forget;
|
|
16
|
+
* awaiting the delivery deadlocks).
|
|
17
|
+
*/
|
|
18
|
+
import http from 'node:http';
|
|
19
|
+
import https from 'node:https';
|
|
20
|
+
import { logger } from '../server/logger.js';
|
|
21
|
+
export const REQUEST_BROWSER_LOGON = 'adtLs/destinations/requestBrowserBasedLogon';
|
|
22
|
+
/** Must run before any destination op. Empty path = global store — DO NOT use. */
|
|
23
|
+
export function initializeDestinationsService(driver, storePath) {
|
|
24
|
+
return driver.sendRequest('adtLs/destinations/initializeService', {
|
|
25
|
+
destinationsStorePath: storePath,
|
|
26
|
+
workspaceFolderUris: [],
|
|
27
|
+
fileUris: [],
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Create a reentrance-ticket destination. Returns the destination id on success. */
|
|
31
|
+
export function createDestination(driver, cfg) {
|
|
32
|
+
return driver.sendRequest('adtLs/destinations/create', {
|
|
33
|
+
id: cfg.id,
|
|
34
|
+
protocol: 'http', // ADT-over-HTTP (vs RFC); the URL scheme lives in systemUrl
|
|
35
|
+
properties: {
|
|
36
|
+
systemUrl: cfg.systemUrl,
|
|
37
|
+
authenticationKind: 'reentranceTicket',
|
|
38
|
+
...(cfg.user ? { user: cfg.user } : {}),
|
|
39
|
+
client: cfg.client ?? '001',
|
|
40
|
+
language: cfg.language ?? 'EN',
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export function ensureLoggedOn(driver, destinationId) {
|
|
45
|
+
return driver.sendRequest('adtLs/destinations/ensureLoggedOn', destinationId);
|
|
46
|
+
}
|
|
47
|
+
export function getLogonInfo(driver, destinationId) {
|
|
48
|
+
return driver.sendRequest('adtLs/destinations/getLogonInfo', destinationId);
|
|
49
|
+
}
|
|
50
|
+
/** Pull the reentrance `logonUrl` out of a requestBrowserBasedLogon params payload. */
|
|
51
|
+
export function extractLogonUrl(params) {
|
|
52
|
+
const p = params;
|
|
53
|
+
for (const item of p?.params ?? []) {
|
|
54
|
+
if (item?.field?.key === 'logonUrl' && typeof item.field.value === 'string')
|
|
55
|
+
return item.field.value;
|
|
56
|
+
}
|
|
57
|
+
// Fallback: find any reentranceticket URL in the payload.
|
|
58
|
+
const m = JSON.stringify(params ?? null).match(/https?:[^"\\]*reentranceticket[^"\\]*/);
|
|
59
|
+
return m ? m[0] : undefined;
|
|
60
|
+
}
|
|
61
|
+
function authHeader(creds) {
|
|
62
|
+
if (creds.kind === 'bearer')
|
|
63
|
+
return { Authorization: `Bearer ${creds.token}` };
|
|
64
|
+
return { Authorization: `Basic ${Buffer.from(`${creds.user}:${creds.password}`).toString('base64')}` };
|
|
65
|
+
}
|
|
66
|
+
/** GET without following redirects; resolves with status + Location. */
|
|
67
|
+
function httpGet(urlStr, opts = {}) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const u = new URL(urlStr);
|
|
70
|
+
const lib = u.protocol === 'https:' ? https : http;
|
|
71
|
+
const req = lib.request({
|
|
72
|
+
hostname: u.hostname,
|
|
73
|
+
port: u.port,
|
|
74
|
+
path: `${u.pathname}${u.search}`,
|
|
75
|
+
method: 'GET',
|
|
76
|
+
headers: opts.headers,
|
|
77
|
+
...(u.protocol === 'https:' ? { rejectUnauthorized: !opts.insecure } : {}),
|
|
78
|
+
}, (res) => {
|
|
79
|
+
res.resume(); // drain so the socket frees
|
|
80
|
+
resolve({ status: res.statusCode ?? 0, location: res.headers.location });
|
|
81
|
+
});
|
|
82
|
+
req.on('error', reject);
|
|
83
|
+
req.end();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Emulate the browser reentrance flow: GET logonUrl with real creds → 307 +
|
|
88
|
+
* reentrance-ticket in Location → deliver it to adt-ls's local 127.0.0.1 listener.
|
|
89
|
+
* `insecure` skips TLS verification when WE call the proxy/backend (self-signed).
|
|
90
|
+
*/
|
|
91
|
+
export async function performReentranceLogon(logonUrl, creds, opts = {}) {
|
|
92
|
+
const r1 = await httpGet(logonUrl, { headers: authHeader(creds), insecure: opts.insecure });
|
|
93
|
+
if (!r1.location) {
|
|
94
|
+
throw new Error(`reentrance: no redirect Location (status ${r1.status}) from ${logonUrl}`);
|
|
95
|
+
}
|
|
96
|
+
// adt-ls's listener is bound on 127.0.0.1; the redirect uses `localhost`.
|
|
97
|
+
const deliver = r1.location.replace('localhost', '127.0.0.1');
|
|
98
|
+
await httpGet(deliver, { insecure: opts.insecure });
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Build the `requestBrowserBasedLogon` handler. Fires the reentrance delivery
|
|
102
|
+
* fire-and-forget and returns `true` immediately — adt-ls's listener won't respond
|
|
103
|
+
* until this resolves (browser-flow semantics), so awaiting it deadlocks.
|
|
104
|
+
*/
|
|
105
|
+
export function makeReentranceLogonHandler(creds, opts = {}) {
|
|
106
|
+
return (params) => {
|
|
107
|
+
const logonUrl = extractLogonUrl(params);
|
|
108
|
+
if (!logonUrl) {
|
|
109
|
+
logger.warn('requestBrowserBasedLogon: could not find logonUrl in params');
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
performReentranceLogon(logonUrl, creds, opts).catch((e) => logger.warn(`reentrance logon failed: ${e instanceof Error ? e.message : String(e)}`));
|
|
113
|
+
return true;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=destinations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"destinations.js","sourceRoot":"","sources":["../../src/adt-ls/destinations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locate a developer-provided `adt-ls` binary. arc-1-lsp never ships or
|
|
3
|
+
* redistributes adt-ls (SAP Developer License) — it discovers one the developer
|
|
4
|
+
* already installed.
|
|
5
|
+
*
|
|
6
|
+
* Resolution order:
|
|
7
|
+
* 1. explicit path (opts.explicitPath / ARC1_ADT_LS_PATH)
|
|
8
|
+
* 2. vendor/adt-ls/ in the repo (build-time injection for containers)
|
|
9
|
+
* 3. newest installed `sapse.adt-vscode-*` VS Code extension
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
/** Platform/arch-specific sub-path under an `adt-ls/` root. */
|
|
15
|
+
export function platformSubPath(platform = process.platform, arch = process.arch) {
|
|
16
|
+
const a = arch === 'arm64' ? 'aarch64' : arch === 'x64' ? 'x86_64' : arch;
|
|
17
|
+
switch (platform) {
|
|
18
|
+
case 'darwin':
|
|
19
|
+
return ['macosx', 'cocoa', a, 'Adt-ls.app', 'Contents', 'MacOS', 'adt-ls'];
|
|
20
|
+
case 'linux':
|
|
21
|
+
return ['linux', 'gtk', a, 'adt-ls'];
|
|
22
|
+
case 'win32':
|
|
23
|
+
return ['win32', 'win32', a, 'adt-lsc.exe'];
|
|
24
|
+
default:
|
|
25
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function resolveAdtLsPath(opts = {}) {
|
|
29
|
+
const tried = [];
|
|
30
|
+
const sub = platformSubPath(opts.platform, opts.arch);
|
|
31
|
+
const explicit = opts.explicitPath ?? process.env.ARC1_ADT_LS_PATH;
|
|
32
|
+
if (explicit) {
|
|
33
|
+
tried.push(explicit);
|
|
34
|
+
if (fs.existsSync(explicit))
|
|
35
|
+
return explicit;
|
|
36
|
+
}
|
|
37
|
+
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
38
|
+
const vendor = path.join(repoRoot, 'vendor', 'adt-ls', ...sub);
|
|
39
|
+
tried.push(vendor);
|
|
40
|
+
if (fs.existsSync(vendor))
|
|
41
|
+
return vendor;
|
|
42
|
+
const extDir = opts.extensionsDir ?? path.join(os.homedir(), '.vscode', 'extensions');
|
|
43
|
+
if (fs.existsSync(extDir)) {
|
|
44
|
+
const candidates = fs
|
|
45
|
+
.readdirSync(extDir)
|
|
46
|
+
.filter((d) => d.startsWith('sapse.adt-vscode-'))
|
|
47
|
+
.sort()
|
|
48
|
+
.reverse();
|
|
49
|
+
for (const c of candidates) {
|
|
50
|
+
const p = path.join(extDir, c, 'adt-ls', ...sub);
|
|
51
|
+
tried.push(p);
|
|
52
|
+
if (fs.existsSync(p))
|
|
53
|
+
return p;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const triedList = tried.map((t) => ` - ${t}`).join('\n');
|
|
57
|
+
throw new Error(`adt-ls binary not found. Set ARC1_ADT_LS_PATH, drop it in vendor/adt-ls/, or install the 'sapse.adt-vscode' extension. Tried:\n${triedList}`);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/adt-ls/discovery.ts"],"names":[],"mappings":"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"}
|