@mcp-abap-adt/proxy 2.0.0 → 4.0.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 +181 -0
- package/LICENSE +669 -17
- package/README.md +79 -8
- package/bin/mcp-abap-adt-proxy-mcp.js +113 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -97
- package/dist/lib/stores.d.ts +23 -2
- package/dist/lib/stores.d.ts.map +1 -1
- package/dist/lib/stores.js +56 -1
- package/dist/mcp/cli.d.ts +2 -0
- package/dist/mcp/cli.d.ts.map +1 -0
- package/dist/mcp/cli.js +21 -0
- package/dist/mcp/configs.d.ts +33 -0
- package/dist/mcp/configs.d.ts.map +1 -0
- package/dist/mcp/configs.js +142 -0
- package/dist/mcp/ports.d.ts +15 -0
- package/dist/mcp/ports.d.ts.map +1 -0
- package/dist/mcp/ports.js +35 -0
- package/dist/mcp/registry.d.ts +57 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +145 -0
- package/dist/mcp/server.d.ts +34 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +79 -0
- package/dist/mcp/shutdown.d.ts +37 -0
- package/dist/mcp/shutdown.d.ts.map +1 -0
- package/dist/mcp/shutdown.js +82 -0
- package/dist/mcp/supervisor.d.ts +125 -0
- package/dist/mcp/supervisor.d.ts.map +1 -0
- package/dist/mcp/supervisor.js +330 -0
- package/dist/mcp/tools.d.ts +28 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +152 -0
- package/dist/proxy/btpProxy.d.ts +24 -73
- package/dist/proxy/btpProxy.d.ts.map +1 -1
- package/dist/proxy/btpProxy.js +65 -616
- package/dist/proxy/credentials.d.ts +45 -0
- package/dist/proxy/credentials.d.ts.map +1 -0
- package/dist/proxy/credentials.js +41 -0
- package/dist/proxy/requestHandler.d.ts +38 -0
- package/dist/proxy/requestHandler.d.ts.map +1 -0
- package/dist/proxy/requestHandler.js +73 -0
- package/dist/proxy/reverseProxy.d.ts +11 -2
- package/dist/proxy/reverseProxy.d.ts.map +1 -1
- package/dist/proxy/reverseProxy.js +52 -9
- package/dist/router/headerAnalyzer.js +2 -2
- package/dist/router/requestInterceptor.js +9 -9
- package/docs/API.md +172 -0
- package/docs/ARCHITECTURE.md +322 -0
- package/docs/CLIENT_SETUP.md +413 -0
- package/docs/CONFIGURATION.md +258 -0
- package/docs/MIGRATION-4.0.md +125 -0
- package/docs/ROUTING_LOGIC.md +126 -0
- package/docs/TROUBLESHOOTING.md +488 -0
- package/docs/USAGE.md +422 -0
- package/docs/YAML_CONFIG.md +273 -0
- package/docs/mcp-proxy-config.example.yaml +62 -0
- package/package.json +17 -10
- package/dist/proxy/cloudLlmHubProxy.d.ts +0 -2
- package/dist/proxy/cloudLlmHubProxy.d.ts.map +0 -1
- package/dist/proxy/cloudLlmHubProxy.js +0 -3
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SHUTDOWN_REMINDER = void 0;
|
|
4
|
+
exports.createProxyTools = createProxyTools;
|
|
5
|
+
// src/mcp/tools.ts
|
|
6
|
+
const zod_1 = require("zod");
|
|
7
|
+
const config_js_1 = require("../lib/config.js");
|
|
8
|
+
const configs_js_1 = require("./configs.js");
|
|
9
|
+
const supervisor_js_1 = require("./supervisor.js");
|
|
10
|
+
/**
|
|
11
|
+
* The thing the client has to keep being told.
|
|
12
|
+
*
|
|
13
|
+
* A running proxy holds a TCP port and a live credential for the destination.
|
|
14
|
+
* The client here is a language model reading tool descriptions selectively,
|
|
15
|
+
* so this appears in the description of `proxy_start`, in the text that comes
|
|
16
|
+
* back with the URL, and in the server's own instructions. Three places for one
|
|
17
|
+
* sentence is not redundancy — it is the difference between a reminder that is
|
|
18
|
+
* read when the work starts and one that is read when the work finishes.
|
|
19
|
+
*/
|
|
20
|
+
exports.SHUTDOWN_REMINDER = 'When you are finished with this proxy, call proxy_stop. ' +
|
|
21
|
+
'A running proxy holds a TCP port and a live credential for its destination ' +
|
|
22
|
+
'until it is stopped or the session ends.';
|
|
23
|
+
const text = (body) => ({
|
|
24
|
+
content: [{ type: 'text', text: body }],
|
|
25
|
+
});
|
|
26
|
+
function createProxyTools(supervisor, configDir = (0, configs_js_1.proxyConfigDir)()) {
|
|
27
|
+
return [
|
|
28
|
+
{
|
|
29
|
+
name: 'proxy_configs',
|
|
30
|
+
title: 'List the proxy configs available',
|
|
31
|
+
description: 'List the proxy configurations on this machine, by the name ' +
|
|
32
|
+
'proxy_start takes. Each one already carries its destination, target ' +
|
|
33
|
+
'URL, default headers and timeouts, so starting a proxy is choosing a ' +
|
|
34
|
+
'name — not assembling settings. Call this first; the names cannot be ' +
|
|
35
|
+
'guessed.',
|
|
36
|
+
inputSchema: {},
|
|
37
|
+
handler: async () => {
|
|
38
|
+
const configs = (0, configs_js_1.listProxyConfigs)(configDir);
|
|
39
|
+
if (configs.length === 0) {
|
|
40
|
+
return text(`No proxy configs found in ${configDir}.`);
|
|
41
|
+
}
|
|
42
|
+
return text([
|
|
43
|
+
`Proxy configs in ${configDir}:`,
|
|
44
|
+
...configs.map((c) => {
|
|
45
|
+
const about = (0, configs_js_1.describeConfig)(c);
|
|
46
|
+
const where = c.destination
|
|
47
|
+
? ` — destination ${c.destination}`
|
|
48
|
+
: '';
|
|
49
|
+
return ` ${c.name}${where}${about ? `\n ${about}` : ''}`;
|
|
50
|
+
}),
|
|
51
|
+
'',
|
|
52
|
+
'Start one with proxy_start { "config": "<name>" }.',
|
|
53
|
+
].join('\n'));
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'proxy_start',
|
|
58
|
+
title: 'Start an authenticating proxy',
|
|
59
|
+
description: 'Start a local proxy from one of the configs proxy_configs lists. The ' +
|
|
60
|
+
'config supplies the destination, target URL, default headers and ' +
|
|
61
|
+
'timeouts — including any credentials, which stay in the config and ' +
|
|
62
|
+
'are never passed through here. The port is NOT taken from the config: ' +
|
|
63
|
+
'a free one is bound instead, so several proxies can run at once, and ' +
|
|
64
|
+
`the URL that comes back is the one actually bound. ${exports.SHUTDOWN_REMINDER}`,
|
|
65
|
+
inputSchema: {
|
|
66
|
+
config: zod_1.z
|
|
67
|
+
.string()
|
|
68
|
+
.describe('Name of a proxy config, as proxy_configs lists it (for example "nvcr_d24").'),
|
|
69
|
+
idleTimeoutMs: zod_1.z
|
|
70
|
+
.number()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe(`Stop the proxy after this long with no request. Defaults to ${supervisor_js_1.DEFAULT_IDLE_TIMEOUT_MS} ms; 0 disables it. This is a backstop, not a substitute for proxy_stop.`),
|
|
73
|
+
},
|
|
74
|
+
handler: async (args) => {
|
|
75
|
+
const name = String(args.config);
|
|
76
|
+
const file = (0, configs_js_1.resolveProxyConfig)(name, configDir);
|
|
77
|
+
const started = await supervisor.start({
|
|
78
|
+
name,
|
|
79
|
+
config: (0, config_js_1.loadConfig)(file),
|
|
80
|
+
idleTimeoutMs: args.idleTimeoutMs,
|
|
81
|
+
});
|
|
82
|
+
return text([
|
|
83
|
+
`Proxy running at ${started.url}`,
|
|
84
|
+
` config: ${started.name}`,
|
|
85
|
+
` instanceId: ${started.instanceId}`,
|
|
86
|
+
` destination: ${started.destination}`,
|
|
87
|
+
'',
|
|
88
|
+
exports.SHUTDOWN_REMINDER,
|
|
89
|
+
].join('\n'));
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'proxy_stop',
|
|
94
|
+
title: 'Stop a proxy this session started',
|
|
95
|
+
description: 'Stop a proxy started in this session, freeing its port and releasing ' +
|
|
96
|
+
'its credential. With no instanceId, stops every proxy this session ' +
|
|
97
|
+
'started. Proxies belonging to other sessions are never touched — ' +
|
|
98
|
+
'proxy_status shows them, and they are theirs to stop.',
|
|
99
|
+
inputSchema: {
|
|
100
|
+
instanceId: zod_1.z
|
|
101
|
+
.string()
|
|
102
|
+
.optional()
|
|
103
|
+
.describe('Which proxy to stop. Omit to stop all of this session’s.'),
|
|
104
|
+
},
|
|
105
|
+
handler: async (args) => {
|
|
106
|
+
const stopped = await supervisor.stop(args.instanceId);
|
|
107
|
+
if (stopped.length === 0) {
|
|
108
|
+
return text(args.instanceId
|
|
109
|
+
? `Nothing stopped: this session does not own a proxy with instanceId ${args.instanceId}. Use proxy_status to see what is running.`
|
|
110
|
+
: 'Nothing stopped: this session has no proxies running.');
|
|
111
|
+
}
|
|
112
|
+
return text([
|
|
113
|
+
`Stopped ${stopped.length} ${stopped.length === 1 ? 'proxy' : 'proxies'}:`,
|
|
114
|
+
...stopped.map((s) => ` ${s.url} — config ${s.name} — port ${s.port} released`),
|
|
115
|
+
].join('\n'));
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'proxy_status',
|
|
120
|
+
title: 'List running proxies',
|
|
121
|
+
description: 'List the proxies this session started, and any started by other ' +
|
|
122
|
+
'sessions on this machine. Records whose process has died are pruned ' +
|
|
123
|
+
'when this is read, so what it reports is what is actually running.',
|
|
124
|
+
inputSchema: {},
|
|
125
|
+
handler: async () => {
|
|
126
|
+
const mine = supervisor.mine();
|
|
127
|
+
const others = supervisor.others();
|
|
128
|
+
if (mine.length === 0 && others.length === 0) {
|
|
129
|
+
return text('No proxies are running on this machine.');
|
|
130
|
+
}
|
|
131
|
+
const lines = [];
|
|
132
|
+
if (mine.length > 0) {
|
|
133
|
+
lines.push('Started by this session:');
|
|
134
|
+
for (const m of mine) {
|
|
135
|
+
lines.push(` ${m.url} — config ${m.name} (destination ${m.destination}) — instanceId ${m.instanceId}`);
|
|
136
|
+
}
|
|
137
|
+
lines.push('', exports.SHUTDOWN_REMINDER);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
lines.push('This session has no proxies running.');
|
|
141
|
+
}
|
|
142
|
+
if (others.length > 0) {
|
|
143
|
+
lines.push('', 'Started by another session (not yours to stop):');
|
|
144
|
+
for (const o of others) {
|
|
145
|
+
lines.push(` ${o.url} — config ${o.config} (destination ${o.destination}) — pid ${o.pid}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return text(lines.join('\n'));
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
}
|
package/dist/proxy/btpProxy.d.ts
CHANGED
|
@@ -6,42 +6,18 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { AuthBroker } from '@mcp-abap-adt/auth-broker';
|
|
8
8
|
import { type ProxyConfig } from '../lib/config.js';
|
|
9
|
-
import type { RoutingDecision } from '../router/headerAnalyzer.js';
|
|
10
9
|
/**
|
|
11
10
|
* Check if error messages should be written to stderr
|
|
12
11
|
* Only output in verbose mode and not in test environment
|
|
13
12
|
*/
|
|
14
13
|
export declare function shouldWriteStderr(): boolean;
|
|
15
|
-
export interface GenericProxyRequest {
|
|
16
|
-
method: string;
|
|
17
|
-
url?: string;
|
|
18
|
-
data?: unknown;
|
|
19
|
-
id?: string | number | null;
|
|
20
|
-
[key: string]: unknown;
|
|
21
|
-
}
|
|
22
|
-
export type ProxyRequest = GenericProxyRequest;
|
|
23
|
-
export interface ProxyResponse {
|
|
24
|
-
jsonrpc: string;
|
|
25
|
-
id?: string | number | null;
|
|
26
|
-
result?: unknown;
|
|
27
|
-
error?: {
|
|
28
|
-
code: number;
|
|
29
|
-
message: string;
|
|
30
|
-
data?: unknown;
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
14
|
/**
|
|
34
15
|
* BTP Proxy Client
|
|
35
16
|
*/
|
|
36
17
|
export declare class BtpProxy {
|
|
37
|
-
private axiosInstance;
|
|
38
18
|
private defaultBtpAuthBroker;
|
|
39
19
|
private btpAuthBrokers;
|
|
40
|
-
private
|
|
41
|
-
private refreshTimers;
|
|
42
|
-
private readonly TOKEN_CACHE_TTL;
|
|
43
|
-
private readonly REFRESH_LEAD_MS;
|
|
44
|
-
private circuitBreaker;
|
|
20
|
+
private readonly credentials;
|
|
45
21
|
private config;
|
|
46
22
|
private unsafe;
|
|
47
23
|
constructor(defaultBtpAuthBroker: AuthBroker, config?: Partial<ProxyConfig>);
|
|
@@ -61,63 +37,38 @@ export declare class BtpProxy {
|
|
|
61
37
|
*/
|
|
62
38
|
private ensureSessionServiceUrl;
|
|
63
39
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
* Schedule background refresh REFRESH_LEAD_MS before token expiry.
|
|
80
|
-
*/
|
|
81
|
-
private scheduleProactiveRefresh;
|
|
82
|
-
/**
|
|
83
|
-
* Background refresh via auth-broker's refreshToken grant.
|
|
40
|
+
* The `Authorization` header value for a destination, or `null` when this
|
|
41
|
+
* credential is not a header at all.
|
|
42
|
+
*
|
|
43
|
+
* A complete header value, not a token: the caller puts it on the request as
|
|
44
|
+
* it stands. The credential is asked every time because it renews behind this
|
|
45
|
+
* call — a cache here would serve the stale token and hide the renewal the
|
|
46
|
+
* broker exists to do, which is precisely what the token cache, the JWT `exp`
|
|
47
|
+
* decoder and the per-destination refresh timer that used to live here did.
|
|
48
|
+
*
|
|
49
|
+
* Retried, because a refusal here is not always an answer. In the standalone
|
|
50
|
+
* proxy an authentication failure is FATAL — it exits so something can start
|
|
51
|
+
* it again — so without this a single transient UAA 503 kills a proxy that
|
|
52
|
+
* used to ride it out. Only failures that can get better are retried;
|
|
53
|
+
* `isRetryableError` says which, and a missing service key is not one of
|
|
54
|
+
* them, so it fails on the first attempt instead of three times slower.
|
|
84
55
|
*/
|
|
85
|
-
|
|
56
|
+
getAuthorizationHeader(destination: string): Promise<string | null>;
|
|
86
57
|
/**
|
|
87
|
-
*
|
|
58
|
+
* Turn the broker's failure into one that names what this proxy needs.
|
|
59
|
+
*
|
|
60
|
+
* The broker speaks of `.env` and `mcp.env` because it can be fed either way.
|
|
61
|
+
* This proxy only ever reads service keys, so passing that message through
|
|
62
|
+
* told the most common failure — a missing key — to go and create a file that
|
|
63
|
+
* would be ignored.
|
|
88
64
|
*/
|
|
65
|
+
private explainAuthFailure;
|
|
89
66
|
dispose(): void;
|
|
90
67
|
/**
|
|
91
68
|
* Get the target service URL for a destination.
|
|
92
69
|
* Priority: config.targetUrl > service key's serviceUrl
|
|
93
70
|
*/
|
|
94
71
|
getTargetUrl(destination: string): Promise<string>;
|
|
95
|
-
/**
|
|
96
|
-
* Helper function to extract string value from header (handles arrays)
|
|
97
|
-
*/
|
|
98
|
-
private getHeaderValue;
|
|
99
|
-
/**
|
|
100
|
-
* Build proxy request with JWT token (BTP authentication only)
|
|
101
|
-
*
|
|
102
|
-
* Process flow:
|
|
103
|
-
*
|
|
104
|
-
* 1. BTP Authentication (XSUAA):
|
|
105
|
-
* 1.1 If x-sap-destination header exists:
|
|
106
|
-
* - Check map for broker with key = destination, get or create, save to map
|
|
107
|
-
* - Get token from xsuaa broker
|
|
108
|
-
* - Add/replace Authorization: Bearer <token> header
|
|
109
|
-
* 1.2 If header doesn't exist but --btp parameter exists:
|
|
110
|
-
* - Use destination from parameter, get or create broker, save to map
|
|
111
|
-
* - Get token from xsuaa broker
|
|
112
|
-
* - Add/replace Authorization: Bearer <token> header
|
|
113
|
-
* 1.3 If neither header nor parameter:
|
|
114
|
-
* - Do nothing, pass request further
|
|
115
|
-
*/
|
|
116
|
-
private buildProxyRequest;
|
|
117
|
-
/**
|
|
118
|
-
* Proxy MCP request to target server with retry, circuit breaker, and error handling
|
|
119
|
-
*/
|
|
120
|
-
proxyRequest(originalRequest: ProxyRequest, routingDecision: RoutingDecision, originalHeaders: Record<string, string | string[] | undefined>): Promise<ProxyResponse>;
|
|
121
72
|
/**
|
|
122
73
|
* Create a new BtpProxy instance
|
|
123
74
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"btpProxy.d.ts","sourceRoot":"","sources":["../../src/proxy/btpProxy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAgB,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"btpProxy.d.ts","sourceRoot":"","sources":["../../src/proxy/btpProxy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAgB,MAAM,2BAA2B,CAAC;AAOrE,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AA+DhE;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAU3C;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,oBAAoB,CAAa;IACzC,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IACrD,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAU;gBAEZ,oBAAoB,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC;IAsC3E;;;OAGG;IACU,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB3D;;;;OAIG;YACW,wBAAwB;IAiGtC;;OAEG;YACW,uBAAuB;IAyFrC;;;;;;;;;;;;;;;;OAgBG;IACG,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA+BzE;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAuBnB,OAAO,IAAI,IAAI;IAKtB;;;OAGG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAexD;;OAEG;WACiB,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;CAuC7E;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,OAAO,CAAC,QAAQ,CAAC,CAEnB"}
|