@mcp-abap-adt/proxy 4.0.0 → 4.1.0
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/CHANGELOG.md +75 -0
- package/README.md +2 -1
- package/bin/mcp-abap-adt-proxy-mcp.js +5 -1
- package/dist/lib/config.d.ts +12 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +27 -4
- package/dist/lib/envInterpolation.d.ts +14 -2
- package/dist/lib/envInterpolation.d.ts.map +1 -1
- package/dist/lib/envInterpolation.js +34 -7
- package/dist/mcp/environments.d.ts +36 -0
- package/dist/mcp/environments.d.ts.map +1 -0
- package/dist/mcp/environments.js +108 -0
- package/dist/mcp/tools.d.ts +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +35 -3
- package/docs/CLIENT_SETUP.md +2 -1
- package/docs/USAGE.md +2 -1
- package/docs/YAML_CONFIG.md +17 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,81 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [4.1.0] - 2026-09-24
|
|
11
|
+
|
|
12
|
+
**The management mode can now say which credentials are available, and start a
|
|
13
|
+
proxy with one.** A config says which service to reach; an environment says what
|
|
14
|
+
to authenticate with. Both are chosen by name, so a client picks them without
|
|
15
|
+
being told any paths.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`proxy_environments`** — lists the environments in `sessions/`, one SAP
|
|
20
|
+
system's credentials each, by the name `proxy_start` takes. Variable **names**
|
|
21
|
+
only, never values: this is read to help a client choose, and a value here
|
|
22
|
+
would be a credential in a tool result. `*.env.template` and anything that is
|
|
23
|
+
not a `.env` are left out.
|
|
24
|
+
|
|
25
|
+
- **`environment` on `proxy_start`** — optional, and the reason it exists: a
|
|
26
|
+
config that references `${SAP_LOGIN}` without naming an `envFile:` of its own
|
|
27
|
+
had nowhere to get it from, so `proxy_start` failed on exactly the configs a
|
|
28
|
+
user runs with `--env-file` on the command line. A name, not a path, for the
|
|
29
|
+
same reason `config` is a name: `sessions/` sits beside `proxy/` and
|
|
30
|
+
`service-keys/`.
|
|
31
|
+
|
|
32
|
+
One `--env-file` on the management process would not have served: it overrides
|
|
33
|
+
every config's own `envFile:`, so one system's secrets would reach every other
|
|
34
|
+
system started in that session. Per call, it cannot.
|
|
35
|
+
|
|
36
|
+
- `loadConfig(configPath, envFileOverride)` carries it. Precedence, most
|
|
37
|
+
specific first: this argument, then `--env-file`, then the config's own
|
|
38
|
+
`envFile:`.
|
|
39
|
+
|
|
40
|
+
### Unchanged, and deliberately
|
|
41
|
+
|
|
42
|
+
The management mode speaks **stdio only** — no `--transport`, and it listens on
|
|
43
|
+
no port of its own. That is the whole point: the client runs it as a child
|
|
44
|
+
process, and when the client goes the stdio closes and every proxy it started
|
|
45
|
+
goes with it. Over HTTP it would outlive its client and leave ports and tokens
|
|
46
|
+
held, which would be worse than starting proxies by hand.
|
|
47
|
+
|
|
48
|
+
## [4.0.1] - 2026-09-24
|
|
49
|
+
|
|
50
|
+
**A variable from an env file could be silently lost, and the failure blamed the
|
|
51
|
+
variable instead of naming the file.** Both hit Windows hardest, with the same
|
|
52
|
+
config and the same `.env` that work on Linux.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **An empty environment variable no longer shadows the env file.** The lookup
|
|
57
|
+
was `process.env[key] ?? envFileMap[key]`, and `??` skips only `undefined` — so
|
|
58
|
+
a `SAP_LOGIN=` sitting in the environment beat the file the user had pointed at
|
|
59
|
+
with `--env-file`, and the header went out empty with nothing reported. Windows
|
|
60
|
+
carries variables nobody set deliberately, which is why the same files behaved
|
|
61
|
+
differently there. A real value in the environment still wins.
|
|
62
|
+
|
|
63
|
+
- **The interpolation failure now names where it looked.** It said only
|
|
64
|
+
`Config references undefined env variable: SAP_LOGIN`, so three different
|
|
65
|
+
causes produced one sentence and the cause had to be guessed:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
... looked in process.env only — no env file was given (--env-file or envFile:)
|
|
69
|
+
... looked in process.env, then /path/e19.env, which yielded NO variables
|
|
70
|
+
(0 bytes) — check its encoding, since a UTF-16 file reads as nothing here
|
|
71
|
+
... looked in process.env, then /path/e19.env (1: SAP_PASSWORD)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
An env file that yields nothing is still not an error — an empty `.env` is
|
|
75
|
+
legitimate, and a config may take every value from the environment. It is
|
|
76
|
+
reported by whoever then needs a variable, where it can be said usefully.
|
|
77
|
+
|
|
78
|
+
### Documentation
|
|
79
|
+
|
|
80
|
+
- `docs/YAML_CONFIG.md`: a Windows path in **double** quotes is a YAML escape
|
|
81
|
+
context. `envFile: "C:\Users\..."` fails to parse (`\U` is a Unicode escape)
|
|
82
|
+
and `"C:\temp\e19.env"` parses *silently wrong* (`\e` becomes 0x1B). Use
|
|
83
|
+
single quotes, no quotes, or forward slashes.
|
|
84
|
+
|
|
10
85
|
## [4.0.0] - 2026-09-24
|
|
11
86
|
|
|
12
87
|
**The proxy stops re-implementing the broker, there is one forwarding path
|
package/README.md
CHANGED
|
@@ -76,7 +76,8 @@ than travelling through a tool call.
|
|
|
76
76
|
| Tool | What it does |
|
|
77
77
|
|---|---|
|
|
78
78
|
| `proxy_configs` | Lists the configs available, by the name `proxy_start` takes. Call it first — the names cannot be guessed. |
|
|
79
|
-
| `
|
|
79
|
+
| `proxy_environments` | Lists the environments available — one SAP system's credentials each, by the name `proxy_start` takes. Variable NAMES only, never values. |
|
|
80
|
+
| `proxy_start` | Starts a proxy from a config and, when the config needs one, an environment. Returns the URL it bound. The **port is not taken from the config**: a free one is bound instead. |
|
|
80
81
|
| `proxy_stop` | Stops a proxy this session started, freeing its port and releasing its credential. Proxies started by other sessions are never touched. |
|
|
81
82
|
| `proxy_status` | Lists this session's proxies and any others on this machine. Records whose process has died are pruned when read, so it cannot report a ghost. |
|
|
82
83
|
|
|
@@ -37,7 +37,11 @@ Tools offered to the client:
|
|
|
37
37
|
proxy_configs List the proxy configs on this machine, by the name
|
|
38
38
|
proxy_start takes. Call this first — the names cannot be
|
|
39
39
|
guessed.
|
|
40
|
-
|
|
40
|
+
proxy_environments
|
|
41
|
+
List the environments — one SAP system's credentials each,
|
|
42
|
+
from sessions/. Variable NAMES only, never values.
|
|
43
|
+
proxy_start Start a proxy from a config plus, when the config needs one,
|
|
44
|
+
an environment. The config supplies
|
|
41
45
|
the destination, target URL, default headers and timeouts;
|
|
42
46
|
the PORT does not come from it — a free one is bound, so
|
|
43
47
|
several proxies can run at once, and the URL returned is the
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -27,7 +27,18 @@ export interface ProxyConfig {
|
|
|
27
27
|
* - If --config is provided, load ONLY from that file
|
|
28
28
|
* - If --config is NOT provided, load ONLY from command line parameters and environment variables
|
|
29
29
|
*/
|
|
30
|
-
export declare function loadConfig(configPath?: string
|
|
30
|
+
export declare function loadConfig(configPath?: string,
|
|
31
|
+
/**
|
|
32
|
+
* The env file for THIS load, ahead of `--env-file` and the config's own
|
|
33
|
+
* `envFile:`.
|
|
34
|
+
*
|
|
35
|
+
* The MCP mode starts a proxy from a config plus an ENVIRONMENT chosen by
|
|
36
|
+
* name, and has no command line of its own to put `--env-file` on. One flag on
|
|
37
|
+
* the management process would not serve either: it overrides every config's
|
|
38
|
+
* own `envFile:`, so one system's secrets would reach every other system
|
|
39
|
+
* started in that session.
|
|
40
|
+
*/
|
|
41
|
+
envFileOverride?: string): ProxyConfig;
|
|
31
42
|
/**
|
|
32
43
|
* Transport-related fields from config file (not part of ProxyConfig but needed by transportConfig)
|
|
33
44
|
*/
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IAEjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAGxC,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAG/B,OAAO,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IAEjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAGxC,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAG/B,OAAO,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,UAAU,CAAC,EAAE,MAAM;AACnB;;;;;;;;;GASG;AACH,eAAe,CAAC,EAAE,MAAM,GACvB,WAAW,CAkDb;AAwED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,mBAAmB,CAQrB;AAsCD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAYhC;AAmJD;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAElD;AAUD;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG;IACnD,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAoDA"}
|
package/dist/lib/config.js
CHANGED
|
@@ -53,7 +53,18 @@ const envInterpolation_js_1 = require("./envInterpolation.js");
|
|
|
53
53
|
* - If --config is provided, load ONLY from that file
|
|
54
54
|
* - If --config is NOT provided, load ONLY from command line parameters and environment variables
|
|
55
55
|
*/
|
|
56
|
-
function loadConfig(configPath
|
|
56
|
+
function loadConfig(configPath,
|
|
57
|
+
/**
|
|
58
|
+
* The env file for THIS load, ahead of `--env-file` and the config's own
|
|
59
|
+
* `envFile:`.
|
|
60
|
+
*
|
|
61
|
+
* The MCP mode starts a proxy from a config plus an ENVIRONMENT chosen by
|
|
62
|
+
* name, and has no command line of its own to put `--env-file` on. One flag on
|
|
63
|
+
* the management process would not serve either: it overrides every config's
|
|
64
|
+
* own `envFile:`, so one system's secrets would reach every other system
|
|
65
|
+
* started in that session.
|
|
66
|
+
*/
|
|
67
|
+
envFileOverride) {
|
|
57
68
|
// Get config path from parameter or command line (--config/-c)
|
|
58
69
|
// Do NOT use environment variable MCP_PROXY_CONFIG - only explicit --config parameter
|
|
59
70
|
const finalConfigPath = configPath || getConfigPath();
|
|
@@ -65,10 +76,18 @@ function loadConfig(configPath) {
|
|
|
65
76
|
throw new Error(`Config file not found: ${finalConfigPath}`);
|
|
66
77
|
}
|
|
67
78
|
const fileConfig = loadConfigFile(finalConfigPath);
|
|
68
|
-
const envFilePath = resolveEnvFilePath(fileConfig, finalConfigPath);
|
|
79
|
+
const envFilePath = resolveEnvFilePath(fileConfig, finalConfigPath, envFileOverride);
|
|
69
80
|
const envFileMap = envFilePath ? (0, envInterpolation_js_1.loadEnvFile)(envFilePath) : {};
|
|
70
81
|
const lookup = (0, envInterpolation_js_1.buildLookup)(envFileMap);
|
|
71
|
-
const
|
|
82
|
+
const keys = Object.keys(envFileMap);
|
|
83
|
+
const sources = !envFilePath
|
|
84
|
+
? 'process.env only — no env file was given (--env-file or envFile:)'
|
|
85
|
+
: keys.length === 0
|
|
86
|
+
? `process.env, then ${envFilePath}, which yielded NO variables ` +
|
|
87
|
+
`(${fs.statSync(envFilePath).size} bytes) — check its encoding, ` +
|
|
88
|
+
`since a UTF-16 file reads as nothing here, and that lines look like KEY=value`
|
|
89
|
+
: `process.env, then ${envFilePath} (${keys.length}: ${keys.join(', ')})`;
|
|
90
|
+
const interpolated = (0, envInterpolation_js_1.interpolateConfig)(fileConfig, lookup, '', sources);
|
|
72
91
|
delete interpolated.envFile;
|
|
73
92
|
const base = applyDefaults(interpolated);
|
|
74
93
|
const cli = readCliOverrides();
|
|
@@ -166,7 +185,11 @@ function loadConfigFile(filePath) {
|
|
|
166
185
|
* cwd); otherwise the YAML `envFile` field, resolved relative to the config
|
|
167
186
|
* file's directory. Returns undefined when neither is set.
|
|
168
187
|
*/
|
|
169
|
-
function resolveEnvFilePath(rawConfig, configPath) {
|
|
188
|
+
function resolveEnvFilePath(rawConfig, configPath, override) {
|
|
189
|
+
// Most specific first: this call, then the command line, then what the config
|
|
190
|
+
// says about itself.
|
|
191
|
+
if (override !== undefined)
|
|
192
|
+
return path.resolve(override);
|
|
170
193
|
const cliEnvFile = getArgValue('--env-file');
|
|
171
194
|
if (cliEnvFile !== undefined)
|
|
172
195
|
return path.resolve(cliEnvFile);
|
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
* default when one is given. A ${VAR} without a default that resolves to
|
|
9
9
|
* undefined throws, naming the variable and the field it came from.
|
|
10
10
|
*/
|
|
11
|
-
export declare function interpolateString(input: string, lookup: (key: string) => string | undefined, fieldPath: string
|
|
11
|
+
export declare function interpolateString(input: string, lookup: (key: string) => string | undefined, fieldPath: string,
|
|
12
|
+
/** Where values were looked for, named in the failure. */
|
|
13
|
+
sources?: string): string;
|
|
12
14
|
/**
|
|
13
15
|
* Recursively interpolate all string values in a parsed config object.
|
|
14
16
|
* Objects/arrays are walked; non-string scalars are returned unchanged.
|
|
15
17
|
* The field path (e.g. `defaultHeaders.x-sap-password`) is threaded through for
|
|
16
18
|
* error messages.
|
|
17
19
|
*/
|
|
18
|
-
export declare function interpolateConfig(value: unknown, lookup: (key: string) => string | undefined, path?: string): unknown;
|
|
20
|
+
export declare function interpolateConfig(value: unknown, lookup: (key: string) => string | undefined, path?: string, sources?: string): unknown;
|
|
19
21
|
/**
|
|
20
22
|
* Parse a .env file into a flat map. Throws if the path is given but missing —
|
|
21
23
|
* a specified-yet-absent secret source is a configuration error, not a no-op.
|
|
@@ -23,6 +25,16 @@ export declare function interpolateConfig(value: unknown, lookup: (key: string)
|
|
|
23
25
|
export declare function loadEnvFile(envFilePath: string): Record<string, string>;
|
|
24
26
|
/**
|
|
25
27
|
* Build a lookup over process.env (highest priority) then the parsed .env map.
|
|
28
|
+
*
|
|
29
|
+
* An EMPTY environment variable does not count as a value. It used to: `??`
|
|
30
|
+
* skips only `undefined`, so a `SAP_LOGIN=` sitting in the environment won
|
|
31
|
+
* against the env file the user had explicitly pointed at with `--env-file`,
|
|
32
|
+
* and the header went out empty with nothing reported. Windows carries
|
|
33
|
+
* variables nobody set deliberately, which is why the same config and the same
|
|
34
|
+
* `.env` behaved differently there.
|
|
35
|
+
*
|
|
36
|
+
* A real value in the environment still wins — that is the documented
|
|
37
|
+
* precedence, and it is how a one-off override is meant to work.
|
|
26
38
|
*/
|
|
27
39
|
export declare function buildLookup(envFileMap: Record<string, string>): (key: string) => string | undefined;
|
|
28
40
|
//# sourceMappingURL=envInterpolation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envInterpolation.d.ts","sourceRoot":"","sources":["../../src/lib/envInterpolation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,EAC3C,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"envInterpolation.d.ts","sourceRoot":"","sources":["../../src/lib/envInterpolation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,EAC3C,SAAS,EAAE,MAAM;AACjB,0DAA0D;AAC1D,OAAO,SAAgB,GACtB,MAAM,CAsBR;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,EAC3C,IAAI,SAAK,EACT,OAAO,SAAgB,GACtB,OAAO,CAsBT;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASvE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAQrC"}
|
|
@@ -50,7 +50,9 @@ const PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
|
|
|
50
50
|
* default when one is given. A ${VAR} without a default that resolves to
|
|
51
51
|
* undefined throws, naming the variable and the field it came from.
|
|
52
52
|
*/
|
|
53
|
-
function interpolateString(input, lookup, fieldPath
|
|
53
|
+
function interpolateString(input, lookup, fieldPath,
|
|
54
|
+
/** Where values were looked for, named in the failure. */
|
|
55
|
+
sources = 'process.env') {
|
|
54
56
|
return input.replace(PLACEHOLDER, (_match, name, defaultVal) => {
|
|
55
57
|
const value = lookup(name);
|
|
56
58
|
const isEmpty = value === undefined || value === '';
|
|
@@ -58,7 +60,12 @@ function interpolateString(input, lookup, fieldPath) {
|
|
|
58
60
|
return isEmpty ? defaultVal : value;
|
|
59
61
|
}
|
|
60
62
|
if (value === undefined) {
|
|
61
|
-
|
|
63
|
+
// Naming where it looked, because the old message named only the
|
|
64
|
+
// variable — so a file never read, a file read empty, and a value
|
|
65
|
+
// shadowed by the environment all produced the same sentence and the
|
|
66
|
+
// cause had to be guessed.
|
|
67
|
+
throw new Error(`Config references undefined env variable: ${name} ` +
|
|
68
|
+
`(referenced in ${fieldPath}; looked in ${sources})`);
|
|
62
69
|
}
|
|
63
70
|
return value;
|
|
64
71
|
});
|
|
@@ -69,17 +76,17 @@ function interpolateString(input, lookup, fieldPath) {
|
|
|
69
76
|
* The field path (e.g. `defaultHeaders.x-sap-password`) is threaded through for
|
|
70
77
|
* error messages.
|
|
71
78
|
*/
|
|
72
|
-
function interpolateConfig(value, lookup, path = '') {
|
|
79
|
+
function interpolateConfig(value, lookup, path = '', sources = 'process.env') {
|
|
73
80
|
if (typeof value === 'string') {
|
|
74
|
-
return interpolateString(value, lookup, path || '(root)');
|
|
81
|
+
return interpolateString(value, lookup, path || '(root)', sources);
|
|
75
82
|
}
|
|
76
83
|
if (Array.isArray(value)) {
|
|
77
|
-
return value.map((item, i) => interpolateConfig(item, lookup, `${path}[${i}]
|
|
84
|
+
return value.map((item, i) => interpolateConfig(item, lookup, `${path}[${i}]`, sources));
|
|
78
85
|
}
|
|
79
86
|
if (value !== null && typeof value === 'object') {
|
|
80
87
|
const out = {};
|
|
81
88
|
for (const [key, val] of Object.entries(value)) {
|
|
82
|
-
out[key] = interpolateConfig(val, lookup, path ? `${path}.${key}` : key);
|
|
89
|
+
out[key] = interpolateConfig(val, lookup, path ? `${path}.${key}` : key, sources);
|
|
83
90
|
}
|
|
84
91
|
return out;
|
|
85
92
|
}
|
|
@@ -93,11 +100,31 @@ function loadEnvFile(envFilePath) {
|
|
|
93
100
|
if (!fs.existsSync(envFilePath)) {
|
|
94
101
|
throw new Error(`env file not found: ${envFilePath}`);
|
|
95
102
|
}
|
|
103
|
+
// Not an error when it yields nothing: an empty `.env` is legitimate, and a
|
|
104
|
+
// config may take every value from the environment. But the emptiness is
|
|
105
|
+
// reported by whoever needs a variable — see the `sources` note threaded into
|
|
106
|
+
// the interpolation failure, which names the file, its size and its key count.
|
|
96
107
|
return dotenv.parse(fs.readFileSync(envFilePath, 'utf-8'));
|
|
97
108
|
}
|
|
98
109
|
/**
|
|
99
110
|
* Build a lookup over process.env (highest priority) then the parsed .env map.
|
|
111
|
+
*
|
|
112
|
+
* An EMPTY environment variable does not count as a value. It used to: `??`
|
|
113
|
+
* skips only `undefined`, so a `SAP_LOGIN=` sitting in the environment won
|
|
114
|
+
* against the env file the user had explicitly pointed at with `--env-file`,
|
|
115
|
+
* and the header went out empty with nothing reported. Windows carries
|
|
116
|
+
* variables nobody set deliberately, which is why the same config and the same
|
|
117
|
+
* `.env` behaved differently there.
|
|
118
|
+
*
|
|
119
|
+
* A real value in the environment still wins — that is the documented
|
|
120
|
+
* precedence, and it is how a one-off override is meant to work.
|
|
100
121
|
*/
|
|
101
122
|
function buildLookup(envFileMap) {
|
|
102
|
-
return (key) =>
|
|
123
|
+
return (key) => {
|
|
124
|
+
const fromEnvironment = process.env[key];
|
|
125
|
+
if (fromEnvironment !== undefined && fromEnvironment !== '') {
|
|
126
|
+
return fromEnvironment;
|
|
127
|
+
}
|
|
128
|
+
return envFileMap[key];
|
|
129
|
+
};
|
|
103
130
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One environment: a `.env` beside the configs, holding one SAP system's
|
|
3
|
+
* credentials.
|
|
4
|
+
*
|
|
5
|
+
* A config names the service to reach; an environment names the credentials to
|
|
6
|
+
* reach it with. They are separate because several configs commonly point at one
|
|
7
|
+
* system — four of the configs in practice all want `e19` — and one config can
|
|
8
|
+
* be run against different systems.
|
|
9
|
+
*/
|
|
10
|
+
export interface EnvironmentEntry {
|
|
11
|
+
/** The file name without its extension — what a client asks for. */
|
|
12
|
+
name: string;
|
|
13
|
+
file: string;
|
|
14
|
+
/**
|
|
15
|
+
* Which variables it defines. Names only, never values: this is read to help
|
|
16
|
+
* a client choose, and a value here would be a credential in a tool result.
|
|
17
|
+
*/
|
|
18
|
+
variables: string[];
|
|
19
|
+
}
|
|
20
|
+
/** Where the environments live, beside `proxy/` and `service-keys/`. */
|
|
21
|
+
export declare function environmentDir(): string;
|
|
22
|
+
/**
|
|
23
|
+
* The environments on disk.
|
|
24
|
+
*
|
|
25
|
+
* One that cannot be read is still listed, with no variables beside it — hiding
|
|
26
|
+
* it would make "no such environment" the answer for a file sitting right there.
|
|
27
|
+
*/
|
|
28
|
+
export declare function listEnvironments(dir?: string): EnvironmentEntry[];
|
|
29
|
+
/**
|
|
30
|
+
* The file an environment name refers to.
|
|
31
|
+
*
|
|
32
|
+
* A name, never a path: `sessions/` sits beside `proxy/` and `service-keys/`,
|
|
33
|
+
* and the value comes from a language model.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveEnvironment(name: string, dir?: string): string;
|
|
36
|
+
//# sourceMappingURL=environments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environments.d.ts","sourceRoot":"","sources":["../../src/mcp/environments.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,wEAAwE;AACxE,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,GAAE,MAAyB,GAC7B,gBAAgB,EAAE,CAgCpB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,MAAyB,GAC7B,MAAM,CAmBR"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.environmentDir = environmentDir;
|
|
37
|
+
exports.listEnvironments = listEnvironments;
|
|
38
|
+
exports.resolveEnvironment = resolveEnvironment;
|
|
39
|
+
// src/mcp/environments.ts
|
|
40
|
+
const node_fs_1 = require("node:fs");
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
42
|
+
const stores_js_1 = require("../lib/stores.js");
|
|
43
|
+
/** Where the environments live, beside `proxy/` and `service-keys/`. */
|
|
44
|
+
function environmentDir() {
|
|
45
|
+
return (0, stores_js_1.storeDir)('sessions');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The environments on disk.
|
|
49
|
+
*
|
|
50
|
+
* One that cannot be read is still listed, with no variables beside it — hiding
|
|
51
|
+
* it would make "no such environment" the answer for a file sitting right there.
|
|
52
|
+
*/
|
|
53
|
+
function listEnvironments(dir = environmentDir()) {
|
|
54
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
55
|
+
return [];
|
|
56
|
+
const entries = [];
|
|
57
|
+
for (const name of (0, node_fs_1.readdirSync)(dir).sort()) {
|
|
58
|
+
// `.env` exactly: this leaves out `e19.env.template`, whose extension is
|
|
59
|
+
// `.template`, along with notes and anything else kept in the folder.
|
|
60
|
+
if (path.extname(name).toLowerCase() !== '.env')
|
|
61
|
+
continue;
|
|
62
|
+
const file = path.join(dir, name);
|
|
63
|
+
try {
|
|
64
|
+
if (!(0, node_fs_1.statSync)(file).isFile())
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const entry = {
|
|
71
|
+
name: path.basename(name, '.env'),
|
|
72
|
+
file,
|
|
73
|
+
variables: [],
|
|
74
|
+
};
|
|
75
|
+
try {
|
|
76
|
+
for (const line of (0, node_fs_1.readFileSync)(file, 'utf-8').split(/\r?\n/)) {
|
|
77
|
+
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
78
|
+
if (match)
|
|
79
|
+
entry.variables.push(match[1]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Listed with no variables. Whoever starts a proxy with it gets the real
|
|
84
|
+
// failure, against a name the caller chose.
|
|
85
|
+
}
|
|
86
|
+
entries.push(entry);
|
|
87
|
+
}
|
|
88
|
+
return entries;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The file an environment name refers to.
|
|
92
|
+
*
|
|
93
|
+
* A name, never a path: `sessions/` sits beside `proxy/` and `service-keys/`,
|
|
94
|
+
* and the value comes from a language model.
|
|
95
|
+
*/
|
|
96
|
+
function resolveEnvironment(name, dir = environmentDir()) {
|
|
97
|
+
if (name.includes('/') || name.includes('\\') || name.includes('..')) {
|
|
98
|
+
throw new Error(`Invalid environment name "${name}": it must be a name from ${dir}, not a path.`);
|
|
99
|
+
}
|
|
100
|
+
const available = listEnvironments(dir);
|
|
101
|
+
const found = available.find((entry) => entry.name === name || path.basename(entry.file) === name);
|
|
102
|
+
if (found)
|
|
103
|
+
return found.file;
|
|
104
|
+
const known = available.map((entry) => entry.name).join(', ');
|
|
105
|
+
throw new Error(known
|
|
106
|
+
? `No environment named "${name}" in ${dir}. Available: ${known}`
|
|
107
|
+
: `No environments found in ${dir}.`);
|
|
108
|
+
}
|
package/dist/mcp/tools.d.ts
CHANGED
|
@@ -24,5 +24,5 @@ export interface ProxyTool {
|
|
|
24
24
|
inputSchema: z.ZodRawShape;
|
|
25
25
|
handler: (args: Record<string, unknown>) => Promise<ToolResult>;
|
|
26
26
|
}
|
|
27
|
-
export declare function createProxyTools(supervisor: ProxySupervisor, configDir?: string): ProxyTool[];
|
|
27
|
+
export declare function createProxyTools(supervisor: ProxySupervisor, configDir?: string, envDir?: string): ProxyTool[];
|
|
28
28
|
//# sourceMappingURL=tools.d.ts.map
|
package/dist/mcp/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEhF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,QAGc,CAAC;AAE7C,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,CAAC,CAAC,WAAW,CAAC;IAC3B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;CACjE;AAMD,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,eAAe,EAC3B,SAAS,GAAE,MAAyB,EACpC,MAAM,GAAE,MAAyB,GAChC,SAAS,EAAE,CAmMb"}
|
package/dist/mcp/tools.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.createProxyTools = createProxyTools;
|
|
|
6
6
|
const zod_1 = require("zod");
|
|
7
7
|
const config_js_1 = require("../lib/config.js");
|
|
8
8
|
const configs_js_1 = require("./configs.js");
|
|
9
|
+
const environments_js_1 = require("./environments.js");
|
|
9
10
|
const supervisor_js_1 = require("./supervisor.js");
|
|
10
11
|
/**
|
|
11
12
|
* The thing the client has to keep being told.
|
|
@@ -23,8 +24,30 @@ exports.SHUTDOWN_REMINDER = 'When you are finished with this proxy, call proxy_s
|
|
|
23
24
|
const text = (body) => ({
|
|
24
25
|
content: [{ type: 'text', text: body }],
|
|
25
26
|
});
|
|
26
|
-
function createProxyTools(supervisor, configDir = (0, configs_js_1.proxyConfigDir)()) {
|
|
27
|
+
function createProxyTools(supervisor, configDir = (0, configs_js_1.proxyConfigDir)(), envDir = (0, environments_js_1.environmentDir)()) {
|
|
27
28
|
return [
|
|
29
|
+
{
|
|
30
|
+
name: 'proxy_environments',
|
|
31
|
+
title: 'List the environments available',
|
|
32
|
+
description: 'List the environments on this machine, by the name proxy_start takes. ' +
|
|
33
|
+
'An environment is one SAP system\u2019s credentials; a config says which ' +
|
|
34
|
+
'service to reach, an environment says what to authenticate with. ' +
|
|
35
|
+
'Several configs commonly want the same environment. Variable NAMES are ' +
|
|
36
|
+
'shown, never their values.',
|
|
37
|
+
inputSchema: {},
|
|
38
|
+
handler: async () => {
|
|
39
|
+
const environments = (0, environments_js_1.listEnvironments)(envDir);
|
|
40
|
+
if (environments.length === 0) {
|
|
41
|
+
return text(`No environments found in ${envDir}.`);
|
|
42
|
+
}
|
|
43
|
+
return text([
|
|
44
|
+
`Environments in ${envDir}:`,
|
|
45
|
+
...environments.map((e) => ` ${e.name}${e.variables.length ? ` — ${e.variables.join(', ')}` : ' — (none readable)'}`),
|
|
46
|
+
'',
|
|
47
|
+
'Pass one to proxy_start as "environment" when the config uses ${VAR}.',
|
|
48
|
+
].join('\n'));
|
|
49
|
+
},
|
|
50
|
+
},
|
|
28
51
|
{
|
|
29
52
|
name: 'proxy_configs',
|
|
30
53
|
title: 'List the proxy configs available',
|
|
@@ -56,7 +79,7 @@ function createProxyTools(supervisor, configDir = (0, configs_js_1.proxyConfigDi
|
|
|
56
79
|
{
|
|
57
80
|
name: 'proxy_start',
|
|
58
81
|
title: 'Start an authenticating proxy',
|
|
59
|
-
description: 'Start a local proxy from
|
|
82
|
+
description: 'Start a local proxy from a config and, when the config needs one, an environment. The ' +
|
|
60
83
|
'config supplies the destination, target URL, default headers and ' +
|
|
61
84
|
'timeouts — including any credentials, which stay in the config and ' +
|
|
62
85
|
'are never passed through here. The port is NOT taken from the config: ' +
|
|
@@ -66,6 +89,10 @@ function createProxyTools(supervisor, configDir = (0, configs_js_1.proxyConfigDi
|
|
|
66
89
|
config: zod_1.z
|
|
67
90
|
.string()
|
|
68
91
|
.describe('Name of a proxy config, as proxy_configs lists it (for example "nvcr_d24").'),
|
|
92
|
+
environment: zod_1.z
|
|
93
|
+
.string()
|
|
94
|
+
.optional()
|
|
95
|
+
.describe('Name of an environment, as proxy_environments lists it (for example "e19"). Supplies the ${VAR} values the config references. Needed when the config names no envFile of its own — proxy_configs marks those.'),
|
|
69
96
|
idleTimeoutMs: zod_1.z
|
|
70
97
|
.number()
|
|
71
98
|
.optional()
|
|
@@ -74,14 +101,19 @@ function createProxyTools(supervisor, configDir = (0, configs_js_1.proxyConfigDi
|
|
|
74
101
|
handler: async (args) => {
|
|
75
102
|
const name = String(args.config);
|
|
76
103
|
const file = (0, configs_js_1.resolveProxyConfig)(name, configDir);
|
|
104
|
+
const environment = args.environment;
|
|
105
|
+
const envFile = environment
|
|
106
|
+
? (0, environments_js_1.resolveEnvironment)(environment, envDir)
|
|
107
|
+
: undefined;
|
|
77
108
|
const started = await supervisor.start({
|
|
78
109
|
name,
|
|
79
|
-
config: (0, config_js_1.loadConfig)(file),
|
|
110
|
+
config: (0, config_js_1.loadConfig)(file, envFile),
|
|
80
111
|
idleTimeoutMs: args.idleTimeoutMs,
|
|
81
112
|
});
|
|
82
113
|
return text([
|
|
83
114
|
`Proxy running at ${started.url}`,
|
|
84
115
|
` config: ${started.name}`,
|
|
116
|
+
` environment: ${environment ?? "(the config's own, or the environment)"}`,
|
|
85
117
|
` instanceId: ${started.instanceId}`,
|
|
86
118
|
` destination: ${started.destination}`,
|
|
87
119
|
'',
|
package/docs/CLIENT_SETUP.md
CHANGED
|
@@ -243,7 +243,8 @@ It works from the configs in `~/.config/mcp-abap-adt/proxy/` — the same files
|
|
|
243
243
|
| Tool | |
|
|
244
244
|
|---|---|
|
|
245
245
|
| `proxy_configs` | the names available; call it first, they cannot be guessed |
|
|
246
|
-
| `
|
|
246
|
+
| `proxy_environments` | the environments available — one SAP system's credentials each. Variable names only, never values |
|
|
247
|
+
| `proxy_start` | starts one on a **free port** and returns the URL bound. Takes `environment` when the config uses `${VAR}` and names no `envFile` |
|
|
247
248
|
| `proxy_stop` | frees the port and releases the credential; never touches another session's proxy |
|
|
248
249
|
| `proxy_status` | this session's proxies and everyone else's, dead records pruned on read |
|
|
249
250
|
|
package/docs/USAGE.md
CHANGED
|
@@ -155,7 +155,8 @@ It works from the configs in `~/.config/mcp-abap-adt/proxy/` — the same files
|
|
|
155
155
|
| Tool | |
|
|
156
156
|
|---|---|
|
|
157
157
|
| `proxy_configs` | the names available; call it first, they cannot be guessed |
|
|
158
|
-
| `
|
|
158
|
+
| `proxy_environments` | the environments available — one SAP system's credentials each. Variable names only, never values |
|
|
159
|
+
| `proxy_start` | starts one on a **free port** and returns the URL bound. Takes `environment` when the config uses `${VAR}` and names no `envFile` |
|
|
159
160
|
| `proxy_stop` | frees the port and releases the credential; never touches another session's proxy |
|
|
160
161
|
| `proxy_status` | this session's proxies and everyone else's, dead records pruned on read |
|
|
161
162
|
|
package/docs/YAML_CONFIG.md
CHANGED
|
@@ -206,6 +206,23 @@ requestTimeout: 120000
|
|
|
206
206
|
| `maxRetries` | `number` | `3` | Maximum number of retry attempts |
|
|
207
207
|
| `retryDelay` | `number` | `1000` | Delay between retries (milliseconds) |
|
|
208
208
|
| `requestTimeout` | `number` | `60000` | Request timeout (milliseconds) |
|
|
209
|
+
### Windows paths must not go in double quotes
|
|
210
|
+
|
|
211
|
+
In YAML, a double-quoted string is an **escape context**, and a Windows path is
|
|
212
|
+
full of backslashes. Measured with the parser this package uses:
|
|
213
|
+
|
|
214
|
+
| written as | result |
|
|
215
|
+
|---|---|
|
|
216
|
+
| `envFile: "C:\Users\me\e19.env"` | **fails** — `expected hexadecimal character`, because `\U` starts a Unicode escape |
|
|
217
|
+
| `envFile: "C:\temp\e19.env"` | **silently wrong** — `\e` becomes the escape character `0x1B`, so the path points nowhere |
|
|
218
|
+
| `envFile: 'C:\Users\me\e19.env'` | correct |
|
|
219
|
+
| `envFile: C:\Users\me\e19.env` | correct |
|
|
220
|
+
| `envFile: "C:/Users/me/e19.env"` | correct |
|
|
221
|
+
|
|
222
|
+
The second row is the dangerous one: it does not fail, it resolves to a path that
|
|
223
|
+
does not exist. Forward slashes are the safest form — Node accepts them on
|
|
224
|
+
Windows, and they carry no meaning inside quotes.
|
|
225
|
+
|
|
209
226
|
| ~~`circuitBreakerThreshold`~~ | `number` | — | **No effect since 4.0.0.** Still accepted so existing files load; the circuit breaker guarded the buffered forward that release removed |
|
|
210
227
|
| ~~`circuitBreakerTimeout`~~ | `number` | — | **No effect since 4.0.0.** As above |
|
|
211
228
|
|
package/package.json
CHANGED