fdic-mcp-server 1.30.0 → 1.30.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/index.js CHANGED
@@ -32,7 +32,7 @@ var import_types = require("@modelcontextprotocol/sdk/types.js");
32
32
  var import_express2 = __toESM(require("express"));
33
33
 
34
34
  // src/constants.ts
35
- var VERSION = true ? "1.30.0" : process.env.npm_package_version ?? "0.0.0-dev";
35
+ var VERSION = true ? "1.30.1" : process.env.npm_package_version ?? "0.0.0-dev";
36
36
  var FDIC_API_BASE_URL = "https://banks.data.fdic.gov/api";
37
37
  var CHARACTER_LIMIT = 5e4;
38
38
  var DEFAULT_FDIC_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
@@ -73,6 +73,42 @@ var RateLimiter = class {
73
73
  return true;
74
74
  }
75
75
  };
76
+ var ConcurrentLimiter = class {
77
+ maxConcurrent;
78
+ active = /* @__PURE__ */ new Map();
79
+ constructor(maxConcurrent) {
80
+ this.maxConcurrent = maxConcurrent;
81
+ }
82
+ acquire(key) {
83
+ const current = this.active.get(key) ?? 0;
84
+ if (current >= this.maxConcurrent) {
85
+ return void 0;
86
+ }
87
+ this.active.set(key, current + 1);
88
+ let released = false;
89
+ return () => {
90
+ if (released) {
91
+ return;
92
+ }
93
+ released = true;
94
+ const latest = this.active.get(key) ?? 0;
95
+ if (latest <= 1) {
96
+ this.active.delete(key);
97
+ return;
98
+ }
99
+ this.active.set(key, latest - 1);
100
+ };
101
+ }
102
+ };
103
+
104
+ // src/requestIdentity.ts
105
+ function getRequestIp(req) {
106
+ const forwarded = req.get("x-forwarded-for");
107
+ if (forwarded) {
108
+ return forwarded.split(",")[0]?.trim() || req.ip || "unknown";
109
+ }
110
+ return req.ip || "unknown";
111
+ }
76
112
 
77
113
  // src/chat.ts
