@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/LICENSE +21 -21
- package/README.md +485 -474
- package/dist/chunk-7SU7UAUW.mjs +272 -0
- package/dist/chunk-LPEX4YW6.mjs +13 -0
- package/dist/dynamodb-session-store-WJ2ALBES.mjs +10 -0
- package/dist/index.d.mts +166 -17
- package/dist/index.d.ts +166 -17
- package/dist/index.js +377 -5
- package/dist/index.mjs +207 -120
- package/dist/server-BEQUIEE2.mjs +25808 -0
- package/package.json +78 -76
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
|
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
|
|
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,
|