From 3de0512e218e98d403cbb89ed8c410f8904f9ebb Mon Sep 17 00:00:00 2001 From: Dustin Healy <54083382+dustinhealy@users.noreply.github.com> Date: Tue, 23 Jun 2026 19:22:25 -0700 Subject: [PATCH] feat: rate-limit /api/mcp/app-tool-call per user Adds mcpAppToolCallLimiter (60 req/min per user) to prevent DoS amplification through the MCP app iframe tool-call proxy. Follows the same express-rate-limit + limiterCache + logViolation pattern as toolCallLimiter. --- .../limiters/mcpAppToolCallLimiter.js | 33 +++++++++++++++++++ api/server/routes/mcp.js | 9 ++++- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 api/server/middleware/limiters/mcpAppToolCallLimiter.js diff --git a/api/server/middleware/limiters/mcpAppToolCallLimiter.js b/api/server/middleware/limiters/mcpAppToolCallLimiter.js new file mode 100644 index 0000000000..67879f8c32 --- /dev/null +++ b/api/server/middleware/limiters/mcpAppToolCallLimiter.js @@ -0,0 +1,33 @@ +const rateLimit = require('express-rate-limit'); +const { limiterCache } = require('@librechat/api'); +const { ViolationTypes } = require('librechat-data-provider'); +const logViolation = require('~/cache/logViolation'); + +const { TOOL_CALL_VIOLATION_SCORE: score } = process.env; + +const handler = async (req, res) => { + const type = ViolationTypes.TOOL_CALL_LIMIT; + const errorMessage = { + type, + max: 60, + limiter: 'user', + windowInMinutes: 1, + }; + + await logViolation(req, res, type, errorMessage, score); + res.status(429).json({ message: 'Too many app tool call requests. Try again later' }); +}; + +const limiterOptions = { + windowMs: 60 * 1000, + max: 60, + handler, + keyGenerator: function (req) { + return req.user?.id; + }, + store: limiterCache('mcp_app_tool_call_limiter'), +}; + +const mcpAppToolCallLimiter = rateLimit(limiterOptions); + +module.exports = mcpAppToolCallLimiter; diff --git a/api/server/routes/mcp.js b/api/server/routes/mcp.js index f1ad2ea0a8..d4b821388c 100644 --- a/api/server/routes/mcp.js +++ b/api/server/routes/mcp.js @@ -32,6 +32,7 @@ const { getMCPTools, } = require('~/server/controllers/mcp'); const { readMCPResource, appToolCall, serveMCPSandbox } = require('~/server/controllers/mcpApps'); +const mcpAppToolCallLimiter = require('~/server/middleware/limiters/mcpAppToolCallLimiter'); const { getOAuthReconnectionManager, getMCPServersRegistry, @@ -991,7 +992,13 @@ router.post('/resources/read', requireJwtAuth, checkMCPUsePermissions, readMCPRe * Proxy tool calls from MCP App iframe to MCP server * @route POST /api/mcp/app-tool-call */ -router.post('/app-tool-call', requireJwtAuth, checkMCPUsePermissions, appToolCall); +router.post( + '/app-tool-call', + requireJwtAuth, + checkMCPUsePermissions, + mcpAppToolCallLimiter, + appToolCall, +); /** * Serve the sandbox proxy HTML for MCP Apps