78
114
  var CHAT_SYSTEM_PROMPT = `You are a demo assistant for the FDIC BankFind MCP Server. You help users
@@ -195,13 +231,6 @@ function mapMessagesToContents(messages) {
195
231
  parts: [{ text: message.content }]
196
232
  }));
197
233
  }
198
- function getRequestIp(req) {
199
- const forwarded = req.get("x-forwarded-for");
200
- if (forwarded) {
201
- return forwarded.split(",")[0]?.trim() || req.ip || "unknown";
202
- }
203
- return req.ip || "unknown";
204
- }
205
234
  function getResponseParts(response) {
206
235
  return response.candidates?.[0]?.content?.parts ?? [];
207
236
  }
@@ -40540,6 +40569,21 @@ function parseAllowedOrigins(rawOrigins, port) {
40540
40569
  }
40541
40570
  var DEFAULT_SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
40542
40571
  var DEFAULT_SESSION_SWEEP_INTERVAL_MS = 5 * 60 * 1e3;
40572
+ var DEFAULT_MCP_RATE_LIMIT_MAX_REQUESTS = 120;
40573
+ var DEFAULT_MCP_RATE_LIMIT_WINDOW_MS = 6e4;
40574
+ var DEFAULT_MCP_STREAM_RATE_LIMIT_MAX_REQUESTS = 2;
40575
+ var DEFAULT_MCP_STREAM_RATE_LIMIT_WINDOW_MS = 60 * 60 * 1e3;
40576
+ var DEFAULT_MCP_MAX_CONCURRENT_STREAMS_PER_IP = 1;
40577
+ function parsePositiveInteger(rawValue, fallback, name) {
40578
+ if (rawValue === void 0 || rawValue.trim().length === 0) {
40579
+ return fallback;
40580
+ }
40581
+ const value = Number.parseInt(rawValue, 10);
40582
+ if (!Number.isInteger(value) || value < 1) {
40583
+ throw new Error(`${name} must be a positive integer. Received: ${rawValue}`);
40584
+ }
40585
+ return value;
40586
+ }
40543
40587
  async function closeSession(sessions, sessionId) {
40544
40588
  const session = sessions.get(sessionId);
40545
40589
  if (!session) {
@@ -40575,6 +40619,17 @@ function sendInvalidSessionResponse(res) {
40575
40619
  id: null
40576
40620
  });
40577
40621
  }
40622
+ function sendMcpRateLimitResponse(res, retryAfterSeconds) {
40623
+ res.setHeader("Retry-After", String(retryAfterSeconds));
40624
+ res.status(429).json({
40625
+ jsonrpc: "2.0",
40626
+ error: {
40627
+ code: -32e3,
40628
+ message: "Rate limit exceeded. Try again shortly."
40629
+ },
40630
+ id: null
40631
+ });
40632
+ }
40578
40633
  function createApp(options = {}) {
40579
40634
  const app = (0, import_express2.default)();
40580
40635
  const serverFactory = options.serverFactory ?? (() => createServer());
@@ -40585,6 +40640,29 @@ function createApp(options = {}) {
40585
40640
  const sessionIdleTimeoutMs = options.sessionIdleTimeoutMs ?? DEFAULT_SESSION_IDLE_TIMEOUT_MS;
40586
40641
  const sessionSweepIntervalMs = options.sessionSweepIntervalMs ?? DEFAULT_SESSION_SWEEP_INTERVAL_MS;
40587
40642
  const stateless = options.stateless ?? process.env.FDIC_MCP_STATELESS_HTTP === "true";
40643
+ const mcpRateLimiter = options.mcpRateLimiter ?? new RateLimiter({
40644
+ maxRequests: parsePositiveInteger(
40645
+ process.env.MCP_RATE_LIMIT_MAX_REQUESTS_PER_MINUTE,
40646
+ DEFAULT_MCP_RATE_LIMIT_MAX_REQUESTS,
40647
+ "MCP_RATE_LIMIT_MAX_REQUESTS_PER_MINUTE"
40648
+ ),
40649
+ windowMs: DEFAULT_MCP_RATE_LIMIT_WINDOW_MS
40650
+ });
40651
+ const mcpStreamRateLimiter = options.mcpStreamRateLimiter ?? new RateLimiter({
40652
+ maxRequests: parsePositiveInteger(
40653
+ process.env.MCP_STREAM_RATE_LIMIT_MAX_REQUESTS_PER_HOUR,
40654
+ DEFAULT_MCP_STREAM_RATE_LIMIT_MAX_REQUESTS,
40655
+ "MCP_STREAM_RATE_LIMIT_MAX_REQUESTS_PER_HOUR"
40656
+ ),
40657
+ windowMs: DEFAULT_MCP_STREAM_RATE_LIMIT_WINDOW_MS
40658
+ });
40659
+ const mcpStreamConcurrentLimiter = options.mcpStreamConcurrentLimiter ?? new ConcurrentLimiter(
40660
+ parsePositiveInteger(
40661
+ process.env.MCP_MAX_CONCURRENT_STREAMS_PER_IP,
40662
+ DEFAULT_MCP_MAX_CONCURRENT_STREAMS_PER_IP,
40663
+ "MCP_MAX_CONCURRENT_STREAMS_PER_IP"
40664
+ )
40665
+ );
40588
40666
  app.use(import_express2.default.json());
40589
40667
  if (!stateless) {
40590
40668
  const sessionSweepTimer = setInterval(() => {
@@ -40597,6 +40675,31 @@ function createApp(options = {}) {
40597
40675
  res.json({ status: "ok", server: "fdic-mcp-server", version: VERSION });
40598
40676
  });
40599
40677
  app.all("/mcp", async (req, res) => {
40678
+ const requestIp = getRequestIp(req);
40679
+ if (!mcpRateLimiter.check(requestIp)) {
40680
+ sendMcpRateLimitResponse(
40681
+ res,
40682
+ Math.ceil(DEFAULT_MCP_RATE_LIMIT_WINDOW_MS / 1e3)
40683
+ );
40684
+ return;
40685
+ }
40686
+ if (req.method === "GET") {
40687
+ const releaseStream = mcpStreamConcurrentLimiter.acquire(requestIp);
40688
+ if (!releaseStream) {
40689
+ sendMcpRateLimitResponse(res, 60);
40690
+ return;
40691
+ }
40692
+ if (!mcpStreamRateLimiter.check(requestIp)) {
40693
+ releaseStream();
40694
+ sendMcpRateLimitResponse(
40695
+ res,
40696
+ Math.ceil(DEFAULT_MCP_STREAM_RATE_LIMIT_WINDOW_MS / 1e3)
40697
+ );
40698
+ return;
40699
+ }
40700
+ res.once("close", releaseStream);
40701
+ res.once("finish", releaseStream);
40702
+ }
40600
40703
  if (stateless) {
40601
40704
  let server;
40602
40705
  let transport;
package/dist/server.js CHANGED
@@ -47,7 +47,7 @@ var import_types = require("@modelcontextprotocol/sdk/types.js");
47
47
  var import_express2 = __toESM(require("express"));
48
48
 
49
49
  // src/constants.ts
50
- var VERSION = true ? "1.30.0" : process.env.npm_package_version ?? "0.0.0-dev";
50
+ var VERSION = true ? "1.30.1" : process.env.npm_package_version ?? "0.0.0-dev";
51
51
  var FDIC_API_BASE_URL = "https://banks.data.fdic.gov/api";
52
52
  var CHARACTER_LIMIT = 5e4;
53
53
  var DEFAULT_FDIC_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
@@ -88,6 +88,42 @@ var RateLimiter = class {
88
88
  return true;
89
89
  }
90
90
  };
91
+ var ConcurrentLimiter = class {
92
+ maxConcurrent;
93
+ active = /* @__PURE__ */ new Map();
94
+ constructor(maxConcurrent) {
95
+ this.maxConcurrent = maxConcurrent;
96
+ }
97
+ acquire(key) {
98
+ const current = this.active.get(key) ?? 0;
99
+ if (current >= this.maxConcurrent) {
100
+ return void 0;
101
+ }
102
+ this.active.set(key, current + 1);
103
+ let released = false;
104
+ return () => {
105
+ if (released) {
106
+ return;
107
+ }
108
+ released = true;
109
+ const latest = this.active.get(key) ?? 0;
110
+ if (latest <= 1) {
111
+ this.active.delete(key);
112
+ return;
113
+ }
114
+ this.active.set(key, latest - 1);
115
+ };
116
+ }
117
+ };
118
+
119
+ // src/requestIdentity.ts
120
+ function getRequestIp(req) {
121
+ const forwarded = req.get("x-forwarded-for");
122
+ if (forwarded) {
123
+ return forwarded.split(",")[0]?.trim() || req.ip || "unknown";
124
+ }
125
+ return req.ip || "unknown";
126
+ }
91
127
 
92
128
  // src/chat.ts
93
129
  var CHAT_SYSTEM_PROMPT = `You are a demo assistant for the FDIC BankFind MCP Server. You help users
