@gethmy/mcp 2.3.3 → 2.3.4
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.
- package/dist/cli.js +199 -4
- package/dist/index.js +199 -4
- package/package.json +1 -1
- package/src/api-client.ts +6 -0
- package/src/memory-cleanup.ts +158 -0
- package/src/server.ts +140 -7
package/dist/cli.js
CHANGED
|
@@ -28465,6 +28465,85 @@ function generateHealthReport(report) {
|
|
|
28465
28465
|
return lines.join(`
|
|
28466
28466
|
`);
|
|
28467
28467
|
}
|
|
28468
|
+
async function purgeMemories(client3, workspaceId, projectId, options) {
|
|
28469
|
+
const dryRun = options.dryRun !== false;
|
|
28470
|
+
const { filters } = options;
|
|
28471
|
+
const hasFilter = filters.tier || filters.scope || filters.type || filters.olderThanDays !== undefined || filters.maxConfidence !== undefined || filters.tags && filters.tags.length > 0;
|
|
28472
|
+
if (!hasFilter) {
|
|
28473
|
+
throw new Error("At least one narrowing filter (tier, scope, type, olderThanDays, maxConfidence, tags) is required.");
|
|
28474
|
+
}
|
|
28475
|
+
const allMatches = [];
|
|
28476
|
+
let offset = 0;
|
|
28477
|
+
const pageSize = 100;
|
|
28478
|
+
const now = Date.now();
|
|
28479
|
+
while (true) {
|
|
28480
|
+
const result = await client3.listMemoryEntities({
|
|
28481
|
+
workspace_id: workspaceId,
|
|
28482
|
+
project_id: projectId,
|
|
28483
|
+
type: filters.type,
|
|
28484
|
+
scope: filters.scope,
|
|
28485
|
+
tags: filters.tags,
|
|
28486
|
+
limit: pageSize,
|
|
28487
|
+
offset
|
|
28488
|
+
});
|
|
28489
|
+
const entities = result.entities || [];
|
|
28490
|
+
if (entities.length === 0)
|
|
28491
|
+
break;
|
|
28492
|
+
for (const entity of entities) {
|
|
28493
|
+
if (filters.tier && entity.memory_tier !== filters.tier)
|
|
28494
|
+
continue;
|
|
28495
|
+
if (filters.maxConfidence !== undefined && entity.confidence > filters.maxConfidence)
|
|
28496
|
+
continue;
|
|
28497
|
+
if (filters.olderThanDays !== undefined) {
|
|
28498
|
+
const ref = entity.last_accessed_at || entity.created_at;
|
|
28499
|
+
const ageDays = (now - new Date(ref).getTime()) / MS_PER_DAY;
|
|
28500
|
+
if (ageDays < filters.olderThanDays)
|
|
28501
|
+
continue;
|
|
28502
|
+
}
|
|
28503
|
+
allMatches.push(entity);
|
|
28504
|
+
}
|
|
28505
|
+
if (entities.length < pageSize)
|
|
28506
|
+
break;
|
|
28507
|
+
offset += pageSize;
|
|
28508
|
+
}
|
|
28509
|
+
const items = allMatches.map((e) => ({
|
|
28510
|
+
id: e.id,
|
|
28511
|
+
title: e.title,
|
|
28512
|
+
type: e.type,
|
|
28513
|
+
tier: e.memory_tier,
|
|
28514
|
+
confidence: e.confidence,
|
|
28515
|
+
ageDays: Math.round((now - new Date(e.last_accessed_at || e.created_at).getTime()) / MS_PER_DAY)
|
|
28516
|
+
}));
|
|
28517
|
+
const errors3 = [];
|
|
28518
|
+
let purged = 0;
|
|
28519
|
+
if (!dryRun) {
|
|
28520
|
+
for (let i = 0;i < allMatches.length; i += CONCURRENCY_LIMIT) {
|
|
28521
|
+
const batch = allMatches.slice(i, i + CONCURRENCY_LIMIT);
|
|
28522
|
+
const results = await Promise.allSettled(batch.map((e) => client3.deleteMemoryEntity(e.id)));
|
|
28523
|
+
for (let j = 0;j < results.length; j++) {
|
|
28524
|
+
if (results[j].status === "fulfilled") {
|
|
28525
|
+
purged++;
|
|
28526
|
+
} else {
|
|
28527
|
+
errors3.push({
|
|
28528
|
+
entityId: batch[j].id,
|
|
28529
|
+
message: results[j].status === "rejected" ? String(results[j].reason) : "Unknown error"
|
|
28530
|
+
});
|
|
28531
|
+
}
|
|
28532
|
+
}
|
|
28533
|
+
}
|
|
28534
|
+
}
|
|
28535
|
+
return {
|
|
28536
|
+
success: errors3.length === 0,
|
|
28537
|
+
dryRun,
|
|
28538
|
+
timestamp: new Date().toISOString(),
|
|
28539
|
+
workspace: { id: workspaceId, projectId },
|
|
28540
|
+
filters,
|
|
28541
|
+
matched: allMatches.length,
|
|
28542
|
+
purged: dryRun ? 0 : purged,
|
|
28543
|
+
items,
|
|
28544
|
+
errors: errors3
|
|
28545
|
+
};
|
|
28546
|
+
}
|
|
28468
28547
|
|
|
28469
28548
|
// src/onboard.ts
|
|
28470
28549
|
async function onboardNewUser(params) {
|
|
@@ -29060,6 +29139,20 @@ var TOOLS = {
|
|
|
29060
29139
|
estimatedMinutesRemaining: {
|
|
29061
29140
|
type: "number",
|
|
29062
29141
|
description: "Updated time estimate"
|
|
29142
|
+
},
|
|
29143
|
+
actions: {
|
|
29144
|
+
type: "array",
|
|
29145
|
+
items: {
|
|
29146
|
+
type: "object",
|
|
29147
|
+
properties: {
|
|
29148
|
+
description: {
|
|
29149
|
+
type: "string",
|
|
29150
|
+
description: "What was done, e.g. 'Edited CardDetailSheet.tsx — added done toggle'"
|
|
29151
|
+
}
|
|
29152
|
+
},
|
|
29153
|
+
required: ["description"]
|
|
29154
|
+
},
|
|
29155
|
+
description: "Actions performed since last update. Each becomes a visible activity log entry."
|
|
29063
29156
|
}
|
|
29064
29157
|
},
|
|
29065
29158
|
required: ["cardId", "agentIdentifier", "agentName"]
|
|
@@ -29941,6 +30034,53 @@ var TOOLS = {
|
|
|
29941
30034
|
},
|
|
29942
30035
|
required: []
|
|
29943
30036
|
}
|
|
30037
|
+
},
|
|
30038
|
+
harmony_purge_memories: {
|
|
30039
|
+
description: "Bulk-delete memory entities matching filters within a project. Requires at least one narrowing filter (tier, scope, type, olderThanDays, maxConfidence, tags). Dry-run by default — preview what would be deleted before executing.",
|
|
30040
|
+
inputSchema: {
|
|
30041
|
+
type: "object",
|
|
30042
|
+
properties: {
|
|
30043
|
+
workspaceId: {
|
|
30044
|
+
type: "string",
|
|
30045
|
+
description: "Workspace ID (optional if context set)"
|
|
30046
|
+
},
|
|
30047
|
+
projectId: {
|
|
30048
|
+
type: "string",
|
|
30049
|
+
description: "Project ID (required — purge is project-scoped). Falls back to active project context."
|
|
30050
|
+
},
|
|
30051
|
+
dryRun: {
|
|
30052
|
+
type: "boolean",
|
|
30053
|
+
description: "Preview what would be deleted without executing (default: true)"
|
|
30054
|
+
},
|
|
30055
|
+
tier: {
|
|
30056
|
+
type: "string",
|
|
30057
|
+
enum: ["draft", "episode", "reference"],
|
|
30058
|
+
description: 'Filter by memory tier (e.g. "draft")'
|
|
30059
|
+
},
|
|
30060
|
+
scope: {
|
|
30061
|
+
type: "string",
|
|
30062
|
+
description: 'Filter by scope (e.g. "private", "project", "workspace")'
|
|
30063
|
+
},
|
|
30064
|
+
type: {
|
|
30065
|
+
type: "string",
|
|
30066
|
+
description: 'Filter by entity type (e.g. "error", "pattern", "lesson", "decision")'
|
|
30067
|
+
},
|
|
30068
|
+
olderThanDays: {
|
|
30069
|
+
type: "number",
|
|
30070
|
+
description: "Only include entities not accessed in at least this many days"
|
|
30071
|
+
},
|
|
30072
|
+
maxConfidence: {
|
|
30073
|
+
type: "number",
|
|
30074
|
+
description: "Only include entities with confidence at or below this value (e.g. 0.3 for low-confidence junk)"
|
|
30075
|
+
},
|
|
30076
|
+
tags: {
|
|
30077
|
+
type: "array",
|
|
30078
|
+
items: { type: "string" },
|
|
30079
|
+
description: "Only include entities matching these tags"
|
|
30080
|
+
}
|
|
30081
|
+
},
|
|
30082
|
+
required: []
|
|
30083
|
+
}
|
|
29944
30084
|
}
|
|
29945
30085
|
};
|
|
29946
30086
|
var RESOURCES = [
|
|
@@ -30570,12 +30710,20 @@ async function handleToolCall(name, args, deps) {
|
|
|
30570
30710
|
const agentIdentifier = exports_external.string().min(1).max(100).parse(args.agentIdentifier);
|
|
30571
30711
|
const agentName = exports_external.string().min(1).max(100).parse(args.agentName);
|
|
30572
30712
|
const progressPercent = args.progressPercent !== undefined ? exports_external.number().min(0).max(100).parse(args.progressPercent) : undefined;
|
|
30573
|
-
const
|
|
30713
|
+
const callerActions = args.actions;
|
|
30714
|
+
const now = new Date().toISOString();
|
|
30715
|
+
const callerRecentActions = [
|
|
30716
|
+
...args.recentActions || [],
|
|
30717
|
+
...(callerActions || []).map((a) => ({
|
|
30718
|
+
action: a.description,
|
|
30719
|
+
ts: now
|
|
30720
|
+
}))
|
|
30721
|
+
];
|
|
30574
30722
|
const memSession = getMemorySession(cardId);
|
|
30575
30723
|
let mergedRecentActions;
|
|
30576
30724
|
if (memSession?.dirty) {
|
|
30577
|
-
mergedRecentActions = mergeMemoryActionsInto(cardId, callerRecentActions
|
|
30578
|
-
} else if (callerRecentActions) {
|
|
30725
|
+
mergedRecentActions = mergeMemoryActionsInto(cardId, callerRecentActions);
|
|
30726
|
+
} else if (callerRecentActions.length > 0) {
|
|
30579
30727
|
mergedRecentActions = callerRecentActions;
|
|
30580
30728
|
}
|
|
30581
30729
|
const result = await client3.updateAgentProgress(cardId, {
|
|
@@ -31485,6 +31633,42 @@ async function handleToolCall(name, args, deps) {
|
|
|
31485
31633
|
healthReport: report.healthReport
|
|
31486
31634
|
};
|
|
31487
31635
|
}
|
|
31636
|
+
case "harmony_purge_memories": {
|
|
31637
|
+
const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
|
|
31638
|
+
if (!workspaceId) {
|
|
31639
|
+
throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
|
|
31640
|
+
}
|
|
31641
|
+
const projectId = args.projectId || deps.getActiveProjectId();
|
|
31642
|
+
if (!projectId) {
|
|
31643
|
+
throw new Error("No project specified. Purge requires a project scope. Use harmony_set_project_context or provide projectId.");
|
|
31644
|
+
}
|
|
31645
|
+
const filters = {};
|
|
31646
|
+
if (args.tier)
|
|
31647
|
+
filters.tier = args.tier;
|
|
31648
|
+
if (args.scope)
|
|
31649
|
+
filters.scope = args.scope;
|
|
31650
|
+
if (args.type)
|
|
31651
|
+
filters.type = args.type;
|
|
31652
|
+
if (args.olderThanDays !== undefined)
|
|
31653
|
+
filters.olderThanDays = args.olderThanDays;
|
|
31654
|
+
if (args.maxConfidence !== undefined)
|
|
31655
|
+
filters.maxConfidence = args.maxConfidence;
|
|
31656
|
+
if (args.tags)
|
|
31657
|
+
filters.tags = args.tags;
|
|
31658
|
+
const report = await purgeMemories(client3, workspaceId, projectId, {
|
|
31659
|
+
dryRun: args.dryRun,
|
|
31660
|
+
filters
|
|
31661
|
+
});
|
|
31662
|
+
return {
|
|
31663
|
+
success: report.success,
|
|
31664
|
+
dryRun: report.dryRun,
|
|
31665
|
+
matched: report.matched,
|
|
31666
|
+
purged: report.purged,
|
|
31667
|
+
items: report.items,
|
|
31668
|
+
errors: report.errors,
|
|
31669
|
+
message: report.dryRun ? `Found ${report.matched} entities matching filters. Run with dryRun=false to delete.` : `Purged ${report.purged} of ${report.matched} matching entities.`
|
|
31670
|
+
};
|
|
31671
|
+
}
|
|
31488
31672
|
default:
|
|
31489
31673
|
throw new Error(`Unknown tool: ${name}`);
|
|
31490
31674
|
}
|
|
@@ -31522,15 +31706,26 @@ class HarmonyMCPServer {
|
|
|
31522
31706
|
const cv = this.server.getClientVersion();
|
|
31523
31707
|
return cv ? { name: cv.name, version: cv.version } : null;
|
|
31524
31708
|
});
|
|
31709
|
+
let exitCode = 0;
|
|
31525
31710
|
const handleShutdown = async () => {
|
|
31526
31711
|
try {
|
|
31527
31712
|
await shutdownAllSessions();
|
|
31528
31713
|
} catch {}
|
|
31529
31714
|
destroyAutoSession();
|
|
31530
|
-
process.exit(
|
|
31715
|
+
process.exit(exitCode);
|
|
31531
31716
|
};
|
|
31532
31717
|
process.on("SIGINT", handleShutdown);
|
|
31533
31718
|
process.on("SIGTERM", handleShutdown);
|
|
31719
|
+
process.on("uncaughtException", (err) => {
|
|
31720
|
+
console.error("MCP server uncaught exception:", err);
|
|
31721
|
+
exitCode = 1;
|
|
31722
|
+
handleShutdown();
|
|
31723
|
+
});
|
|
31724
|
+
process.on("unhandledRejection", (reason) => {
|
|
31725
|
+
console.error("MCP server unhandled rejection:", reason);
|
|
31726
|
+
exitCode = 1;
|
|
31727
|
+
handleShutdown();
|
|
31728
|
+
});
|
|
31534
31729
|
try {
|
|
31535
31730
|
if (isConfigured()) {
|
|
31536
31731
|
const workspaceId = getActiveWorkspaceId();
|
package/dist/index.js
CHANGED
|
@@ -26225,6 +26225,85 @@ function generateHealthReport(report) {
|
|
|
26225
26225
|
return lines.join(`
|
|
26226
26226
|
`);
|
|
26227
26227
|
}
|
|
26228
|
+
async function purgeMemories(client3, workspaceId, projectId, options) {
|
|
26229
|
+
const dryRun = options.dryRun !== false;
|
|
26230
|
+
const { filters } = options;
|
|
26231
|
+
const hasFilter = filters.tier || filters.scope || filters.type || filters.olderThanDays !== undefined || filters.maxConfidence !== undefined || filters.tags && filters.tags.length > 0;
|
|
26232
|
+
if (!hasFilter) {
|
|
26233
|
+
throw new Error("At least one narrowing filter (tier, scope, type, olderThanDays, maxConfidence, tags) is required.");
|
|
26234
|
+
}
|
|
26235
|
+
const allMatches = [];
|
|
26236
|
+
let offset = 0;
|
|
26237
|
+
const pageSize = 100;
|
|
26238
|
+
const now = Date.now();
|
|
26239
|
+
while (true) {
|
|
26240
|
+
const result = await client3.listMemoryEntities({
|
|
26241
|
+
workspace_id: workspaceId,
|
|
26242
|
+
project_id: projectId,
|
|
26243
|
+
type: filters.type,
|
|
26244
|
+
scope: filters.scope,
|
|
26245
|
+
tags: filters.tags,
|
|
26246
|
+
limit: pageSize,
|
|
26247
|
+
offset
|
|
26248
|
+
});
|
|
26249
|
+
const entities = result.entities || [];
|
|
26250
|
+
if (entities.length === 0)
|
|
26251
|
+
break;
|
|
26252
|
+
for (const entity of entities) {
|
|
26253
|
+
if (filters.tier && entity.memory_tier !== filters.tier)
|
|
26254
|
+
continue;
|
|
26255
|
+
if (filters.maxConfidence !== undefined && entity.confidence > filters.maxConfidence)
|
|
26256
|
+
continue;
|
|
26257
|
+
if (filters.olderThanDays !== undefined) {
|
|
26258
|
+
const ref = entity.last_accessed_at || entity.created_at;
|
|
26259
|
+
const ageDays = (now - new Date(ref).getTime()) / MS_PER_DAY;
|
|
26260
|
+
if (ageDays < filters.olderThanDays)
|
|
26261
|
+
continue;
|
|
26262
|
+
}
|
|
26263
|
+
allMatches.push(entity);
|
|
26264
|
+
}
|
|
26265
|
+
if (entities.length < pageSize)
|
|
26266
|
+
break;
|
|
26267
|
+
offset += pageSize;
|
|
26268
|
+
}
|
|
26269
|
+
const items = allMatches.map((e) => ({
|
|
26270
|
+
id: e.id,
|
|
26271
|
+
title: e.title,
|
|
26272
|
+
type: e.type,
|
|
26273
|
+
tier: e.memory_tier,
|
|
26274
|
+
confidence: e.confidence,
|
|
26275
|
+
ageDays: Math.round((now - new Date(e.last_accessed_at || e.created_at).getTime()) / MS_PER_DAY)
|
|
26276
|
+
}));
|
|
26277
|
+
const errors3 = [];
|
|
26278
|
+
let purged = 0;
|
|
26279
|
+
if (!dryRun) {
|
|
26280
|
+
for (let i = 0;i < allMatches.length; i += CONCURRENCY_LIMIT) {
|
|
26281
|
+
const batch = allMatches.slice(i, i + CONCURRENCY_LIMIT);
|
|
26282
|
+
const results = await Promise.allSettled(batch.map((e) => client3.deleteMemoryEntity(e.id)));
|
|
26283
|
+
for (let j = 0;j < results.length; j++) {
|
|
26284
|
+
if (results[j].status === "fulfilled") {
|
|
26285
|
+
purged++;
|
|
26286
|
+
} else {
|
|
26287
|
+
errors3.push({
|
|
26288
|
+
entityId: batch[j].id,
|
|
26289
|
+
message: results[j].status === "rejected" ? String(results[j].reason) : "Unknown error"
|
|
26290
|
+
});
|
|
26291
|
+
}
|
|
26292
|
+
}
|
|
26293
|
+
}
|
|
26294
|
+
}
|
|
26295
|
+
return {
|
|
26296
|
+
success: errors3.length === 0,
|
|
26297
|
+
dryRun,
|
|
26298
|
+
timestamp: new Date().toISOString(),
|
|
26299
|
+
workspace: { id: workspaceId, projectId },
|
|
26300
|
+
filters,
|
|
26301
|
+
matched: allMatches.length,
|
|
26302
|
+
purged: dryRun ? 0 : purged,
|
|
26303
|
+
items,
|
|
26304
|
+
errors: errors3
|
|
26305
|
+
};
|
|
26306
|
+
}
|
|
26228
26307
|
|
|
26229
26308
|
// src/onboard.ts
|
|
26230
26309
|
async function onboardNewUser(params) {
|
|
@@ -26820,6 +26899,20 @@ var TOOLS = {
|
|
|
26820
26899
|
estimatedMinutesRemaining: {
|
|
26821
26900
|
type: "number",
|
|
26822
26901
|
description: "Updated time estimate"
|
|
26902
|
+
},
|
|
26903
|
+
actions: {
|
|
26904
|
+
type: "array",
|
|
26905
|
+
items: {
|
|
26906
|
+
type: "object",
|
|
26907
|
+
properties: {
|
|
26908
|
+
description: {
|
|
26909
|
+
type: "string",
|
|
26910
|
+
description: "What was done, e.g. 'Edited CardDetailSheet.tsx — added done toggle'"
|
|
26911
|
+
}
|
|
26912
|
+
},
|
|
26913
|
+
required: ["description"]
|
|
26914
|
+
},
|
|
26915
|
+
description: "Actions performed since last update. Each becomes a visible activity log entry."
|
|
26823
26916
|
}
|
|
26824
26917
|
},
|
|
26825
26918
|
required: ["cardId", "agentIdentifier", "agentName"]
|
|
@@ -27701,6 +27794,53 @@ var TOOLS = {
|
|
|
27701
27794
|
},
|
|
27702
27795
|
required: []
|
|
27703
27796
|
}
|
|
27797
|
+
},
|
|
27798
|
+
harmony_purge_memories: {
|
|
27799
|
+
description: "Bulk-delete memory entities matching filters within a project. Requires at least one narrowing filter (tier, scope, type, olderThanDays, maxConfidence, tags). Dry-run by default — preview what would be deleted before executing.",
|
|
27800
|
+
inputSchema: {
|
|
27801
|
+
type: "object",
|
|
27802
|
+
properties: {
|
|
27803
|
+
workspaceId: {
|
|
27804
|
+
type: "string",
|
|
27805
|
+
description: "Workspace ID (optional if context set)"
|
|
27806
|
+
},
|
|
27807
|
+
projectId: {
|
|
27808
|
+
type: "string",
|
|
27809
|
+
description: "Project ID (required — purge is project-scoped). Falls back to active project context."
|
|
27810
|
+
},
|
|
27811
|
+
dryRun: {
|
|
27812
|
+
type: "boolean",
|
|
27813
|
+
description: "Preview what would be deleted without executing (default: true)"
|
|
27814
|
+
},
|
|
27815
|
+
tier: {
|
|
27816
|
+
type: "string",
|
|
27817
|
+
enum: ["draft", "episode", "reference"],
|
|
27818
|
+
description: 'Filter by memory tier (e.g. "draft")'
|
|
27819
|
+
},
|
|
27820
|
+
scope: {
|
|
27821
|
+
type: "string",
|
|
27822
|
+
description: 'Filter by scope (e.g. "private", "project", "workspace")'
|
|
27823
|
+
},
|
|
27824
|
+
type: {
|
|
27825
|
+
type: "string",
|
|
27826
|
+
description: 'Filter by entity type (e.g. "error", "pattern", "lesson", "decision")'
|
|
27827
|
+
},
|
|
27828
|
+
olderThanDays: {
|
|
27829
|
+
type: "number",
|
|
27830
|
+
description: "Only include entities not accessed in at least this many days"
|
|
27831
|
+
},
|
|
27832
|
+
maxConfidence: {
|
|
27833
|
+
type: "number",
|
|
27834
|
+
description: "Only include entities with confidence at or below this value (e.g. 0.3 for low-confidence junk)"
|
|
27835
|
+
},
|
|
27836
|
+
tags: {
|
|
27837
|
+
type: "array",
|
|
27838
|
+
items: { type: "string" },
|
|
27839
|
+
description: "Only include entities matching these tags"
|
|
27840
|
+
}
|
|
27841
|
+
},
|
|
27842
|
+
required: []
|
|
27843
|
+
}
|
|
27704
27844
|
}
|
|
27705
27845
|
};
|
|
27706
27846
|
var RESOURCES = [
|
|
@@ -28330,12 +28470,20 @@ async function handleToolCall(name, args, deps) {
|
|
|
28330
28470
|
const agentIdentifier = exports_external.string().min(1).max(100).parse(args.agentIdentifier);
|
|
28331
28471
|
const agentName = exports_external.string().min(1).max(100).parse(args.agentName);
|
|
28332
28472
|
const progressPercent = args.progressPercent !== undefined ? exports_external.number().min(0).max(100).parse(args.progressPercent) : undefined;
|
|
28333
|
-
const
|
|
28473
|
+
const callerActions = args.actions;
|
|
28474
|
+
const now = new Date().toISOString();
|
|
28475
|
+
const callerRecentActions = [
|
|
28476
|
+
...args.recentActions || [],
|
|
28477
|
+
...(callerActions || []).map((a) => ({
|
|
28478
|
+
action: a.description,
|
|
28479
|
+
ts: now
|
|
28480
|
+
}))
|
|
28481
|
+
];
|
|
28334
28482
|
const memSession = getMemorySession(cardId);
|
|
28335
28483
|
let mergedRecentActions;
|
|
28336
28484
|
if (memSession?.dirty) {
|
|
28337
|
-
mergedRecentActions = mergeMemoryActionsInto(cardId, callerRecentActions
|
|
28338
|
-
} else if (callerRecentActions) {
|
|
28485
|
+
mergedRecentActions = mergeMemoryActionsInto(cardId, callerRecentActions);
|
|
28486
|
+
} else if (callerRecentActions.length > 0) {
|
|
28339
28487
|
mergedRecentActions = callerRecentActions;
|
|
28340
28488
|
}
|
|
28341
28489
|
const result = await client3.updateAgentProgress(cardId, {
|
|
@@ -29245,6 +29393,42 @@ async function handleToolCall(name, args, deps) {
|
|
|
29245
29393
|
healthReport: report.healthReport
|
|
29246
29394
|
};
|
|
29247
29395
|
}
|
|
29396
|
+
case "harmony_purge_memories": {
|
|
29397
|
+
const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
|
|
29398
|
+
if (!workspaceId) {
|
|
29399
|
+
throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
|
|
29400
|
+
}
|
|
29401
|
+
const projectId = args.projectId || deps.getActiveProjectId();
|
|
29402
|
+
if (!projectId) {
|
|
29403
|
+
throw new Error("No project specified. Purge requires a project scope. Use harmony_set_project_context or provide projectId.");
|
|
29404
|
+
}
|
|
29405
|
+
const filters = {};
|
|
29406
|
+
if (args.tier)
|
|
29407
|
+
filters.tier = args.tier;
|
|
29408
|
+
if (args.scope)
|
|
29409
|
+
filters.scope = args.scope;
|
|
29410
|
+
if (args.type)
|
|
29411
|
+
filters.type = args.type;
|
|
29412
|
+
if (args.olderThanDays !== undefined)
|
|
29413
|
+
filters.olderThanDays = args.olderThanDays;
|
|
29414
|
+
if (args.maxConfidence !== undefined)
|
|
29415
|
+
filters.maxConfidence = args.maxConfidence;
|
|
29416
|
+
if (args.tags)
|
|
29417
|
+
filters.tags = args.tags;
|
|
29418
|
+
const report = await purgeMemories(client3, workspaceId, projectId, {
|
|
29419
|
+
dryRun: args.dryRun,
|
|
29420
|
+
filters
|
|
29421
|
+
});
|
|
29422
|
+
return {
|
|
29423
|
+
success: report.success,
|
|
29424
|
+
dryRun: report.dryRun,
|
|
29425
|
+
matched: report.matched,
|
|
29426
|
+
purged: report.purged,
|
|
29427
|
+
items: report.items,
|
|
29428
|
+
errors: report.errors,
|
|
29429
|
+
message: report.dryRun ? `Found ${report.matched} entities matching filters. Run with dryRun=false to delete.` : `Purged ${report.purged} of ${report.matched} matching entities.`
|
|
29430
|
+
};
|
|
29431
|
+
}
|
|
29248
29432
|
default:
|
|
29249
29433
|
throw new Error(`Unknown tool: ${name}`);
|
|
29250
29434
|
}
|
|
@@ -29282,15 +29466,26 @@ class HarmonyMCPServer {
|
|
|
29282
29466
|
const cv = this.server.getClientVersion();
|
|
29283
29467
|
return cv ? { name: cv.name, version: cv.version } : null;
|
|
29284
29468
|
});
|
|
29469
|
+
let exitCode = 0;
|
|
29285
29470
|
const handleShutdown = async () => {
|
|
29286
29471
|
try {
|
|
29287
29472
|
await shutdownAllSessions();
|
|
29288
29473
|
} catch {}
|
|
29289
29474
|
destroyAutoSession();
|
|
29290
|
-
process.exit(
|
|
29475
|
+
process.exit(exitCode);
|
|
29291
29476
|
};
|
|
29292
29477
|
process.on("SIGINT", handleShutdown);
|
|
29293
29478
|
process.on("SIGTERM", handleShutdown);
|
|
29479
|
+
process.on("uncaughtException", (err) => {
|
|
29480
|
+
console.error("MCP server uncaught exception:", err);
|
|
29481
|
+
exitCode = 1;
|
|
29482
|
+
handleShutdown();
|
|
29483
|
+
});
|
|
29484
|
+
process.on("unhandledRejection", (reason) => {
|
|
29485
|
+
console.error("MCP server unhandled rejection:", reason);
|
|
29486
|
+
exitCode = 1;
|
|
29487
|
+
handleShutdown();
|
|
29488
|
+
});
|
|
29294
29489
|
try {
|
|
29295
29490
|
if (isConfigured()) {
|
|
29296
29491
|
const workspaceId = getActiveWorkspaceId();
|
package/package.json
CHANGED
package/src/api-client.ts
CHANGED
|
@@ -502,6 +502,9 @@ export class HarmonyApiClient {
|
|
|
502
502
|
phase?: string;
|
|
503
503
|
filesChanged?: number;
|
|
504
504
|
costCents?: number;
|
|
505
|
+
inputTokens?: number;
|
|
506
|
+
outputTokens?: number;
|
|
507
|
+
recentActions?: { action: string; ts: string }[];
|
|
505
508
|
},
|
|
506
509
|
): Promise<{ session: unknown; created: boolean }> {
|
|
507
510
|
return this.request("POST", `/cards/${cardId}/agent-context`, data);
|
|
@@ -512,6 +515,9 @@ export class HarmonyApiClient {
|
|
|
512
515
|
data?: {
|
|
513
516
|
status?: "completed" | "paused";
|
|
514
517
|
progressPercent?: number;
|
|
518
|
+
costCents?: number;
|
|
519
|
+
inputTokens?: number;
|
|
520
|
+
outputTokens?: number;
|
|
515
521
|
},
|
|
516
522
|
): Promise<{ session: unknown }> {
|
|
517
523
|
return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
|
package/src/memory-cleanup.ts
CHANGED
|
@@ -49,6 +49,43 @@ export interface CleanupOptions {
|
|
|
49
49
|
orphanAgeDays?: number;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Purge types
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
export interface PurgeFilters {
|
|
57
|
+
tier?: "draft" | "episode" | "reference";
|
|
58
|
+
scope?: string;
|
|
59
|
+
type?: string;
|
|
60
|
+
olderThanDays?: number;
|
|
61
|
+
maxConfidence?: number;
|
|
62
|
+
tags?: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface PurgeOptions {
|
|
66
|
+
dryRun?: boolean;
|
|
67
|
+
filters: PurgeFilters;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface PurgeReport {
|
|
71
|
+
success: boolean;
|
|
72
|
+
dryRun: boolean;
|
|
73
|
+
timestamp: string;
|
|
74
|
+
workspace: { id: string; projectId: string };
|
|
75
|
+
filters: PurgeFilters;
|
|
76
|
+
matched: number;
|
|
77
|
+
purged: number;
|
|
78
|
+
items: Array<{
|
|
79
|
+
id: string;
|
|
80
|
+
title: string;
|
|
81
|
+
type: string;
|
|
82
|
+
tier: string;
|
|
83
|
+
confidence: number;
|
|
84
|
+
ageDays: number;
|
|
85
|
+
}>;
|
|
86
|
+
errors: Array<{ entityId: string; message: string }>;
|
|
87
|
+
}
|
|
88
|
+
|
|
52
89
|
interface PruneStepResult {
|
|
53
90
|
staleDraftsFound: number;
|
|
54
91
|
pruned: number;
|
|
@@ -654,3 +691,124 @@ function generateHealthReport(report: CleanupReport): string {
|
|
|
654
691
|
|
|
655
692
|
return lines.join("\n");
|
|
656
693
|
}
|
|
694
|
+
|
|
695
|
+
// ---------------------------------------------------------------------------
|
|
696
|
+
// Purge — filtered bulk deletion
|
|
697
|
+
// ---------------------------------------------------------------------------
|
|
698
|
+
|
|
699
|
+
export async function purgeMemories(
|
|
700
|
+
client: HarmonyApiClient,
|
|
701
|
+
workspaceId: string,
|
|
702
|
+
projectId: string,
|
|
703
|
+
options: PurgeOptions,
|
|
704
|
+
): Promise<PurgeReport> {
|
|
705
|
+
const dryRun = options.dryRun !== false;
|
|
706
|
+
const { filters } = options;
|
|
707
|
+
|
|
708
|
+
// Safety: require at least one narrowing filter
|
|
709
|
+
const hasFilter =
|
|
710
|
+
filters.tier ||
|
|
711
|
+
filters.scope ||
|
|
712
|
+
filters.type ||
|
|
713
|
+
filters.olderThanDays !== undefined ||
|
|
714
|
+
filters.maxConfidence !== undefined ||
|
|
715
|
+
(filters.tags && filters.tags.length > 0);
|
|
716
|
+
|
|
717
|
+
if (!hasFilter) {
|
|
718
|
+
throw new Error(
|
|
719
|
+
"At least one narrowing filter (tier, scope, type, olderThanDays, maxConfidence, tags) is required.",
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Paginate through all matching entities
|
|
724
|
+
const allMatches: MemoryEntity[] = [];
|
|
725
|
+
let offset = 0;
|
|
726
|
+
const pageSize = 100;
|
|
727
|
+
const now = Date.now();
|
|
728
|
+
|
|
729
|
+
while (true) {
|
|
730
|
+
const result = await client.listMemoryEntities({
|
|
731
|
+
workspace_id: workspaceId,
|
|
732
|
+
project_id: projectId,
|
|
733
|
+
type: filters.type,
|
|
734
|
+
scope: filters.scope,
|
|
735
|
+
tags: filters.tags,
|
|
736
|
+
limit: pageSize,
|
|
737
|
+
offset,
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
const entities = (result.entities || []) as MemoryEntity[];
|
|
741
|
+
if (entities.length === 0) break;
|
|
742
|
+
|
|
743
|
+
// Client-side filtering for fields the API doesn't support natively
|
|
744
|
+
for (const entity of entities) {
|
|
745
|
+
if (filters.tier && entity.memory_tier !== filters.tier) continue;
|
|
746
|
+
if (
|
|
747
|
+
filters.maxConfidence !== undefined &&
|
|
748
|
+
entity.confidence > filters.maxConfidence
|
|
749
|
+
)
|
|
750
|
+
continue;
|
|
751
|
+
if (filters.olderThanDays !== undefined) {
|
|
752
|
+
const ref = entity.last_accessed_at || entity.created_at;
|
|
753
|
+
const ageDays = (now - new Date(ref).getTime()) / MS_PER_DAY;
|
|
754
|
+
if (ageDays < filters.olderThanDays) continue;
|
|
755
|
+
}
|
|
756
|
+
allMatches.push(entity);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (entities.length < pageSize) break;
|
|
760
|
+
offset += pageSize;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Build preview items
|
|
764
|
+
const items = allMatches.map((e) => ({
|
|
765
|
+
id: e.id,
|
|
766
|
+
title: e.title,
|
|
767
|
+
type: e.type,
|
|
768
|
+
tier: e.memory_tier,
|
|
769
|
+
confidence: e.confidence,
|
|
770
|
+
ageDays: Math.round(
|
|
771
|
+
(now - new Date(e.last_accessed_at || e.created_at).getTime()) /
|
|
772
|
+
MS_PER_DAY,
|
|
773
|
+
),
|
|
774
|
+
}));
|
|
775
|
+
|
|
776
|
+
// Execute deletions if not dry-run
|
|
777
|
+
const errors: Array<{ entityId: string; message: string }> = [];
|
|
778
|
+
let purged = 0;
|
|
779
|
+
|
|
780
|
+
if (!dryRun) {
|
|
781
|
+
// Delete in batches to avoid overwhelming the API
|
|
782
|
+
for (let i = 0; i < allMatches.length; i += CONCURRENCY_LIMIT) {
|
|
783
|
+
const batch = allMatches.slice(i, i + CONCURRENCY_LIMIT);
|
|
784
|
+
const results = await Promise.allSettled(
|
|
785
|
+
batch.map((e) => client.deleteMemoryEntity(e.id)),
|
|
786
|
+
);
|
|
787
|
+
for (let j = 0; j < results.length; j++) {
|
|
788
|
+
if (results[j].status === "fulfilled") {
|
|
789
|
+
purged++;
|
|
790
|
+
} else {
|
|
791
|
+
errors.push({
|
|
792
|
+
entityId: batch[j].id,
|
|
793
|
+
message:
|
|
794
|
+
results[j].status === "rejected"
|
|
795
|
+
? String((results[j] as PromiseRejectedResult).reason)
|
|
796
|
+
: "Unknown error",
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return {
|
|
804
|
+
success: errors.length === 0,
|
|
805
|
+
dryRun,
|
|
806
|
+
timestamp: new Date().toISOString(),
|
|
807
|
+
workspace: { id: workspaceId, projectId },
|
|
808
|
+
filters,
|
|
809
|
+
matched: allMatches.length,
|
|
810
|
+
purged: dryRun ? 0 : purged,
|
|
811
|
+
items,
|
|
812
|
+
errors,
|
|
813
|
+
};
|
|
814
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
} from "./context-assembly.js";
|
|
59
59
|
import { autoExpandGraph } from "./graph-expansion.js";
|
|
60
60
|
import { runLifecycleMaintenance } from "./lifecycle-maintenance.js";
|
|
61
|
-
import { runMemoryCleanup } from "./memory-cleanup.js";
|
|
61
|
+
import { runMemoryCleanup, purgeMemories, type PurgeFilters } from "./memory-cleanup.js";
|
|
62
62
|
import { onboardNewUser } from "./onboard.js";
|
|
63
63
|
import type { PromptVariant } from "./prompt-builder.js";
|
|
64
64
|
|
|
@@ -721,6 +721,22 @@ const TOOLS = {
|
|
|
721
721
|
type: "number",
|
|
722
722
|
description: "Updated time estimate",
|
|
723
723
|
},
|
|
724
|
+
actions: {
|
|
725
|
+
type: "array",
|
|
726
|
+
items: {
|
|
727
|
+
type: "object",
|
|
728
|
+
properties: {
|
|
729
|
+
description: {
|
|
730
|
+
type: "string",
|
|
731
|
+
description:
|
|
732
|
+
"What was done, e.g. 'Edited CardDetailSheet.tsx — added done toggle'",
|
|
733
|
+
},
|
|
734
|
+
},
|
|
735
|
+
required: ["description"],
|
|
736
|
+
},
|
|
737
|
+
description:
|
|
738
|
+
"Actions performed since last update. Each becomes a visible activity log entry.",
|
|
739
|
+
},
|
|
724
740
|
},
|
|
725
741
|
required: ["cardId", "agentIdentifier", "agentName"],
|
|
726
742
|
},
|
|
@@ -1683,6 +1699,60 @@ const TOOLS = {
|
|
|
1683
1699
|
required: [],
|
|
1684
1700
|
},
|
|
1685
1701
|
},
|
|
1702
|
+
harmony_purge_memories: {
|
|
1703
|
+
description:
|
|
1704
|
+
"Bulk-delete memory entities matching filters within a project. Requires at least one narrowing filter (tier, scope, type, olderThanDays, maxConfidence, tags). Dry-run by default — preview what would be deleted before executing.",
|
|
1705
|
+
inputSchema: {
|
|
1706
|
+
type: "object",
|
|
1707
|
+
properties: {
|
|
1708
|
+
workspaceId: {
|
|
1709
|
+
type: "string",
|
|
1710
|
+
description: "Workspace ID (optional if context set)",
|
|
1711
|
+
},
|
|
1712
|
+
projectId: {
|
|
1713
|
+
type: "string",
|
|
1714
|
+
description:
|
|
1715
|
+
"Project ID (required — purge is project-scoped). Falls back to active project context.",
|
|
1716
|
+
},
|
|
1717
|
+
dryRun: {
|
|
1718
|
+
type: "boolean",
|
|
1719
|
+
description:
|
|
1720
|
+
"Preview what would be deleted without executing (default: true)",
|
|
1721
|
+
},
|
|
1722
|
+
tier: {
|
|
1723
|
+
type: "string",
|
|
1724
|
+
enum: ["draft", "episode", "reference"],
|
|
1725
|
+
description: 'Filter by memory tier (e.g. "draft")',
|
|
1726
|
+
},
|
|
1727
|
+
scope: {
|
|
1728
|
+
type: "string",
|
|
1729
|
+
description:
|
|
1730
|
+
'Filter by scope (e.g. "private", "project", "workspace")',
|
|
1731
|
+
},
|
|
1732
|
+
type: {
|
|
1733
|
+
type: "string",
|
|
1734
|
+
description:
|
|
1735
|
+
'Filter by entity type (e.g. "error", "pattern", "lesson", "decision")',
|
|
1736
|
+
},
|
|
1737
|
+
olderThanDays: {
|
|
1738
|
+
type: "number",
|
|
1739
|
+
description:
|
|
1740
|
+
"Only include entities not accessed in at least this many days",
|
|
1741
|
+
},
|
|
1742
|
+
maxConfidence: {
|
|
1743
|
+
type: "number",
|
|
1744
|
+
description:
|
|
1745
|
+
"Only include entities with confidence at or below this value (e.g. 0.3 for low-confidence junk)",
|
|
1746
|
+
},
|
|
1747
|
+
tags: {
|
|
1748
|
+
type: "array",
|
|
1749
|
+
items: { type: "string" },
|
|
1750
|
+
description: "Only include entities matching these tags",
|
|
1751
|
+
},
|
|
1752
|
+
},
|
|
1753
|
+
required: [],
|
|
1754
|
+
},
|
|
1755
|
+
},
|
|
1686
1756
|
};
|
|
1687
1757
|
|
|
1688
1758
|
// Resource URIs
|
|
@@ -2643,18 +2713,26 @@ async function handleToolCall(
|
|
|
2643
2713
|
args.progressPercent !== undefined
|
|
2644
2714
|
? z.number().min(0).max(100).parse(args.progressPercent)
|
|
2645
2715
|
: undefined;
|
|
2646
|
-
//
|
|
2647
|
-
const
|
|
2648
|
-
| {
|
|
2716
|
+
// Convert actions parameter to recentActions format and merge with memory actions
|
|
2717
|
+
const callerActions = args.actions as
|
|
2718
|
+
| { description: string }[]
|
|
2649
2719
|
| undefined;
|
|
2720
|
+
const now = new Date().toISOString();
|
|
2721
|
+
const callerRecentActions: { action: string; ts: string }[] = [
|
|
2722
|
+
...((args.recentActions as { action: string; ts: string }[]) || []),
|
|
2723
|
+
...(callerActions || []).map((a) => ({
|
|
2724
|
+
action: a.description,
|
|
2725
|
+
ts: now,
|
|
2726
|
+
})),
|
|
2727
|
+
];
|
|
2650
2728
|
const memSession = getMemorySession(cardId);
|
|
2651
2729
|
let mergedRecentActions: { action: string; ts: string }[] | undefined;
|
|
2652
2730
|
if (memSession?.dirty) {
|
|
2653
2731
|
mergedRecentActions = mergeMemoryActionsInto(
|
|
2654
2732
|
cardId,
|
|
2655
|
-
callerRecentActions
|
|
2733
|
+
callerRecentActions,
|
|
2656
2734
|
);
|
|
2657
|
-
} else if (callerRecentActions) {
|
|
2735
|
+
} else if (callerRecentActions.length > 0) {
|
|
2658
2736
|
mergedRecentActions = callerRecentActions;
|
|
2659
2737
|
}
|
|
2660
2738
|
|
|
@@ -4056,6 +4134,50 @@ async function handleToolCall(
|
|
|
4056
4134
|
};
|
|
4057
4135
|
}
|
|
4058
4136
|
|
|
4137
|
+
case "harmony_purge_memories": {
|
|
4138
|
+
const workspaceId =
|
|
4139
|
+
(args.workspaceId as string) || deps.getActiveWorkspaceId();
|
|
4140
|
+
if (!workspaceId) {
|
|
4141
|
+
throw new Error(
|
|
4142
|
+
"No workspace specified. Use harmony_set_workspace_context or provide workspaceId.",
|
|
4143
|
+
);
|
|
4144
|
+
}
|
|
4145
|
+
const projectId =
|
|
4146
|
+
(args.projectId as string) || deps.getActiveProjectId();
|
|
4147
|
+
if (!projectId) {
|
|
4148
|
+
throw new Error(
|
|
4149
|
+
"No project specified. Purge requires a project scope. Use harmony_set_project_context or provide projectId.",
|
|
4150
|
+
);
|
|
4151
|
+
}
|
|
4152
|
+
|
|
4153
|
+
const filters: PurgeFilters = {};
|
|
4154
|
+
if (args.tier) filters.tier = args.tier as PurgeFilters["tier"];
|
|
4155
|
+
if (args.scope) filters.scope = args.scope as string;
|
|
4156
|
+
if (args.type) filters.type = args.type as string;
|
|
4157
|
+
if (args.olderThanDays !== undefined)
|
|
4158
|
+
filters.olderThanDays = args.olderThanDays as number;
|
|
4159
|
+
if (args.maxConfidence !== undefined)
|
|
4160
|
+
filters.maxConfidence = args.maxConfidence as number;
|
|
4161
|
+
if (args.tags) filters.tags = args.tags as string[];
|
|
4162
|
+
|
|
4163
|
+
const report = await purgeMemories(client, workspaceId, projectId, {
|
|
4164
|
+
dryRun: args.dryRun as boolean | undefined,
|
|
4165
|
+
filters,
|
|
4166
|
+
});
|
|
4167
|
+
|
|
4168
|
+
return {
|
|
4169
|
+
success: report.success,
|
|
4170
|
+
dryRun: report.dryRun,
|
|
4171
|
+
matched: report.matched,
|
|
4172
|
+
purged: report.purged,
|
|
4173
|
+
items: report.items,
|
|
4174
|
+
errors: report.errors,
|
|
4175
|
+
message: report.dryRun
|
|
4176
|
+
? `Found ${report.matched} entities matching filters. Run with dryRun=false to delete.`
|
|
4177
|
+
: `Purged ${report.purged} of ${report.matched} matching entities.`,
|
|
4178
|
+
};
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4059
4181
|
default:
|
|
4060
4182
|
throw new Error(`Unknown tool: ${name}`);
|
|
4061
4183
|
}
|
|
@@ -4109,6 +4231,7 @@ export class HarmonyMCPServer {
|
|
|
4109
4231
|
);
|
|
4110
4232
|
|
|
4111
4233
|
// Graceful shutdown: end all auto-sessions
|
|
4234
|
+
let exitCode = 0;
|
|
4112
4235
|
const handleShutdown = async () => {
|
|
4113
4236
|
try {
|
|
4114
4237
|
await shutdownAllSessions();
|
|
@@ -4116,10 +4239,20 @@ export class HarmonyMCPServer {
|
|
|
4116
4239
|
// Best-effort
|
|
4117
4240
|
}
|
|
4118
4241
|
destroyAutoSession();
|
|
4119
|
-
process.exit(
|
|
4242
|
+
process.exit(exitCode);
|
|
4120
4243
|
};
|
|
4121
4244
|
process.on("SIGINT", handleShutdown);
|
|
4122
4245
|
process.on("SIGTERM", handleShutdown);
|
|
4246
|
+
process.on("uncaughtException", (err) => {
|
|
4247
|
+
console.error("MCP server uncaught exception:", err);
|
|
4248
|
+
exitCode = 1;
|
|
4249
|
+
handleShutdown();
|
|
4250
|
+
});
|
|
4251
|
+
process.on("unhandledRejection", (reason) => {
|
|
4252
|
+
console.error("MCP server unhandled rejection:", reason);
|
|
4253
|
+
exitCode = 1;
|
|
4254
|
+
handleShutdown();
|
|
4255
|
+
});
|
|
4123
4256
|
|
|
4124
4257
|
// Attempt startup sync (non-blocking, best-effort)
|
|
4125
4258
|
try {
|