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.
Files changed (51) hide show
  1. package/LICENSE +30 -0
  2. package/README.md +295 -0
  3. package/dist/adt-ls/cert.js +121 -0
  4. package/dist/adt-ls/cert.js.map +1 -0
  5. package/dist/adt-ls/destinations.js +116 -0
  6. package/dist/adt-ls/destinations.js.map +1 -0
  7. package/dist/adt-ls/discovery.js +59 -0
  8. package/dist/adt-ls/discovery.js.map +1 -0
  9. package/dist/adt-ls/driver.js +150 -0
  10. package/dist/adt-ls/driver.js.map +1 -0
  11. package/dist/adt-ls/lifecycle.js +96 -0
  12. package/dist/adt-ls/lifecycle.js.map +1 -0
  13. package/dist/adt-ls/mcp-federation.js +67 -0
  14. package/dist/adt-ls/mcp-federation.js.map +1 -0
  15. package/dist/adt-ls/mcp-lifecycle.js +10 -0
  16. package/dist/adt-ls/mcp-lifecycle.js.map +1 -0
  17. package/dist/adt-ls/repository.js +59 -0
  18. package/dist/adt-ls/repository.js.map +1 -0
  19. package/dist/adt-ls/session-retry.js +79 -0
  20. package/dist/adt-ls/session-retry.js.map +1 -0
  21. package/dist/adt-ls/tls-reverse-proxy.js +80 -0
  22. package/dist/adt-ls/tls-reverse-proxy.js.map +1 -0
  23. package/dist/btp/bridge.js +75 -0
  24. package/dist/btp/bridge.js.map +1 -0
  25. package/dist/btp/connectivity.js +27 -0
  26. package/dist/btp/connectivity.js.map +1 -0
  27. package/dist/btp/destination.js +22 -0
  28. package/dist/btp/destination.js.map +1 -0
  29. package/dist/btp/token.js +23 -0
  30. package/dist/btp/token.js.map +1 -0
  31. package/dist/btp/types.js +7 -0
  32. package/dist/btp/types.js.map +1 -0
  33. package/dist/btp/vcap.js +58 -0
  34. package/dist/btp/vcap.js.map +1 -0
  35. package/dist/index.js +35 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/server/auth.js +26 -0
  38. package/dist/server/auth.js.map +1 -0
  39. package/dist/server/config.js +53 -0
  40. package/dist/server/config.js.map +1 -0
  41. package/dist/server/engine.js +239 -0
  42. package/dist/server/engine.js.map +1 -0
  43. package/dist/server/http.js +52 -0
  44. package/dist/server/http.js.map +1 -0
  45. package/dist/server/logger.js +14 -0
  46. package/dist/server/logger.js.map +1 -0
  47. package/dist/server/safety.js +25 -0
  48. package/dist/server/safety.js.map +1 -0
  49. package/dist/server/server.js +186 -0
  50. package/dist/server/server.js.map +1 -0
  51. package/package.json +66 -0
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Connectivity bridge: a local HTTP forward proxy that adt-ls routes through.
3
+ * adt-ls sends absolute-form proxy requests (`GET http://target/path`); the
4
+ * bridge re-emits them to the BTP connectivity proxy using **standard HTTP-proxy
5
+ * protocol** (NOT CONNECT — the connectivity proxy 405s on CONNECT), adding
6
+ * `Proxy-Authorization: Bearer <connectivity token>` and the Cloud-Connector
7
+ * `SAP-Connectivity-SCC-Location_ID` header. Mirrors arc-1's `doProxyRequest`.
8
+ *
9
+ * Backend auth (e.g. basic `DEVELOPER`) is adt-ls's concern and flows through
10
+ * untouched — the bridge only owns the connectivity-proxy hop.
11
+ */
12
+ import { createServer, request as httpRequest, } from 'node:http';
13
+ import { logger } from '../server/logger.js';
14
+ const HOP_BY_HOP = new Set([
15
+ 'proxy-authorization',
16
+ 'proxy-connection',
17
+ 'connection',
18
+ 'host',
19
+ 'keep-alive',
20
+ 'te',
21
+ 'trailer',
22
+ 'transfer-encoding',
23
+ 'upgrade',
24
+ ]);
25
+ export async function startConnectivityBridge(proxy) {
26
+ const server = createServer((req, res) => {
27
+ void handle(req, res, proxy);
28
+ });
29
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', () => resolve()));
30
+ const port = server.address().port;
31
+ logger.info(`connectivity bridge on 127.0.0.1:${port} → ${proxy.host}:${proxy.port}`);
32
+ return {
33
+ server,
34
+ port,
35
+ close: () => new Promise((resolve) => server.close(() => resolve())),
36
+ };
37
+ }
38
+ async function handle(req, res, proxy) {
39
+ const target = req.url ?? '';
40
+ if (!target.startsWith('http://') && !target.startsWith('https://')) {
41
+ res.writeHead(400, { 'content-type': 'text/plain' }).end('expected absolute-form proxy request');
42
+ return;
43
+ }
44
+ try {
45
+ const token = await proxy.getProxyToken();
46
+ const targetUrl = new URL(target);
47
+ const headers = {};
48
+ for (const [k, v] of Object.entries(req.headers)) {
49
+ if (v == null || HOP_BY_HOP.has(k.toLowerCase()))
50
+ continue;
51
+ headers[k] = Array.isArray(v) ? v.join(', ') : v;
52
+ }
53
+ headers.Host = targetUrl.port ? `${targetUrl.hostname}:${targetUrl.port}` : targetUrl.hostname;
54
+ headers['Proxy-Authorization'] = `Bearer ${token}`;
55
+ if (proxy.locationId)
56
+ headers['SAP-Connectivity-SCC-Location_ID'] = proxy.locationId;
57
+ // Standard HTTP proxy: send the FULL target URL as the request path.
58
+ const proxyReq = httpRequest({ host: proxy.host, port: proxy.port, method: req.method, path: target, headers }, (proxyRes) => {
59
+ res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
60
+ proxyRes.pipe(res);
61
+ });
62
+ proxyReq.on('error', (e) => {
63
+ logger.error(`bridge upstream error: ${e.message}`);
64
+ if (!res.headersSent)
65
+ res.writeHead(502, { 'content-type': 'text/plain' }).end('bridge upstream error');
66
+ });
67
+ req.pipe(proxyReq);
68
+ }
69
+ catch (e) {
70
+ logger.error(`bridge error: ${e instanceof Error ? e.message : String(e)}`);
71
+ if (!res.headersSent)
72
+ res.writeHead(502, { 'content-type': 'text/plain' }).end('bridge error');
73
+ }
74
+ }
75
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../src/btp/bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Connectivity proxy config (Cloud Connector hop). Ported from arc-1's
3
+ * `src/adt/btp.ts` `createConnectivityProxy` — caches the connectivity JWT and
4
+ * refreshes 60s before expiry.
5
+ */
6
+ import { fetchClientCredentialsToken } from './token.js';
7
+ export function createConnectivityProxy(btpConfig, locationId) {
8
+ if (!btpConfig.connectivityProxyHost)
9
+ return null;
10
+ let cachedToken = '';
11
+ let expiresAt = 0;
12
+ return {
13
+ host: btpConfig.connectivityProxyHost,
14
+ port: Number.parseInt(btpConfig.connectivityProxyPort || '20003', 10),
15
+ protocol: 'http',
16
+ locationId,
17
+ getProxyToken: async () => {
18
+ if (cachedToken && Date.now() < expiresAt)
19
+ return cachedToken;
20
+ const { accessToken, expiresIn } = await fetchClientCredentialsToken(btpConfig.connectivityTokenUrl, btpConfig.connectivityClientId, btpConfig.connectivitySecret);
21
+ cachedToken = accessToken;
22
+ expiresAt = Date.now() + (expiresIn - 60) * 1000;
23
+ return cachedToken;
24
+ },
25
+ };
26
+ }
27
+ //# sourceMappingURL=connectivity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connectivity.js","sourceRoot":"","sources":["../../src/btp/connectivity.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAGzD,MAAM,UAAU,uBAAuB,CAAC,SAAoB,EAAE,UAAmB;IAC/E,IAAI,CAAC,SAAS,CAAC,qBAAqB;QAAE,OAAO,IAAI,CAAC;IAElD,IAAI,WAAW,GAAG,EAAE,CAAC;IACrB,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,OAAO;QACL,IAAI,EAAE,SAAS,CAAC,qBAAqB;QACrC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,qBAAqB,IAAI,OAAO,EAAE,EAAE,CAAC;QACrE,QAAQ,EAAE,MAAM;QAChB,UAAU;QACV,aAAa,EAAE,KAAK,IAAI,EAAE;YACxB,IAAI,WAAW,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;gBAAE,OAAO,WAAW,CAAC;YAC9D,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,MAAM,2BAA2B,CAClE,SAAS,CAAC,oBAAoB,EAC9B,SAAS,CAAC,oBAAoB,EAC9B,SAAS,CAAC,kBAAkB,CAC7B,CAAC;YACF,WAAW,GAAG,WAAW,CAAC;YAC1B,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,SAAS,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;YACjD,OAAO,WAAW,CAAC;QACrB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * BTP Destination Service lookup (client-credentials, fixed-user path). Ported
3
+ * from arc-1's `src/adt/btp.ts` `lookupDestination`. Per-user PP (the SAP Cloud
4
+ * SDK + jwt-bearer exchange) is plan 05.
5
+ */
6
+ import { logger } from '../server/logger.js';
7
+ import { fetchClientCredentialsToken } from './token.js';
8
+ export async function lookupDestination(btpConfig, destinationName) {
9
+ const tokenUrl = btpConfig.destinationTokenUrl || `${btpConfig.xsuaaUrl}/oauth/token`;
10
+ const { accessToken } = await fetchClientCredentialsToken(tokenUrl, btpConfig.destinationClientId, btpConfig.destinationSecret);
11
+ const destUrl = `${btpConfig.destinationUrl.replace(/\/$/, '')}/destination-configuration/v1/destinations/${encodeURIComponent(destinationName)}`;
12
+ const resp = await fetch(destUrl, { headers: { Authorization: `Bearer ${accessToken}` } });
13
+ if (!resp.ok) {
14
+ const text = await resp.text();
15
+ throw new Error(`Destination Service returned HTTP ${resp.status}: ${text.slice(0, 200)}`);
16
+ }
17
+ const data = (await resp.json());
18
+ const d = data.destinationConfiguration;
19
+ logger.info(`BTP destination resolved: name=${d.Name} url=${d.URL} auth=${d.Authentication} proxyType=${d.ProxyType} locationId=${d.CloudConnectorLocationId ?? ''}`);
20
+ return d;
21
+ }
22
+ //# sourceMappingURL=destination.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destination.js","sourceRoot":"","sources":["../../src/btp/destination.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAGzD,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,SAAoB,EAAE,eAAuB;IACnF,MAAM,QAAQ,GAAG,SAAS,CAAC,mBAAmB,IAAI,GAAG,SAAS,CAAC,QAAQ,cAAc,CAAC;IACtF,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,2BAA2B,CACvD,QAAQ,EACR,SAAS,CAAC,mBAAmB,EAC7B,SAAS,CAAC,iBAAiB,CAC5B,CAAC;IAEF,MAAM,OAAO,GAAG,GAAG,SAAS,CAAC,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,8CAA8C,kBAAkB,CAAC,eAAe,CAAC,EAAE,CAAC;IAClJ,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;IAE3F,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,qCAAqC,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAA8C,CAAC;IAC9E,MAAM,CAAC,GAAG,IAAI,CAAC,wBAAwB,CAAC;IACxC,MAAM,CAAC,IAAI,CACT,kCAAkC,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,cAAc,cAAc,CAAC,CAAC,SAAS,eAAe,CAAC,CAAC,wBAAwB,IAAI,EAAE,EAAE,CACzJ,CAAC;IACF,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * OAuth2 client_credentials token fetch — used for both Destination Service and
3
+ * Connectivity Service tokens. Ported from arc-1's `src/adt/btp.ts`.
4
+ */
5
+ export async function fetchClientCredentialsToken(tokenUrl, clientId, clientSecret) {
6
+ const body = new URLSearchParams({
7
+ grant_type: 'client_credentials',
8
+ client_id: clientId,
9
+ client_secret: clientSecret,
10
+ });
11
+ const resp = await fetch(tokenUrl, {
12
+ method: 'POST',
13
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
14
+ body: body.toString(),
15
+ });
16
+ if (!resp.ok) {
17
+ const text = await resp.text();
18
+ throw new Error(`Token endpoint returned HTTP ${resp.status}: ${text.slice(0, 200)}`);
19
+ }
20
+ const data = (await resp.json());
21
+ return { accessToken: data.access_token, expiresIn: data.expires_in };
22
+ }
23
+ //# sourceMappingURL=token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/btp/token.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,QAAgB,EAChB,QAAgB,EAChB,YAAoB;IAEpB,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC;QAC/B,UAAU,EAAE,oBAAoB;QAChC,SAAS,EAAE,QAAQ;QACnB,aAAa,EAAE,YAAY;KAC5B,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;QACjC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;QAChE,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE;KACtB,CAAC,CAAC;IAEH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAiD,CAAC;IACjF,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;AACxE,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * BTP service-binding + destination types. Ported from arc-1's `src/adt/btp.ts`
3
+ * (kept dependency-light + engine-agnostic so it can later become a shared
4
+ * `@marianfoo/btp-connectivity` module consumed by both arc-1 and arc-1-lsp).
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/btp/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Parse VCAP_SERVICES → BTPConfig. Ported from arc-1's `src/adt/btp.ts`.
3
+ * Returns null when not running on BTP (VCAP_SERVICES unset).
4
+ */
5
+ import { logger } from '../server/logger.js';
6
+ export function parseVCAPServices(env = process.env) {
7
+ const vcapJson = env.VCAP_SERVICES;
8
+ if (!vcapJson)
9
+ return null;
10
+ const vcap = JSON.parse(vcapJson);
11
+ const config = {
12
+ xsuaaUrl: '',
13
+ xsuaaClientId: '',
14
+ xsuaaSecret: '',
15
+ destinationUrl: '',
16
+ destinationClientId: '',
17
+ destinationSecret: '',
18
+ destinationTokenUrl: '',
19
+ connectivityProxyHost: '',
20
+ connectivityProxyPort: '',
21
+ connectivityClientId: '',
22
+ connectivitySecret: '',
23
+ connectivityTokenUrl: '',
24
+ };
25
+ if (vcap.xsuaa?.[0]?.credentials) {
26
+ const c = vcap.xsuaa[0].credentials;
27
+ config.xsuaaUrl = c.url || '';
28
+ config.xsuaaClientId = c.clientid || '';
29
+ config.xsuaaSecret = c.clientsecret || '';
30
+ }
31
+ if (vcap.destination?.[0]?.credentials) {
32
+ const c = vcap.destination[0].credentials;
33
+ config.destinationUrl = c.uri || c.url || '';
34
+ config.destinationClientId = c.clientid || '';
35
+ config.destinationSecret = c.clientsecret || '';
36
+ config.destinationTokenUrl = c.token_service_url || '';
37
+ if (!config.destinationTokenUrl && c.url) {
38
+ config.destinationTokenUrl = `${c.url.replace(/\/$/, '')}/oauth/token`;
39
+ }
40
+ }
41
+ if (vcap.connectivity?.[0]?.credentials) {
42
+ const c = vcap.connectivity[0].credentials;
43
+ config.connectivityProxyHost = c.onpremise_proxy_host || '';
44
+ config.connectivityProxyPort = c.onpremise_proxy_http_port || '';
45
+ config.connectivityClientId = c.clientid || '';
46
+ config.connectivitySecret = c.clientsecret || '';
47
+ config.connectivityTokenUrl = c.token_service_url || '';
48
+ if (!config.connectivityTokenUrl && c.url) {
49
+ config.connectivityTokenUrl = `${c.url.replace(/\/$/, '')}/oauth/token`;
50
+ }
51
+ else if (config.connectivityTokenUrl && !config.connectivityTokenUrl.endsWith('/oauth/token')) {
52
+ config.connectivityTokenUrl = `${config.connectivityTokenUrl.replace(/\/$/, '')}/oauth/token`;
53
+ }
54
+ }
55
+ logger.info(`BTP VCAP_SERVICES parsed: xsuaa=${!!config.xsuaaUrl} destination=${!!config.destinationUrl} connectivity=${!!config.connectivityProxyHost}`);
56
+ return config;
57
+ }
58
+ //# sourceMappingURL=vcap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vcap.js","sourceRoot":"","sources":["../../src/btp/vcap.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * arc-1-lsp entry point. Boots the embedded adt-ls engine and serves an MCP
4
+ * server. Foundation supports stdio; the http-streamable transport (needed for
5
+ * the BTP CF deploy) lands in the deploy plan.
6
+ */
7
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
+ import { loadConfig } from './server/config.js';
9
+ import { startEngine } from './server/engine.js';
10
+ import { startHttpServer } from './server/http.js';
11
+ import { logger } from './server/logger.js';
12
+ import { createMcpServer } from './server/server.js';
13
+ async function main() {
14
+ const config = loadConfig();
15
+ const engine = await startEngine(config);
16
+ if (config.transport === 'http-streamable') {
17
+ startHttpServer(() => createMcpServer(engine), config);
18
+ }
19
+ else {
20
+ await createMcpServer(engine).connect(new StdioServerTransport());
21
+ logger.info('arc-1-lsp MCP server ready (stdio)');
22
+ }
23
+ const shutdown = async () => {
24
+ logger.info('shutting down…');
25
+ await engine.dispose();
26
+ process.exit(0);
27
+ };
28
+ process.on('SIGINT', shutdown);
29
+ process.on('SIGTERM', shutdown);
30
+ }
31
+ main().catch((e) => {
32
+ logger.error(`fatal: ${e instanceof Error ? e.message : String(e)}`);
33
+ process.exit(1);
34
+ });
35
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;GAIG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;IAEzC,IAAI,MAAM,CAAC,SAAS,KAAK,iBAAiB,EAAE,CAAC;QAC3C,eAAe,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IACzD,CAAC;SAAM,CAAC;QACN,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;QAClE,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC9B,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IACjB,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,26 @@
1
+ export function parseApiKeys(raw) {
2
+ if (!raw)
3
+ return [];
4
+ return raw
5
+ .split(',')
6
+ .map((s) => s.trim())
7
+ .filter(Boolean)
8
+ .map((entry) => {
9
+ const idx = entry.indexOf(':');
10
+ return idx > 0 ? { key: entry.slice(0, idx), label: entry.slice(idx + 1) } : { key: entry };
11
+ });
12
+ }
13
+ function header(value) {
14
+ return Array.isArray(value) ? value[0] : value;
15
+ }
16
+ export function checkApiKey(headers, keys) {
17
+ if (keys.length === 0)
18
+ return true; // auth disabled
19
+ const auth = header(headers.authorization);
20
+ const bearer = auth?.startsWith('Bearer ') ? auth.slice(7) : undefined;
21
+ const presented = bearer ?? header(headers['x-api-key']);
22
+ if (!presented)
23
+ return false;
24
+ return keys.some((k) => k.key === presented);
25
+ }
26
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,OAAO,GAAG;SACP,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;IAC9F,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,MAAM,CAAC,KAAoC;IAClD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,OAA+E,EAC/E,IAAc;IAEd,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,gBAAgB;IACpD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACvE,MAAM,SAAS,GAAG,MAAM,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IACzD,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,53 @@
1
+ function bool(v, dflt) {
2
+ if (v === undefined)
3
+ return dflt;
4
+ return v === 'true' || v === '1' || v === 'yes';
5
+ }
6
+ /** Build the SAP target from flags/env, or undefined if host/port/creds missing. */
7
+ function loadSapTarget(argv, env) {
8
+ const host = flag(argv, 'sap-host') ?? env.ARC1_SAP_HOST;
9
+ const port = flag(argv, 'sap-port') ?? env.ARC1_SAP_PORT;
10
+ const user = flag(argv, 'sap-user') ?? env.ARC1_SAP_USER;
11
+ const password = flag(argv, 'sap-password') ?? env.ARC1_SAP_PASSWORD;
12
+ if (!host || !port || !user || !password)
13
+ return undefined;
14
+ return {
15
+ destinationId: flag(argv, 'sap-destination') ?? env.ARC1_SAP_DESTINATION ?? 'SAP',
16
+ host,
17
+ port: Number(port),
18
+ user,
19
+ password,
20
+ client: flag(argv, 'sap-client') ?? env.ARC1_SAP_CLIENT ?? '001',
21
+ language: flag(argv, 'sap-language') ?? env.ARC1_SAP_LANGUAGE ?? 'EN',
22
+ insecure: bool(flag(argv, 'sap-insecure') ?? env.ARC1_SAP_INSECURE, true),
23
+ };
24
+ }
25
+ function flag(argv, name) {
26
+ const i = argv.indexOf(`--${name}`);
27
+ return i >= 0 && i + 1 < argv.length ? argv[i + 1] : undefined;
28
+ }
29
+ export function loadConfig(argv = process.argv.slice(2), env = process.env) {
30
+ const transport = (flag(argv, 'transport') ?? env.ARC1_TRANSPORT ?? 'stdio');
31
+ if (transport !== 'stdio' && transport !== 'http-streamable') {
32
+ throw new Error(`Invalid transport "${transport}" (expected stdio | http-streamable)`);
33
+ }
34
+ const port = flag(argv, 'adt-ls-mcp-port') ?? env.ARC1_ADT_LS_MCP_PORT;
35
+ // CF assigns $PORT at runtime — honor it (after explicit flag/ARC1_PORT).
36
+ const httpPort = flag(argv, 'port') ?? env.ARC1_PORT ?? env.PORT;
37
+ return {
38
+ adtLsPath: flag(argv, 'adt-ls-path') ?? env.ARC1_ADT_LS_PATH,
39
+ adtLsMcpPort: port ? Number(port) : 2240,
40
+ adtLsMcpToken: flag(argv, 'adt-ls-mcp-token') ?? env.ARC1_ADT_LS_MCP_TOKEN,
41
+ transport,
42
+ httpPort: httpPort ? Number(httpPort) : 8080,
43
+ apiKeys: flag(argv, 'api-keys') ?? env.ARC1_API_KEYS,
44
+ sapTarget: loadSapTarget(argv, env),
45
+ sapDestination: flag(argv, 'sap-destination') ?? env.ARC1_SAP_DESTINATION,
46
+ allowWrites: bool(flag(argv, 'allow-writes') ?? env.ARC1_ALLOW_WRITES, false),
47
+ allowedPackages: (flag(argv, 'allowed-packages') ?? env.ARC1_ALLOWED_PACKAGES ?? '$TMP')
48
+ .split(',')
49
+ .map((p) => p.trim())
50
+ .filter(Boolean),
51
+ };
52
+ }
53
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,239 @@
1
+ /**
2
+ * The arc-1-lsp engine: discovers a developer-provided adt-ls, spawns it
3
+ * headless, starts adt-ls's own MCP server over LSP, and connects a federation
4
+ * client to it. All ABAP/ADT work happens inside adt-ls — arc-1-lsp orchestrates.
5
+ *
6
+ * When a SAP target is configured, the engine also performs the headless
7
+ * connection (ADR-0005/0006): build TLS material from adt-ls's own JRE, start a
8
+ * TLS-terminating reverse proxy, spawn adt-ls trusting it, then create the
9
+ * destination + reentrance-logon + bind it to adt-ls's MCP server. Two paths:
10
+ * - DIRECT (local, ARC1_SAP_*): reverse proxy connects straight to the backend.
11
+ * - CONNECTIVITY (CF, ARC1_SAP_DESTINATION + bound connectivity): resolve the
12
+ * BTP destination, start the connectivity bridge, and forward the reverse
13
+ * proxy's upstream through it → Cloud Connector → backend.
14
+ */
15
+ import crypto from 'node:crypto';
16
+ import { promises as fsp } from 'node:fs';
17
+ import os from 'node:os';
18
+ import path from 'node:path';
19
+ import { prepareAdtLsTls } from '../adt-ls/cert.js';
20
+ import { REQUEST_BROWSER_LOGON, createDestination, ensureLoggedOn, initializeDestinationsService, makeReentranceLogonHandler, } from '../adt-ls/destinations.js';
21
+ import { resolveAdtLsPath } from '../adt-ls/discovery.js';
22
+ import { AdtLsDriver } from '../adt-ls/driver.js';
23
+ import { createLifecycle } from '../adt-ls/lifecycle.js';
24
+ import { AdtLsMcpClient } from '../adt-ls/mcp-federation.js';
25
+ import { setMcpDestination, startMcpServer, stopMcpServer } from '../adt-ls/mcp-lifecycle.js';
26
+ import { getInactiveObjects, getUsers, quickSearch } from '../adt-ls/repository.js';
27
+ import { isLoggedOffFederatedResult, makeRelogon, makeWithRelogon } from '../adt-ls/session-retry.js';
28
+ import { startTlsReverseProxy } from '../adt-ls/tls-reverse-proxy.js';
29
+ import { startConnectivityBridge } from '../btp/bridge.js';
30
+ import { createConnectivityProxy } from '../btp/connectivity.js';
31
+ import { lookupDestination } from '../btp/destination.js';
32
+ import { parseVCAPServices } from '../btp/vcap.js';
33
+ import { logger } from './logger.js';
34
+ /**
35
+ * Decide how to connect (pure). On BTP (connectivity bound) a destination name
36
+ * wins → Cloud-Connector path. Otherwise a full local target → direct. Else none.
37
+ */
38
+ export function planConnection(config, btp) {
39
+ const onBtp = !!btp?.connectivityProxyHost;
40
+ if (onBtp && config.sapDestination)
41
+ return { mode: 'connectivity', destinationName: config.sapDestination };
42
+ if (config.sapTarget)
43
+ return { mode: 'direct', target: config.sapTarget };
44
+ return { mode: 'none' };
45
+ }
46
+ export async function startEngine(config) {
47
+ const bin = resolveAdtLsPath({ explicitPath: config.adtLsPath });
48
+ logger.info(`engine: using adt-ls at ${bin}`);
49
+ const btp = parseVCAPServices();
50
+ const plan = planConnection(config, btp);
51
+ // TLS material must exist before adt-ls spawns (JAVA_TOOL_OPTIONS truststore).
52
+ let tlsWorkDir;
53
+ let proxyKeyPem;
54
+ let proxyCertPem;
55
+ const driverOpts = {};
56
+ if (plan.mode !== 'none') {
57
+ tlsWorkDir = path.join(os.tmpdir(), `arc1lsp-tls-${crypto.randomBytes(6).toString('hex')}`);
58
+ const tls = await prepareAdtLsTls({ adtLsBin: bin, workDir: tlsWorkDir });
59
+ proxyKeyPem = tls.proxyKeyPem;
60
+ proxyCertPem = tls.proxyCertPem;
61
+ driverOpts.extraEnv = { JAVA_TOOL_OPTIONS: tls.javaToolOptions };
62
+ }
63
+ const driver = new AdtLsDriver(bin, driverOpts);
64
+ const init = await driver.start();
65
+ const token = config.adtLsMcpToken || crypto.randomBytes(24).toString('hex');
66
+ const started = await startMcpServer(driver, { port: config.adtLsMcpPort, token });
67
+ logger.info(`engine: adt-ls MCP server on http://localhost:${started.port}/mcp`);
68
+ const federation = new AdtLsMcpClient(`http://localhost:${started.port}/mcp`, started.token);
69
+ await federation.connect();
70
+ let proxy;
71
+ let bridge;
72
+ let connectedDestination;
73
+ if (plan.mode !== 'none') {
74
+ // Non-fatal: a logon/connectivity failure must NOT crash the MCP server —
75
+ // health/tools still come up (reporting disconnected) so it's diagnosable.
76
+ try {
77
+ const conn = await connect(plan, btp, driver, {
78
+ keyPem: proxyKeyPem,
79
+ certPem: proxyCertPem,
80
+ tlsWorkDir: tlsWorkDir,
81
+ });
82
+ proxy = conn.proxy;
83
+ bridge = conn.bridge;
84
+ connectedDestination = conn.destinationId;
85
+ }
86
+ catch (e) {
87
+ logger.error(`engine: SAP connection failed (server starts disconnected): ${e instanceof Error ? e.message : String(e)}`);
88
+ }
89
+ }
90
+ // Self-healing SAP session: the backend security session behind adt-ls expires
91
+ // on inactivity; afterwards every call fails with "logged off" until a reconnect
92
+ // (previously: restart the instance). On detecting that, re-fire the proven
93
+ // startup logon (ensureLoggedOn re-triggers our still-registered reentrance
94
+ // handler) and retry the call once. Wrap BOTH channels — federated MCP tool
95
+ // calls and raw LSP requests — since either can hit the dead session.
96
+ const relogon = makeRelogon(async () => {
97
+ if (!connectedDestination)
98
+ return false;
99
+ logger.warn(`engine: SAP session lost — re-logging on to ${connectedDestination}`);
100
+ try {
101
+ const logon = await ensureLoggedOn(driver, connectedDestination);
102
+ if (logon.logonState !== 'connected') {
103
+ logger.error(`engine: re-logon to ${connectedDestination} returned ${logon.logonState}`);
104
+ return false;
105
+ }
106
+ // Re-point adt-ls's MCP server at the refreshed session (idempotent, best-effort).
107
+ await setMcpDestination(driver, connectedDestination).catch((e) => logger.warn(`engine: re-bind after re-logon failed: ${e instanceof Error ? e.message : String(e)}`));
108
+ logger.info(`engine: re-logon to ${connectedDestination} succeeded`);
109
+ return true;
110
+ }
111
+ catch (e) {
112
+ logger.error(`engine: re-logon failed: ${e instanceof Error ? e.message : String(e)}`);
113
+ return false;
114
+ }
115
+ });
116
+ const withRelogon = makeWithRelogon(relogon);
117
+ const sessionCallTool = (name, args = {}) => withRelogon(() => federation.callTool(name, args), isLoggedOffFederatedResult);
118
+ const sessionRequester = {
119
+ sendRequest: (method, params) => withRelogon(() => driver.sendRequest(method, params)),
120
+ };
121
+ const lifecycle = createLifecycle({
122
+ driver: sessionRequester,
123
+ callTool: sessionCallTool,
124
+ destination: () => connectedDestination,
125
+ safety: { allowWrites: config.allowWrites, allowedPackages: config.allowedPackages },
126
+ });
127
+ const engine = {
128
+ connectedDestination,
129
+ lifecycle,
130
+ health: () => ({
131
+ adtLs: { name: init.serverInfo?.name, version: init.serverInfo?.version, up: true },
132
+ mcpPort: started.port,
133
+ connectedDestination,
134
+ }),
135
+ listTools: () => federation.listTools(),
136
+ callTool: (name, args = {}) => sessionCallTool(name, args),
137
+ setDestination: async (destinationId) => {
138
+ await setMcpDestination(driver, destinationId);
139
+ },
140
+ search: async (pattern, opts = {}) => {
141
+ if (!connectedDestination)
142
+ throw new Error('No ABAP destination is connected.');
143
+ const r = await quickSearch(sessionRequester, {
144
+ destination: connectedDestination,
145
+ pattern,
146
+ maxResults: opts.maxResults,
147
+ types: opts.types,
148
+ });
149
+ return r.references ?? [];
150
+ },
151
+ listInactiveObjects: async () => {
152
+ if (!connectedDestination)
153
+ throw new Error('No ABAP destination is connected.');
154
+ return getInactiveObjects(sessionRequester, connectedDestination);
155
+ },
156
+ listUsers: async () => {
157
+ if (!connectedDestination)
158
+ throw new Error('No ABAP destination is connected.');
159
+ return getUsers(sessionRequester, connectedDestination);
160
+ },
161
+ reconnect: () => relogon(),
162
+ dispose: async () => {
163
+ await stopMcpServer(driver).catch(() => { });
164
+ await driver.dispose();
165
+ await proxy?.close().catch(() => { });
166
+ await bridge?.close().catch(() => { });
167
+ if (tlsWorkDir)
168
+ await fsp.rm(tlsWorkDir, { recursive: true, force: true }).catch(() => { });
169
+ },
170
+ };
171
+ return engine;
172
+ }
173
+ /** Resolve the backend, start the proxy (+bridge on CF), register the logon handler, connect. */
174
+ async function connect(plan, btp, driver, tls) {
175
+ let backend;
176
+ let creds;
177
+ let proxy;
178
+ let bridge;
179
+ if (plan.mode === 'connectivity') {
180
+ const dest = await lookupDestination(btp, plan.destinationName);
181
+ const url = new URL(dest.URL);
182
+ const scheme = url.protocol === 'https:' ? 'https' : 'http';
183
+ const port = Number(url.port || (scheme === 'https' ? 443 : 80));
184
+ creds = { kind: 'basic', user: dest.User ?? '', password: dest.Password ?? '' };
185
+ backend = {
186
+ destinationId: plan.destinationName,
187
+ user: dest.User,
188
+ client: dest['sap-client'] ?? '001',
189
+ language: 'EN',
190
+ };
191
+ const proxyCfg = createConnectivityProxy(btp, dest.CloudConnectorLocationId);
192
+ if (!proxyCfg)
193
+ throw new Error('connectivity service binding missing onpremise_proxy_host');
194
+ bridge = await startConnectivityBridge(proxyCfg);
195
+ proxy = await startTlsReverseProxy({
196
+ key: tls.keyPem,
197
+ cert: tls.certPem,
198
+ target: { host: url.hostname, port, protocol: scheme },
199
+ forwardProxy: { host: '127.0.0.1', port: bridge.port },
200
+ });
201
+ }
202
+ else {
203
+ const t = plan.target;
204
+ creds = { kind: 'basic', user: t.user, password: t.password };
205
+ backend = { destinationId: t.destinationId, user: t.user, client: t.client, language: t.language };
206
+ proxy = await startTlsReverseProxy({
207
+ key: tls.keyPem,
208
+ cert: tls.certPem,
209
+ target: { host: t.host, port: t.port },
210
+ insecureUpstream: t.insecure,
211
+ });
212
+ }
213
+ // Register the reentrance handler (our own GET to the localhost proxy skips TLS).
214
+ driver.setRequestHandler(REQUEST_BROWSER_LOGON, makeReentranceLogonHandler(creds, { insecure: true }));
215
+ const destinationId = await connectDestination(driver, backend, proxy, tls.tlsWorkDir);
216
+ return { proxy, bridge, destinationId };
217
+ }
218
+ /** Create + reentrance-logon + bind the destination. Returns its id, or throws. */
219
+ export async function connectDestination(driver, backend, proxy, tlsWorkDir) {
220
+ // Isolated store — NEVER the global ~/.adtls/destinations.json (shared with IDEs).
221
+ const storePath = path.join(tlsWorkDir, 'destinations');
222
+ await fsp.mkdir(storePath, { recursive: true });
223
+ await initializeDestinationsService(driver, storePath);
224
+ await createDestination(driver, {
225
+ id: backend.destinationId,
226
+ systemUrl: proxy.url,
227
+ user: backend.user,
228
+ client: backend.client,
229
+ language: backend.language,
230
+ });
231
+ const logon = await ensureLoggedOn(driver, backend.destinationId);
232
+ if (logon.logonState !== 'connected') {
233
+ throw new Error(`adt-ls logon to ${backend.destinationId} failed: ${logon.logonState}${logon.message ? ` — ${logon.message}` : ''}`);
234
+ }
235
+ await setMcpDestination(driver, backend.destinationId);
236
+ logger.info(`engine: connected destination ${backend.destinationId}`);
237
+ return backend.destinationId;
238
+ }
239
+ //# sourceMappingURL=engine.js.map