@@ -210,13 +246,6 @@ function mapMessagesToContents(messages) {
210
246
  parts: [{ text: message.content }]
211
247
  }));
212
248
  }
213
- function getRequestIp(req) {
214
- const forwarded = req.get("x-forwarded-for");
215
- if (forwarded) {
216
- return forwarded.split(",")[0]?.trim() || req.ip || "unknown";
217
- }
218
- return req.ip || "unknown";
219
- }
220
249
  function getResponseParts(response) {
221
250
  return response.candidates?.[0]?.content?.parts ?? [];
222
251
  }
@@ -40555,6 +40584,21 @@ function parseAllowedOrigins(rawOrigins, port) {
40555
40584
  }
40556
40585
  var DEFAULT_SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
40557
40586
  var DEFAULT_SESSION_SWEEP_INTERVAL_MS = 5 * 60 * 1e3;
40587
+ var DEFAULT_MCP_RATE_LIMIT_MAX_REQUESTS = 120;
40588
+ var DEFAULT_MCP_RATE_LIMIT_WINDOW_MS = 6e4;
40589
+ var DEFAULT_MCP_STREAM_RATE_LIMIT_MAX_REQUESTS = 2;
40590
+ var DEFAULT_MCP_STREAM_RATE_LIMIT_WINDOW_MS = 60 * 60 * 1e3;
40591
+ var DEFAULT_MCP_MAX_CONCURRENT_STREAMS_PER_IP = 1;
40592
+ function parsePositiveInteger(rawValue, fallback, name) {
40593
+ if (rawValue === void 0 || rawValue.trim().length === 0) {
40594
+ return fallback;
40595
+ }
40596
+ const value = Number.parseInt(rawValue, 10);
40597
+ if (!Number.isInteger(value) || value < 1) {
40598
+ throw new Error(`${name} must be a positive integer. Received: ${rawValue}`);
40599
+ }
40600
+ return value;
40601
+ }
40558
40602
  async function closeSession(sessions, sessionId) {
40559
40603
  const session = sessions.get(sessionId);
40560
40604
  if (!session) {
@@ -40590,6 +40634,17 @@ function sendInvalidSessionResponse(res) {
40590
40634
  id: null
40591
40635
  });
40592
40636
  }
