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 +111 -8
- package/dist/server.js +111 -8
- package/package.json +1 -1
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.
|
|
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.
|
|
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;
|