Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .gitignore
Binary file not shown.
22 changes: 22 additions & 0 deletions app/api/health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Lightweight liveness/readiness with basic system health checks
import express from "express";
import metricsCollector from "../lib/monitoring/metrics";
import os from "os";

const router = express.Router();

router.get("/health", async (req, res) => {
// Basic checks: memory and event loop lag sample
const mem = process.memoryUsage();
// naive event loop lag measurement
const start = Date.now();
setImmediate(() => {
const lag = Date.now() - start;
metricsCollector.recordSystemHealth({ eventLoopLagMs: lag, memoryRssBytes: mem.rss });
});

// Add any DB pool checks or external service checks here and return non-200 when failing
res.json({ status: "ok", memoryRss: mem.rss, cpus: os.cpus().length });
});

export default router;
20 changes: 20 additions & 0 deletions app/api/metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Example Express handler to expose metrics at /metrics
import express from "express";
import metricsCollector from "../lib/monitoring/metrics";

const router = express.Router();

router.get("/metrics", async (req, res) => {
try {
res.set("Content-Type", metricsCollector.getRegister().contentType);
const body = await metricsCollector.getRegister().metrics();
res.send(body);
} catch (err: unknown) {
// Normalize unknown errors to a string message before sending
const message =
err instanceof Error ? err.message : typeof err === 'string' ? err : 'Failed to collect metrics';
res.status(500).send(message);
}
});

export default router;
33 changes: 33 additions & 0 deletions app/lib/db/instrumentedQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Wrap your DB query execution to record metrics.
// Example for pg Pool / any query function
import { metricsCollector } from "../monitoring/metrics";

export async function instrumentedQuery<T>(
queryName: string,
runQuery: () => Promise<T>,
opts?: { userId?: string; db?: string }
): Promise<T> {
const start = Date.now();
try {
const result = await runQuery();
const dur = Date.now() - start;
metricsCollector.recordQueryExecution({
queryName,
durationMs: dur,
userId: opts?.userId,
status: "success",
db: opts?.db,
});
return result;
} catch (err) {
const dur = Date.now() - start;
metricsCollector.recordQueryExecution({
queryName,
durationMs: dur,
userId: opts?.userId,
status: "error",
db: opts?.db,
});
throw err;
}
}
40 changes: 40 additions & 0 deletions app/lib/monitoring/grafana/dashboard.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"annotations": { "list": [] },
"panels": [
{
"type": "timeseries",
"title": "Query latency (95th percentile)",
"targets": [
{
"expr": "histogram_quantile(0.95, sum(rate(mcp_query_duration_seconds_bucket[5m])) by (le, query_name))",
"legendFormat": "{{query_name}}"
}
],
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 0 }
},
{
"type": "timeseries",
"title": "Pool busy vs idle",
"targets": [
{
"expr": "sum(mcp_pool_total_connections) by(pool_id)",
"legendFormat": "total {{pool_id}}"
},
{
"expr": "sum(mcp_pool_idle_connections) by(pool_id)",
"legendFormat": "idle {{pool_id}}"
}
],
"gridPos": { "h": 6, "w": 24, "x": 0, "y": 8 }
},
{
"type": "timeseries",
"title": "Event loop lag (ms)",
"targets": [
{ "expr": "mcp_event_loop_lag_ms" }
],
"gridPos": { "h": 4, "w": 24, "x": 0, "y": 14 }
}
],
"title": "MCP Performance Overview"
}
106 changes: 106 additions & 0 deletions app/lib/monitoring/metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import client from "prom-client";

client.collectDefaultMetrics({ prefix: "mcp_" });

const register = client.register;

// Query metrics
export const queryDurationSeconds = new client.Histogram({
name: "mcp_query_duration_seconds",
help: "Query execution duration in seconds",
labelNames: ["query_name", "user_id", "status", "db"],
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
});

// Query counter (total queries)
export const queryCount = new client.Counter({
name: "mcp_query_total",
help: "Total number of queries executed",
labelNames: ["query_name", "user_id", "status", "db"],
});

// Connection pool gauges
export const poolTotal = new client.Gauge({
name: "mcp_pool_total_connections",
help: "Total connections in pool",
labelNames: ["pool_id"],
});
export const poolIdle = new client.Gauge({
name: "mcp_pool_idle_connections",
help: "Idle connections in pool",
labelNames: ["pool_id"],
});
export const poolWaiting = new client.Gauge({
name: "mcp_pool_waiting_requests",
help: "Requests waiting for a connection",
labelNames: ["pool_id"],
});

// User activity: counts of actions per user
export const userActivityCounter = new client.Counter({
name: "mcp_user_activity_total",
help: "User activity counters",
labelNames: ["user_id", "action"],
});

// System health gauges (set from code)
export const eventLoopLagMs = new client.Gauge({
name: "mcp_event_loop_lag_ms",
help: "Event loop lag in milliseconds",
});
export const memoryUsageBytes = new client.Gauge({
name: "mcp_memory_rss_bytes",
help: "RSS memory usage in bytes",
});

// Utility helper for safe labels
function safeLabel(v?: string) {
return v ? String(v) : "unknown";
}

