memory-journal-mcp 6.3.0 → 7.0.0
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/README.md +91 -74
- package/dist/{chunk-VH4SRTLB.js → chunk-2BJHLTYP.js} +1237 -97
- package/dist/{chunk-K2SCUSN4.js → chunk-ARLH46WS.js} +34 -3
- package/dist/{chunk-QQ2ZY3CH.js → chunk-SBNQ7MXZ.js} +412 -365
- package/dist/cli.js +26 -4
- package/dist/github-integration-PDRLXKGM.js +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +3 -3
- package/dist/tools-FFFGXIKN.js +3 -0
- package/package.json +14 -13
- package/dist/github-integration-DTNYAKVJ.js +0 -1
- package/dist/tools-P4XXHO3Z.js +0 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { setDefaultSandboxMode, getTools, callTool, sendProgress, execQuery, transformEntryRow, resolveGitHubRepo, isResourceError, milestoneCompletionPct, DEFAULT_BRIEFING_CONFIG } from './chunk-
|
|
2
|
-
import { logger, GitHubIntegration, ConfigurationError, ResourceNotFoundError, ConnectionError, QueryError, assertNoPathTraversal, ValidationError, MemoryJournalMcpError, validateDateFormatPattern } from './chunk-
|
|
1
|
+
import { withSessionInit, withPriority, ASSISTANT_FOCUSED, TOOL_GROUPS, HIGH_PRIORITY, LOW_PRIORITY, MEDIUM_PRIORITY, setDefaultSandboxMode, initializeAuditLogger, parseToolFilter, getFilterSummary, getToolFilterFromEnv, getTools, getEnabledGroups, callTool, getGlobalAuditLogger, sendProgress, SUPPORTED_SCOPES, getRequiredScope, hasScope, getAuditResourceDef, execQuery, transformEntryRow, resolveGitHubRepo, isResourceError, milestoneCompletionPct, parseScopes, BASE_SCOPES, getAllToolNames, globalMetrics, DEFAULT_BRIEFING_CONFIG } from './chunk-2BJHLTYP.js';
|
|
2
|
+
import { logger, GitHubIntegration, ConfigurationError, ResourceNotFoundError, ConnectionError, QueryError, assertNoPathTraversal, ValidationError, MemoryJournalMcpError, validateDateFormatPattern } from './chunk-ARLH46WS.js';
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
4
|
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -17,216 +17,6 @@ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
|
17
17
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
18
18
|
import { z } from 'zod';
|
|
19
19
|
|
|
20
|
-
// src/filtering/tool-filter.ts
|
|
21
|
-
var TOOL_GROUPS = {
|
|
22
|
-
core: [
|
|
23
|
-
"create_entry",
|
|
24
|
-
"get_entry_by_id",
|
|
25
|
-
"get_recent_entries",
|
|
26
|
-
"create_entry_minimal",
|
|
27
|
-
"test_simple",
|
|
28
|
-
"list_tags"
|
|
29
|
-
],
|
|
30
|
-
search: ["search_entries", "search_by_date_range", "semantic_search", "get_vector_index_stats"],
|
|
31
|
-
analytics: ["get_statistics", "get_cross_project_insights"],
|
|
32
|
-
relationships: ["link_entries", "visualize_relationships"],
|
|
33
|
-
export: ["export_entries"],
|
|
34
|
-
admin: [
|
|
35
|
-
"update_entry",
|
|
36
|
-
"delete_entry",
|
|
37
|
-
"rebuild_vector_index",
|
|
38
|
-
"add_to_vector_index",
|
|
39
|
-
"merge_tags"
|
|
40
|
-
],
|
|
41
|
-
github: [
|
|
42
|
-
"get_github_issues",
|
|
43
|
-
"get_github_prs",
|
|
44
|
-
"get_github_issue",
|
|
45
|
-
"get_github_pr",
|
|
46
|
-
"get_github_context",
|
|
47
|
-
"get_kanban_board",
|
|
48
|
-
"move_kanban_item",
|
|
49
|
-
"create_github_issue_with_entry",
|
|
50
|
-
"close_github_issue_with_entry",
|
|
51
|
-
"get_github_milestones",
|
|
52
|
-
"get_github_milestone",
|
|
53
|
-
"create_github_milestone",
|
|
54
|
-
"update_github_milestone",
|
|
55
|
-
"delete_github_milestone",
|
|
56
|
-
"get_repo_insights",
|
|
57
|
-
"get_copilot_reviews"
|
|
58
|
-
],
|
|
59
|
-
backup: ["backup_journal", "list_backups", "restore_backup", "cleanup_backups"],
|
|
60
|
-
team: [
|
|
61
|
-
"team_create_entry",
|
|
62
|
-
"team_get_entry_by_id",
|
|
63
|
-
"team_get_recent",
|
|
64
|
-
"team_list_tags",
|
|
65
|
-
"team_search",
|
|
66
|
-
"team_search_by_date_range",
|
|
67
|
-
"team_update_entry",
|
|
68
|
-
"team_delete_entry",
|
|
69
|
-
"team_merge_tags",
|
|
70
|
-
"team_get_statistics",
|
|
71
|
-
"team_link_entries",
|
|
72
|
-
"team_visualize_relationships",
|
|
73
|
-
"team_export_entries",
|
|
74
|
-
"team_backup",
|
|
75
|
-
"team_list_backups",
|
|
76
|
-
"team_semantic_search",
|
|
77
|
-
"team_get_vector_index_stats",
|
|
78
|
-
"team_rebuild_vector_index",
|
|
79
|
-
"team_add_to_vector_index",
|
|
80
|
-
"team_get_cross_project_insights"
|
|
81
|
-
],
|
|
82
|
-
codemode: ["mj_execute_code"]
|
|
83
|
-
};
|
|
84
|
-
var META_GROUPS = {
|
|
85
|
-
starter: ["core", "search", "codemode"],
|
|
86
|
-
essential: ["core", "codemode"],
|
|
87
|
-
full: [
|
|
88
|
-
"core",
|
|
89
|
-
"search",
|
|
90
|
-
"analytics",
|
|
91
|
-
"relationships",
|
|
92
|
-
"export",
|
|
93
|
-
"admin",
|
|
94
|
-
"github",
|
|
95
|
-
"backup",
|
|
96
|
-
"team",
|
|
97
|
-
"codemode"
|
|
98
|
-
],
|
|
99
|
-
readonly: ["core", "search", "analytics", "relationships", "export"]
|
|
100
|
-
};
|
|
101
|
-
function getAllToolNames() {
|
|
102
|
-
const allTools = [];
|
|
103
|
-
for (const tools of Object.values(TOOL_GROUPS)) {
|
|
104
|
-
allTools.push(...tools);
|
|
105
|
-
}
|
|
106
|
-
return allTools;
|
|
107
|
-
}
|
|
108
|
-
function getToolGroup(toolName) {
|
|
109
|
-
for (const [group, tools] of Object.entries(TOOL_GROUPS)) {
|
|
110
|
-
if (tools.includes(toolName)) {
|
|
111
|
-
return group;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return void 0;
|
|
115
|
-
}
|
|
116
|
-
function getEnabledGroups(enabledTools) {
|
|
117
|
-
const groups = /* @__PURE__ */ new Set();
|
|
118
|
-
for (const [group, tools] of Object.entries(TOOL_GROUPS)) {
|
|
119
|
-
if (tools.some((t) => enabledTools.has(t))) {
|
|
120
|
-
groups.add(group);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return groups;
|
|
124
|
-
}
|
|
125
|
-
function isGroup(name) {
|
|
126
|
-
return name in TOOL_GROUPS;
|
|
127
|
-
}
|
|
128
|
-
function isMetaGroup(name) {
|
|
129
|
-
return name in META_GROUPS;
|
|
130
|
-
}
|
|
131
|
-
function parseToolFilter(filterString) {
|
|
132
|
-
const rules = [];
|
|
133
|
-
const parts = filterString.split(",").map((p) => p.trim()).filter(Boolean);
|
|
134
|
-
let enabledTools = /* @__PURE__ */ new Set();
|
|
135
|
-
let isWhitelistMode = false;
|
|
136
|
-
for (let i = 0; i < parts.length; i++) {
|
|
137
|
-
const part = parts[i];
|
|
138
|
-
if (!part) continue;
|
|
139
|
-
const isAdd = part.startsWith("+");
|
|
140
|
-
const isRemove = part.startsWith("-");
|
|
141
|
-
const name = isAdd || isRemove ? part.slice(1) : part;
|
|
142
|
-
if (i === 0 && !isAdd && !isRemove) {
|
|
143
|
-
isWhitelistMode = true;
|
|
144
|
-
if (isMetaGroup(name)) {
|
|
145
|
-
for (const group of META_GROUPS[name]) {
|
|
146
|
-
enabledTools = /* @__PURE__ */ new Set([...enabledTools, ...TOOL_GROUPS[group]]);
|
|
147
|
-
}
|
|
148
|
-
} else if (isGroup(name)) {
|
|
149
|
-
enabledTools = /* @__PURE__ */ new Set([...enabledTools, ...TOOL_GROUPS[name]]);
|
|
150
|
-
} else {
|
|
151
|
-
enabledTools.add(name);
|
|
152
|
-
}
|
|
153
|
-
rules.push({
|
|
154
|
-
type: "include",
|
|
155
|
-
target: name,
|
|
156
|
-
isGroup: isGroup(name) || isMetaGroup(name)
|
|
157
|
-
});
|
|
158
|
-
} else if (isRemove) {
|
|
159
|
-
if (isGroup(name)) {
|
|
160
|
-
for (const tool of TOOL_GROUPS[name]) {
|
|
161
|
-
enabledTools.delete(tool);
|
|
162
|
-
}
|
|
163
|
-
} else {
|
|
164
|
-
enabledTools.delete(name);
|
|
165
|
-
}
|
|
166
|
-
rules.push({
|
|
167
|
-
type: "exclude",
|
|
168
|
-
target: name,
|
|
169
|
-
isGroup: isGroup(name)
|
|
170
|
-
});
|
|
171
|
-
} else {
|
|
172
|
-
if (isMetaGroup(name)) {
|
|
173
|
-
for (const group of META_GROUPS[name]) {
|
|
174
|
-
enabledTools = /* @__PURE__ */ new Set([...enabledTools, ...TOOL_GROUPS[group]]);
|
|
175
|
-
}
|
|
176
|
-
} else if (isGroup(name)) {
|
|
177
|
-
enabledTools = /* @__PURE__ */ new Set([...enabledTools, ...TOOL_GROUPS[name]]);
|
|
178
|
-
} else {
|
|
179
|
-
enabledTools.add(name);
|
|
180
|
-
}
|
|
181
|
-
rules.push({
|
|
182
|
-
type: "include",
|
|
183
|
-
target: name,
|
|
184
|
-
isGroup: isGroup(name) || isMetaGroup(name)
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (!isWhitelistMode && rules.length > 0 && rules[0]?.type === "exclude") {
|
|
189
|
-
enabledTools = new Set(getAllToolNames());
|
|
190
|
-
for (const rule of rules) {
|
|
191
|
-
if (rule.type === "exclude") {
|
|
192
|
-
if (isGroup(rule.target)) {
|
|
193
|
-
for (const tool of TOOL_GROUPS[rule.target]) {
|
|
194
|
-
enabledTools.delete(tool);
|
|
195
|
-
}
|
|
196
|
-
} else {
|
|
197
|
-
enabledTools.delete(rule.target);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return {
|
|
203
|
-
raw: filterString,
|
|
204
|
-
rules,
|
|
205
|
-
enabledTools
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
function isToolEnabled(toolName, filterConfig) {
|
|
209
|
-
return filterConfig.enabledTools.has(toolName);
|
|
210
|
-
}
|
|
211
|
-
function filterTools(tools, filterConfig) {
|
|
212
|
-
return tools.filter((tool) => isToolEnabled(tool.name, filterConfig));
|
|
213
|
-
}
|
|
214
|
-
function getToolFilterFromEnv() {
|
|
215
|
-
const filterString = process.env["MEMORY_JOURNAL_MCP_TOOL_FILTER"];
|
|
216
|
-
if (!filterString) return null;
|
|
217
|
-
return parseToolFilter(filterString);
|
|
218
|
-
}
|
|
219
|
-
function calculateTokenSavings(totalTools, enabledTools, avgTokensPerTool = 150) {
|
|
220
|
-
const savedTokens = (totalTools - enabledTools) * avgTokensPerTool;
|
|
221
|
-
const reduction = (totalTools - enabledTools) / totalTools * 100;
|
|
222
|
-
return { reduction, savedTokens };
|
|
223
|
-
}
|
|
224
|
-
function getFilterSummary(filterConfig) {
|
|
225
|
-
const total = getAllToolNames().length;
|
|
226
|
-
const enabled = filterConfig.enabledTools.size;
|
|
227
|
-
const { reduction } = calculateTokenSavings(total, enabled);
|
|
228
|
-
return `${enabled}/${total} tools enabled (${reduction.toFixed(0)}% reduction)`;
|
|
229
|
-
}
|
|
230
20
|
var require2 = createRequire(import.meta.url);
|
|
231
21
|
var pkg = require2("../package.json");
|
|
232
22
|
var VERSION = pkg.version;
|
|
@@ -421,7 +211,11 @@ var NativeConnectionManager = class {
|
|
|
421
211
|
}
|
|
422
212
|
}
|
|
423
213
|
db.prepare("UPDATE tags SET usage_count = 0 WHERE usage_count IS NULL").run();
|
|
424
|
-
|
|
214
|
+
let ftsCount = 0;
|
|
215
|
+
try {
|
|
216
|
+
ftsCount = db.prepare("SELECT COUNT(*) as c FROM fts_content_docsize").get().c;
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
425
219
|
const entryCount = db.prepare("SELECT COUNT(*) as c FROM memory_journal").get().c;
|
|
426
220
|
if (ftsCount === 0 && entryCount > 0) {
|
|
427
221
|
db.exec("INSERT INTO fts_content(fts_content) VALUES ('rebuild')");
|
|
@@ -930,6 +724,12 @@ function buildSearchQuery(queryStr, options, useFts) {
|
|
|
930
724
|
FROM memory_journal e
|
|
931
725
|
`;
|
|
932
726
|
}
|
|
727
|
+
if (options?.tags && options.tags.length > 0) {
|
|
728
|
+
query += `
|
|
729
|
+
JOIN entry_tags et ON e.id = et.entry_id
|
|
730
|
+
JOIN tags t ON et.tag_id = t.id
|
|
731
|
+
`;
|
|
732
|
+
}
|
|
933
733
|
const params = [];
|
|
934
734
|
const conditions = ["e.deleted_at IS NULL"];
|
|
935
735
|
if (queryStr.length > 0) {
|
|
@@ -965,6 +765,27 @@ function buildSearchQuery(queryStr, options, useFts) {
|
|
|
965
765
|
conditions.push(`e.workflow_run_id = ?`);
|
|
966
766
|
params.push(options.workflowRunId);
|
|
967
767
|
}
|
|
768
|
+
if (options?.tags && options.tags.length > 0) {
|
|
769
|
+
const placeholders = options.tags.map(() => "?").join(",");
|
|
770
|
+
conditions.push(`t.name IN (${placeholders})`);
|
|
771
|
+
params.push(...options.tags);
|
|
772
|
+
}
|
|
773
|
+
if (options?.entryType !== void 0) {
|
|
774
|
+
conditions.push(`e.entry_type = ?`);
|
|
775
|
+
params.push(options.entryType);
|
|
776
|
+
}
|
|
777
|
+
if (options?.startDate) {
|
|
778
|
+
let start = options.startDate;
|
|
779
|
+
if (!start.includes("T")) start += "T00:00:00.000Z";
|
|
780
|
+
conditions.push(`e.timestamp >= ?`);
|
|
781
|
+
params.push(start);
|
|
782
|
+
}
|
|
783
|
+
if (options?.endDate) {
|
|
784
|
+
let end = options.endDate;
|
|
785
|
+
if (!end.includes("T")) end += "T23:59:59.999Z";
|
|
786
|
+
conditions.push(`e.timestamp <= ?`);
|
|
787
|
+
params.push(end);
|
|
788
|
+
}
|
|
968
789
|
if (conditions.length > 0) {
|
|
969
790
|
query += ` WHERE ${conditions.join(" AND ")}`;
|
|
970
791
|
}
|
|
@@ -1119,7 +940,7 @@ function getStatistics(context, groupBy = "week", startDate, endDate, projectBre
|
|
|
1119
940
|
const countRow = db.prepare(
|
|
1120
941
|
`SELECT COUNT(*) as count FROM memory_journal WHERE deleted_at IS NULL${dateFilter}`
|
|
1121
942
|
).get(...dateParams);
|
|
1122
|
-
totalEntries = countRow
|
|
943
|
+
totalEntries = countRow.count;
|
|
1123
944
|
const typeRows = db.prepare(
|
|
1124
945
|
`SELECT entry_type, COUNT(*) as count FROM memory_journal WHERE deleted_at IS NULL${dateFilter} GROUP BY entry_type`
|
|
1125
946
|
).all(...dateParams);
|
|
@@ -1128,7 +949,7 @@ function getStatistics(context, groupBy = "week", startDate, endDate, projectBre
|
|
|
1128
949
|
}
|
|
1129
950
|
} else {
|
|
1130
951
|
const countRow = db.prepare("SELECT COUNT(*) as count FROM memory_journal WHERE deleted_at IS NULL").get();
|
|
1131
|
-
totalEntries = countRow
|
|
952
|
+
totalEntries = countRow.count;
|
|
1132
953
|
const typeRows = db.prepare(
|
|
1133
954
|
`SELECT entry_type, COUNT(*) as count FROM memory_journal WHERE deleted_at IS NULL GROUP BY entry_type`
|
|
1134
955
|
).all();
|
|
@@ -1165,7 +986,7 @@ function getStatistics(context, groupBy = "week", startDate, endDate, projectBre
|
|
|
1165
986
|
GROUP BY relationship_type
|
|
1166
987
|
`
|
|
1167
988
|
).all();
|
|
1168
|
-
const totalRelationships = relCountRow
|
|
989
|
+
const totalRelationships = relCountRow.count;
|
|
1169
990
|
const avgPerEntry = totalEntries > 0 ? totalRelationships / totalEntries : 0;
|
|
1170
991
|
const currentPeriod = entriesByPeriod[0]?.period ?? "";
|
|
1171
992
|
const previousPeriod = entriesByPeriod[1]?.period ?? "";
|
|
@@ -1194,8 +1015,8 @@ function getStatistics(context, groupBy = "week", startDate, endDate, projectBre
|
|
|
1194
1015
|
};
|
|
1195
1016
|
if (startDate || endDate) {
|
|
1196
1017
|
result["dateRange"] = {
|
|
1197
|
-
startDate: startDate
|
|
1198
|
-
endDate: endDate
|
|
1018
|
+
startDate: startDate || "",
|
|
1019
|
+
endDate: endDate || ""
|
|
1199
1020
|
};
|
|
1200
1021
|
}
|
|
1201
1022
|
if (projectBreakdown) {
|
|
@@ -1315,6 +1136,7 @@ var BackupManager = class {
|
|
|
1315
1136
|
constructor(ctx) {
|
|
1316
1137
|
this.ctx = ctx;
|
|
1317
1138
|
}
|
|
1139
|
+
ctx;
|
|
1318
1140
|
async exportToFile(backupName) {
|
|
1319
1141
|
const backupsDir = this.ctx.getBackupsDir();
|
|
1320
1142
|
if (backupName) {
|
|
@@ -1407,9 +1229,7 @@ var BackupManager = class {
|
|
|
1407
1229
|
await this.exportToFile(`pre_restore_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`);
|
|
1408
1230
|
this.ctx.closeDbBeforeRestore();
|
|
1409
1231
|
await fs2.promises.copyFile(backupPath, this.ctx.getDbPath());
|
|
1410
|
-
|
|
1411
|
-
const newDb = new DatabaseAdapter3(this.ctx.getDbPath());
|
|
1412
|
-
this.ctx.setDbAndInitialized(newDb);
|
|
1232
|
+
await this.ctx.initialize();
|
|
1413
1233
|
const newCountResult = this.ctx.exec(
|
|
1414
1234
|
"SELECT COUNT(*) FROM memory_journal WHERE deleted_at IS NULL"
|
|
1415
1235
|
);
|
|
@@ -1598,9 +1418,16 @@ var VectorSearchManager = class {
|
|
|
1598
1418
|
this.dbAdapter = dbAdapter;
|
|
1599
1419
|
this.modelName = modelName;
|
|
1600
1420
|
}
|
|
1421
|
+
dbAdapter;
|
|
1601
1422
|
// Use a more flexible type since FeatureExtractionPipeline doesn't fully implement Pipeline
|
|
1602
1423
|
embedder = null;
|
|
1603
|
-
db
|
|
1424
|
+
get db() {
|
|
1425
|
+
try {
|
|
1426
|
+
return this.dbAdapter.getRawDb();
|
|
1427
|
+
} catch {
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1604
1431
|
modelName;
|
|
1605
1432
|
initialized = false;
|
|
1606
1433
|
initializing = false;
|
|
@@ -1625,7 +1452,6 @@ var VectorSearchManager = class {
|
|
|
1625
1452
|
// Quantized int8 for faster inference and smaller model size
|
|
1626
1453
|
});
|
|
1627
1454
|
logger.info("Embedding model loaded", { module: "VectorSearch" });
|
|
1628
|
-
this.db = this.dbAdapter.getRawDb();
|
|
1629
1455
|
this.initialized = true;
|
|
1630
1456
|
this.initializing = false;
|
|
1631
1457
|
logger.info("Vector search initialized successfully", { module: "VectorSearch" });
|
|
@@ -1728,6 +1554,60 @@ var VectorSearchManager = class {
|
|
|
1728
1554
|
return [];
|
|
1729
1555
|
}
|
|
1730
1556
|
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Find entries related to a given entry by its existing embedding.
|
|
1559
|
+
* Uses the stored embedding directly, skipping the re-embedding step.
|
|
1560
|
+
*
|
|
1561
|
+
* @param entryId - Entry ID whose embedding is used as the search vector
|
|
1562
|
+
* @param limit - Max number of results
|
|
1563
|
+
* @param similarityThreshold - Minimum similarity score
|
|
1564
|
+
*/
|
|
1565
|
+
async searchByEntryId(entryId, limit = 10, similarityThreshold = 0.3) {
|
|
1566
|
+
if (!this.initialized) {
|
|
1567
|
+
try {
|
|
1568
|
+
await this.initialize();
|
|
1569
|
+
} catch {
|
|
1570
|
+
return [];
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
if (!this.db) {
|
|
1574
|
+
return [];
|
|
1575
|
+
}
|
|
1576
|
+
try {
|
|
1577
|
+
const row = this.db.prepare("SELECT embedding FROM vec_embeddings WHERE entry_id = ?").get(BigInt(entryId));
|
|
1578
|
+
if (!row) {
|
|
1579
|
+
logger.debug("No embedding found for entry", {
|
|
1580
|
+
module: "VectorSearch",
|
|
1581
|
+
entityId: entryId
|
|
1582
|
+
});
|
|
1583
|
+
return [];
|
|
1584
|
+
}
|
|
1585
|
+
const queryVec = new Float32Array(
|
|
1586
|
+
row.embedding.buffer,
|
|
1587
|
+
row.embedding.byteOffset,
|
|
1588
|
+
EMBEDDING_DIMENSIONS
|
|
1589
|
+
);
|
|
1590
|
+
const results = this.db.prepare(
|
|
1591
|
+
`SELECT entry_id, distance
|
|
1592
|
+
FROM vec_embeddings
|
|
1593
|
+
WHERE embedding MATCH ?
|
|
1594
|
+
ORDER BY distance
|
|
1595
|
+
LIMIT ?`
|
|
1596
|
+
).all(queryVec, (limit + 1) * 2);
|
|
1597
|
+
const filteredResults = results.filter((r) => r.entry_id !== entryId).map((r) => ({
|
|
1598
|
+
entryId: r.entry_id,
|
|
1599
|
+
score: 1 / (1 + r.distance)
|
|
1600
|
+
})).filter((r) => r.score >= similarityThreshold).slice(0, limit);
|
|
1601
|
+
return filteredResults;
|
|
1602
|
+
} catch (error) {
|
|
1603
|
+
logger.error("searchByEntryId failed", {
|
|
1604
|
+
module: "VectorSearch",
|
|
1605
|
+
entityId: entryId,
|
|
1606
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1607
|
+
});
|
|
1608
|
+
return [];
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1731
1611
|
/**
|
|
1732
1612
|
* Remove an entry from the vector index
|
|
1733
1613
|
*/
|
|
@@ -1938,30 +1818,6 @@ var ICON_PROMPT = {
|
|
|
1938
1818
|
sizes: ["24x24"]
|
|
1939
1819
|
};
|
|
1940
1820
|
|
|
1941
|
-
// src/utils/resource-annotations.ts
|
|
1942
|
-
var HIGH_PRIORITY = {
|
|
1943
|
-
priority: 0.9,
|
|
1944
|
-
audience: ["user", "assistant"]
|
|
1945
|
-
};
|
|
1946
|
-
var MEDIUM_PRIORITY = {
|
|
1947
|
-
priority: 0.6,
|
|
1948
|
-
audience: ["user", "assistant"]
|
|
1949
|
-
};
|
|
1950
|
-
var LOW_PRIORITY = {
|
|
1951
|
-
priority: 0.4,
|
|
1952
|
-
audience: ["user", "assistant"]
|
|
1953
|
-
};
|
|
1954
|
-
var ASSISTANT_FOCUSED = {
|
|
1955
|
-
priority: 0.5,
|
|
1956
|
-
audience: ["assistant"]
|
|
1957
|
-
};
|
|
1958
|
-
function withPriority(priority, base = MEDIUM_PRIORITY) {
|
|
1959
|
-
return { ...base, priority };
|
|
1960
|
-
}
|
|
1961
|
-
function withSessionInit(base = HIGH_PRIORITY) {
|
|
1962
|
-
return { ...base, sessionInit: true };
|
|
1963
|
-
}
|
|
1964
|
-
|
|
1965
1821
|
// src/handlers/resources/core/briefing/github-section.ts
|
|
1966
1822
|
async function buildGitHubSection(github, config) {
|
|
1967
1823
|
if (!github) return null;
|
|
@@ -2012,7 +1868,10 @@ async function fetchCiStatus(github, owner, repo, config) {
|
|
|
2012
1868
|
const runLimit = Math.max(1, config.workflowCount, config.workflowStatusBreakdown ? 10 : 1);
|
|
2013
1869
|
const runs = await github.getWorkflowRuns(owner, repo, runLimit);
|
|
2014
1870
|
if (runs.length === 0) return { status: "unknown" };
|
|
2015
|
-
const
|
|
1871
|
+
const primaryRun = runs.find(
|
|
1872
|
+
(r) => r.status !== "completed" || ["success", "failure", "cancelled"].includes(r.conclusion ?? "")
|
|
1873
|
+
) ?? runs[0];
|
|
1874
|
+
const latestRun = primaryRun;
|
|
2016
1875
|
let status;
|
|
2017
1876
|
if (!latestRun) {
|
|
2018
1877
|
status = "unknown";
|
|
@@ -2377,7 +2236,7 @@ var briefingResource = {
|
|
|
2377
2236
|
}
|
|
2378
2237
|
};
|
|
2379
2238
|
var dynamicBriefingResource = {
|
|
2380
|
-
uri: "memory://briefing/{repo}",
|
|
2239
|
+
uri: "memory://briefing/{+repo}",
|
|
2381
2240
|
name: "Dynamic Briefing",
|
|
2382
2241
|
title: "Project-Specific Session Context",
|
|
2383
2242
|
description: "Project-specific briefing context for AI agents. Same as memory://briefing but targets a specific repository name from the registered workspaces.",
|
|
@@ -2465,32 +2324,37 @@ async function buildBriefingData(context, targetRepo) {
|
|
|
2465
2324
|
// src/constants/server-instructions.ts
|
|
2466
2325
|
var CORE_INSTRUCTIONS = `# memory-journal-mcp
|
|
2467
2326
|
|
|
2468
|
-
##
|
|
2327
|
+
## ESSENTIAL SESSION START!**
|
|
2469
2328
|
|
|
2470
|
-
|
|
2329
|
+
1. You **MUST** read the \`memory://briefing/{repo_name}\` at the start of each chat!
|
|
2330
|
+
2. Use the standard MCP \`read_resource\` tool for this (do NOT use Code Mode/execute_code).
|
|
2331
|
+
3. Infer the \`repo_name\` from the user's prompt or your active workspace context.
|
|
2332
|
+
4. Once read, present the \`userMessage\` from the briefing to the user as a formatted bullet list containing the key facts for all available fields including:
|
|
2471
2333
|
|
|
2334
|
+
- Project Name:
|
|
2472
2335
|
- Entry counts (journal + team)
|
|
2473
|
-
-
|
|
2336
|
+
- Latest Entry (journal + team):
|
|
2337
|
+
- GitHub: repo, branch, CI status, open issues/PRs, insights
|
|
2474
2338
|
- Milestone progress (if any)
|
|
2475
2339
|
- Template resources count
|
|
2476
2340
|
- Registered Workspaces (if available - provides automatic repo-to-project routing)
|
|
2477
2341
|
- Optional metadata present (rulesFile, skillsDir, workflowSummary, copilotReviews, Team DB)
|
|
2478
2342
|
|
|
2479
|
-
**Server name for resource calls**: Derive from tool prefixes \u2014 strip the tool name suffix to get the server name.
|
|
2480
|
-
|
|
2481
2343
|
- **AntiGravity**: Tools are \`mcp_{name}_{tool}\` \u2192 server name = \`memory-journal-mcp\`
|
|
2482
2344
|
- **Cursor**: Tools are \`user-{name}-{tool}\` \u2192 server name = \`user-memory-journal-mcp\`
|
|
2483
2345
|
- **Other clients**: Use configured name exactly. Use tool-prefix discovery if unsure.
|
|
2484
2346
|
|
|
2485
2347
|
## Behaviors
|
|
2486
2348
|
|
|
2349
|
+
### memory-journal-mcp Behaviors
|
|
2350
|
+
|
|
2487
2351
|
- **Personal vs Team**: **ALWAYS use the personal journal** (e.g., \`create_entry\`) by default. ONLY save to the team journal (e.g., \`team_create_entry\`) if the user explicitly requests it.
|
|
2488
2352
|
- **Create entries for**: implementations, decisions, bug fixes, milestones, user requests to "remember"
|
|
2489
2353
|
- **Search before**: major decisions, referencing prior work, understanding project context
|
|
2490
2354
|
- **Analyze insights**: Use cross-project insights (\`get_cross_project_insights\`) before defining architectures or cross-cutting abstractions. Use repo insights (\`memory://github/insights\`) to gauge traction.
|
|
2491
2355
|
- **Link entries**: implementation\u2192spec, bugfix\u2192issue, followup\u2192prior work
|
|
2492
2356
|
|
|
2493
|
-
|
|
2357
|
+
### Rule & Skill Suggestions
|
|
2494
2358
|
|
|
2495
2359
|
When you notice the user consistently applies patterns, preferences, or workflows that could be codified:
|
|
2496
2360
|
|
|
@@ -2556,18 +2420,65 @@ function buildQuickAccess(groups) {
|
|
|
2556
2420
|
return table;
|
|
2557
2421
|
}
|
|
2558
2422
|
var CODE_MODE_NAMESPACE_ROWS = [
|
|
2559
|
-
{
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
{
|
|
2566
|
-
|
|
2567
|
-
|
|
2423
|
+
{
|
|
2424
|
+
group: "core",
|
|
2425
|
+
label: "Core",
|
|
2426
|
+
namespace: "`mj.core.*`",
|
|
2427
|
+
example: '`mj.core.createEntry("Implemented feature X")`'
|
|
2428
|
+
},
|
|
2429
|
+
{
|
|
2430
|
+
group: "search",
|
|
2431
|
+
label: "Search",
|
|
2432
|
+
namespace: "`mj.search.*`",
|
|
2433
|
+
example: '`mj.search.searchEntries("performance")`'
|
|
2434
|
+
},
|
|
2435
|
+
{
|
|
2436
|
+
group: "analytics",
|
|
2437
|
+
label: "Analytics",
|
|
2438
|
+
namespace: "`mj.analytics.*`",
|
|
2439
|
+
example: "`mj.analytics.getStatistics()`"
|
|
2440
|
+
},
|
|
2441
|
+
{
|
|
2442
|
+
group: "relationships",
|
|
2443
|
+
label: "Relationships",
|
|
2444
|
+
namespace: "`mj.relationships.*`",
|
|
2445
|
+
example: '`mj.relationships.linkEntries(1, 2, "implements")`'
|
|
2446
|
+
},
|
|
2447
|
+
{
|
|
2448
|
+
group: "export",
|
|
2449
|
+
label: "Export",
|
|
2450
|
+
namespace: "`mj.export.*`",
|
|
2451
|
+
example: '`mj.export.exportEntries("json")`'
|
|
2452
|
+
},
|
|
2453
|
+
{
|
|
2454
|
+
group: "admin",
|
|
2455
|
+
label: "Admin",
|
|
2456
|
+
namespace: "`mj.admin.*`",
|
|
2457
|
+
example: "`mj.admin.rebuildVectorIndex()`"
|
|
2458
|
+
},
|
|
2459
|
+
{
|
|
2460
|
+
group: "github",
|
|
2461
|
+
label: "GitHub",
|
|
2462
|
+
namespace: "`mj.github.*`",
|
|
2463
|
+
example: '`mj.github.getGithubIssues({ state: "open" })`'
|
|
2464
|
+
},
|
|
2465
|
+
{
|
|
2466
|
+
group: "backup",
|
|
2467
|
+
label: "Backup",
|
|
2468
|
+
namespace: "`mj.backup.*`",
|
|
2469
|
+
example: "`mj.backup.backupJournal()`"
|
|
2470
|
+
},
|
|
2471
|
+
{
|
|
2472
|
+
group: "team",
|
|
2473
|
+
label: "Team",
|
|
2474
|
+
namespace: "`mj.team.*`",
|
|
2475
|
+
example: '`mj.team.teamCreateEntry("Team update")`'
|
|
2476
|
+
}
|
|
2568
2477
|
];
|
|
2569
2478
|
function buildCodeModeInstructions(groups) {
|
|
2570
|
-
const rows = CODE_MODE_NAMESPACE_ROWS.filter((r) => groups.has(r.group)).map(
|
|
2479
|
+
const rows = CODE_MODE_NAMESPACE_ROWS.filter((r) => groups.has(r.group)).map(
|
|
2480
|
+
(r) => `| ${r.label.padEnd(13)} | ${r.namespace.padEnd(20)} | ${r.example.padEnd(50)} |`
|
|
2481
|
+
).join("\n");
|
|
2571
2482
|
const fullSection = CODE_MODE_FULL_TEXT;
|
|
2572
2483
|
const tableStart = fullSection.indexOf("| Group");
|
|
2573
2484
|
const tableEnd = fullSection.indexOf("\n\n**Features**");
|
|
@@ -2687,10 +2598,13 @@ var GOTCHAS_CONTENT = `# memory-journal-mcp \u2014 Field Notes & Gotchas
|
|
|
2687
2598
|
## Semantic Search
|
|
2688
2599
|
|
|
2689
2600
|
- **Indexing**: Entries are auto-indexed on creation (fire-and-forget). If index count drifts from DB count, use \`rebuild_vector_index\` or enable \`AUTO_REBUILD_INDEX=true\` for automatic reconciliation on server startup.
|
|
2601
|
+
- **Related by ID**: Provide \`entry_id\` instead of a query string to find entries semantically related to an existing entry (reuses the existing embedding to avoid inference costs).
|
|
2602
|
+
- **Metadata Filters**: Semantic search supports explicit filtering by \`tags\`, \`entry_type\`, \`start_date\`, and \`end_date\`.
|
|
2690
2603
|
- **Thresholds**: Default similarity threshold is 0.25. For broader matches, try 0.15-0.2. Higher values (0.4+) return only very close semantic matches. A quality floor of 0.5 is always enforced: if all results score below 0.5, a hint is included indicating results may be noise. The \`hint_on_empty\` flag (default true) only controls advisory hints for empty indexes and zero-match queries \u2014 the quality gate hint is always shown.
|
|
2691
2604
|
|
|
2692
2605
|
## Search
|
|
2693
2606
|
|
|
2607
|
+
- **Hybrid Ranking**: \`search_entries\` defaults to \`mode: 'auto'\`. Conversational prompts automatically utilize Reciprocal Rank Fusion (true Hybrid) bridging keyword and vector algorithms.
|
|
2694
2608
|
- **\`search_entries\` FTS5 query syntax**: Uses FTS5 full-text search with Porter stemmer. Phrase queries: \`"error handling"\`. Prefix: \`auth*\`. Boolean: \`deploy OR release\`, \`error NOT warning\`. Word-boundary matching ("log" matches "log" but not "catalog"). Results ranked by BM25 relevance. Falls back to LIKE substring matching for queries with unbalanced quotes or special characters.
|
|
2695
2609
|
|
|
2696
2610
|
## Relationships & Analytics
|
|
@@ -2712,7 +2626,7 @@ var GOTCHAS_CONTENT = `# memory-journal-mcp \u2014 Field Notes & Gotchas
|
|
|
2712
2626
|
|
|
2713
2627
|
- **Team cross-database search**: \`search_entries\` and \`search_by_date_range\` automatically merge team DB results when \`TEAM_DB_PATH\` is configured. Results include a \`source\` field ("personal" or "team").
|
|
2714
2628
|
- **Team vector search**: Team has its own isolated vector index. Use \`team_rebuild_vector_index\` if the team index drifts. \`team_semantic_search\` works identically to personal \`semantic_search\`.
|
|
2715
|
-
- **Team tools without \`TEAM_DB_PATH\`**: All
|
|
2629
|
+
- **Team tools without \`TEAM_DB_PATH\`**: All 20 team tools return \`{ success: false, error: "Team collaboration is not configured..." }\` \u2014 no crash, no partial results.
|
|
2716
2630
|
`;
|
|
2717
2631
|
function generateInstructions(enabledTools, prompts, latestEntry, level = "standard", enabledGroups) {
|
|
2718
2632
|
const groups = enabledGroups ?? getEnabledGroups(enabledTools);
|
|
@@ -3472,6 +3386,19 @@ var healthResource = {
|
|
|
3472
3386
|
filterString: context.filterConfig?.raw ?? null
|
|
3473
3387
|
};
|
|
3474
3388
|
const lastModified = (/* @__PURE__ */ new Date()).toISOString();
|
|
3389
|
+
const metricsSummary = (() => {
|
|
3390
|
+
try {
|
|
3391
|
+
const s = globalMetrics.getSummary();
|
|
3392
|
+
return {
|
|
3393
|
+
totalCalls: s.totalCalls,
|
|
3394
|
+
totalErrors: s.totalErrors,
|
|
3395
|
+
totalOutputTokens: s.totalOutputTokens,
|
|
3396
|
+
upSince: s.upSince
|
|
3397
|
+
};
|
|
3398
|
+
} catch {
|
|
3399
|
+
return null;
|
|
3400
|
+
}
|
|
3401
|
+
})();
|
|
3475
3402
|
return {
|
|
3476
3403
|
data: {
|
|
3477
3404
|
...dbHealth,
|
|
@@ -3482,6 +3409,7 @@ var healthResource = {
|
|
|
3482
3409
|
...context.teamDb.getHealthStatus()
|
|
3483
3410
|
} : { configured: false },
|
|
3484
3411
|
scheduler: context.scheduler ? context.scheduler.getStatus() : { active: false, jobs: [] },
|
|
3412
|
+
metrics: metricsSummary,
|
|
3485
3413
|
timestamp: lastModified
|
|
3486
3414
|
},
|
|
3487
3415
|
annotations: { lastModified }
|
|
@@ -3778,6 +3706,143 @@ var skillsResource = {
|
|
|
3778
3706
|
}
|
|
3779
3707
|
};
|
|
3780
3708
|
|
|
3709
|
+
// src/handlers/resources/core/metrics-resource.ts
|
|
3710
|
+
function nowIso() {
|
|
3711
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3712
|
+
}
|
|
3713
|
+
var metricsSummaryResource = {
|
|
3714
|
+
uri: "memory://metrics/summary",
|
|
3715
|
+
name: "Metrics Summary",
|
|
3716
|
+
title: "Tool Call Metrics Summary",
|
|
3717
|
+
description: "Aggregate metrics across all tool calls since server start. Includes total calls, errors, duration, and token estimates.",
|
|
3718
|
+
mimeType: "text/plain",
|
|
3719
|
+
annotations: {
|
|
3720
|
+
...HIGH_PRIORITY,
|
|
3721
|
+
audience: ["assistant"]
|
|
3722
|
+
},
|
|
3723
|
+
handler: (_uri, _ctx) => {
|
|
3724
|
+
const lastModified = nowIso();
|
|
3725
|
+
const s = globalMetrics.getSummary();
|
|
3726
|
+
const errorRate = s.totalCalls > 0 ? (s.totalErrors / s.totalCalls * 100).toFixed(1) : "0.0";
|
|
3727
|
+
const avgDuration = s.totalCalls > 0 ? Math.round(s.totalDurationMs / s.totalCalls) : 0;
|
|
3728
|
+
const text = `metrics_summary:
|
|
3729
|
+
up_since: ${s.upSince}
|
|
3730
|
+
as_of: ${lastModified}
|
|
3731
|
+
total_calls: ${s.totalCalls}
|
|
3732
|
+
total_errors: ${s.totalErrors}
|
|
3733
|
+
error_rate_pct: ${errorRate}
|
|
3734
|
+
total_duration_ms: ${s.totalDurationMs}
|
|
3735
|
+
avg_duration_ms: ${avgDuration}
|
|
3736
|
+
total_input_tokens: ${s.totalInputTokens}
|
|
3737
|
+
total_output_tokens: ${s.totalOutputTokens}
|
|
3738
|
+
tools_called: ${Object.keys(s.toolBreakdown).length}
|
|
3739
|
+
`;
|
|
3740
|
+
return { data: text, annotations: { lastModified } };
|
|
3741
|
+
}
|
|
3742
|
+
};
|
|
3743
|
+
var metricsTokensResource = {
|
|
3744
|
+
uri: "memory://metrics/tokens",
|
|
3745
|
+
name: "Metrics Tokens",
|
|
3746
|
+
title: "Token Usage Breakdown by Tool",
|
|
3747
|
+
description: "Per-tool token usage breakdown sorted by total output tokens. Use this to identify which tools are consuming the most context window.",
|
|
3748
|
+
mimeType: "text/plain",
|
|
3749
|
+
annotations: {
|
|
3750
|
+
...MEDIUM_PRIORITY,
|
|
3751
|
+
audience: ["assistant"]
|
|
3752
|
+
},
|
|
3753
|
+
handler: (_uri, _ctx) => {
|
|
3754
|
+
const lastModified = nowIso();
|
|
3755
|
+
const breakdown = globalMetrics.getTokenBreakdown();
|
|
3756
|
+
if (breakdown.length === 0) {
|
|
3757
|
+
return {
|
|
3758
|
+
data: `token_breakdown:
|
|
3759
|
+
note: No tool calls recorded yet.
|
|
3760
|
+
as_of: ${lastModified}
|
|
3761
|
+
`,
|
|
3762
|
+
annotations: { lastModified }
|
|
3763
|
+
};
|
|
3764
|
+
}
|
|
3765
|
+
const rows = breakdown.map(
|
|
3766
|
+
(t) => ` - tool: ${t.toolName}
|
|
3767
|
+
calls: ${t.callCount}
|
|
3768
|
+
input_tokens: ${t.inputTokens}
|
|
3769
|
+
output_tokens: ${t.outputTokens}
|
|
3770
|
+
avg_output_tokens: ${t.avgOutputTokens}`
|
|
3771
|
+
).join("\n");
|
|
3772
|
+
const text = `token_breakdown:
|
|
3773
|
+
as_of: ${lastModified}
|
|
3774
|
+
${rows}
|
|
3775
|
+
`;
|
|
3776
|
+
return { data: text, annotations: { lastModified } };
|
|
3777
|
+
}
|
|
3778
|
+
};
|
|
3779
|
+
var metricsSystemResource = {
|
|
3780
|
+
uri: "memory://metrics/system",
|
|
3781
|
+
name: "Metrics System",
|
|
3782
|
+
title: "System Metrics",
|
|
3783
|
+
description: "Process-level system metrics: memory usage, uptime, Node.js version, and platform.",
|
|
3784
|
+
mimeType: "text/plain",
|
|
3785
|
+
annotations: {
|
|
3786
|
+
...MEDIUM_PRIORITY,
|
|
3787
|
+
audience: ["assistant"]
|
|
3788
|
+
},
|
|
3789
|
+
handler: (_uri, _ctx) => {
|
|
3790
|
+
const lastModified = nowIso();
|
|
3791
|
+
const sys = globalMetrics.getSystemMetrics();
|
|
3792
|
+
const text = `system_metrics:
|
|
3793
|
+
up_since: ${sys.upSince}
|
|
3794
|
+
uptime_seconds: ${sys.uptimeSeconds}
|
|
3795
|
+
process_memory_mb: ${sys.processMemoryMb}
|
|
3796
|
+
node_version: ${sys.nodeVersion}
|
|
3797
|
+
platform: ${sys.platform}
|
|
3798
|
+
as_of: ${lastModified}
|
|
3799
|
+
`;
|
|
3800
|
+
return { data: text, annotations: { lastModified } };
|
|
3801
|
+
}
|
|
3802
|
+
};
|
|
3803
|
+
var metricsUsersResource = {
|
|
3804
|
+
uri: "memory://metrics/users",
|
|
3805
|
+
name: "Metrics Users",
|
|
3806
|
+
title: "Per-User Call Counts",
|
|
3807
|
+
description: "Per-user tool call counts. Populated when user identifiers are provided via OAuth or request metadata. Returns empty breakdown when no user tracking configured.",
|
|
3808
|
+
mimeType: "text/plain",
|
|
3809
|
+
annotations: {
|
|
3810
|
+
...LOW_PRIORITY,
|
|
3811
|
+
audience: ["assistant"]
|
|
3812
|
+
},
|
|
3813
|
+
handler: (_uri, _ctx) => {
|
|
3814
|
+
const lastModified = nowIso();
|
|
3815
|
+
const userBreakdown = globalMetrics.getUserBreakdown();
|
|
3816
|
+
const users = Object.entries(userBreakdown);
|
|
3817
|
+
if (users.length === 0) {
|
|
3818
|
+
return {
|
|
3819
|
+
data: `user_metrics:
|
|
3820
|
+
note: No user tracking data available.
|
|
3821
|
+
hint: User tracking activates when OAuth user identifiers are present.
|
|
3822
|
+
as_of: ${lastModified}
|
|
3823
|
+
`,
|
|
3824
|
+
annotations: { lastModified }
|
|
3825
|
+
};
|
|
3826
|
+
}
|
|
3827
|
+
const sorted = users.sort(([, a], [, b]) => b - a);
|
|
3828
|
+
const rows = sorted.map(([user, count]) => ` - user: ${user}
|
|
3829
|
+
calls: ${count}`).join("\n");
|
|
3830
|
+
const text = `user_metrics:
|
|
3831
|
+
as_of: ${lastModified}
|
|
3832
|
+
${rows}
|
|
3833
|
+
`;
|
|
3834
|
+
return { data: text, annotations: { lastModified } };
|
|
3835
|
+
}
|
|
3836
|
+
};
|
|
3837
|
+
function getMetricsResourceDefinitions() {
|
|
3838
|
+
return [
|
|
3839
|
+
metricsSummaryResource,
|
|
3840
|
+
metricsTokensResource,
|
|
3841
|
+
metricsSystemResource,
|
|
3842
|
+
metricsUsersResource
|
|
3843
|
+
];
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3781
3846
|
// src/handlers/resources/core/index.ts
|
|
3782
3847
|
function getCoreResourceDefinitions() {
|
|
3783
3848
|
return [
|
|
@@ -3791,7 +3856,8 @@ function getCoreResourceDefinitions() {
|
|
|
3791
3856
|
rulesResource,
|
|
3792
3857
|
workflowsResource,
|
|
3793
3858
|
skillsResource,
|
|
3794
|
-
healthResource
|
|
3859
|
+
healthResource,
|
|
3860
|
+
...getMetricsResourceDefinitions()
|
|
3795
3861
|
];
|
|
3796
3862
|
}
|
|
3797
3863
|
|
|
@@ -3872,11 +3938,11 @@ function getGraphResourceDefinitions() {
|
|
|
3872
3938
|
annotations: MEDIUM_PRIORITY,
|
|
3873
3939
|
handler: async (_uri, context) => {
|
|
3874
3940
|
if (!context.github) {
|
|
3875
|
-
return 'graph LR\n NoGitHub["GitHub integration not available \u2014 set GITHUB_TOKEN
|
|
3941
|
+
return 'graph LR\n NoGitHub["GitHub integration not available \u2014 set GITHUB_TOKEN"]';
|
|
3876
3942
|
}
|
|
3877
3943
|
const repoInfo = await context.github.getRepoInfo();
|
|
3878
3944
|
if (!repoInfo.owner || !repoInfo.repo) {
|
|
3879
|
-
return 'graph LR\n NoRepo["Repository not detected \u2014
|
|
3945
|
+
return 'graph LR\n NoRepo["Repository not detected \u2014 run in valid git repo or configure PROJECT_REGISTRY"]';
|
|
3880
3946
|
}
|
|
3881
3947
|
const workflowRuns = await context.github.getWorkflowRuns(
|
|
3882
3948
|
repoInfo.owner,
|
|
@@ -3984,7 +4050,11 @@ function getGitHubResourceDefinitions() {
|
|
|
3984
4050
|
handler: async (uri, context) => {
|
|
3985
4051
|
const match = /memory:\/\/github\/status\/?(.*)?/.exec(uri);
|
|
3986
4052
|
const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
|
|
3987
|
-
const resolved = await resolveGitHubRepo(
|
|
4053
|
+
const resolved = await resolveGitHubRepo(
|
|
4054
|
+
context.github,
|
|
4055
|
+
context.briefingConfig,
|
|
4056
|
+
targetRepo
|
|
4057
|
+
);
|
|
3988
4058
|
if (isResourceError(resolved)) return resolved;
|
|
3989
4059
|
const { owner, repo, branch, lastModified, github } = resolved;
|
|
3990
4060
|
const defaultProjectNumber = context.briefingConfig?.defaultProjectNumber;
|
|
@@ -4121,7 +4191,11 @@ function getGitHubResourceDefinitions() {
|
|
|
4121
4191
|
handler: async (uri, context) => {
|
|
4122
4192
|
const match = /memory:\/\/github\/insights\/?(.*)?/.exec(uri);
|
|
4123
4193
|
const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
|
|
4124
|
-
const resolved = await resolveGitHubRepo(
|
|
4194
|
+
const resolved = await resolveGitHubRepo(
|
|
4195
|
+
context.github,
|
|
4196
|
+
context.briefingConfig,
|
|
4197
|
+
targetRepo
|
|
4198
|
+
);
|
|
4125
4199
|
if (isResourceError(resolved)) return resolved;
|
|
4126
4200
|
const { owner, repo, lastModified, github } = resolved;
|
|
4127
4201
|
const stats = await github.getRepoStats(owner, repo);
|
|
@@ -4161,7 +4235,11 @@ function getGitHubResourceDefinitions() {
|
|
|
4161
4235
|
handler: async (uri, context) => {
|
|
4162
4236
|
const match = /memory:\/\/github\/milestones\/?(.*)?/.exec(uri);
|
|
4163
4237
|
const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
|
|
4164
|
-
const resolved = await resolveGitHubRepo(
|
|
4238
|
+
const resolved = await resolveGitHubRepo(
|
|
4239
|
+
context.github,
|
|
4240
|
+
context.briefingConfig,
|
|
4241
|
+
targetRepo
|
|
4242
|
+
);
|
|
4165
4243
|
if (isResourceError(resolved)) return resolved;
|
|
4166
4244
|
const { owner, repo, lastModified, github } = resolved;
|
|
4167
4245
|
const milestones = await github.getMilestones(
|
|
@@ -4207,7 +4285,11 @@ function getGitHubResourceDefinitions() {
|
|
|
4207
4285
|
annotations: { lastModified }
|
|
4208
4286
|
};
|
|
4209
4287
|
}
|
|
4210
|
-
const resolved = await resolveGitHubRepo(
|
|
4288
|
+
const resolved = await resolveGitHubRepo(
|
|
4289
|
+
context.github,
|
|
4290
|
+
context.briefingConfig,
|
|
4291
|
+
targetRepo
|
|
4292
|
+
);
|
|
4211
4293
|
if (isResourceError(resolved)) return resolved;
|
|
4212
4294
|
const { owner, repo, github } = resolved;
|
|
4213
4295
|
const milestone = await github.getMilestone(owner, repo, milestoneNumber);
|
|
@@ -4236,9 +4318,9 @@ function getGitHubResourceDefinitions() {
|
|
|
4236
4318
|
const dynamicName = def.name + " (Dynamic)";
|
|
4237
4319
|
let dynamicUri;
|
|
4238
4320
|
if (def.uri === "memory://milestones/{number}") {
|
|
4239
|
-
dynamicUri = "memory://milestones/{repo}/{number}";
|
|
4321
|
+
dynamicUri = "memory://milestones/{+repo}/{number}";
|
|
4240
4322
|
} else {
|
|
4241
|
-
dynamicUri = def.uri + "/{repo}";
|
|
4323
|
+
dynamicUri = def.uri + "/{+repo}";
|
|
4242
4324
|
}
|
|
4243
4325
|
return {
|
|
4244
4326
|
...def,
|
|
@@ -4433,7 +4515,7 @@ function getTemplateResourceDefinitions() {
|
|
|
4433
4515
|
if (!context.github) {
|
|
4434
4516
|
return {
|
|
4435
4517
|
error: "GitHub integration not available",
|
|
4436
|
-
hint: "Set GITHUB_TOKEN
|
|
4518
|
+
hint: "Set GITHUB_TOKEN environment variable."
|
|
4437
4519
|
};
|
|
4438
4520
|
}
|
|
4439
4521
|
const repoInfo = await context.github.getRepoInfo();
|
|
@@ -4442,7 +4524,7 @@ function getTemplateResourceDefinitions() {
|
|
|
4442
4524
|
if (!owner) {
|
|
4443
4525
|
return {
|
|
4444
4526
|
error: "Could not detect repository owner",
|
|
4445
|
-
hint: "
|
|
4527
|
+
hint: "Run the MCP server from a valid git repository or configure PROJECT_REGISTRY."
|
|
4446
4528
|
};
|
|
4447
4529
|
}
|
|
4448
4530
|
const board = await context.github.getProjectKanban(owner, projectNumber, repo);
|
|
@@ -4812,7 +4894,7 @@ function getHelpResourceDefinitions() {
|
|
|
4812
4894
|
var toolIndexModule = null;
|
|
4813
4895
|
async function getAllToolDefinitionsAsync(context) {
|
|
4814
4896
|
try {
|
|
4815
|
-
toolIndexModule ??= await import('./tools-
|
|
4897
|
+
toolIndexModule ??= await import('./tools-FFFGXIKN.js');
|
|
4816
4898
|
if (toolIndexModule === null) return [];
|
|
4817
4899
|
const tools = toolIndexModule.getTools(context.db, null);
|
|
4818
4900
|
return tools.map((t) => ({
|
|
@@ -4933,9 +5015,13 @@ async function readResource(uri, db, vectorManager, filterConfig, github, schedu
|
|
|
4933
5015
|
}
|
|
4934
5016
|
for (const resource of resources) {
|
|
4935
5017
|
if (resource.uri.includes("{")) {
|
|
4936
|
-
const pattern = resource.uri.replace(
|
|
4937
|
-
|
|
4938
|
-
|
|
5018
|
+
const pattern = resource.uri.replace(
|
|
5019
|
+
/\{([^}]+)\}/g,
|
|
5020
|
+
(_match, paramName) => {
|
|
5021
|
+
const cleanParam = paramName.startsWith("+") ? paramName.slice(1) : paramName;
|
|
5022
|
+
return cleanParam === "repo" ? "(.+)" : "([^/]+)";
|
|
5023
|
+
}
|
|
5024
|
+
);
|
|
4939
5025
|
const regex = new RegExp(`^${pattern}$`);
|
|
4940
5026
|
if (regex.test(baseUri)) {
|
|
4941
5027
|
const result = await Promise.resolve(resource.handler(uri, context));
|
|
@@ -4955,7 +5041,9 @@ function getAllResourceDefinitions() {
|
|
|
4955
5041
|
...getGitHubResourceDefinitions(),
|
|
4956
5042
|
...getTemplateResourceDefinitions(),
|
|
4957
5043
|
...getTeamResourceDefinitions(),
|
|
4958
|
-
...getHelpResourceDefinitions()
|
|
5044
|
+
...getHelpResourceDefinitions(),
|
|
5045
|
+
// Audit resource — bound to the global audit logger (or null if unconfigured)
|
|
5046
|
+
getAuditResourceDef(getGlobalAuditLogger)
|
|
4959
5047
|
];
|
|
4960
5048
|
}
|
|
4961
5049
|
|
|
@@ -5367,81 +5455,6 @@ var JwksFetchError = class extends OAuthError {
|
|
|
5367
5455
|
function isOAuthError(error) {
|
|
5368
5456
|
return error instanceof OAuthError;
|
|
5369
5457
|
}
|
|
5370
|
-
|
|
5371
|
-
// src/auth/scopes.ts
|
|
5372
|
-
var SCOPES = {
|
|
5373
|
-
/** Read-only access */
|
|
5374
|
-
READ: "read",
|
|
5375
|
-
/** Read and write access */
|
|
5376
|
-
WRITE: "write",
|
|
5377
|
-
/** Administrative access */
|
|
5378
|
-
ADMIN: "admin",
|
|
5379
|
-
/** Unrestricted access to all operations */
|
|
5380
|
-
FULL: "full"
|
|
5381
|
-
};
|
|
5382
|
-
var BASE_SCOPES = ["read", "write", "admin", "full"];
|
|
5383
|
-
var SUPPORTED_SCOPES = ["read", "write", "admin", "full"];
|
|
5384
|
-
var TOOL_GROUP_SCOPES = {
|
|
5385
|
-
core: SCOPES.READ,
|
|
5386
|
-
search: SCOPES.READ,
|
|
5387
|
-
analytics: SCOPES.READ,
|
|
5388
|
-
relationships: SCOPES.READ,
|
|
5389
|
-
export: SCOPES.READ,
|
|
5390
|
-
admin: SCOPES.ADMIN,
|
|
5391
|
-
github: SCOPES.WRITE,
|
|
5392
|
-
backup: SCOPES.ADMIN,
|
|
5393
|
-
team: SCOPES.WRITE,
|
|
5394
|
-
codemode: SCOPES.ADMIN
|
|
5395
|
-
};
|
|
5396
|
-
var groupsForScope = (maxScope) => {
|
|
5397
|
-
const hierarchy = {
|
|
5398
|
-
read: 0,
|
|
5399
|
-
write: 1,
|
|
5400
|
-
admin: 2,
|
|
5401
|
-
full: 3
|
|
5402
|
-
};
|
|
5403
|
-
const maxLevel = hierarchy[maxScope];
|
|
5404
|
-
return Object.entries(TOOL_GROUP_SCOPES).filter(([, scope]) => hierarchy[scope] <= maxLevel).map(([group]) => group);
|
|
5405
|
-
};
|
|
5406
|
-
groupsForScope(SCOPES.READ);
|
|
5407
|
-
groupsForScope(SCOPES.WRITE);
|
|
5408
|
-
groupsForScope(SCOPES.ADMIN);
|
|
5409
|
-
function parseScopes(scopeString) {
|
|
5410
|
-
return scopeString.split(/\s+/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
5411
|
-
}
|
|
5412
|
-
function hasScope(grantedScopes, requiredScope) {
|
|
5413
|
-
if (grantedScopes.includes(SCOPES.FULL)) {
|
|
5414
|
-
return true;
|
|
5415
|
-
}
|
|
5416
|
-
if (grantedScopes.includes(requiredScope)) {
|
|
5417
|
-
return true;
|
|
5418
|
-
}
|
|
5419
|
-
if (requiredScope === SCOPES.READ || requiredScope === SCOPES.WRITE) {
|
|
5420
|
-
if (grantedScopes.includes(SCOPES.ADMIN)) {
|
|
5421
|
-
return true;
|
|
5422
|
-
}
|
|
5423
|
-
}
|
|
5424
|
-
if (requiredScope === SCOPES.READ) {
|
|
5425
|
-
if (grantedScopes.includes(SCOPES.WRITE)) {
|
|
5426
|
-
return true;
|
|
5427
|
-
}
|
|
5428
|
-
}
|
|
5429
|
-
return false;
|
|
5430
|
-
}
|
|
5431
|
-
|
|
5432
|
-
// src/auth/scope-map.ts
|
|
5433
|
-
var toolScopeMap = /* @__PURE__ */ new Map();
|
|
5434
|
-
for (const [group, tools] of Object.entries(TOOL_GROUPS)) {
|
|
5435
|
-
const scope = TOOL_GROUP_SCOPES[group];
|
|
5436
|
-
if (scope) {
|
|
5437
|
-
for (const toolName of tools) {
|
|
5438
|
-
toolScopeMap.set(toolName, scope);
|
|
5439
|
-
}
|
|
5440
|
-
}
|
|
5441
|
-
}
|
|
5442
|
-
function getRequiredScope(toolName) {
|
|
5443
|
-
return toolScopeMap.get(toolName) ?? SCOPES.READ;
|
|
5444
|
-
}
|
|
5445
5458
|
new AsyncLocalStorage();
|
|
5446
5459
|
|
|
5447
5460
|
// src/auth/oauth-resource-server.ts
|
|
@@ -6462,6 +6475,15 @@ async function createServer(options) {
|
|
|
6462
6475
|
const db = await DatabaseAdapterFactory.create(dbPath);
|
|
6463
6476
|
await db.initialize();
|
|
6464
6477
|
logger.info("Database initialized", { module: "McpServer", dbPath });
|
|
6478
|
+
if (options.auditConfig?.enabled) {
|
|
6479
|
+
initializeAuditLogger(options.auditConfig);
|
|
6480
|
+
logger.info("Audit logging enabled", {
|
|
6481
|
+
module: "McpServer",
|
|
6482
|
+
path: options.auditConfig.logPath,
|
|
6483
|
+
redact: options.auditConfig.redact,
|
|
6484
|
+
auditReads: options.auditConfig.auditReads
|
|
6485
|
+
});
|
|
6486
|
+
}
|
|
6465
6487
|
let teamDb;
|
|
6466
6488
|
if (teamDbPath) {
|
|
6467
6489
|
teamDb = await DatabaseAdapterFactory.create(teamDbPath);
|
|
@@ -6486,12 +6508,20 @@ async function createServer(options) {
|
|
|
6486
6508
|
});
|
|
6487
6509
|
}
|
|
6488
6510
|
let githubPath = ".";
|
|
6489
|
-
if (options.
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6511
|
+
if (options.projectRegistry && Object.keys(options.projectRegistry).length > 0) {
|
|
6512
|
+
if (options.defaultProjectNumber !== void 0) {
|
|
6513
|
+
const defaultEntry = Object.values(options.projectRegistry).find(
|
|
6514
|
+
(r) => r.project_number === options.defaultProjectNumber
|
|
6515
|
+
);
|
|
6516
|
+
if (defaultEntry?.path) {
|
|
6517
|
+
githubPath = defaultEntry.path;
|
|
6518
|
+
}
|
|
6519
|
+
}
|
|
6520
|
+
if (githubPath === ".") {
|
|
6521
|
+
const firstEntry = Object.values(options.projectRegistry)[0];
|
|
6522
|
+
if (firstEntry?.path) {
|
|
6523
|
+
githubPath = firstEntry.path;
|
|
6524
|
+
}
|
|
6495
6525
|
}
|
|
6496
6526
|
}
|
|
6497
6527
|
const github = new GitHubIntegration(githubPath);
|
|
@@ -6596,7 +6626,16 @@ async function createServer(options) {
|
|
|
6596
6626
|
}
|
|
6597
6627
|
}
|
|
6598
6628
|
if (tool.outputSchema !== void 0) {
|
|
6599
|
-
|
|
6629
|
+
const outSchema = tool.outputSchema;
|
|
6630
|
+
if (typeof outSchema === "object" && outSchema !== null && "passthrough" in outSchema && typeof outSchema.passthrough === "function") {
|
|
6631
|
+
try {
|
|
6632
|
+
toolOptions["outputSchema"] = outSchema.passthrough();
|
|
6633
|
+
} catch {
|
|
6634
|
+
toolOptions["outputSchema"] = outSchema;
|
|
6635
|
+
}
|
|
6636
|
+
} else {
|
|
6637
|
+
toolOptions["outputSchema"] = outSchema;
|
|
6638
|
+
}
|
|
6600
6639
|
}
|
|
6601
6640
|
if (tool.annotations !== void 0) {
|
|
6602
6641
|
toolOptions["annotations"] = tool.annotations;
|
|
@@ -6720,6 +6759,10 @@ async function createServer(options) {
|
|
|
6720
6759
|
logger.info("MCP server started on stdio", { module: "McpServer" });
|
|
6721
6760
|
process.on("SIGINT", () => {
|
|
6722
6761
|
logger.info("Shutting down...", { module: "McpServer" });
|
|
6762
|
+
const auditLogger = getGlobalAuditLogger();
|
|
6763
|
+
if (auditLogger) {
|
|
6764
|
+
void auditLogger.close();
|
|
6765
|
+
}
|
|
6723
6766
|
db.close();
|
|
6724
6767
|
teamDb?.close();
|
|
6725
6768
|
process.exit(0);
|
|
@@ -6747,6 +6790,10 @@ async function createServer(options) {
|
|
|
6747
6790
|
process.on("SIGINT", () => {
|
|
6748
6791
|
void (async () => {
|
|
6749
6792
|
await httpTransport.stop(scheduler);
|
|
6793
|
+
const auditLogger = getGlobalAuditLogger();
|
|
6794
|
+
if (auditLogger) {
|
|
6795
|
+
await auditLogger.close();
|
|
6796
|
+
}
|
|
6750
6797
|
db.close();
|
|
6751
6798
|
teamDb?.close();
|
|
6752
6799
|
process.exit(0);
|
|
@@ -6755,4 +6802,4 @@ async function createServer(options) {
|
|
|
6755
6802
|
}
|
|
6756
6803
|
}
|
|
6757
6804
|
|
|
6758
|
-
export {
|
|
6805
|
+
export { VERSION, createServer };
|