@leanmcp/core 0.3.19 → 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.
@@ -0,0 +1,272 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/dynamodb-session-store.ts
5
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
6
+ import { DynamoDBDocumentClient, GetCommand, PutCommand, DeleteCommand, UpdateCommand } from "@aws-sdk/lib-dynamodb";
7
+
8
+ // src/logger.ts
9
+ var LogLevel = /* @__PURE__ */ (function(LogLevel2) {
10
+ LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
11
+ LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
12
+ LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
13
+ LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
14
+ LogLevel2[LogLevel2["NONE"] = 4] = "NONE";
15
+ return LogLevel2;
16
+ })({});
17
+ var COLORS = {
18
+ reset: "\x1B[0m",
19
+ gray: "\x1B[38;5;244m",
20
+ blue: "\x1B[1;34m",
21
+ amber: "\x1B[38;5;214m",
22
+ red: "\x1B[1;31m"
23
+ };
24
+ var levelStyles = {
25
+ [0]: {
26
+ label: "DEBUG",
27
+ color: COLORS.gray
28
+ },
29
+ [1]: {
30
+ label: "INFO",
31
+ color: COLORS.blue
32
+ },
33
+ [2]: {
34
+ label: "WARN",
35
+ color: COLORS.amber
36
+ },
37
+ [3]: {
38
+ label: "ERROR",
39
+ color: COLORS.red
40
+ },
41
+ [4]: {
42
+ label: "NONE",
43
+ color: COLORS.gray
44
+ }
45
+ };
46
+ var Logger = class {
47
+ static {
48
+ __name(this, "Logger");
49
+ }
50
+ level;
51
+ prefix;
52
+ timestamps;
53
+ colorize;
54
+ context;
55
+ handlers;
56
+ constructor(options = {}) {
57
+ this.level = options.level ?? 1;
58
+ this.prefix = options.prefix ?? "";
59
+ this.timestamps = options.timestamps ?? true;
60
+ this.colorize = options.colorize ?? true;
61
+ this.context = options.context;
62
+ this.handlers = options.handlers ?? [];
63
+ }
64
+ format(level, message) {
65
+ const style = levelStyles[level];
66
+ const timestamp = this.timestamps ? `[${(/* @__PURE__ */ new Date()).toISOString()}]` : "";
67
+ const prefix = this.prefix ? `[${this.prefix}]` : "";
68
+ const context = this.context ? `[${this.context}]` : "";
69
+ const label = `[${style.label}]`;
70
+ const parts = `${timestamp}${prefix}${context}${label} ${message}`;
71
+ if (!this.colorize) return parts;
72
+ return `${style.color}${parts}${COLORS.reset}`;
73
+ }
74
+ shouldLog(level) {
75
+ return level >= this.level && this.level !== 4;
76
+ }
77
+ emit(level, message, consoleFn, ...args) {
78
+ if (!this.shouldLog(level)) return;
79
+ const payload = {
80
+ level,
81
+ levelLabel: levelStyles[level].label,
82
+ message,
83
+ args,
84
+ prefix: this.prefix,
85
+ context: this.context,
86
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
87
+ };
88
+ consoleFn(this.format(level, message), ...args);
89
+ this.handlers.forEach((handler) => {
90
+ try {
91
+ handler(payload);
92
+ } catch (err) {
93
+ console.debug("Logger handler error", err);
94
+ }
95
+ });
96
+ }
97
+ debug(message, ...args) {
98
+ this.emit(0, message, console.debug, ...args);
99
+ }
100
+ info(message, ...args) {
101
+ this.emit(1, message, console.info, ...args);
102
+ }
103
+ warn(message, ...args) {
104
+ this.emit(2, message, console.warn, ...args);
105
+ }
106
+ error(message, ...args) {
107
+ this.emit(3, message, console.error, ...args);
108
+ }
109
+ setLevel(level) {
110
+ this.level = level;
111
+ }
112
+ getLevel() {
113
+ return this.level;
114
+ }
115
+ };
116
+ var defaultLogger = new Logger({
117
+ level: 1,
118
+ prefix: "LeanMCP"
119
+ });
120
+
121
+ // src/dynamodb-session-store.ts
122
+ var DEFAULT_TABLE_NAME = "leanmcp-sessions";
123
+ var DEFAULT_TTL_SECONDS = 86400;
124
+ var DynamoDBSessionStore = class {
125
+ static {
126
+ __name(this, "DynamoDBSessionStore");
127
+ }
128
+ client;
129
+ tableName;
130
+ ttlSeconds;
131
+ logger;
132
+ constructor(options) {
133
+ this.tableName = options?.tableName || process.env.DYNAMODB_TABLE_NAME || DEFAULT_TABLE_NAME;
134
+ this.ttlSeconds = options?.ttlSeconds || DEFAULT_TTL_SECONDS;
135
+ this.logger = new Logger({
136
+ level: options?.logging ? LogLevel.INFO : LogLevel.NONE,
137
+ prefix: "DynamoDBSessionStore"
138
+ });
139
+ const dynamoClient = new DynamoDBClient({
140
+ region: options?.region || process.env.AWS_REGION || "us-east-1"
141
+ });
142
+ this.client = DynamoDBDocumentClient.from(dynamoClient);
143
+ this.logger.info(`Initialized with table: ${this.tableName}, TTL: ${this.ttlSeconds}s`);
144
+ }
145
+ /**
146
+ * Check if a session exists in DynamoDB
147
+ */
148
+ async sessionExists(sessionId) {
149
+ try {
150
+ const result = await this.client.send(new GetCommand({
151
+ TableName: this.tableName,
152
+ Key: {
153
+ sessionId
154
+ },
155
+ ProjectionExpression: "sessionId"
156
+ }));
157
+ const exists = !!result.Item;
158
+ this.logger.debug(`Session ${sessionId} exists: ${exists}`);
159
+ return exists;
160
+ } catch (error) {
161
+ this.logger.error(`Error checking session existence: ${error.message}`);
162
+ return false;
163
+ }
164
+ }
165
+ /**
166
+ * Create a new session in DynamoDB
167
+ */
168
+ async createSession(sessionId, data) {
169
+ try {
170
+ const now = /* @__PURE__ */ new Date();
171
+ const ttl = Math.floor(Date.now() / 1e3) + this.ttlSeconds;
172
+ await this.client.send(new PutCommand({
173
+ TableName: this.tableName,
174
+ Item: {
175
+ sessionId,
176
+ createdAt: now.toISOString(),
177
+ updatedAt: now.toISOString(),
178
+ ttl,
179
+ data: data || {}
180
+ }
181
+ }));
182
+ this.logger.info(`Created session: ${sessionId} (TTL: ${new Date(ttl * 1e3).toISOString()})`);
183
+ } catch (error) {
184
+ this.logger.error(`Error creating session ${sessionId}: ${error.message}`);
185
+ throw new Error(`Failed to create session: ${error.message}`);
186
+ }
187
+ }
188
+ /**
189
+ * Get session data from DynamoDB
190
+ */
191
+ async getSession(sessionId) {
192
+ try {
193
+ const result = await this.client.send(new GetCommand({
194
+ TableName: this.tableName,
195
+ Key: {
196
+ sessionId
197
+ }
198
+ }));
199
+ if (!result.Item) {
200
+ this.logger.debug(`Session ${sessionId} not found`);
201
+ return null;
202
+ }
203
+ const sessionData = {
204
+ sessionId: result.Item.sessionId,
205
+ createdAt: new Date(result.Item.createdAt),
206
+ updatedAt: new Date(result.Item.updatedAt),
207
+ ttl: result.Item.ttl,
208
+ data: result.Item.data
209
+ };
210
+ this.logger.debug(`Retrieved session: ${sessionId}`);
211
+ return sessionData;
212
+ } catch (error) {
213
+ this.logger.error(`Error getting session ${sessionId}: ${error.message}`);
214
+ return null;
215
+ }
216
+ }
217
+ /**
218
+ * Update session data in DynamoDB
219
+ * Automatically refreshes TTL on each update
220
+ */
221
+ async updateSession(sessionId, updates) {
222
+ try {
223
+ const ttl = Math.floor(Date.now() / 1e3) + this.ttlSeconds;
224
+ await this.client.send(new UpdateCommand({
225
+ TableName: this.tableName,
226
+ Key: {
227
+ sessionId
228
+ },
229
+ UpdateExpression: "SET updatedAt = :now, #data = :data, #ttl = :ttl",
230
+ ExpressionAttributeNames: {
231
+ "#data": "data",
232
+ "#ttl": "ttl"
233
+ },
234
+ ExpressionAttributeValues: {
235
+ ":now": (/* @__PURE__ */ new Date()).toISOString(),
236
+ ":data": updates.data || {},
237
+ ":ttl": ttl
238
+ }
239
+ }));
240
+ this.logger.debug(`Updated session: ${sessionId}`);
241
+ } catch (error) {
242
+ this.logger.error(`Error updating session ${sessionId}: ${error.message}`);
243
+ throw new Error(`Failed to update session: ${error.message}`);
244
+ }
245
+ }
246
+ /**
247
+ * Delete a session from DynamoDB
248
+ */
249
+ async deleteSession(sessionId) {
250
+ try {
251
+ await this.client.send(new DeleteCommand({
252
+ TableName: this.tableName,
253
+ Key: {
254
+ sessionId
255
+ }
256
+ }));
257
+ this.logger.info(`Deleted session: ${sessionId}`);
258
+ } catch (error) {
259
+ this.logger.error(`Error deleting session ${sessionId}: ${error.message}`);
260
+ }
261
+ }
262
+ };
263
+
264
+ export {
265
+ __name,
266
+ LogLevel,
267
+ Logger,
268
+ defaultLogger,
269
+ DEFAULT_TABLE_NAME,
270
+ DEFAULT_TTL_SECONDS,
271
+ DynamoDBSessionStore
272
+ };
@@ -0,0 +1,13 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
9
+
10
+ export {
11
+ __name,
12
+ __require
13
+ };
@@ -0,0 +1,10 @@
1
+ import {
2
+ DEFAULT_TABLE_NAME,
3
+ DEFAULT_TTL_SECONDS,
4
+ DynamoDBSessionStore
5
+ } from "./chunk-7SU7UAUW.mjs";
6
+ export {
7
+ DEFAULT_TABLE_NAME,
8
+ DEFAULT_TTL_SECONDS,
9
+ DynamoDBSessionStore
10
+ };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
2
3
 