export class MetricsCollector {
recordQueryExecution(opts: {
queryName?: string;
durationMs: number;
userId?: string;
status?: "success" | "error";
db?: string;
}) {
const q = safeLabel(opts.queryName);
const u = safeLabel(opts.userId);
const s = safeLabel(opts.status);
const db = safeLabel(opts.db);

queryDurationSeconds
.labels(q, u, s, db)
.observe(Math.max(opts.durationMs, 0) / 1000);
queryCount.labels(q, u, s, db).inc();
}

recordConnectionPoolStats(poolId: string, stats: { total: number; idle: number; waiting: number }) {
const id = safeLabel(poolId);
poolTotal.labels(id).set(stats.total);
poolIdle.labels(id).set(stats.idle);
poolWaiting.labels(id).set(stats.waiting);
}

recordUserActivity(userId: string, action: string) {
userActivityCounter.labels(safeLabel(userId), safeLabel(action)).inc();
}

recordSystemHealth(metrics: { eventLoopLagMs?: number; memoryRssBytes?: number }) {
if (typeof metrics.eventLoopLagMs === "number") {
eventLoopLagMs.set(metrics.eventLoopLagMs);
}
if (typeof metrics.memoryRssBytes === "number") {
memoryUsageBytes.set(metrics.memoryRssBytes);
}
}

getRegister() {
return register;
}
}

export const metricsCollector = new MetricsCollector();
export default metricsCollector;
26 changes: 26 additions & 0 deletions app/lib/monitoring/poolMonitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Example connection pool monitor. Adapt to your pool implementation (pg, mysql, etc.)
import { metricsCollector } from "./metrics";

export function startPoolMonitor(opts: {
getPoolStats: () => { poolId: string; total: number; idle: number; waiting: number }[];
intervalMs?: number;
}) {
const interval = opts.intervalMs ?? 5000;
const timer = setInterval(() => {
try {
const all = opts.getPoolStats();
for (const s of all) {
metricsCollector.recordConnectionPoolStats(s.poolId, {
total: s.total,
idle: s.idle,
waiting: s.waiting,
});
}
} catch {
// swallow errors to avoid crash; optionally increment an internal error metric
// console.error("pool monitor error");
}
}, interval).unref();

return () => clearInterval(timer);
}
29 changes: 29 additions & 0 deletions app/lib/monitoring/prometheus/rules.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
groups:
- name: mcp-performance
rules:
- alert: HighQueryLatency
expr: histogram_quantile(0.95, sum(rate(mcp_query_duration_seconds_bucket[5m])) by (le, query_name)) > 1
for: 2m
labels:
severity: page
annotations:
summary: "95th percentile query latency > 1s ({{ $labels.query_name }})"
description: "Query {{ $labels.query_name }} has high latency over the last 5 minutes."

- alert: PoolConnectionsExhausted
expr: sum(mcp_pool_total_connections) by (pool_id) - sum(mcp_pool_idle_connections) by (pool_id) > 90
for: 2m
labels:
severity: page
annotations:
summary: "Connection pool near exhaustion ({{ $labels.pool_id }})"
description: "High number of busy connections in pool {{ $labels.pool_id }}."

- alert: HighEventLoopLag
expr: mcp_event_loop_lag_ms > 200
for: 1m
labels:
severity: ticket
annotations:
summary: "High event loop lag"
description: "Event loop lag exceeded 200ms."
25 changes: 25 additions & 0 deletions app/middleware/userActivity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Express middleware to track user actions. Call metricsCollector.recordUserActivity for operations too.
import { Request, Response, NextFunction } from "express";
import metricsCollector from "../lib/monitoring/metrics";

// Example: mark each HTTP request as a user action 'http_request' with method+path
export function userActivityMiddleware(req: Request, res: Response, next: NextFunction) {
const start = Date.now();
// Avoid `any` by narrowing the request type for user property access
const typedReq = req as Request & { user?: { id?: string } };
const userId = typedReq.user?.id ?? "anonymous";
res.on("finish", () => {
const action = `http_${req.method.toLowerCase()}`;
metricsCollector.recordUserActivity(String(userId), action);
// optionally record request duration as a query-like metric
const dur = Date.now() - start;
metricsCollector.recordQueryExecution({
queryName: `http_${req.method}_${req.route?.path || req.path}`,
durationMs: dur,
userId: String(userId),
status: res.statusCode < 400 ? "success" : "error",
db: "http",
});
});
next();
}
15 changes: 15 additions & 0 deletions logs/.98aae86f902c54177e4e8cf98ac2b88574c20417-audit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"keep": {
"days": true,
"amount": 14
},
"auditLog": "C:\\Users\\prash\\Desktop\\mcp\\mcp-for-database\\logs\\.98aae86f902c54177e4e8cf98ac2b88574c20417-audit.json",
"files": [
{
"date": 1761052911906,
"name": "C:\\Users\\prash\\Desktop\\mcp\\mcp-for-database\\logs\\app-2025-10-21.log",
"hash": "b863daf803a8026a8fc302b65ed8ab81fc9d3eb5c5a10a00b0754fb3b8f88e1a"
}
],
"hashType": "sha256"
}
Empty file added logs/app-2025-10-21.log
Empty file.
Loading