@leanmcp/core 0.3.18 → 0.4.0

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.mjs CHANGED
@@ -1,5 +1,12 @@
1
- var __defProp = Object.defineProperty;
2
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
1
+ import {
2
+ DEFAULT_TABLE_NAME,
3
+ DEFAULT_TTL_SECONDS,
4
+ DynamoDBSessionStore,
5
+ LogLevel,
6
+ Logger,
7
+ __name,
8
+ defaultLogger
9
+ } from "./chunk-7SU7UAUW.mjs";
3
10
 
4
11
  // src/index.ts
5
12
  import "reflect-metadata";
@@ -285,119 +292,6 @@ __name(classToJsonSchemaWithConstraints, "classToJsonSchemaWithConstraints");
285
292
  // src/http-server.ts
286
293
  import { randomUUID } from "crypto";
287
294
 
288
- // src/logger.ts
289
- var LogLevel = /* @__PURE__ */ (function(LogLevel2) {
290
- LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
291
- LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
292
- LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
293
- LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
294
- LogLevel2[LogLevel2["NONE"] = 4] = "NONE";
295
- return LogLevel2;
296
- })({});
297
- var COLORS = {
298
- reset: "\x1B[0m",
299
- gray: "\x1B[38;5;244m",
300
- blue: "\x1B[1;34m",
301
- amber: "\x1B[38;5;214m",
302
- red: "\x1B[1;31m"
303
- };
304
- var levelStyles = {
305
- [0]: {
306
- label: "DEBUG",
307
- color: COLORS.gray
308
- },
309
- [1]: {
310
- label: "INFO",
311
- color: COLORS.blue
312
- },
313
- [2]: {
314
- label: "WARN",
315
- color: COLORS.amber
316
- },
317
- [3]: {
318
- label: "ERROR",
319
- color: COLORS.red
320
- },
321
- [4]: {
322
- label: "NONE",
323
- color: COLORS.gray
324
- }
325
- };
326
- var Logger = class {
327
- static {
328
- __name(this, "Logger");
329
- }
330
- level;
331
- prefix;
332
- timestamps;
333
- colorize;
334
- context;
335
- handlers;
336
- constructor(options = {}) {
337
- this.level = options.level ?? 1;
338
- this.prefix = options.prefix ?? "";
339
- this.timestamps = options.timestamps ?? true;
340
- this.colorize = options.colorize ?? true;
341
- this.context = options.context;
342
- this.handlers = options.handlers ?? [];
343
- }
344
- format(level, message) {
345
- const style = levelStyles[level];
346
- const timestamp = this.timestamps ? `[${(/* @__PURE__ */ new Date()).toISOString()}]` : "";
347
- const prefix = this.prefix ? `[${this.prefix}]` : "";
348
- const context = this.context ? `[${this.context}]` : "";
349
- const label = `[${style.label}]`;
350
- const parts = `${timestamp}${prefix}${context}${label} ${message}`;
351
- if (!this.colorize) return parts;
352
- return `${style.color}${parts}${COLORS.reset}`;
353
- }
354
- shouldLog(level) {
355
- return level >= this.level && this.level !== 4;
356
- }
357
- emit(level, message, consoleFn, ...args) {
358
- if (!this.shouldLog(level)) return;
359
- const payload = {
360
- level,
361
- levelLabel: levelStyles[level].label,
362
- message,
363
- args,
364
- prefix: this.prefix,
365
- context: this.context,
366
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
367
- };
368
- consoleFn(this.format(level, message), ...args);
369
- this.handlers.forEach((handler) => {
370
- try {
371
- handler(payload);
372
- } catch (err) {
373
- console.debug("Logger handler error", err);
374
- }
375
- });
376
- }
377
- debug(message, ...args) {
378
- this.emit(0, message, console.debug, ...args);
379
- }
380
- info(message, ...args) {
381
- this.emit(1, message, console.info, ...args);
382
- }
383
- warn(message, ...args) {
384
- this.emit(2, message, console.warn, ...args);
385
- }
386
- error(message, ...args) {
387
- this.emit(3, message, console.error, ...args);
388
- }
389
- setLevel(level) {
390
- this.level = level;
391
- }
392
- getLevel() {
393
- return this.level;
394
- }
395
- };
396
- var defaultLogger = new Logger({
397
- level: 1,
398
- prefix: "LeanMCP"
399
- });
400
-
401
295
  // src/validation.ts
