@mcp-abap-adt/proxy 1.6.4 → 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 +217 -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 +116 -631
- 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,322 @@
|
|
|
1
|
+
# Architecture Documentation
|
|
2
|
+
|
|
3
|
+
This document describes the architecture of `@mcp-abap-adt/proxy`.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The MCP ABAP ADT Proxy is a simple middleware server that sits between MCP clients (like Cline) and any MCP server. It adds JWT authentication tokens to requests and forwards them to the target MCP server.
|
|
8
|
+
|
|
9
|
+
## System Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─────────────────┐
|
|
13
|
+
│ MCP Client │
|
|
14
|
+
│ (Cline, etc.) │
|
|
15
|
+
└────────┬─────────┘
|
|
16
|
+
│
|
|
17
|
+
│ HTTP/SSE/Stdio
|
|
18
|
+
│ (with x-sap-destination header)
|
|
19
|
+
│
|
|
20
|
+
┌────────▼─────────────────────────────────────┐
|
|
21
|
+
│ MCP ABAP ADT Proxy │
|
|
22
|
+
│ │
|
|
23
|
+
│ ┌──────────────────────────────────────┐ │
|
|
24
|
+
│ │ Request Interceptor │ │
|
|
25
|
+
│ │ - Extract x-sap-destination │ │
|
|
26
|
+
│ │ - Extract x-target-url │ │
|
|
27
|
+
│ └──────────────┬───────────────────────┘ │
|
|
28
|
+
│ │ │
|
|
29
|
+
│ ┌──────────────▼───────────────────────┐ │
|
|
30
|
+
│ │ Proxy Client │ │
|
|
31
|
+
│ │ - Get JWT Token (AuthBroker) │ │
|
|
32
|
+
│ │ - Add Authorization Header │ │
|
|
33
|
+
│ │ - Forward to MCP server │ │
|
|
34
|
+
│ │ - Error Handling │ │
|
|
35
|
+
│ └───────────────────────────────────────┘ │
|
|
36
|
+
└───────────────────────────────────────────────┘
|
|
37
|
+
│
|
|
38
|
+
│ HTTP Request
|
|
39
|
+
│ (with JWT token)
|
|
40
|
+
│
|
|
41
|
+
┌────────▼─────────────────────────────────────┐
|
|
42
|
+
│ Target MCP Server │
|
|
43
|
+
│ (URL from service key or x-target-url) │
|
|
44
|
+
└───────────────────────────────────────────────┘
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Process Model
|
|
48
|
+
|
|
49
|
+
**Location:** `bin/mcp-abap-adt-proxy.js`
|
|
50
|
+
|
|
51
|
+
The CLI entry point handles `--help` and `--version`, then loads
|
|
52
|
+
`dist/index.js` **in the same process** with `require`. It does not spawn a
|
|
53
|
+
child, and must not start doing so again.
|
|
54
|
+
|
|
55
|
+
That constraint is the whole point. The launcher previously spawned the server
|
|
56
|
+
and forwarded only `SIGINT`, so `SIGTERM` — what `kill`, `pkill`, service
|
|
57
|
+
managers and MCP clients send — killed the launcher and left the server running,
|
|
58
|
+
re-parented to `init`/`systemd`, still holding its HTTP port and, if a login was
|
|
59
|
+
in progress, its OAuth callback port. Running in one process means every signal
|
|
60
|
+
reaches the server directly.
|
|
61
|
+
|
|
62
|
+
Shutdown is handled in `src/index.ts`: `SIGINT`, `SIGTERM` and `SIGHUP` all run
|
|
63
|
+
the same handler, which closes the MCP server and the HTTP listener and releases
|
|
64
|
+
the callback port. The handler is idempotent, so repeated or overlapping signals
|
|
65
|
+
shut down once.
|
|
66
|
+
|
|
67
|
+
`SIGHUP` is registered deliberately rather than left to Node's default, because
|
|
68
|
+
Node terminates on it only while nothing has registered a listener. That used to
|
|
69
|
+
matter for a second reason as well: `@mcp-abap-adt/auth-providers` registered its
|
|
70
|
+
own `SIGHUP` listener during a login, which suppressed the default terminate, so
|
|
71
|
+
without this handler a closing terminal did not stop the proxy at all — it kept
|
|
72
|
+
running and holding its ports until the authentication timeout.
|
|
73
|
+
|
|
74
|
+
That is no longer how the provider works. Since `auth-providers@1.2.0` the
|
|
75
|
+
callback socket has one owner and one release point, and the port is freed when
|
|
76
|
+
the login scope ends however it ends; `2.0.0` registers no process signal
|
|
77
|
+
handlers at all. The proxy's own handler is what shuts it down, and it is worth
|
|
78
|
+
keeping registered rather than relying on Node's default, which a future
|
|
79
|
+
dependency could suppress again just as silently.
|
|
80
|
+
|
|
81
|
+
Regression tests for this live in `src/__tests__/bin/signalHandling.test.ts` and
|
|
82
|
+
`src/__tests__/bin/callbackPortLifecycle.test.ts`. They drive the built binary,
|
|
83
|
+
so they need `npm run build` first, and they are POSIX-only.
|
|
84
|
+
|
|
85
|
+
## Component Architecture
|
|
86
|
+
|
|
87
|
+
### 1. Request Interceptor
|
|
88
|
+
|
|
89
|
+
**Location:** `src/router/requestInterceptor.ts`
|
|
90
|
+
|
|
91
|
+
**Responsibilities:**
|
|
92
|
+
- Intercept incoming HTTP requests
|
|
93
|
+
- Extract headers and body
|
|
94
|
+
- Analyze request for routing decisions
|
|
95
|
+
- Sanitize headers for logging
|
|
96
|
+
|
|
97
|
+
**Key Functions:**
|
|
98
|
+
- `interceptRequest()` - Main interception function
|
|
99
|
+
- `sanitizeHeadersForLogging()` - Remove sensitive data from headers for safe logging
|
|
100
|
+
|
|
101
|
+
### 2. Header Analyzer
|
|
102
|
+
|
|
103
|
+
**Location:** `src/router/headerAnalyzer.ts`
|
|
104
|
+
|
|
105
|
+
**Responsibilities:**
|
|
106
|
+
- Extract `x-sap-destination` header (for BTP authentication)
|
|
107
|
+
- Extract `x-target-url` header (optional target URL override)
|
|
108
|
+
- Determine routing decision
|
|
109
|
+
|
|
110
|
+
**Key Functions:**
|
|
111
|
+
- `analyzeHeaders()` - Main analysis function, extracts routing info and returns `RoutingDecision`
|
|
112
|
+
- `shouldProxy()` - Check if request should be proxied
|
|
113
|
+
|
|
114
|
+
**Routing Strategy:**
|
|
115
|
+
- `PROXY` - Proxy request with JWT authentication (when `x-sap-destination` / `--btp` is present)
|
|
116
|
+
- `UNKNOWN` - No BTP destination provided; request cannot be routed (rejected with `400`)
|
|
117
|
+
|
|
118
|
+
### 3. Proxy Client
|
|
119
|
+
|
|
120
|
+
**Location:** `src/proxy/credentials.ts`, `src/proxy/btpProxy.ts`, `src/proxy/reverseProxy.ts`
|
|
121
|
+
|
|
122
|
+
**Responsibilities:**
|
|
123
|
+
- Turn a destination into the credential it authenticates with and the base URL its requests go to
|
|
124
|
+
- Forward the request transparently, with that credential's header on it
|
|
125
|
+
- Retry getting a token when the failure can get better, and explain it when it cannot
|
|
126
|
+
|
|
127
|
+
**Key Features:**
|
|
128
|
+
- The ecosystem's shared credential — `TokenAuthProvider` over `AuthBroker.createTokenRefresher()`
|
|
129
|
+
- One credential and one broker per destination; **no token cache and no refresh timer here**. The credential is asked for a header per request and renews behind that call
|
|
130
|
+
- Retry with exponential backoff while acquiring a token — scoped: 5xx and network failures are retried, a missing service key is not, so the common failure answers at once instead of three times more slowly
|
|
131
|
+
- **No circuit breaker.** It guarded the buffered forward that is gone; the streaming path has nowhere to put one without buffering the response again
|
|
132
|
+
|
|
133
|
+
**Flow:**
|
|
134
|
+
1. Receive request with `x-sap-destination` header (or `--btp`)
|
|
135
|
+
2. Ask the destination's credential for an `Authorization` header value
|
|
136
|
+
3. Take the base URL from the service key, unless `x-target-url` / `--target-url` overrides it
|
|
137
|
+
4. `forwardRequest()` streams the request to that URL with the header as given
|
|
138
|
+
5. Stream the response back untouched
|
|
139
|
+
|
|
140
|
+
**One path, not two.** An earlier version carried the SSE transport over axios,
|
|
141
|
+
buffering the response and rewrapping it as a JSON-RPC envelope. Every transport
|
|
142
|
+
now uses the same pipe. The SSE path reads the body first — its error envelopes
|
|
143
|
+
have to echo the JSON-RPC `id` — and hands those exact bytes to the pipe, since
|
|
144
|
+
a stream read once cannot be piped.
|
|
145
|
+
|
|
146
|
+
### 6. Error Handler
|
|
147
|
+
|
|
148
|
+
**Location:** `src/lib/errorHandler.ts`
|
|
149
|
+
|
|
150
|
+
**Responsibilities:**
|
|
151
|
+
- Retry logic with exponential backoff
|
|
152
|
+
- Deciding which failures are worth retrying
|
|
153
|
+
|
|
154
|
+
**Key Components:**
|
|
155
|
+
- `retryWithBackoff()` - Retry function; used around token acquisition
|
|
156
|
+
- `isRetryableError()` - Which failures can get better: 5xx, network errors, and an expired-or-invalid token
|
|
157
|
+
- `CircuitBreaker` - **still exported, no longer used by this package.** It guarded the buffered forward removed in 4.0.0
|
|
158
|
+
|
|
159
|
+
### 7. Configuration Manager
|
|
160
|
+
|
|
161
|
+
**Location:** `src/lib/config.ts`
|
|
162
|
+
|
|
163
|
+
**Responsibilities:**
|
|
164
|
+
- Load configuration from files and environment
|
|
165
|
+
- Validate configuration
|
|
166
|
+
- Merge configurations with precedence
|
|
167
|
+
|
|
168
|
+
**Configuration Sources (mutually exclusive):**
|
|
169
|
+
- With `--config`/`-c`: loaded **only** from the given YAML/JSON file
|
|
170
|
+
- Without `--config`: CLI params + environment variables + defaults
|
|
171
|
+
|
|
172
|
+
## Request Flow
|
|
173
|
+
|
|
174
|
+
### Proxy Request Flow
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
1. Client Request (with x-sap-destination header)
|
|
178
|
+
↓
|
|
179
|
+
2. Request Interceptor
|
|
180
|
+
- Extract headers
|
|
181
|
+
- Parse request body
|
|
182
|
+
↓
|
|
183
|
+
3. Header Analyzer
|
|
184
|
+
- Extract x-sap-destination (for BTP auth)
|
|
185
|
+
- Extract x-target-url (optional URL override)
|
|
186
|
+
↓
|
|
187
|
+
4. Proxy Client
|
|
188
|
+
↓
|
|
189
|
+
5. Ask the destination's credential for an Authorization header
|
|
190
|
+
(retried on a failure that can get better)
|
|
191
|
+
↓
|
|
192
|
+
7. Build Proxy Request
|
|
193
|
+
- Add JWT to Authorization header
|
|
194
|
+
- Get MCP server URL from service key
|
|
195
|
+
↓
|
|
196
|
+
8. Forward to Target MCP Server (with retry)
|
|
197
|
+
↓
|
|
198
|
+
9. Handle Response/Errors
|
|
199
|
+
↓
|
|
200
|
+
10. Return Response to Client
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Token Management
|
|
204
|
+
|
|
205
|
+
### Token Caching
|
|
206
|
+
|
|
207
|
+
JWT tokens are cached by BTP destination name.
|
|
208
|
+
|
|
209
|
+
**Cache Key:**
|
|
210
|
+
```typescript
|
|
211
|
+
btpDestination // e.g., "btp-cloud", "ai"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Cache TTL:**
|
|
215
|
+
- Tokens cached for 30 minutes
|
|
216
|
+
- Automatic refresh on expiration
|
|
217
|
+
- Force refresh on 401/403 errors
|
|
218
|
+
|
|
219
|
+
### Token Lifecycle
|
|
220
|
+
|
|
221
|
+
1. **Retrieval**: Get token from AuthBroker for BTP destination
|
|
222
|
+
2. **Caching**: Cache token with expiration time
|
|
223
|
+
3. **Usage**: Reuse cached token for subsequent requests
|
|
224
|
+
4. **Refresh**: Automatically refresh on expiration or error
|
|
225
|
+
|
|
226
|
+
## Error Handling & Resilience
|
|
227
|
+
|
|
228
|
+
### Retry Logic
|
|
229
|
+
|
|
230
|
+
- **Exponential Backoff**: Delay increases exponentially with each retry
|
|
231
|
+
- **Retryable Errors**: 500, 502, 503, 504 status codes
|
|
232
|
+
- **Network Errors**: Automatically retried
|
|
233
|
+
- **Token Errors**: Handled separately with token refresh
|
|
234
|
+
|
|
235
|
+
### ~~Circuit Breaker~~ — removed in 4.0.0
|
|
236
|
+
|
|
237
|
+
It only ever guarded the buffered axios forward. The forwarding path streams, and
|
|
238
|
+
a breaker there would mean buffering the response again. A target that keeps
|
|
239
|
+
failing now fails visibly each time instead of being short-circuited.
|
|
240
|
+
|
|
241
|
+
## Security Considerations
|
|
242
|
+
|
|
243
|
+
### Header Sanitization
|
|
244
|
+
|
|
245
|
+
Sensitive headers are sanitized in logs:
|
|
246
|
+
- `authorization`
|
|
247
|
+
- `x-sap-jwt-token`
|
|
248
|
+
- `x-sap-refresh-token`
|
|
249
|
+
- `x-sap-password`
|
|
250
|
+
- `x-sap-uaa-client-secret`
|
|
251
|
+
|
|
252
|
+
### Token Security
|
|
253
|
+
|
|
254
|
+
- Tokens never logged in plain text
|
|
255
|
+
- Tokens cached securely in memory
|
|
256
|
+
- Token refresh handled automatically
|
|
257
|
+
|
|
258
|
+
### Connection Isolation
|
|
259
|
+
|
|
260
|
+
- Each session has isolated connections
|
|
261
|
+
- No cross-session data leakage
|
|
262
|
+
- Session-based connection caching
|
|
263
|
+
|
|
264
|
+
## Performance Optimizations
|
|
265
|
+
|
|
266
|
+
### Token Caching
|
|
267
|
+
|
|
268
|
+
- JWT tokens cached for 30 minutes
|
|
269
|
+
- Reduces AuthBroker calls
|
|
270
|
+
- Automatic refresh on expiration
|
|
271
|
+
- Per-destination caching
|
|
272
|
+
|
|
273
|
+
### Request Reuse
|
|
274
|
+
|
|
275
|
+
- Axios instance reused for all requests
|
|
276
|
+
- Efficient HTTP connection pooling
|
|
277
|
+
- Automatic retry with exponential backoff
|
|
278
|
+
|
|
279
|
+
## Scalability
|
|
280
|
+
|
|
281
|
+
### Horizontal Scaling
|
|
282
|
+
|
|
283
|
+
- Stateless design (except token cache)
|
|
284
|
+
- Multiple instances can run in parallel
|
|
285
|
+
- Load balancer can distribute requests
|
|
286
|
+
|
|
287
|
+
### Vertical Scaling
|
|
288
|
+
|
|
289
|
+
- No token is cached here, so nothing goes stale and no refresh timer runs per destination
|
|
290
|
+
- The response is never buffered, so a large or long-lived one costs a socket rather than memory
|
|
291
|
+
|
|
292
|
+
## Monitoring & Observability
|
|
293
|
+
|
|
294
|
+
### Logging
|
|
295
|
+
|
|
296
|
+
- Structured logging with types
|
|
297
|
+
- Debug mode for detailed logs
|
|
298
|
+
- Error tracking with context
|
|
299
|
+
|
|
300
|
+
### Metrics (Future)
|
|
301
|
+
|
|
302
|
+
- Request count by strategy
|
|
303
|
+
- Circuit breaker state
|
|
304
|
+
- Token cache size
|
|
305
|
+
- Token refresh count
|
|
306
|
+
- Error rates
|
|
307
|
+
|
|
308
|
+
## Extension Points
|
|
309
|
+
|
|
310
|
+
### Custom Error Handlers
|
|
311
|
+
|
|
312
|
+
Error handling can be customized by:
|
|
313
|
+
1. Extending `errorHandler.ts`
|
|
314
|
+
2. Implementing custom retry logic
|
|
315
|
+
3. Adding custom circuit breaker behavior
|
|
316
|
+
|
|
317
|
+
### Custom Token Providers
|
|
318
|
+
|
|
319
|
+
Token management can be extended by:
|
|
320
|
+
1. Implementing custom AuthBroker integration
|
|
321
|
+
2. Adding custom token caching strategies
|
|
322
|
+
3. Supporting additional authentication methods
|