@peac/mcp-server 0.16.4 → 0.16.5

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/cli.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  import { randomUUID } from 'crypto';
3
3
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
4
  import { createServer } from 'http';
5
+ import { SUPPORTED_PROTOCOL_VERSIONS as SUPPORTED_PROTOCOL_VERSIONS$1 } from '@modelcontextprotocol/sdk/types.js';
5
6
  import { stat, realpath, readFile, mkdir, writeFile, rm, mkdtemp, rename, lstat } from 'fs/promises';
6
7
  import { Command } from 'commander';
7
8
  import { z } from 'zod';
@@ -28,7 +29,7 @@ var SERVER_NAME, SERVER_VERSION, MCP_PROTOCOL_VERSION;
28
29
  var init_constants = __esm({
29
30
  "src/infra/constants.ts"() {
30
31
  SERVER_NAME = "peac-mcp-server";
31
- SERVER_VERSION = "0.16.4";
32
+ SERVER_VERSION = "0.16.5";
32
33
  MCP_PROTOCOL_VERSION = "2025-11-25";
33
34
  }
34
35
  });
@@ -93,6 +94,11 @@ var init_session_manager = __esm({
93
94
  }
94
95
  return entry;
95
96
  }
97
+ /** Whether an unexpired session exists; unlike getSession(), does not refresh lastSeen. */
98
+ hasSession(sessionId) {
99
+ const entry = this.sessions.get(sessionId);
100
+ return entry !== void 0 && Date.now() - entry.lastSeen <= this.ttlMs;
101
+ }
96
102
  /** Terminate and clean up a specific session */