402
296
  function validatePort(port) {
403
297
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
@@ -520,7 +414,7 @@ async function createHTTPServer(serverInput, options) {
520
414
  auth: serverOptions.auth
521
415
  };
522
416
  }
523
- const [express, { StreamableHTTPServerTransport }, cors] = await Promise.all([
417
+ const [express, { StreamableHTTPServerTransport: StreamableHTTPServerTransport2 }, cors] = await Promise.all([
524
418
  // @ts-ignore
525
419
  import("express").catch(() => {
526
420
  throw new Error("Express not found. Install with: npm install express @types/express");
@@ -635,6 +529,19 @@ async function createHTTPServer(serverInput, options) {
635
529
  app.use(express.json());
636
530
  const isStateless = httpOptions.stateless !== false;
637
531
  console.log(`Starting LeanMCP HTTP Server (${isStateless ? "STATELESS" : "STATEFUL"})...`);
532
+ if (!isStateless && !httpOptions.sessionStore) {
533
+ if (process.env.LEANMCP_LAMBDA === "true") {
534
+ try {
535
+ const { DynamoDBSessionStore: DynamoDBSessionStore2 } = await import("./dynamodb-session-store-WJ2ALBES.mjs");
536
+ httpOptions.sessionStore = new DynamoDBSessionStore2({
537
+ logging: httpOptions.logging
538
+ });
539
+ logger.info("Auto-configured DynamoDB session store for LeanMCP Lambda");
540
+ } catch (e) {
541
+ logger.warn(`Running on LeanMCP Lambda but failed to initialize DynamoDB session store: ${e.message}`);
542
+ }
543
+ }
544
+ }
638
545
  const DASHBOARD_URL = process.env.DASHBOARD_URL || "https://s3-dashboard-build.s3.us-west-2.amazonaws.com/out/index.html";
639
546
  let cachedDashboard = null;
640
547
  let cacheTimestamp = 0;
@@ -776,19 +683,72 @@ async function createHTTPServer(serverInput, options) {
776
683
  if (sessionId && transports[sessionId]) {
777
684
  transport = transports[sessionId];
778
685
  logger.debug(`Reusing session: ${sessionId}`);
686
+ } else if (sessionId && !isInitializeRequest(req.body)) {
687
+ logger.info(`Transport missing for session ${sessionId}, checking session store...`);
688
+ if (httpOptions.sessionStore) {
689
+ const exists = await httpOptions.sessionStore.sessionExists(sessionId);
690
+ if (!exists) {
691
+ res.status(404).json({
692
+ jsonrpc: "2.0",
693
+ error: {
694
+ code: -32001,
695
+ message: "Session not found"
696
+ },
697
+ id: req.body?.id || null
698
+ });
699
+ return;
700
+ }
701
+ transport = new StreamableHTTPServerTransport2({
702
+ sessionIdGenerator: /* @__PURE__ */ __name(() => sessionId, "sessionIdGenerator"),
703
+ onsessioninitialized: /* @__PURE__ */ __name((sid) => {
704
+ transports[sid] = transport;
705
+ logger.info(`Transport recreated for session: ${sid}`);
706
+ }, "onsessioninitialized")
707
+ });
708
+ transport.onclose = async () => {
709
+ if (transport.sessionId) {
710
+ delete transports[transport.sessionId];
711
+ logger.debug(`Session cleaned up: ${transport.sessionId}`);
712
+ if (httpOptions.sessionStore) {
713
+ await httpOptions.sessionStore.deleteSession(transport.sessionId);
714
+ }
715
+ }
716
+ };
717
+ const freshServer = await serverFactory();
718
+ if (freshServer && typeof freshServer.waitForInit === "function") {
719
+ await freshServer.waitForInit();
720
+ }
721
+ await freshServer.connect(transport);
722
+ } else {
723
+ res.status(400).json({
724
+ jsonrpc: "2.0",
725
+ error: {
726
+ code: -32e3,
727
+ message: "Session expired (no session store configured)"
728
+ },
729
+ id: req.body?.id || null
730
+ });
731
+ return;
732
+ }
779
733
  } else if (!sessionId && isInitializeRequest(req.body)) {
780
734
  logger.info("Creating new MCP session...");
781
- transport = new StreamableHTTPServerTransport({
735
+ transport = new StreamableHTTPServerTransport2({
782
736
  sessionIdGenerator: /* @__PURE__ */ __name(() => randomUUID(), "sessionIdGenerator"),
783
- onsessioninitialized: /* @__PURE__ */ __name((newSessionId) => {
737
+ onsessioninitialized: /* @__PURE__ */ __name(async (newSessionId) => {
784
738
  transports[newSessionId] = transport;
785
739
  logger.info(`Session initialized: ${newSessionId}`);
740
+ if (httpOptions.sessionStore) {
741
+ await httpOptions.sessionStore.createSession(newSessionId);
742
+ }
786
743
  }, "onsessioninitialized")
787
744
  });
788
- transport.onclose = () => {
745
+ transport.onclose = async () => {
789
746
  if (transport.sessionId) {
790
747
  delete transports[transport.sessionId];
791
748
  logger.debug(`Session cleaned up: ${transport.sessionId}`);
749
+ if (httpOptions.sessionStore) {
750
+ await httpOptions.sessionStore.deleteSession(transport.sessionId);
751
+ }
792
752
  }
793
753
  };
794
754
  if (!mcpServer) {
@@ -844,7 +804,7 @@ async function createHTTPServer(serverInput, options) {
844
804
  if (freshServer && typeof freshServer.waitForInit === "function") {
845
805
  await freshServer.waitForInit();
846
806
  }
847
- const transport = new StreamableHTTPServerTransport({
807
+ const transport = new StreamableHTTPServerTransport2({
848
808
  sessionIdGenerator: void 0
849
809
  });
850
810
  await freshServer.connect(transport);
@@ -1030,6 +990,129 @@ function createProtectedResourceMetadata(options) {
1030
990
  }
1031
991
  __name(createProtectedResourceMetadata, "createProtectedResourceMetadata");
1032
992
 
993
+ // src/session-provider.ts
994
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
995
+ var LeanMCPSessionProvider = class {
996
+ static {
997
+ __name(this, "LeanMCPSessionProvider");
998
+ }
999
+ transports = /* @__PURE__ */ new Map();
1000
+ sessionStore;
1001
+ constructor(options) {
1002
+ if (options?.sessionStore) {
1003
+ this.sessionStore = options.sessionStore;
1004
+ } else {
1005
+ this.sessionStore = new DynamoDBSessionStore({
1006
+ tableName: options?.tableName,
1007
+ region: options?.region,
1008
+ ttlSeconds: options?.ttlSeconds,
1009
+ logging: options?.logging
1010
+ });
1011
+ }
1012
+ }
1013
+ /**
1014
+ * Get transport from memory
1015
+ */
1016
+ get(sessionId) {
1017
+ return this.transports.get(sessionId);
1018
+ }
1019
+ /**
1020
+ * Check if session exists (memory or DynamoDB)
1021
+ */
1022
+ async has(sessionId) {
1023
+ if (this.transports.has(sessionId)) return true;
1024
+ return this.sessionStore.sessionExists(sessionId);
1025
+ }
1026
+ /**
1027
+ * Store transport and create session in DynamoDB
1028
+ */
1029
+ async set(sessionId, transport) {
1030
+ this.transports.set(sessionId, transport);
1031
+ await this.sessionStore.createSession(sessionId);
1032
+ }
1033
+ /**
1034
+ * Delete transport and session
1035
+ */
1036
+ async delete(sessionId) {
1037
+ this.transports.delete(sessionId);
1038
+ await this.sessionStore.deleteSession(sessionId);
1039
+ }
1040
+ /**
1041
+ * Get or recreate transport for a session
1042
+ * This is the key method for Lambda support - handles container recycling
1043
+ *
1044
+ * @param sessionId - Session ID to get or recreate
1045
+ * @param serverFactory - Factory function to create fresh MCP server instances
1046
+ * @param transportOptions - Optional callbacks for transport lifecycle events
1047
+ * @returns Transport instance or null if session doesn't exist
1048
+ */
1049
+ async getOrRecreate(sessionId, serverFactory, transportOptions) {
1050
+ const existing = this.transports.get(sessionId);
1051
+ if (existing) return existing;
1052
+ const exists = await this.sessionStore.sessionExists(sessionId);
1053
+ if (!exists) return null;
1054
+ const transport = new StreamableHTTPServerTransport({
1055
+ sessionIdGenerator: /* @__PURE__ */ __name(() => sessionId, "sessionIdGenerator"),
1056
+ onsessioninitialized: /* @__PURE__ */ __name((sid) => {
1057
+ this.transports.set(sid, transport);
1058
+ transportOptions?.onsessioninitialized?.(sid);
1059
+ }, "onsessioninitialized")
1060
+ });
1061
+ transport.onclose = () => {
1062
+ this.transports.delete(sessionId);
1063
+ transportOptions?.onclose?.();
1064
+ };
1065
+ const server = await serverFactory();
1066
+ await server.connect(transport);
1067
+ return transport;
1068
+ }
1069
+ /**
1070
+ * Get session data from DynamoDB
1071
+ */
1072
+ async getSessionData(sessionId) {
1073
+ const session = await this.sessionStore.getSession(sessionId);
1074
+ return session?.data || null;
1075
+ }
1076
+ /**
1077
+ * Update session data in DynamoDB
1078
+ */
1079
+ async updateSessionData(sessionId, data) {
1080
+ await this.sessionStore.updateSession(sessionId, {
1081
+ data
1082
+ });
1083
+ }
1084
+ /**
1085
+ * Get number of in-memory transports
1086
+ */
1087
+ get size() {
1088
+ return this.transports.size;
1089
+ }
1090
+ /**
1091
+ * Get all session IDs in memory
1092
+ */
1093
+ keys() {
1094
+ return this.transports.keys();
1095
+ }
1096
+ /**
1097
+ * Get all transports in memory
1098
+ */
1099
+ values() {
1100
+ return this.transports.values();
1101
+ }
1102
+ /**
1103
+ * Iterate over all sessions in memory
1104
+ */
1105
+ entries() {
1106
+ return this.transports.entries();
1107
+ }
1108
+ /**
1109
+ * Clear all in-memory transports (does not affect DynamoDB)
1110
+ */
1111
+ clear() {
1112
+ this.transports.clear();
1113
+ }
1114
+ };
1115
+
1033
1116
  // src/index.ts
1034
1117
  var ajv = new Ajv();
1035
1118
  var MCPServer = class {
@@ -2031,7 +2114,11 @@ async function startMCPServer(options) {
2031
2114
  __name(startMCPServer, "startMCPServer");
2032
2115
  export {
2033
2116
  Auth,
2117
+ DEFAULT_TABLE_NAME,
2118
+ DEFAULT_TTL_SECONDS,
2034
2119
  Deprecated,
2120
+ DynamoDBSessionStore,
2121
+ LeanMCPSessionProvider,
2035
2122
  LogLevel,
2036
2123
  Logger,
2037
2124
  MCPServer,