UNPKG

@dollhousemcp/mcp-server

Version:

DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.

90 lines 12.2 kB
/** * Metrics API routes for the unified dev console. * * Mounts on the existing Express app to provide: * - GET /api/metrics — JSON query (delegates to MemoryMetricsSink) * - GET /api/metrics/stream — SSE endpoint, pushes each new snapshot */ import { Router } from 'express'; import { UnicodeValidator } from '../../security/validators/unicodeValidator.js'; function normalizedStringParam(query, key) { const val = query[key]; if (typeof val === 'string' && val) { return UnicodeValidator.normalize(val).normalizedContent; } return undefined; } function parseMetricsQueryOptions(query) { const options = {}; const names = normalizedStringParam(query, 'names'); if (names) options['names'] = names.split(',').map(s => s.trim()); const source = normalizedStringParam(query, 'source'); if (source) options['source'] = source; const type = normalizedStringParam(query, 'type'); if (type) options['type'] = type; for (const field of ['since', 'until']) { if (typeof query[field] === 'string' && query[field]) options[field] = query[field]; } if (typeof query['latest'] === 'string') { options['latest'] = query['latest'] !== 'false'; } for (const field of ['limit', 'offset']) { if (typeof query[field] === 'string') { const parsed = Number.parseInt(query[field], 10); if (!Number.isNaN(parsed)) options[field] = parsed; } } return options; } export function createMetricsRoutes(metricsSink) { const router = Router(); const clients = new Set(); // GET /api/metrics — JSON query router.get('/metrics', (req, res) => { const result = metricsSink.query(parseMetricsQueryOptions(req.query)); res.json(result); }); // GET /api/metrics/stream — SSE endpoint for real-time metric snapshots. // Currently unused by the built-in web console (which polls /api/metrics). // Retained for third-party consumers (Prometheus, Grafana agent, custom dashboards) // that benefit from push-based metric delivery. router.get('/metrics/stream', (req, res) => { res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', }); res.write(':connected\n\n'); const client = { res }; clients.add(client); // Keep-alive heartbeat — prevents proxies from closing idle connections const heartbeat = setInterval(() => { res.write(': heartbeat\n\n'); }, 30_000); req.on('close', () => { clearInterval(heartbeat); clients.delete(client); }); }); function onSnapshot(snapshot) { const data = JSON.stringify(snapshot); for (const client of clients) { if (client.res.destroyed) { clients.delete(client); continue; } client.res.write(`data: ${data}\n\n`); } } return { router, onSnapshot, clientCount: () => clients.size, }; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljc1JvdXRlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy93ZWIvcm91dGVzL21ldHJpY3NSb3V0ZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7OztHQU1HO0FBRUgsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUlqQyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSwrQ0FBK0MsQ0FBQztBQVlqRixTQUFTLHFCQUFxQixDQUFDLEtBQXVCLEVBQUUsR0FBVztJQUNqRSxNQUFNLEdBQUcsR0FBRyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDdkIsSUFBSSxPQUFPLEdBQUcsS0FBSyxRQUFRLElBQUksR0FBRyxFQUFFLENBQUM7UUFDbkMsT0FBTyxnQkFBZ0IsQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUMsaUJBQWlCLENBQUM7SUFDM0QsQ0FBQztJQUNELE9BQU8sU0FBUyxDQUFDO0FBQ25CLENBQUM7QUFFRCxTQUFTLHdCQUF3QixDQUFDLEtBQXVCO0lBQ3ZELE1BQU0sT0FBTyxHQUE0QixFQUFFLENBQUM7SUFFNUMsTUFBTSxLQUFLLEdBQUcscUJBQXFCLENBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3BELElBQUksS0FBSztRQUFFLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxLQUFLLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBRWxFLE1BQU0sTUFBTSxHQUFHLHFCQUFxQixDQUFDLEtBQUssRUFBRSxRQUFRLENBQUMsQ0FBQztJQUN0RCxJQUFJLE1BQU07UUFBRSxPQUFPLENBQUMsUUFBUSxDQUFDLEdBQUcsTUFBTSxDQUFDO0lBRXZDLE1BQU0sSUFBSSxHQUFHLHFCQUFxQixDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsQ0FB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QU0sTUFBTSxJQUFJLE9BQU8sRUFBRSxDQUFDO1lBQzdCLElBQUksTUFBTSxDQUFDLEdBQUcsQ0FBQyxTQUFTLEVBQUUsQ0FBQztnQkFBQyxPQUFPLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDO2dCQUFDLFNBQVM7WUFBQyxDQUFDO1lBQy9ELE1BQU0sQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLFNBQVMsSUFBSSxNQUFNLENBQUMsQ0FBQztRQUN4QyxDQUFDO0lBQ0gsQ0FBQztJQUVELE9BQU87UUFDTCxNQUFNO1FBQ04sVUFBVTtRQUNWLFdBQVcsRUFBRSxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUMsSUFBSTtLQUNoQyxDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogTWV0cmljcyBBUEkgcm91dGVzIGZvciB0aGUgdW5pZmllZCBkZXYgY29uc29sZS5cbiAqXG4gKiBNb3VudHMgb24gdGhlIGV4aXN0aW5nIEV4cHJlc3MgYXBwIHRvIHByb3ZpZGU6XG4gKiAtIEdFVCAvYXBpL21ldHJpY3MgICAgICAgIOKAlCBKU09OIHF1ZXJ5IChkZWxlZ2F0ZXMgdG8gTWVtb3J5TWV0cmljc1NpbmspXG4gKiAtIEdFVCAvYXBpL21ldHJpY3Mvc3RyZWFtIOKAlCBTU0UgZW5kcG9pbnQsIHB1c2hlcyBlYWNoIG5ldyBzbmFwc2hvdFxuICovXG5cbmltcG9ydCB7IFJvdXRlciB9IGZyb20gJ2V4cHJlc3MnO1xuaW1wb3J0IHR5cGUgeyBSZXF1ZXN0LCBSZXNwb25zZSB9IGZyb20gJ2V4cHJlc3MnO1xuaW1wb3J0IHR5cGUgeyBNZW1vcnlNZXRyaWNzU2luayB9IGZyb20gJy4uLy4uL21ldHJpY3Mvc2lua3MvTWVtb3J5TWV0cmljc1NpbmsuanMnO1xuaW1wb3J0IHR5cGUgeyBNZXRyaWNTbmFwc2hvdCwgTWV0cmljVHlwZSB9IGZyb20gJy4uLy4uL21ldHJpY3MvdHlwZXMuanMnO1xuaW1wb3J0IHsgVW5pY29kZVZhbGlkYXRvciB9IGZyb20gJy4uLy4uL3NlY3VyaXR5L3ZhbGlkYXRvcnMvdW5pY29kZVZhbGlkYXRvci5qcyc7XG5cbmludGVyZmFjZSBTU0VDbGllbnQge1xuICByZXM6IFJlc3BvbnNlO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIE1ldHJpY3NSb3V0ZXNSZXN1bHQge1xuICByb3V0ZXI6IFJvdXRlcjtcbiAgb25TbmFwc2hvdDogKHNuYXBzaG90OiBNZXRyaWNTbmFwc2hvdCkgPT4gdm9pZDtcbiAgY2xpZW50Q291bnQ6ICgpID0+IG51bWJlcjtcbn1cblxuZnVuY3Rpb24gbm9ybWFsaXplZFN0cmluZ1BhcmFtKHF1ZXJ5OiBSZXF1ZXN0WydxdWVyeSddLCBrZXk6IHN0cmluZyk6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IHZhbCA9IHF1ZXJ5W2tleV07XG4gIGlmICh0eXBlb2YgdmFsID09PSAnc3RyaW5nJyAmJiB2YWwpIHtcbiAgICByZXR1cm4gVW5pY29kZVZhbGlkYXRvci5ub3JtYWxpemUodmFsKS5ub3JtYWxpemVkQ29udGVudDtcbiAgfVxuICByZXR1cm4gdW5kZWZpbmVkO1xufVxuXG5mdW5jdGlvbiBwYXJzZU1ldHJpY3NRdWVyeU9wdGlvbnMocXVlcnk6IFJlcXVlc3RbJ3F1ZXJ5J10pOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB7XG4gIGNvbnN0IG9wdGlvbnM6IFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0ge307XG5cbiAgY29uc3QgbmFtZXMgPSBub3JtYWxpemVkU3RyaW5nUGFyYW0ocXVlcnksICduYW1lcycpO1xuICBpZiAobmFtZXMpIG9wdGlvbnNbJ25hbWVzJ10gPSBuYW1lcy5zcGxpdCgnLCcpLm1hcChzID0+IHMudHJpbSgpKTtcblxuICBjb25zdCBzb3VyY2UgPSBub3JtYWxpemVkU3RyaW5nUGFyYW0ocXVlcnksICdzb3VyY2UnKTtcbiAgaWYgKHNvdXJjZSkgb3B0aW9uc1snc291cmNlJ10gPSBzb3VyY2U7XG5cbiAgY29uc3QgdHlwZSA9IG5vcm1hbGl6ZWRTdHJpbmdQYXJhbShxdWVyeSwgJ3R5cGUnKTtcbiAgaWYgKHR5cGUpIG9wdGlvbnNbJ3R5cGUnXSA9IHR5cGUgYXMgTWV0cmljVHlwZTtcblxuICBmb3IgKGNvbnN0IGZpZWxkIG9mIFsnc2luY2UnLCAndW50aWwnXSBhcyBjb25zdCkge1xuICAgIGlmICh0eXBlb2YgcXVlcnlbZmllbGRdID09PSAnc3RyaW5nJyAmJiBxdWVyeVtmaWVsZF0pIG9wdGlvbnNbZmllbGRdID0gcXVlcnlbZmllbGRdO1xuICB9XG4gIGlmICh0eXBlb2YgcXVlcnlbJ2xhdGVzdCddID09PSAnc3RyaW5nJykge1xuICAgIG9wdGlvbnNbJ2xhdGVzdCddID0gcXVlcnlbJ2xhdGVzdCddICE9PSAnZmFsc2UnO1xuICB9XG4gIGZvciAoY29uc3QgZmllbGQgb2YgWydsaW1pdCcsICdvZmZzZXQnXSBhcyBjb25zdCkge1xuICAgIGlmICh0eXBlb2YgcXVlcnlbZmllbGRdID09PSAnc3RyaW5nJykge1xuICAgICAgY29uc3QgcGFyc2VkID0gTnVtYmVyLnBhcnNlSW50KHF1ZXJ5W2ZpZWxkXSwgMTApO1xuICAgICAgaWYgKCFOdW1iZXIuaXNOYU4ocGFyc2VkKSkgb3B0aW9uc1tmaWVsZF0gPSBwYXJzZWQ7XG4gICAgfVxuICB9XG4gIHJldHVybiBvcHRpb25zO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlTWV0cmljc1JvdXRlcyhtZXRyaWNzU2luazogTWVtb3J5TWV0cmljc1NpbmspOiBNZXRyaWNzUm91dGVzUmVzdWx0IHtcbiAgY29uc3Qgcm91dGVyID0gUm91dGVyKCk7XG4gIGNvbnN0IGNsaWVudHMgPSBuZXcgU2V0PFNTRUNsaWVudD4oKTtcblxuICAvLyBHRVQgL2FwaS9tZXRyaWNzIOKAlCBKU09OIHF1ZXJ5XG4gIHJvdXRlci5nZXQoJy9tZXRyaWNzJywgKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSkgPT4ge1xuICAgIGNvbnN0IHJlc3VsdCA9IG1ldHJpY3NTaW5rLnF1ZXJ5KHBhcnNlTWV0cmljc1F1ZXJ5T3B0aW9ucyhyZXEucXVlcnkpKTtcbiAgICByZXMuanNvbihyZXN1bHQpO1xuICB9KTtcblxuICAvLyBHRVQgL2FwaS9tZXRyaWNzL3N0cmVhbSDigJQgU1NFIGVuZHBvaW50IGZvciByZWFsLXRpbWUgbWV0cmljIHNuYXBzaG90cy5cbiAgLy8gQ3VycmVudGx5IHVudXNlZCBieSB0aGUgYnVpbHQtaW4gd2ViIGNvbnNvbGUgKHdoaWNoIHBvbGxzIC9hcGkvbWV0cmljcykuXG4gIC8vIFJldGFpbmVkIGZvciB0aGlyZC1wYXJ0eSBjb25zdW1lcnMgKFByb21ldGhldXMsIEdyYWZhbmEgYWdlbnQsIGN1c3RvbSBkYXNoYm9hcmRzKVxuICAvLyB0aGF0IGJlbmVmaXQgZnJvbSBwdXNoLWJhc2VkIG1ldHJpYyBkZWxpdmVyeS5cbiAgcm91dGVyLmdldCgnL21ldHJpY3Mvc3RyZWFtJywgKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSkgPT4ge1xuICAgIHJlcy53cml0ZUhlYWQoMjAwLCB7XG4gICAgICAnQ29udGVudC1UeXBlJzogJ3RleHQvZXZlbnQtc3RyZWFtJyxcbiAgICAgICdDYWNoZS1Db250cm9sJzogJ25vLWNhY2hlJyxcbiAgICAgICdDb25uZWN0aW9uJzogJ2tlZXAtYWxpdmUnLFxuICAgIH0pO1xuICAgIHJlcy53cml0ZSgnOmNvbm5lY3RlZFxcblxcbicpO1xuXG4gICAgY29uc3QgY2xpZW50OiBTU0VDbGllbnQgPSB7IHJlcyB9O1xuICAgIGNsaWVudHMuYWRkKGNsaWVudCk7XG5cbiAgICAvLyBLZWVwLWFsaXZlIGhlYXJ0YmVhdCDigJQgcHJldmVudHMgcHJveGllcyBmcm9tIGNsb3NpbmcgaWRsZSBjb25uZWN0aW9uc1xuICAgIGNvbnN0IGhlYXJ0YmVhdCA9IHNldEludGVydmFsKCgpID0+IHtcbiAgICAgIHJlcy53cml0ZSgnOiBoZWFydGJlYXRcXG5cXG4nKTtcbiAgICB9LCAzMF8wMDApO1xuXG4gICAgcmVxLm9uKCdjbG9zZScsICgpID0+IHtcbiAgICAgIGNsZWFySW50ZXJ2YWwoaGVhcnRiZWF0KTtcbiAgICAgIGNsaWVudHMuZGVsZXRlKGNsaWVudCk7XG4gICAgfSk7XG4gIH0pO1xuXG4gIGZ1bmN0aW9uIG9uU25hcHNob3Qoc25hcHNob3Q6IE1ldHJpY1NuYXBzaG90KTogdm9pZCB7XG4gICAgY29uc3QgZGF0YSA9IEpTT04uc3RyaW5naWZ5KHNuYXBzaG90KTtcbiAgICBmb3IgKGNvbnN0IGNsaWVudCBvZiBjbGllbnRzKSB7XG4gICAgICBpZiAoY2xpZW50LnJlcy5kZXN0cm95ZWQpIHsgY2xpZW50cy5kZWxldGUoY2xpZW50KTsgY29udGludWU7IH1cbiAgICAgIGNsaWVudC5yZXMud3JpdGUoYGRhdGE6ICR7ZGF0YX1cXG5cXG5gKTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4ge1xuICAgIHJvdXRlcixcbiAgICBvblNuYXBzaG90LFxuICAgIGNsaWVudENvdW50OiAoKSA9PiBjbGllbnRzLnNpemUsXG4gIH07XG59XG4iXX0=