@aiwerk/mcp-bridge 2.7.3 → 2.7.4
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.
|
@@ -70,7 +70,7 @@ export class AdaptivePromotion {
|
|
|
70
70
|
server: entry.server,
|
|
71
71
|
tool: entry.tool,
|
|
72
72
|
callCount: recentCalls.length,
|
|
73
|
-
lastCall:
|
|
73
|
+
lastCall: entry.callTimestamps.reduce((a, b) => a > b ? a : b, 0)
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -85,7 +85,7 @@ export class AdaptivePromotion {
|
|
|
85
85
|
entry.callTimestamps = entry.callTimestamps.filter(t => t > windowCutoff);
|
|
86
86
|
// Remove entire entry if no calls within decay period
|
|
87
87
|
const lastCall = entry.callTimestamps.length > 0
|
|
88
|
-
?
|
|
88
|
+
? entry.callTimestamps.reduce((a, b) => a > b ? a : b, 0)
|
|
89
89
|
: 0;
|
|
90
90
|
if (lastCall === 0 || lastCall < decayCutoff) {
|
|
91
91
|
this.usage.delete(k);
|
package/dist/src/mcp-router.js
CHANGED
|
@@ -252,10 +252,10 @@ export class McpRouter {
|
|
|
252
252
|
return { server, action: "call", tool, result: cachedResult };
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
|
-
// Rate limit check BEFORE
|
|
256
|
-
const
|
|
257
|
-
if (!
|
|
258
|
-
return this.error("mcp_error",
|
|
255
|
+
// Rate limit: check BEFORE call, increment AFTER success
|
|
256
|
+
const rateLimitCheck = this.rateLimiter.checkLimit(server, serverConfig.rateLimit);
|
|
257
|
+
if (!rateLimitCheck.allowed) {
|
|
258
|
+
return this.error("mcp_error", rateLimitCheck.error || "Rate limit reached");
|
|
259
259
|
}
|
|
260
260
|
this.markUsed(server);
|
|
261
261
|
const callOutcome = await this.callToolWithRetry(server, tool, params ?? {}, state.transport);
|
|
@@ -263,6 +263,8 @@ export class McpRouter {
|
|
|
263
263
|
if (response.error) {
|
|
264
264
|
return this.error("mcp_error", response.error.message, undefined, response.error.code);
|
|
265
265
|
}
|
|
266
|
+
// Only increment usage counter on successful calls
|
|
267
|
+
const rateLimitIncrement = this.rateLimiter.increment(server, serverConfig.rateLimit);
|
|
266
268
|
// Record usage for adaptive promotion
|
|
267
269
|
if (this.promotion) {
|
|
268
270
|
this.promotion.recordCall(server, tool);
|
|
@@ -278,7 +280,7 @@ export class McpRouter {
|
|
|
278
280
|
action: "call",
|
|
279
281
|
tool,
|
|
280
282
|
result,
|
|
281
|
-
...(
|
|
283
|
+
...(rateLimitIncrement.warning ? { warning: rateLimitIncrement.warning } : {}),
|
|
282
284
|
...(callOutcome.retries > 0 ? { retries: callOutcome.retries } : {})
|
|
283
285
|
};
|
|
284
286
|
}
|
|
@@ -10,7 +10,15 @@ export interface RateLimitResult {
|
|
|
10
10
|
export declare class RateLimiter {
|
|
11
11
|
private readonly usageDir;
|
|
12
12
|
constructor(usageDir?: string);
|
|
13
|
+
/**
|
|
14
|
+
* Check if a call is allowed (without incrementing). Use with increment() after success.
|
|
15
|
+
*/
|
|
13
16
|
checkLimit(serverId: string, config?: RateLimitConfig): RateLimitResult;
|
|
17
|
+
/**
|
|
18
|
+
* Increment usage counters after a successful call. Returns warning if near limit.
|
|
19
|
+
*/
|
|
20
|
+
increment(serverId: string, config?: RateLimitConfig): RateLimitResult;
|
|
21
|
+
/** @deprecated Use checkLimit() + increment() separately. Kept for backward compatibility. */
|
|
14
22
|
checkAndIncrement(serverId: string, config?: RateLimitConfig): RateLimitResult;
|
|
15
23
|
getUsage(serverId: string): {
|
|
16
24
|
daily: number;
|
package/dist/src/rate-limiter.js
CHANGED
|
@@ -21,6 +21,9 @@ export class RateLimiter {
|
|
|
21
21
|
constructor(usageDir) {
|
|
22
22
|
this.usageDir = usageDir ?? join(homedir(), ".mcp-bridge", "usage");
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if a call is allowed (without incrementing). Use with increment() after success.
|
|
26
|
+
*/
|
|
24
27
|
checkLimit(serverId, config) {
|
|
25
28
|
const dailyLimit = isPositiveLimit(config?.maxCallsPerDay) ? config.maxCallsPerDay : undefined;
|
|
26
29
|
const monthlyLimit = isPositiveLimit(config?.maxCallsPerMonth) ? config.maxCallsPerMonth : undefined;
|
|
@@ -45,29 +48,19 @@ export class RateLimiter {
|
|
|
45
48
|
}
|
|
46
49
|
return { allowed: true };
|
|
47
50
|
}
|
|
48
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Increment usage counters after a successful call. Returns warning if near limit.
|
|
53
|
+
*/
|
|
54
|
+
increment(serverId, config) {
|
|
49
55
|
const dailyLimit = isPositiveLimit(config?.maxCallsPerDay) ? config.maxCallsPerDay : undefined;
|
|
50
56
|
const monthlyLimit = isPositiveLimit(config?.maxCallsPerMonth) ? config.maxCallsPerMonth : undefined;
|
|
51
57
|
if (!dailyLimit && !monthlyLimit) {
|
|
52
58
|
return { allowed: true };
|
|
53
59
|
}
|
|
54
|
-
// Single loadUsage call — check + increment in one pass (avoids double file read)
|
|
55
60
|
const { usage, changed } = this.loadUsage(serverId);
|
|
56
61
|
if (changed) {
|
|
57
62
|
this.saveUsage(serverId, usage);
|
|
58
63
|
}
|
|
59
|
-
if (dailyLimit && usage.daily.count >= dailyLimit) {
|
|
60
|
-
return {
|
|
61
|
-
allowed: false,
|
|
62
|
-
error: `❌ Rate limit reached for ${serverId}: ${usage.daily.count}/${dailyLimit} daily calls used. Resets at midnight UTC. To adjust: mcp-bridge limit ${serverId} --daily ${nextSuggestedLimit(dailyLimit)}. To check usage: mcp-bridge usage. To disable limit: mcp-bridge limit ${serverId} --daily 0`
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
if (monthlyLimit && usage.monthly.count >= monthlyLimit) {
|
|
66
|
-
return {
|
|
67
|
-
allowed: false,
|
|
68
|
-
error: `❌ Rate limit reached for ${serverId}: ${usage.monthly.count}/${monthlyLimit} monthly calls used. Resets on the 1st of each month at midnight UTC. To adjust: mcp-bridge limit ${serverId} --monthly ${nextSuggestedLimit(monthlyLimit)}. To check usage: mcp-bridge usage. To disable limit: mcp-bridge limit ${serverId} --monthly 0`
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
64
|
usage.daily.count += 1;
|
|
72
65
|
usage.monthly.count += 1;
|
|
73
66
|
this.saveUsage(serverId, usage);
|
|
@@ -85,6 +78,13 @@ export class RateLimiter {
|
|
|
85
78
|
}
|
|
86
79
|
return { allowed: true };
|
|
87
80
|
}
|
|
81
|
+
/** @deprecated Use checkLimit() + increment() separately. Kept for backward compatibility. */
|
|
82
|
+
checkAndIncrement(serverId, config) {
|
|
83
|
+
const check = this.checkLimit(serverId, config);
|
|
84
|
+
if (!check.allowed)
|
|
85
|
+
return check;
|
|
86
|
+
return this.increment(serverId, config);
|
|
87
|
+
}
|
|
88
88
|
getUsage(serverId) {
|
|
89
89
|
const { usage, changed } = this.loadUsage(serverId);
|
|
90
90
|
if (changed) {
|
package/dist/src/token-store.js
CHANGED
|
@@ -54,7 +54,7 @@ export class FileTokenStore {
|
|
|
54
54
|
}
|
|
55
55
|
tokenPath(serverName) {
|
|
56
56
|
// Sanitize server name to prevent path traversal
|
|
57
|
-
const safe = serverName
|
|
57
|
+
const safe = encodeURIComponent(serverName);
|
|
58
58
|
return join(this.tokensDir, `${safe}.json`);
|
|
59
59
|
}
|
|
60
60
|
ensureDir() {
|
|
@@ -259,6 +259,7 @@ export class SseTransport extends BaseTransport {
|
|
|
259
259
|
this.sseAbortController.abort();
|
|
260
260
|
this.sseAbortController = null;
|
|
261
261
|
}
|
|
262
|
+
this.endpointUrl = null;
|
|
262
263
|
for (const [, controller] of this.pendingRequestControllers) {
|
|
263
264
|
controller.abort();
|
|
264
265
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiwerk/mcp-bridge",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.4",
|
|
4
4
|
"description": "Standalone MCP server that multiplexes multiple MCP servers into one interface",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"build": "tsc",
|
|
45
45
|
"test": "node --import tsx --test tests/*.test.ts",
|
|
46
46
|
"typecheck": "tsc --noEmit",
|
|
47
|
-
"prepublishOnly": "tsc && bash scripts/validate-recipes.sh",
|
|
47
|
+
"prepublishOnly": "tsc && bash scripts/validate-recipes.sh && node -e \"const v=require('./package.json').version;const fs=require('fs');const cl=fs.readFileSync('CHANGELOG.md','utf8');if(!cl.includes('['+v+']')){console.error('ERROR: CHANGELOG.md missing entry for v'+v);process.exit(1)}\"",
|
|
48
48
|
"validate-recipe": "npx tsx bin/validate-recipe.ts",
|
|
49
49
|
"lint": "eslint src/",
|
|
50
50
|
"format": "prettier --write src/",
|