@deotio/mcp-sigv4-proxy 0.4.0 → 0.4.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.
- package/dist/proxy.js +41 -14
- package/package.json +4 -4
package/dist/proxy.js
CHANGED
|
@@ -294,6 +294,8 @@ export async function processLine(line, config, signer) {
|
|
|
294
294
|
const WARM_CACHEABLE = new Set([
|
|
295
295
|
'initialize', 'tools/list', 'resources/list', 'prompts/list',
|
|
296
296
|
]);
|
|
297
|
+
// Exported for testing
|
|
298
|
+
export { syntheticInitializeResult };
|
|
297
299
|
/**
|
|
298
300
|
* Synthetic MCP initialize response returned when the backend hasn't responded yet.
|
|
299
301
|
* Advertises tools/resources/prompts capabilities so Claude Code proceeds to list calls.
|
|
@@ -302,7 +304,7 @@ function syntheticInitializeResult() {
|
|
|
302
304
|
return {
|
|
303
305
|
protocolVersion: '2025-03-26',
|
|
304
306
|
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
305
|
-
serverInfo: { name: 'mcp-sigv4-proxy-warm', version: '0.4.
|
|
307
|
+
serverInfo: { name: 'mcp-sigv4-proxy-warm', version: '0.4.1' },
|
|
306
308
|
};
|
|
307
309
|
}
|
|
308
310
|
export async function warmBackend(config, signer) {
|
|
@@ -437,29 +439,54 @@ export function tryWarmResponse(body, requestId, warmState) {
|
|
|
437
439
|
// Claude Code sends this too, but in warm mode we intercepted initialize so we handle it.
|
|
438
440
|
return true;
|
|
439
441
|
}
|
|
442
|
+
/**
|
|
443
|
+
* Handle a single parsed JSON-RPC message in warm mode.
|
|
444
|
+
* Returns true if the message was served locally (no need to forward to backend).
|
|
445
|
+
*/
|
|
446
|
+
export async function handleWarmLine(input, ws) {
|
|
447
|
+
const method = JSON.parse(input.body).method;
|
|
448
|
+
if (method === 'initialize') {
|
|
449
|
+
// Respond IMMEDIATELY — never block on ws.ready here.
|
|
450
|
+
// This is the critical path: Claude Code's 30s timeout applies to this response.
|
|
451
|
+
const result = ws.cache.initialize ?? syntheticInitializeResult();
|
|
452
|
+
process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: input.requestId, result }) + '\n');
|
|
453
|
+
log('DEBUG', `warm: served initialize ${ws.cache.initialize ? 'from cache' : 'synthetic'}`);
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
if (WARM_CACHEABLE.has(method)) {
|
|
457
|
+
// List methods: serve from cache immediately if available…
|
|
458
|
+
if (tryWarmResponse(input.body, input.requestId, ws))
|
|
459
|
+
return true;
|
|
460
|
+
// …otherwise wait for warm-up to complete, then try cache again before forwarding
|
|
461
|
+
await ws.ready;
|
|
462
|
+
if (tryWarmResponse(input.body, input.requestId, ws))
|
|
463
|
+
return true;
|
|
464
|
+
// Fall through to forward if warm-up failed or cache still empty
|
|
465
|
+
}
|
|
466
|
+
// Non-cacheable methods (tools/call etc): return false to forward normally.
|
|
467
|
+
// fetchWithRetry handles any residual 424s if the backend isn't warm yet.
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
440
470
|
// --- Main entry ---
|
|
441
471
|
export function startProxy() {
|
|
442
472
|
const config = validateEnv();
|
|
443
473
|
const signer = createSigner(config);
|
|
444
474
|
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
445
|
-
// Start warm-up
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
475
|
+
// Start warm-up immediately. warmBackend() has no awaits before returning the WarmState
|
|
476
|
+
// object, so this promise resolves in the next microtask — effectively instant. The
|
|
477
|
+
// warmState.ready promise inside it is what takes minutes to resolve.
|
|
478
|
+
const warmStateP = config.warm
|
|
479
|
+
? warmBackend(config, signer)
|
|
480
|
+
: null;
|
|
450
481
|
let pending = Promise.resolve();
|
|
451
482
|
rl.on('line', (line) => {
|
|
452
483
|
pending = pending.then(async () => {
|
|
453
|
-
|
|
454
|
-
if (warmState) {
|
|
484
|
+
if (warmStateP) {
|
|
455
485
|
const input = parseInputLine(line);
|
|
456
486
|
if (input) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
return; // served from cache
|
|
461
|
-
}
|
|
462
|
-
// Fall through: warm mode inactive or method not cacheable — forward normally
|
|
487
|
+
const ws = await warmStateP; // resolves almost instantly
|
|
488
|
+
if (await handleWarmLine(input, ws))
|
|
489
|
+
return;
|
|
463
490
|
}
|
|
464
491
|
}
|
|
465
492
|
await processLine(line, config, signer);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deotio/mcp-sigv4-proxy",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "stdio MCP proxy with AWS SigV4 signing — connect Claude Code to any IAM-authenticated MCP server using a named AWS profile",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,16 +19,16 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@aws-sdk/credential-providers": "^3.0.0",
|
|
21
21
|
"@aws-crypto/sha256-js": "^5.0.0",
|
|
22
|
-
"@smithy/protocol-http": "^
|
|
22
|
+
"@smithy/protocol-http": "^5.3.12",
|
|
23
23
|
"@smithy/signature-v4": "^4.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@types/jest": "^
|
|
26
|
+
"@types/jest": "^30.0.0",
|
|
27
27
|
"@types/node": "^22.0.0",
|
|
28
28
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
29
29
|
"@typescript-eslint/parser": "^7.0.0",
|
|
30
30
|
"eslint": "^8.0.0",
|
|
31
|
-
"jest": "^
|
|
31
|
+
"jest": "^30.3.0",
|
|
32
32
|
"prettier": "^3.0.0",
|
|
33
33
|
"ts-jest": "^29.0.0",
|
|
34
34
|
"typescript": "^5.0.0"
|