@mcp-abap-adt/proxy 2.0.0 → 4.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 (67) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/LICENSE +669 -17
  3. package/README.md +79 -8
  4. package/bin/mcp-abap-adt-proxy-mcp.js +113 -0
  5. package/dist/index.d.ts +10 -4
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +54 -97
  8. package/dist/lib/config.d.ts.map +1 -1
  9. package/dist/lib/config.js +9 -1
  10. package/dist/lib/envInterpolation.d.ts +14 -2
  11. package/dist/lib/envInterpolation.d.ts.map +1 -1
  12. package/dist/lib/envInterpolation.js +34 -7
  13. package/dist/lib/stores.d.ts +23 -2
  14. package/dist/lib/stores.d.ts.map +1 -1
  15. package/dist/lib/stores.js +56 -1
  16. package/dist/mcp/cli.d.ts +2 -0
  17. package/dist/mcp/cli.d.ts.map +1 -0
  18. package/dist/mcp/cli.js +21 -0
  19. package/dist/mcp/configs.d.ts +33 -0
  20. package/dist/mcp/configs.d.ts.map +1 -0
  21. package/dist/mcp/configs.js +142 -0
  22. package/dist/mcp/ports.d.ts +15 -0
  23. package/dist/mcp/ports.d.ts.map +1 -0
  24. package/dist/mcp/ports.js +35 -0
  25. package/dist/mcp/registry.d.ts +57 -0
  26. package/dist/mcp/registry.d.ts.map +1 -0
  27. package/dist/mcp/registry.js +145 -0
  28. package/dist/mcp/server.d.ts +34 -0
  29. package/dist/mcp/server.d.ts.map +1 -0
  30. package/dist/mcp/server.js +79 -0
  31. package/dist/mcp/shutdown.d.ts +37 -0
  32. package/dist/mcp/shutdown.d.ts.map +1 -0
  33. package/dist/mcp/shutdown.js +82 -0
  34. package/dist/mcp/supervisor.d.ts +125 -0
  35. package/dist/mcp/supervisor.d.ts.map +1 -0
  36. package/dist/mcp/supervisor.js +330 -0
  37. package/dist/mcp/tools.d.ts +28 -0
  38. package/dist/mcp/tools.d.ts.map +1 -0
  39. package/dist/mcp/tools.js +152 -0
  40. package/dist/proxy/btpProxy.d.ts +24 -73
  41. package/dist/proxy/btpProxy.d.ts.map +1 -1
  42. package/dist/proxy/btpProxy.js +65 -616
  43. package/dist/proxy/credentials.d.ts +45 -0
  44. package/dist/proxy/credentials.d.ts.map +1 -0
  45. package/dist/proxy/credentials.js +41 -0
  46. package/dist/proxy/requestHandler.d.ts +38 -0
  47. package/dist/proxy/requestHandler.d.ts.map +1 -0
  48. package/dist/proxy/requestHandler.js +73 -0
  49. package/dist/proxy/reverseProxy.d.ts +11 -2
  50. package/dist/proxy/reverseProxy.d.ts.map +1 -1
  51. package/dist/proxy/reverseProxy.js +52 -9
  52. package/dist/router/headerAnalyzer.js +2 -2
  53. package/dist/router/requestInterceptor.js +9 -9
  54. package/docs/API.md +172 -0
  55. package/docs/ARCHITECTURE.md +322 -0
  56. package/docs/CLIENT_SETUP.md +413 -0
  57. package/docs/CONFIGURATION.md +258 -0
  58. package/docs/MIGRATION-4.0.md +125 -0
  59. package/docs/ROUTING_LOGIC.md +126 -0
  60. package/docs/TROUBLESHOOTING.md +488 -0
  61. package/docs/USAGE.md +422 -0
  62. package/docs/YAML_CONFIG.md +290 -0
  63. package/docs/mcp-proxy-config.example.yaml +62 -0
  64. package/package.json +17 -10
  65. package/dist/proxy/cloudLlmHubProxy.d.ts +0 -2
  66. package/dist/proxy/cloudLlmHubProxy.d.ts.map +0 -1
  67. 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