mcp-chain-of-draft-server
Version:
A Model Context Protocol server which provides Chain of Draft style thinking
103 lines (102 loc) • 3.49 kB
JavaScript
import { logger } from "./logging.js";
export class InMemorySessionStorage {
constructor() {
this.sessions = new Map();
}
async get(sessionId) {
const session = this.sessions.get(sessionId);
if (session) {
// Deep clone to prevent direct mutation
return structuredClone(session);
}
return null;
}
async set(sessionId, data) {
this.sessions.set(sessionId, structuredClone(data));
}
async delete(sessionId) {
this.sessions.delete(sessionId);
}
async cleanup(maxAge) {
const now = Date.now();
for (const [sessionId, session] of this.sessions.entries()) {
if (now - session.metadata.lastAccessed.getTime() > maxAge) {
this.sessions.delete(sessionId);
logger.info(`Cleaned up expired session: ${sessionId}`);
}
}
}
}
export class SessionManager {
constructor(storage, options = {}) {
this.cleanupInterval = null;
this.storage = storage;
this.maxSessionAge = options.maxSessionAge || 24 * 60 * 60 * 1000; // 24 hours
this.maxSessionSize = options.maxSessionSize || 5 * 1024 * 1024; // 5MB
if (options.cleanupIntervalMs) {
this.startCleanupInterval(options.cleanupIntervalMs);
}
}
startCleanupInterval(intervalMs) {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
this.cleanupInterval = setInterval(() => {
this.cleanup().catch(err => {
logger.error('Session cleanup failed:', err);
});
}, intervalMs);
}
async getSession(sessionId) {
const session = await this.storage.get(sessionId);
if (!session) {
return this.createSession(sessionId);
}
// Update last accessed time
session.metadata.lastAccessed = new Date();
await this.storage.set(sessionId, session);
return session;
}
async createSession(sessionId) {
const session = {
data: {}, // Initialize with empty data
metadata: {
created: new Date(),
lastAccessed: new Date(),
size: 0,
version: 1
}
};
await this.storage.set(sessionId, session);
logger.info(`Created new session: ${sessionId}`);
return session;
}
async updateSession(sessionId, data) {
const session = await this.getSession(sessionId);
const newSize = this.calculateSize(data);
if (newSize > this.maxSessionSize) {
throw new Error(`Session size limit exceeded. Max: ${this.maxSessionSize}, Attempted: ${newSize}`);
}
session.data = data;
session.metadata.lastAccessed = new Date();
session.metadata.size = newSize;
await this.storage.set(sessionId, session);
}
async deleteSession(sessionId) {
await this.storage.delete(sessionId);
logger.info(`Deleted session: ${sessionId}`);
}
async cleanup() {
await this.storage.cleanup(this.maxSessionAge);
}
calculateSize(data) {
// Rough estimation of size in bytes
return new TextEncoder().encode(JSON.stringify(data)).length;
}
destroy() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = null;
}
}
}