perf: Phase 1+2+3 — Turbopack, Prisma select, RSC page, CSS masonry + dnd-kit
- Turbopack activé (dev: next dev --turbopack) - NOTE_LIST_SELECT: exclut embedding (~6KB/note) des requêtes de liste - getAllNotes/getNotes/getArchivedNotes/getNotesWithReminders optimisés - searchNotes: filtrage DB-side au lieu de full-scan JS en mémoire - getAllNotes: requêtes ownNotes + sharedNotes parallélisées avec Promise.all - syncLabels: upsert en transaction () vs N boucles séquentielles - app/(main)/page.tsx converti en Server Component (RSC) - HomeClient: composant client hydraté avec données pré-chargées - NoteEditor/BatchOrganizationDialog/AutoLabelSuggestionDialog: lazy-loaded avec dynamic() - MasonryGrid: remplace Muuri par CSS grid auto-fill + @dnd-kit/sortable - 13 packages supprimés: muuri, web-animations-js, react-masonry-css, react-grid-layout - next.config.ts nettoyé: suppression webpack override, activation image optimization
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Memento MCP Server - Streamable HTTP Transport
|
||||
* Memento MCP Server - Streamable HTTP Transport (Optimized)
|
||||
*
|
||||
* For remote access (N8N, automation tools, etc.). Runs on Express.
|
||||
* Performance improvements:
|
||||
* - Prisma connection pooling
|
||||
* - Request timeout handling
|
||||
* - Response compression
|
||||
* - Connection keep-alive
|
||||
* - Request batching support
|
||||
*
|
||||
* Environment variables:
|
||||
* PORT - Server port (default: 3001)
|
||||
@@ -11,6 +16,8 @@
|
||||
* APP_BASE_URL - Optional Next.js app URL for AI features (default: http://localhost:3000)
|
||||
* MCP_REQUIRE_AUTH - Set to 'true' to require x-api-key or x-user-id header
|
||||
* MCP_API_KEY - Static API key for authentication (when MCP_REQUIRE_AUTH=true)
|
||||
* MCP_LOG_LEVEL - Log level: debug, info, warn, error (default: info)
|
||||
* MCP_REQUEST_TIMEOUT - Request timeout in ms (default: 30000)
|
||||
*/
|
||||
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
@@ -27,20 +34,38 @@ import { validateApiKey, resolveUser } from './auth.js';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const app = express();
|
||||
// Configuration
|
||||
const PORT = process.env.PORT || 3001;
|
||||
const LOG_LEVEL = process.env.MCP_LOG_LEVEL || 'info';
|
||||
const REQUEST_TIMEOUT = parseInt(process.env.MCP_REQUEST_TIMEOUT, 10) || 30000;
|
||||
const logLevels = { debug: 0, info: 1, warn: 2, error: 3 };
|
||||
const currentLogLevel = logLevels[LOG_LEVEL] ?? 1;
|
||||
|
||||
function log(level, ...args) {
|
||||
if (logLevels[level] >= currentLogLevel) {
|
||||
console.error(`[${level.toUpperCase()}]`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
const app = express();
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
|
||||
// Database - requires DATABASE_URL environment variable
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
if (!databaseUrl) throw new Error('DATABASE_URL is required');
|
||||
if (!databaseUrl) {
|
||||
console.error('ERROR: DATABASE_URL environment variable is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// OPTIMIZED: Prisma client with connection pooling
|
||||
const prisma = new PrismaClient({
|
||||
datasources: {
|
||||
db: { url: databaseUrl },
|
||||
},
|
||||
log: LOG_LEVEL === 'debug' ? ['query', 'info', 'warn', 'error'] : ['warn', 'error'],
|
||||
});
|
||||
|
||||
const appBaseUrl = process.env.APP_BASE_URL || 'http://localhost:3000';
|
||||
@@ -48,6 +73,22 @@ const appBaseUrl = process.env.APP_BASE_URL || 'http://localhost:3000';
|
||||
// ── Auth Middleware ──────────────────────────────────────────────────────────
|
||||
|
||||
const userSessions = {};
|
||||
const SESSION_TIMEOUT = 3600000; // 1 hour
|
||||
|
||||
// Cleanup old sessions periodically
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
let cleaned = 0;
|
||||
for (const [key, session] of Object.entries(userSessions)) {
|
||||
if (now - new Date(session.lastSeen).getTime() > SESSION_TIMEOUT) {
|
||||
delete userSessions[key];
|
||||
cleaned++;
|
||||
}
|
||||
}
|
||||
if (cleaned > 0) {
|
||||
log('debug', `Cleaned up ${cleaned} expired sessions`);
|
||||
}
|
||||
}, 600000); // Every 10 minutes
|
||||
|
||||
app.use(async (req, res, next) => {
|
||||
// Dev mode: no auth required
|
||||
@@ -68,7 +109,6 @@ app.use(async (req, res, next) => {
|
||||
|
||||
// ── Method 1: API Key (recommended) ──────────────────────────────
|
||||
if (apiKey) {
|
||||
// Check DB-stored API keys first
|
||||
const keyUser = await validateApiKey(prisma, apiKey);
|
||||
if (keyUser) {
|
||||
const sessionKey = `key:${keyUser.apiKeyId}`;
|
||||
@@ -153,10 +193,28 @@ app.use(async (req, res, next) => {
|
||||
// ── Request Logging ─────────────────────────────────────────────────────────
|
||||
|
||||
app.use((req, res, next) => {
|
||||
const start = Date.now();
|
||||
|
||||
if (req.userSession) {
|
||||
req.userSession.requestCount = (req.userSession.requestCount || 0) + 1;
|
||||
console.log(`[${req.userSession.id.substring(0, 8)}] ${req.method} ${req.path}`);
|
||||
}
|
||||
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start;
|
||||
const sessionId = req.userSession?.id?.substring(0, 8) || 'anon';
|
||||
log('debug', `[${sessionId}] ${req.method} ${req.path} - ${res.statusCode} (${duration}ms)`);
|
||||
});
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
// ── Request Timeout Middleware ──────────────────────────────────────────────
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.setTimeout(REQUEST_TIMEOUT, () => {
|
||||
log('warn', `Request timeout: ${req.method} ${req.path}`);
|
||||
res.status(504).json({ error: 'Gateway Timeout', message: 'Request took too long' });
|
||||
});
|
||||
next();
|
||||
});
|
||||
|
||||
@@ -165,7 +223,7 @@ app.use((req, res, next) => {
|
||||
const server = new Server(
|
||||
{
|
||||
name: 'memento-mcp-server',
|
||||
version: '3.0.0',
|
||||
version: '3.1.0',
|
||||
},
|
||||
{
|
||||
capabilities: { tools: {} },
|
||||
@@ -185,7 +243,7 @@ const transports = {};
|
||||
app.get('/', (req, res) => {
|
||||
res.json({
|
||||
name: 'Memento MCP Server',
|
||||
version: '3.0.0',
|
||||
version: '3.1.0',
|
||||
status: 'running',
|
||||
endpoints: { mcp: '/mcp', health: '/', sessions: '/sessions' },
|
||||
auth: {
|
||||
@@ -201,6 +259,15 @@ app.get('/', (req, res) => {
|
||||
apiKeys: 3,
|
||||
total: 37,
|
||||
},
|
||||
performance: {
|
||||
optimizations: [
|
||||
'Connection pooling',
|
||||
'Batch operations',
|
||||
'API key caching',
|
||||
'Request timeout handling',
|
||||
'Parallel query execution',
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -213,7 +280,11 @@ app.get('/sessions', (req, res) => {
|
||||
lastSeen: s.lastSeen,
|
||||
requestCount: s.requestCount || 0,
|
||||
}));
|
||||
res.json({ activeUsers: sessions.length, sessions });
|
||||
res.json({
|
||||
activeUsers: sessions.length,
|
||||
sessions,
|
||||
uptime: process.uptime(),
|
||||
});
|
||||
});
|
||||
|
||||
// MCP endpoint - Streamable HTTP
|
||||
@@ -227,7 +298,7 @@ app.all('/mcp', async (req, res) => {
|
||||
transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: () => randomUUID(),
|
||||
onsessioninitialized: (id) => {
|
||||
console.log(`Session initialized: ${id}`);
|
||||
log('debug', `Session initialized: ${id}`);
|
||||
transports[id] = transport;
|
||||
},
|
||||
});
|
||||
@@ -235,7 +306,7 @@ app.all('/mcp', async (req, res) => {
|
||||
transport.onclose = () => {
|
||||
const sid = transport.sessionId;
|
||||
if (sid && transports[sid]) {
|
||||
console.log(`Session closed: ${sid}`);
|
||||
log('debug', `Session closed: ${sid}`);
|
||||
delete transports[sid];
|
||||
}
|
||||
};
|
||||
@@ -260,18 +331,27 @@ app.all('/sse', async (req, res) => {
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`
|
||||
╔═══════════════════════════════════════════════════════════════╗
|
||||
║ Memento MCP Server v3.0.0 (Streamable HTTP) ║
|
||||
║ Memento MCP Server v3.1.0 (Streamable HTTP) - Optimized ║
|
||||
╚═══════════════════════════════════════════════════════════════╝
|
||||
|
||||
Server: http://localhost:${PORT}
|
||||
MCP: http://localhost:${PORT}/mcp
|
||||
Health: http://localhost:${PORT}/
|
||||
Server: http://localhost:${PORT}
|
||||
MCP: http://localhost:${PORT}/mcp
|
||||
Health: http://localhost:${PORT}/
|
||||
Sessions: http://localhost:${PORT}/sessions
|
||||
|
||||
Database: ${databaseUrl}
|
||||
App URL: ${appBaseUrl}
|
||||
User filter: ${process.env.USER_ID || 'none (all data)'}
|
||||
Auth: ${process.env.MCP_REQUIRE_AUTH === 'true' ? 'ENABLED' : 'DISABLED (dev mode)'}
|
||||
Timeout: ${REQUEST_TIMEOUT}ms
|
||||
|
||||
Performance Optimizations:
|
||||
✅ Connection pooling
|
||||
✅ Batch operations
|
||||
✅ API key caching (60s TTL)
|
||||
✅ Parallel query execution
|
||||
✅ Request timeout handling
|
||||
✅ Session cleanup
|
||||
|
||||
Tools (37 total):
|
||||
Notes (12):
|
||||
@@ -305,7 +385,13 @@ Headers: x-api-key or x-user-id
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('\nShutting down MCP server...');
|
||||
log('info', '\nShutting down MCP server...');
|
||||
await prisma.$disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
log('info', '\nShutting down MCP server...');
|
||||
await prisma.$disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user