3
4
  /**
4
5
  * Simple configurable logger for LeanMCP SDK
@@ -58,6 +59,43 @@ declare class Logger {
58
59
  }
59
60
  declare const defaultLogger: Logger;
60
61
 
62
+ /**
63
+ * Session data stored in persistent storage (e.g., DynamoDB)
64
+ */
65
+ interface SessionData {
66
+ sessionId: string;
67
+ createdAt: Date;
68
+ updatedAt: Date;
69
+ ttl?: number;
70
+ data?: Record<string, any>;
71
+ }
72
+ /**
73
+ * Interface for session storage backends
74
+ * Implementations can use DynamoDB, Redis, or other persistent stores
75
+ */
76
+ interface ISessionStore {
77
+ /**
78
+ * Check if a session exists in the store
79
+ */
80
+ sessionExists(sessionId: string): Promise<boolean>;
81
+ /**
82
+ * Create a new session in the store
83
+ */
84
+ createSession(sessionId: string, data?: Record<string, any>): Promise<void>;
85
+ /**
86
+ * Get session data from the store
87
+ */
88
+ getSession(sessionId: string): Promise<SessionData | null>;
89
+ /**
90
+ * Update session data in the store
91
+ */
92
+ updateSession(sessionId: string, data: Partial<SessionData>): Promise<void>;
93
+ /**
94
+ * Delete a session from the store
95
+ */
96
+ deleteSession(sessionId: string): Promise<void>;
97
+ }
98
+
61
99
  interface HTTPServerOptions {
62
100
  port?: number;
63
101
  cors?: boolean | {
@@ -71,6 +109,8 @@ interface HTTPServerOptions {
71
109
  dashboard?: boolean;
72
110
  /** OAuth/Auth configuration (MCP authorization spec) */
73
111
  auth?: HTTPServerAuthOptions;
112
+ /** Session store for stateful mode (enables session persistence across Lambda container recycling) */
113
+ sessionStore?: ISessionStore;
74
114
  }
75
115
  /**
76
116
  * OAuth/Auth configuration for MCP server
@@ -530,6 +570,127 @@ declare function createProtectedResourceMetadata(options: {
530
570
  documentationUrl?: string;
531
571
  }): ProtectedResourceMetadata;
532
572
 
573
+ interface LeanMCPSessionProviderOptions {
574
+ tableName?: string;
575
+ region?: string;
576
+ ttlSeconds?: number;
577
+ logging?: boolean;
578
+ sessionStore?: ISessionStore;
579
+ }
580
+ /**
581
+ * Drop-in replacement for Map<string, StreamableHTTPServerTransport>
582
+ * Provides automatic session persistence for Lambda deployments
583
+ *
584
+ * @example
585
+ * // Before: const transports = new Map<string, StreamableHTTPServerTransport>();
586
+ * // After: const sessions = new LeanMCPSessionProvider();
587
+ *
588
+ * // Then use sessions.get(), sessions.set(), sessions.getOrRecreate()
589
+ */
590
+ declare class LeanMCPSessionProvider {
591
+ private transports;
592
+ private sessionStore;
593
+ constructor(options?: LeanMCPSessionProviderOptions);
594
+ /**
595
+ * Get transport from memory
596
+ */
597
+ get(sessionId: string): StreamableHTTPServerTransport | undefined;
598
+ /**
599
+ * Check if session exists (memory or DynamoDB)
600
+ */
601
+ has(sessionId: string): Promise<boolean>;
602
+ /**
603
+ * Store transport and create session in DynamoDB
604
+ */
605
+ set(sessionId: string, transport: StreamableHTTPServerTransport): Promise<void>;
606
+ /**
607
+ * Delete transport and session
608
+ */
609
+ delete(sessionId: string): Promise<void>;
610
+ /**
611
+ * Get or recreate transport for a session
612
+ * This is the key method for Lambda support - handles container recycling
613
+ *
614
+ * @param sessionId - Session ID to get or recreate
615
+ * @param serverFactory - Factory function to create fresh MCP server instances
616
+ * @param transportOptions - Optional callbacks for transport lifecycle events
617
+ * @returns Transport instance or null if session doesn't exist
618
+ */
619
+ getOrRecreate(sessionId: string, serverFactory: () => Server | Promise<Server>, transportOptions?: {
620
+ onsessioninitialized?: (sid: string) => void;
621
+ onclose?: () => void;
622
+ }): Promise<StreamableHTTPServerTransport | null>;
623
+ /**
624
+ * Get session data from DynamoDB
625
+ */
626
+ getSessionData(sessionId: string): Promise<Record<string, any> | null>;
627
+ /**
628
+ * Update session data in DynamoDB
629
+ */
630
+ updateSessionData(sessionId: string, data: Record<string, any>): Promise<void>;
631
+ /**
632
+ * Get number of in-memory transports
633
+ */
634
+ get size(): number;
635
+ /**
636
+ * Get all session IDs in memory
637
+ */
638
+ keys(): IterableIterator<string>;
639
+ /**
640
+ * Get all transports in memory
641
+ */
642
+ values(): IterableIterator<StreamableHTTPServerTransport>;
643
+ /**
644
+ * Iterate over all sessions in memory
645
+ */
646
+ entries(): IterableIterator<[string, StreamableHTTPServerTransport]>;
647
+ /**
648
+ * Clear all in-memory transports (does not affect DynamoDB)
649
+ */
650
+ clear(): void;
651
+ }
652
+
653
+ declare const DEFAULT_TABLE_NAME = "leanmcp-sessions";
654
+ declare const DEFAULT_TTL_SECONDS = 86400;
655
+ interface DynamoDBSessionStoreOptions {
656
+ tableName?: string;
657
+ region?: string;
658
+ ttlSeconds?: number;
659
+ logging?: boolean;
660
+ }
661
+ /**
662
+ * DynamoDB-backed session store for stateful Lambda MCP servers
663
+ * Automatically handles session persistence across Lambda container recycling
664
+ */
665
+ declare class DynamoDBSessionStore implements ISessionStore {
666
+ private client;
667
+ private tableName;
668
+ private ttlSeconds;
669
+ private logger;
670
+ constructor(options?: DynamoDBSessionStoreOptions);
671
+ /**
672
+ * Check if a session exists in DynamoDB
673
+ */
674
+ sessionExists(sessionId: string): Promise<boolean>;
675
+ /**
676
+ * Create a new session in DynamoDB
677
+ */
678
+ createSession(sessionId: string, data?: Record<string, any>): Promise<void>;
679
+ /**
680
+ * Get session data from DynamoDB
681
+ */
682
+ getSession(sessionId: string): Promise<SessionData | null>;
683
+ /**
684
+ * Update session data in DynamoDB
685
+ * Automatically refreshes TTL on each update
686
+ */
687
+ updateSession(sessionId: string, updates: Partial<SessionData>): Promise<void>;
688
+ /**
689
+ * Delete a session from DynamoDB
690
+ */
691
+ deleteSession(sessionId: string): Promise<void>;
692
+ }
693
+
533
694
  interface MCPServerOptions {
534
695
  servicesDir: string;
535
696
  port?: number;
@@ -673,16 +834,10 @@ declare class MCPServer {
673
834
  method: string;
674
835
  params?: {
675
836
  [x: string]: unknown;
676
- task?: {
677
- [x: string]: unknown;
678
- ttl?: number | null | undefined;
679
- pollInterval?: number | undefined;
680
- } | undefined;
681
837
  _meta?: {
682
838
  [x: string]: unknown;
683
839
  progressToken?: string | number | undefined;
684
840
  "io.modelcontextprotocol/related-task"?: {
685
- [x: string]: unknown;
686
841
  taskId: string;
687
842
  } | undefined;
688
843
  } | undefined;
@@ -693,8 +848,8 @@ declare class MCPServer {
693
848
  [x: string]: unknown;
694
849
  _meta?: {
695
850
  [x: string]: unknown;
851
+ progressToken?: string | number | undefined;
696
852
  "io.modelcontextprotocol/related-task"?: {
697
- [x: string]: unknown;
698
853
  taskId: string;
699
854
  } | undefined;
700
855
  } | undefined;
@@ -703,8 +858,8 @@ declare class MCPServer {
703
858
  [x: string]: unknown;
704
859
  _meta?: {
705
860
  [x: string]: unknown;
861
+ progressToken?: string | number | undefined;
706
862
  "io.modelcontextprotocol/related-task"?: {
707
- [x: string]: unknown;
708
863
  taskId: string;
709
864
  } | undefined;
710
865
  } | undefined;
@@ -734,16 +889,10 @@ declare class MCPServerRuntime {
734
889
  method: string;
735
890
  params?: {
736
891
  [x: string]: unknown;
737
- task?: {
738
- [x: string]: unknown;
739
- ttl?: number | null | undefined;
740
- pollInterval?: number | undefined;
741
- } | undefined;
742
892
  _meta?: {
743
893
  [x: string]: unknown;
744
894
  progressToken?: string | number | undefined;
745
895
  "io.modelcontextprotocol/related-task"?: {
746
- [x: string]: unknown;
747
896
  taskId: string;
748
897
  } | undefined;
749
898
  } | undefined;
@@ -754,8 +903,8 @@ declare class MCPServerRuntime {
754
903
  [x: string]: unknown;
755
904
  _meta?: {
756
905
  [x: string]: unknown;
906
+ progressToken?: string | number | undefined;
757
907
  "io.modelcontextprotocol/related-task"?: {
758
- [x: string]: unknown;
759
908
  taskId: string;
760
909
  } | undefined;
761
910
  } | undefined;
@@ -764,8 +913,8 @@ declare class MCPServerRuntime {
764
913
  [x: string]: unknown;
765
914
  _meta?: {
766
915
  [x: string]: unknown;
916
+ progressToken?: string | number | undefined;
767
917
  "io.modelcontextprotocol/related-task"?: {
768
- [x: string]: unknown;
769
918
  taskId: string;
770
919
  } | undefined;
771
920
  } | undefined;
@@ -779,4 +928,4 @@ declare class MCPServerRuntime {
779
928
  */
780
929
  declare function startMCPServer(options: MCPServerOptions): Promise<MCPServerRuntime>;
781
930
 
782
- export { Auth, type AuthErrorOptions, type AuthErrorResult, type AuthOptions, Deprecated, type HTTPServerAuthOptions, type HTTPServerInput, type HTTPServerOptions, LogLevel, type LogPayload, Logger, type LoggerHandler, type LoggerOptions, MCPServer, type MCPServerConstructorOptions, type MCPServerFactory, type MCPServerOptions, MCPServerRuntime, Optional, Prompt, type PromptOptions, type ProtectedResourceMetadata, Render, Resource, type ResourceOptions, SchemaConstraint, type SecurityScheme, Tool, type ToolOptions, UI, UserEnvs, classToJsonSchema, classToJsonSchemaWithConstraints, createAuthError, createHTTPServer, createProtectedResourceMetadata, defaultLogger, extractBearerToken, getDecoratedMethods, getMethodMetadata, isAuthError, startMCPServer, validateNonEmpty, validatePath, validatePort, validateServiceName, validateUrl };
931
+ export { Auth, type AuthErrorOptions, type AuthErrorResult, type AuthOptions, DEFAULT_TABLE_NAME, DEFAULT_TTL_SECONDS, Deprecated, DynamoDBSessionStore, type HTTPServerAuthOptions, type HTTPServerInput, type HTTPServerOptions, type ISessionStore, LeanMCPSessionProvider, type LeanMCPSessionProviderOptions, LogLevel, type LogPayload, Logger, type LoggerHandler, type LoggerOptions, MCPServer, type MCPServerConstructorOptions, type MCPServerFactory, type MCPServerOptions, MCPServerRuntime, Optional, Prompt, type PromptOptions, type ProtectedResourceMetadata, Render, Resource, type ResourceOptions, SchemaConstraint, type SecurityScheme, type SessionData, Tool, type ToolOptions, UI, UserEnvs, classToJsonSchema, classToJsonSchemaWithConstraints, createAuthError, createHTTPServer, createProtectedResourceMetadata, defaultLogger, extractBearerToken, getDecoratedMethods, getMethodMetadata, isAuthError, startMCPServer, validateNonEmpty, validatePath, validatePort, validateServiceName, validateUrl };