40637
+ function sendMcpRateLimitResponse(res, retryAfterSeconds) {
40638
+ res.setHeader("Retry-After", String(retryAfterSeconds));
40639
+ res.status(429).json({
40640
+ jsonrpc: "2.0",
40641
+ error: {
40642
+ code: -32e3,
40643
+ message: "Rate limit exceeded. Try again shortly."
40644
+ },
40645
+ id: null
40646
+ });
40647
+ }
40593
40648
  function createApp(options = {}) {
40594
40649
  const app = (0, import_express2.default)();
40595
40650
  const serverFactory = options.serverFactory ?? (() => createServer());
@@ -40600,6 +40655,29 @@ function createApp(options = {}) {
40600
40655
  const sessionIdleTimeoutMs = options.sessionIdleTimeoutMs ?? DEFAULT_SESSION_IDLE_TIMEOUT_MS;
40601
40656
  const sessionSweepIntervalMs = options.sessionSweepIntervalMs ?? DEFAULT_SESSION_SWEEP_INTERVAL_MS;
40602
40657
  const stateless = options.stateless ?? process.env.FDIC_MCP_STATELESS_HTTP === "true";
40658
+ const mcpRateLimiter = options.mcpRateLimiter ?? new RateLimiter({
40659
+ maxRequests: parsePositiveInteger(
40660
+ process.env.MCP_RATE_LIMIT_MAX_REQUESTS_PER_MINUTE,
40661
+ DEFAULT_MCP_RATE_LIMIT_MAX_REQUESTS,
40662
+ "MCP_RATE_LIMIT_MAX_REQUESTS_PER_MINUTE"
40663
+ ),
40664
+ windowMs: DEFAULT_MCP_RATE_LIMIT_WINDOW_MS
40665
+ });
40666
+ const mcpStreamRateLimiter = options.mcpStreamRateLimiter ?? new RateLimiter({
40667
+ maxRequests: parsePositiveInteger(
40668
+ process.env.MCP_STREAM_RATE_LIMIT_MAX_REQUESTS_PER_HOUR,
40669
+ DEFAULT_MCP_STREAM_RATE_LIMIT_MAX_REQUESTS,
40670
+ "MCP_STREAM_RATE_LIMIT_MAX_REQUESTS_PER_HOUR"
40671
+ ),
40672
+ windowMs: DEFAULT_MCP_STREAM_RATE_LIMIT_WINDOW_MS
40673
+ });
40674
+ const mcpStreamConcurrentLimiter = options.mcpStreamConcurrentLimiter ?? new ConcurrentLimiter(
40675
+ parsePositiveInteger(
40676
+ process.env.MCP_MAX_CONCURRENT_STREAMS_PER_IP,
40677
+ DEFAULT_MCP_MAX_CONCURRENT_STREAMS_PER_IP,
40678
+ "MCP_MAX_CONCURRENT_STREAMS_PER_IP"
40679
+ )
40680
+ );
40603
40681
  app.use(import_express2.default.json());
40604
40682
  if (!stateless) {
40605
40683
  const sessionSweepTimer = setInterval(() => {
@@ -40612,6 +40690,31 @@ function createApp(options = {}) {
40612
40690
  res.json({ status: "ok", server: "fdic-mcp-server", version: VERSION });
40613
40691
  });
40614
40692
  app.all("/mcp", async (req, res) => {
40693
+ const requestIp = getRequestIp(req);
40694
+ if (!mcpRateLimiter.check(requestIp)) {
40695
+ sendMcpRateLimitResponse(
40696
+ res,
40697
+ Math.ceil(DEFAULT_MCP_RATE_LIMIT_WINDOW_MS / 1e3)
40698
+ );
40699
+ return;
40700
+ }
40701
+ if (req.method === "GET") {
40702
+ const releaseStream = mcpStreamConcurrentLimiter.acquire(requestIp);
40703
+ if (!releaseStream) {
40704
+ sendMcpRateLimitResponse(res, 60);
40705
+ return;
40706
+ }
40707
+ if (!mcpStreamRateLimiter.check(requestIp)) {
40708
+ releaseStream();
40709
+ sendMcpRateLimitResponse(
40710
+ res,
40711
+ Math.ceil(DEFAULT_MCP_STREAM_RATE_LIMIT_WINDOW_MS / 1e3)
40712
+ );
40713
+ return;
40714
+ }
40715
+ res.once("close", releaseStream);
40716
+ res.once("finish", releaseStream);
40717
+ }
40615
40718
  if (stateless) {
40616
40719
  let server;
40617
40720
  let transport;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fdic-mcp-server",
3
- "version": "1.30.0",
3
+ "version": "1.30.1",
4
4
  "description": "MCP server for the FDIC BankFind Suite API",
5
5
  "mcpName": "io.github.jflamb/fdic-mcp-server",
6
6
  "main": "dist/server.js",