@mandujs/mcp 0.20.1 → 0.20.2

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.
Files changed (2) hide show
  1. package/package.json +3 -3
  2. package/src/server.ts +90 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/mcp",
3
- "version": "0.20.1",
3
+ "version": "0.20.2",
4
4
  "description": "Mandu MCP Server - Agent-native interface for Mandu framework operations",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -34,9 +34,9 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@mandujs/core": "^0.24.0",
37
+ "@mandujs/core": "^0.25.0",
38
38
  "@mandujs/ate": "^0.19.1",
39
- "@mandujs/skills": "^4.0.0",
39
+ "@mandujs/skills": "^5.0.0",
40
40
  "@modelcontextprotocol/sdk": "^1.25.3"
41
41
  },
42
42
  "engines": {
package/src/server.ts CHANGED
@@ -58,6 +58,54 @@ import { type McpProfile, isValidProfile } from "./profiles.js";
58
58
  */
59
59
  const MCP_VERSION = "0.12.0";
60
60
 
61
+ /**
62
+ * Phase 17 — heap heartbeat interval (ms). Every tick we log
63
+ * `Bun.memoryUsage()` + RSS / heapUsed / external + uptime so operators
64
+ * tailing stderr can spot runaway growth without hooking a debugger.
65
+ *
66
+ * 5 minutes strikes a balance between "noisy enough to catch slow leaks"
67
+ * and "not spammy enough to drown the log" (288 lines/day). Override
68
+ * with `MANDU_MCP_HEAP_INTERVAL_MS` when running under stress tests.
69
+ */
70
+ const DEFAULT_HEAP_LOG_INTERVAL_MS = 5 * 60 * 1000;
71
+
72
+ /**
73
+ * Emit a single heap-usage line to stderr. Called once on startup and
74
+ * again on every heartbeat tick.
75
+ *
76
+ * Format is plain KV — easy to grep / pipe through `awk`:
77
+ *
78
+ * [MCP heap] rss=142MB heapUsed=56MB heapTotal=80MB external=3MB uptime=310s
79
+ *
80
+ * Uses `Bun.memoryUsage()` when available (returns RSS in bytes + JSC
81
+ * gauges); falls back to `process.memoryUsage()` on Node test runners.
82
+ * Errors are swallowed — a memory probe must never take down the server.
83
+ */
84
+ export function logMcpHeapUsage(label: string = "heap"): void {
85
+ try {
86
+ const mem = process.memoryUsage();
87
+ const bunGlobal = (globalThis as { Bun?: { memoryUsage?: () => Record<string, number> } }).Bun;
88
+ let rss = mem.rss;
89
+ let external = mem.external;
90
+ if (bunGlobal?.memoryUsage) {
91
+ try {
92
+ const bunMem = bunGlobal.memoryUsage();
93
+ if (typeof bunMem.rss === "number") rss = bunMem.rss;
94
+ if (typeof bunMem.external === "number") external = bunMem.external;
95
+ } catch {
96
+ // fall back to process.memoryUsage values
97
+ }
98
+ }
99
+ const mb = (n: number) => `${Math.round(n / 1024 / 1024)}MB`;
100
+ const uptime = Math.round(process.uptime());
101
+ console.error(
102
+ `[MCP ${label}] rss=${mb(rss)} heapUsed=${mb(mem.heapUsed)} heapTotal=${mb(mem.heapTotal)} external=${mb(external)} uptime=${uptime}s`,
103
+ );
104
+ } catch {
105
+ // best-effort — never propagate
106
+ }
107
+ }
108
+
61
109
  /**
62
110
  * ManduMcpServer v2
63
111
  *
@@ -71,6 +119,11 @@ export class ManduMcpServer {
71
119
  private configWatcher?: McpConfigWatcher;
72
120
  private toolExecutor: ToolExecutor;
73
121
  private profile: McpProfile;
122
+ /**
123
+ * Phase 17 — 5-minute heap-usage heartbeat. Lazily started in `run()`
124
+ * and cleared by `stop()`. Disabled with `MANDU_MCP_HEAP_INTERVAL_MS=0`.
125
+ */
126
+ private heapLogTimer: ReturnType<typeof setInterval> | null = null;
74
127
 
75
128
  constructor(projectRoot: string) {
76
129
  this.projectRoot = projectRoot;
@@ -344,6 +397,37 @@ export class ManduMcpServer {
344
397
  console.error(` Project: ${this.projectRoot}`);
345
398
  console.error(` Profile: ${this.profile}`);
346
399
  console.error(` Tools: ${summary.total} (${summary.categories.join(", ")})`);
400
+
401
+ // Phase 17 — start the heap heartbeat. Startup line is tagged
402
+ // `startup` so operators can anchor before/after diffs to it.
403
+ logMcpHeapUsage("startup");
404
+ this.startHeapHeartbeat();
405
+ }
406
+
407
+ /**
408
+ * Phase 17 — configure the heap-usage heartbeat. Reads
409
+ * `MANDU_MCP_HEAP_INTERVAL_MS` for override (`0` = disabled).
410
+ * Idempotent: repeat calls replace the previous timer.
411
+ */
412
+ private startHeapHeartbeat(): void {
413
+ if (this.heapLogTimer) {
414
+ clearInterval(this.heapLogTimer);
415
+ this.heapLogTimer = null;
416
+ }
417
+ const raw = process.env.MANDU_MCP_HEAP_INTERVAL_MS;
418
+ let intervalMs = DEFAULT_HEAP_LOG_INTERVAL_MS;
419
+ if (raw !== undefined) {
420
+ const parsed = Number(raw);
421
+ if (Number.isFinite(parsed) && parsed >= 0) intervalMs = parsed;
422
+ }
423
+ if (intervalMs === 0) return; // explicitly disabled
424
+ this.heapLogTimer = setInterval(() => logMcpHeapUsage("heartbeat"), intervalMs);
425
+ // `unref()` so the heartbeat never keeps the event loop alive on its
426
+ // own — `stop()` must actively clear it, but a forgotten stop should
427
+ // not hang the process.
428
+ if (typeof this.heapLogTimer.unref === "function") {
429
+ this.heapLogTimer.unref();
430
+ }
347
431
  }
348
432
 
349
433
  /**
@@ -353,6 +437,12 @@ export class ManduMcpServer {
353
437
  // 설정 감시 중지
354
438
  this.configWatcher?.stop();
355
439
 
440
+ // Phase 17 — stop the heap heartbeat. Safe to call when never started.
441
+ if (this.heapLogTimer) {
442
+ clearInterval(this.heapLogTimer);
443
+ this.heapLogTimer = null;
444
+ }
445
+
356
446
  // 로깅 해제
357
447
  teardownMcpLogging();
358
448