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.
@@ -1,5 +1,5 @@
1
- import { setDefaultSandboxMode, getTools, callTool, sendProgress, execQuery, transformEntryRow, resolveGitHubRepo, isResourceError, milestoneCompletionPct, DEFAULT_BRIEFING_CONFIG } from './chunk-VH4SRTLB.js';
2
- import { logger, GitHubIntegration, ConfigurationError, ResourceNotFoundError, ConnectionError, QueryError, assertNoPathTraversal, ValidationError, MemoryJournalMcpError, validateDateFormatPattern } from './chunk-K2SCUSN4.js';
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
- const ftsCount = db.prepare("SELECT COUNT(*) as c FROM fts_content").get().c;
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?.count ?? 0;
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?.count ?? 0;
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?.count ?? 0;
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
- const DatabaseAdapter3 = await import('better-sqlite3').then((m) => m.default);
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 = null;
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 latestRun = runs[0];
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
- ## Session Start
2327
+ ## ESSENTIAL SESSION START!**
2469
2328
 
2470
- **REQUIRED**: Before processing any user request, read \`memory://briefing/{repo_name}\` inferring \`repo_name\` strictly from the context of the user's prompt and present the \`userMessage\` to the user as a formatted bullet list of key facts, with all available fields, including:
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
- - GitHub: repo, branch, CI status, open issues/PRs
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
- ## Rule & Skill Suggestions
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
- { group: "core", label: "Core", namespace: "`mj.core.*`", example: '`mj.core.createEntry("Implemented feature X")`' },
2560
- { group: "search", label: "Search", namespace: "`mj.search.*`", example: '`mj.search.searchEntries("performance")`' },
2561
- { group: "analytics", label: "Analytics", namespace: "`mj.analytics.*`", example: "`mj.analytics.getStatistics()`" },
2562
- { group: "relationships", label: "Relationships", namespace: "`mj.relationships.*`", example: '`mj.relationships.linkEntries(1, 2, "implements")`' },
2563
- { group: "export", label: "Export", namespace: "`mj.export.*`", example: '`mj.export.exportEntries("json")`' },
2564
- { group: "admin", label: "Admin", namespace: "`mj.admin.*`", example: "`mj.admin.rebuildVectorIndex()`" },
2565
- { group: "github", label: "GitHub", namespace: "`mj.github.*`", example: '`mj.github.getGithubIssues({ state: "open" })`' },
2566
- { group: "backup", label: "Backup", namespace: "`mj.backup.*`", example: "`mj.backup.backupJournal()`" },
2567
- { group: "team", label: "Team", namespace: "`mj.team.*`", example: '`mj.team.teamCreateEntry("Team update")`' }
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((r) => `| ${r.label.padEnd(13)} | ${r.namespace.padEnd(20)} | ${r.example.padEnd(50)} |`).join("\n");
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 ${TOOL_GROUPS.team.length} team tools return \`{ success: false, error: "Team collaboration is not configured..." }\` \u2014 no crash, no partial results.
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 and GITHUB_REPO_PATH"]';
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 set GITHUB_REPO_PATH in your config"]';
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(context.github, context.briefingConfig, targetRepo);
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(context.github, context.briefingConfig, targetRepo);
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(context.github, context.briefingConfig, targetRepo);
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(context.github, context.briefingConfig, targetRepo);
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 and GITHUB_REPO_PATH environment variables."
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: "Set GITHUB_REPO_PATH to your git repository."
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-P4XXHO3Z.js');
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(/\{([^}]+)\}/g, (_match, paramName) => {
4937
- return paramName === "repo" ? "(.+)" : "([^/]+)";
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.defaultProjectNumber !== void 0 && options.projectRegistry) {
6490
- const defaultEntry = Object.values(options.projectRegistry).find(
6491
- (r) => r.project_number === options.defaultProjectNumber
6492
- );
6493
- if (defaultEntry?.path) {
6494
- githubPath = defaultEntry.path;
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
- toolOptions["outputSchema"] = tool.outputSchema;
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 { META_GROUPS, TOOL_GROUPS, VERSION, calculateTokenSavings, createServer, filterTools, getAllToolNames, getFilterSummary, getToolFilterFromEnv, getToolGroup, isToolEnabled, parseToolFilter };
6805
+ export { VERSION, createServer };