97
103
  async terminateSession(sessionId) {
98
104
  const entry = this.sessions.get(sessionId);
@@ -159,8 +165,17 @@ var init_session_manager = __esm({
159
165
  // src/http-transport.ts
160
166
  var http_transport_exports = {};
161
167
  __export(http_transport_exports, {
162
- createHttpTransport: () => createHttpTransport
168
+ MAX_RATE_LIMIT_KEYS: () => MAX_RATE_LIMIT_KEYS,
169
+ createHttpTransport: () => createHttpTransport,
170
+ rateLimitKeyFor: () => rateLimitKeyFor
163
171
  });
172
+ function checkProtocolVersion(req) {
173
+ const protocolVersion = req.headers["mcp-protocol-version"];
174
+ if (protocolVersion && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
175
+ return `Unsupported MCP protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`;
176
+ }
177
+ return null;
178
+ }
164
179
  function isValidSessionId(id) {
165
180
  if (id.length === 0 || id.length > MAX_SESSION_ID_LENGTH) return false;
166
181
  for (let i = 0; i < id.length; i++) {
@@ -169,6 +184,9 @@ function isValidSessionId(id) {
169
184
  }
170
185
  return true;
171
186
  }
187
+ function rateLimitKeyFor(sessionId, clientIp, isLiveSession) {
188
+ return sessionId !== void 0 && isValidSessionId(sessionId) && isLiveSession(sessionId) ? `session:${sessionId}` : `ip:${clientIp}`;
189
+ }
172
190
  function isLoopbackAddr(addr) {
173
191
  return addr.startsWith("127.") || addr === "::1" || addr === "::ffff:127.0.0.1";
174
192
  }
@@ -223,7 +241,12 @@ async function createHttpTransport(options) {
223
241
  const now = Date.now();
224
242
  let entry = rateLimits.get(key);
225
243
  if (!entry || now - entry.windowStart > ONE_MINUTE_MS) {
244
+ if (!entry && rateLimits.size >= MAX_RATE_LIMIT_KEYS) {
245
+ const oldest = rateLimits.keys().next().value;
246
+ if (oldest !== void 0) rateLimits.delete(oldest);
247
+ }
226
248
  entry = { count: 0, windowStart: now };
249
+ rateLimits.delete(key);
227
250
  rateLimits.set(key, entry);
228
251
  }
229
252
  entry.count++;
@@ -402,7 +425,11 @@ async function createHttpTransport(options) {
402
425
  }
403
426
  if (req.method === "POST") {
404
427
  const sessionId = req.headers["mcp-session-id"];
405
- const rateLimitKey = sessionId ?? `ip:${getClientIp(req)}`;
428
+ const rateLimitKey = rateLimitKeyFor(
429
+ sessionId,
430
+ getClientIp(req),
431
+ (id) => sessionManager.hasSession(id)
432
+ );
406
433
  if (!checkRateLimit(rateLimitKey)) {
407
434
  res.writeHead(429, { "Content-Type": "application/json", "Retry-After": "60" });
408
435
  res.end(JSON.stringify({ error: "Rate limit exceeded" }));
@@ -419,9 +446,9 @@ async function createHttpTransport(options) {
419
446
  }
420
447
  const isInit = typeof body === "object" && body !== null && "method" in body && body.method === "initialize";
421
448
  if (isInit) {
422
- const protocolVersion = req.headers["mcp-protocol-version"];
423
- if (protocolVersion && protocolVersion !== MCP_PROTOCOL_VERSION && protocolVersion !== "2025-03-26") {
424
- sendJson(res, 400, { error: `Unsupported MCP protocol version: ${protocolVersion}` });
449
+ const versionError2 = checkProtocolVersion(req);
450
+ if (versionError2) {
451
+ sendJson(res, 400, { error: versionError2 });
425
452
  return;
426
453
  }
427
454
  const accept = req.headers.accept;
@@ -442,6 +469,11 @@ async function createHttpTransport(options) {
442
469
  await entry2.transport.handleRequest(req, res, body);
443
470
  return;
444
471
  }
472
+ const versionError = checkProtocolVersion(req);
473
+ if (versionError) {
474
+ sendJson(res, 400, { error: versionError });
475
+ return;
476
+ }
445
477
  if (!sessionId) {
446
478
  sendJson(res, 400, { error: "Missing Mcp-Session-Id header" });
447
479
  return;
@@ -495,26 +527,27 @@ async function createHttpTransport(options) {
495
527
  ` Sessions: max ${options.maxSessions ?? 100} (${options.maxSessionsPerIp ?? 10}/IP), TTL ${(options.sessionTtlMs ?? 18e5) / 1e3}s
496
528
  `
497
529
  );
498
- process.stderr.write(` Rate limit: ${rateLimitRpm} req/min per session
530
+ process.stderr.write(` Rate limit: ${rateLimitRpm} req/min per session or client address
499
531
  `);
500
532
  process.stderr.write(
501
533
  ` CORS: ${corsOrigins.size > 0 ? [...corsOrigins].join(", ") : "deny all"}
502
534
  `
503
535
  );
504
536
  process.stderr.write(
505
- ` Mode: unprotected (token validation deferred to v0.11.x+; deployer auth via reverse proxy)
537
+ ` Mode: unprotected (no token validation; deploy behind an authenticating reverse proxy)
506
538
  `
507
539
  );
508
540
  resolve2({
509
541
  cleanup: async () => {
510
542
  await sessionManager.cleanup();
511
543
  await new Promise((res) => server.close(() => res()));
512
- }
544
+ },
545
+ rateLimitKeyCount: () => rateLimits.size
513
546
  });
514
547
  });
515
548
  });
516
549
  }
517
- var ONE_MINUTE_MS, DEFAULT_MAX_REQUEST_BYTES, DEFAULT_RATE_LIMIT_RPM, MAX_SESSION_ID_LENGTH;
550
+ var ONE_MINUTE_MS, DEFAULT_MAX_REQUEST_BYTES, DEFAULT_RATE_LIMIT_RPM, MAX_SESSION_ID_LENGTH, MAX_RATE_LIMIT_KEYS, SUPPORTED_PROTOCOL_VERSIONS;
518
551
  var init_http_transport = __esm({
519
552
  "src/http-transport.ts"() {
520
553
  init_session_manager();
@@ -523,6 +556,8 @@ var init_http_transport = __esm({
523
556
  DEFAULT_MAX_REQUEST_BYTES = 1048576;
524
557
  DEFAULT_RATE_LIMIT_RPM = 100;
525
558
  MAX_SESSION_ID_LENGTH = 128;
559
+ MAX_RATE_LIMIT_KEYS = 1e4;
560
+ SUPPORTED_PROTOCOL_VERSIONS = SUPPORTED_PROTOCOL_VERSIONS$1;
526
561
  }
527
562
  });
528
563
 
@@ -1969,6 +2004,7 @@ var BUNDLE_TOOL_ANNOTATIONS = {
1969
2004
  openWorldHint: true
1970
2005
  };
1971
2006
  var MAX_INPUT_DEPTH = 10;
2007
+ var MAX_TOOL_INPUT_ELEMENTS = 1024;
1972
2008
  function errorCallToolResult(meta, code, message) {
1973
2009
  const errorDef = ERRORS[code];
1974
2010
  return {
@@ -2077,7 +2113,10 @@ function createPeacMcpServer(options) {
2077
2113
  { name: SERVER_NAME, version },
2078
2114
  // listChanged: false -- tool set is static per server instance (determined
2079
2115
  // at startup from CLI flags). The server never emits notifications/tools/list_changed.
2080
- { capabilities: { tools: { listChanged: false } } }
2116
+ {
2117
+ capabilities: { tools: { listChanged: false } },
2118
+ maxToolInputElements: MAX_TOOL_INPUT_ELEMENTS
2119
+ }
2081
2120
  );
2082
2121
  const register = server.registerTool.bind(server);
2083
2122
  register(