@vheins/local-memory-mcp 0.16.0 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,131 @@
1
- // src/mcp/utils/logger.ts
1
+ // src/mcp/capabilities.ts
2
+ import { fileURLToPath as fileURLToPath2 } from "url";
3
+ import path2 from "path";
4
+
5
+ // src/mcp/prompts/loader.ts
2
6
  import fs from "fs";
7
+ import path from "path";
8
+ import { fileURLToPath } from "url";
9
+ import matter from "gray-matter";
10
+ var __filename = fileURLToPath(import.meta.url);
11
+ var __dirname = path.dirname(__filename);
12
+ function findPromptDir() {
13
+ const candidates = [
14
+ // Production if chunked into dist/
15
+ "./prompts",
16
+ // Production if inlined into dist/mcp/
17
+ "../prompts",
18
+ // Dev: /src/mcp/prompts/definitions (next to loader.ts)
19
+ "./definitions"
20
+ ].map((relPath) => path.resolve(__dirname, relPath));
21
+ for (const dir of candidates) {
22
+ if (fs.existsSync(dir)) {
23
+ const files = fs.readdirSync(dir);
24
+ if (files.some((f) => f.endsWith(".md"))) {
25
+ return dir;
26
+ }
27
+ }
28
+ }
29
+ return path.resolve(__dirname, "./definitions");
30
+ }
31
+ var PROMPT_DIR = findPromptDir();
32
+ function listPromptFiles() {
33
+ if (!fs.existsSync(PROMPT_DIR)) return [];
34
+ return fs.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
35
+ }
36
+ function loadPromptFromMarkdown(name) {
37
+ const filePath = path.join(PROMPT_DIR, `${name}.md`);
38
+ if (!fs.existsSync(filePath)) {
39
+ throw new Error(`Prompt file not found: ${filePath}`);
40
+ }
41
+ const fileContent = fs.readFileSync(filePath, "utf-8");
42
+ const { data, content } = matter(fileContent);
43
+ return {
44
+ name: data.name || name,
45
+ description: data.description || "",
46
+ arguments: data.arguments || [],
47
+ agent: data.agent,
48
+ content: content.trim()
49
+ };
50
+ }
51
+ function findServerInstructionsDir() {
52
+ const candidates = [
53
+ // Production if chunked into dist/
54
+ "./prompts/server",
55
+ // Production if inlined into dist/mcp/
56
+ "../prompts/server",
57
+ // Dev: /src/mcp/prompts/server (next to loader.ts)
58
+ "./server"
59
+ ].map((relPath) => path.resolve(__dirname, relPath));
60
+ for (const dir of candidates) {
61
+ if (fs.existsSync(dir)) {
62
+ const filePath = path.join(dir, "instructions.md");
63
+ if (fs.existsSync(filePath)) {
64
+ return dir;
65
+ }
66
+ }
67
+ }
68
+ return path.resolve(__dirname, "./server");
69
+ }
70
+ var SERVER_DIR = findServerInstructionsDir();
71
+ function loadServerInstructions() {
72
+ const filePath = path.join(SERVER_DIR, "instructions.md");
73
+ if (!fs.existsSync(filePath)) {
74
+ throw new Error(`Server instructions file not found: ${filePath}`);
75
+ }
76
+ const fileContent = fs.readFileSync(filePath, "utf-8");
77
+ const { content } = matter(fileContent);
78
+ return content.trim();
79
+ }
80
+
81
+ // src/mcp/capabilities.ts
82
+ var __dirname2 = path2.dirname(fileURLToPath2(import.meta.url));
83
+ var pkgVersion = "0.1.0";
84
+ if ("0.16.2") {
85
+ pkgVersion = "0.16.2";
86
+ } else {
87
+ let searchDir = __dirname2;
88
+ for (let i = 0; i < 5; i++) {
89
+ const candidate = path2.join(searchDir, "package.json");
90
+ try {
91
+ if (fs.existsSync(candidate)) {
92
+ const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
93
+ if (pkg.name === "@vheins/local-memory-mcp" && pkg.version) {
94
+ pkgVersion = pkg.version;
95
+ break;
96
+ }
97
+ }
98
+ } catch {
99
+ }
100
+ searchDir = path2.dirname(searchDir);
101
+ }
102
+ }
103
+ var MCP_PROTOCOL_VERSION = "2025-03-26";
104
+ var SERVER_INSTRUCTIONS = loadServerInstructions();
105
+ var CAPABILITIES = {
106
+ serverInfo: {
107
+ name: "local-memory-mcp",
108
+ version: pkgVersion,
109
+ instructions: SERVER_INSTRUCTIONS
110
+ },
111
+ capabilities: {
112
+ completions: {},
113
+ logging: {},
114
+ resources: {
115
+ subscribe: true,
116
+ listChanged: true
117
+ },
118
+ tools: {
119
+ listChanged: false
120
+ },
121
+ prompts: {
122
+ listChanged: true
123
+ }
124
+ }
125
+ };
126
+
127
+ // src/mcp/utils/logger.ts
128
+ import fs2 from "fs";
3
129
  var LEVELS = {
4
130
  debug: 0,
5
131
  info: 1,
@@ -119,11 +245,11 @@ function addLogSink(sink) {
119
245
  }
120
246
  var LOG_LEVEL_VALUES = Object.keys(LEVELS);
121
247
  function createFileSink(logDir, maxFiles = 5) {
122
- fs.mkdirSync(logDir, { recursive: true });
123
- const existing = fs.readdirSync(logDir).filter((f) => f.startsWith("mcp-") && f.endsWith(".log")).sort();
248
+ fs2.mkdirSync(logDir, { recursive: true });
249
+ const existing = fs2.readdirSync(logDir).filter((f) => f.startsWith("mcp-") && f.endsWith(".log")).sort();
124
250
  while (existing.length >= maxFiles) {
125
251
  try {
126
- fs.unlinkSync(`${logDir}/${existing.shift()}`);
252
+ fs2.unlinkSync(`${logDir}/${existing.shift()}`);
127
253
  } catch {
128
254
  }
129
255
  }
@@ -133,118 +259,16 @@ function createFileSink(logDir, maxFiles = 5) {
133
259
  const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${payload.level.toUpperCase()}] [pid:${process.pid}] ${JSON.stringify(payload.data)}
134
260
  `;
135
261
  try {
136
- fs.appendFileSync(logFile, line);
262
+ fs2.appendFileSync(logFile, line);
137
263
  } catch {
138
264
  }
139
265
  };
140
266
  }
141
267
 
142
- // src/mcp/session.ts
143
- import path from "path";
144
- import { fileURLToPath } from "url";
145
- function createSessionContext() {
146
- return {
147
- roots: [],
148
- supportsRoots: false,
149
- supportsSampling: false,
150
- supportsSamplingTools: false,
151
- supportsElicitation: false,
152
- supportsElicitationForm: false,
153
- supportsElicitationUrl: false
154
- };
155
- }
156
- function updateSessionFromInitialize(session, params) {
157
- const capabilities = params?.capabilities || {};
158
- session.clientInfo = params?.clientInfo;
159
- session.clientCapabilities = capabilities;
160
- session.supportsRoots = Boolean(capabilities.roots);
161
- session.supportsSampling = Boolean(capabilities.sampling);
162
- const sampling = capabilities.sampling;
163
- session.supportsSamplingTools = Boolean(sampling?.tools);
164
- session.supportsElicitation = Boolean(capabilities.elicitation);
165
- session.supportsElicitationForm = supportsElicitationMode(capabilities.elicitation, "form");
166
- session.supportsElicitationUrl = supportsElicitationMode(capabilities.elicitation, "url");
167
- }
168
- function supportsElicitationMode(capability, mode) {
169
- if (!capability || typeof capability !== "object") {
170
- return false;
171
- }
172
- const cap = capability;
173
- if (mode === "form") {
174
- return Object.keys(cap).length === 0 || typeof cap.form === "object";
175
- }
176
- return typeof cap.url === "object";
177
- }
178
- function updateSessionRoots(session, roots) {
179
- const normalized = normalizeRoots(roots);
180
- const previous = JSON.stringify(session.roots);
181
- const next = JSON.stringify(normalized);
182
- session.roots = normalized;
183
- return previous !== next;
184
- }
185
- function normalizeRoots(roots) {
186
- if (!Array.isArray(roots)) return [];
187
- const seen = /* @__PURE__ */ new Set();
188
- const normalized = [];
189
- for (const root of roots) {
190
- if (!root || typeof root !== "object") continue;
191
- const r = root;
192
- const uri = typeof r.uri === "string" ? r.uri : void 0;
193
- const name = typeof r.name === "string" ? r.name : void 0;
194
- if (!uri || seen.has(uri)) continue;
195
- seen.add(uri);
196
- normalized.push({ uri, name });
197
- }
198
- return normalized;
199
- }
200
- function extractRootsFromResult(result) {
201
- return normalizeRoots(result?.roots);
202
- }
203
- function getFilesystemRoots(session) {
204
- if (!session) return [];
205
- const resolved = [];
206
- for (const root of session.roots) {
207
- if (!root.uri.startsWith("file://")) continue;
208
- try {
209
- resolved.push(path.resolve(fileURLToPath(root.uri)));
210
- } catch {
211
- }
212
- }
213
- return resolved;
214
- }
215
- function isPathWithinRoots(targetPath, session) {
216
- const roots = getFilesystemRoots(session);
217
- if (roots.length === 0) return true;
218
- const normalizedTarget = path.resolve(targetPath);
219
- return roots.some((rootPath) => {
220
- const relative = path.relative(rootPath, normalizedTarget);
221
- return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
222
- });
223
- }
224
- function findContainingRoot(targetPath, session) {
225
- const roots = getFilesystemRoots(session);
226
- if (roots.length === 0) return null;
227
- const normalizedTarget = path.resolve(targetPath);
228
- for (const rootPath of roots) {
229
- const relative = path.relative(rootPath, normalizedTarget);
230
- if (relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative)) {
231
- return rootPath;
232
- }
233
- }
234
- return null;
235
- }
236
- function inferRepoFromSession(session) {
237
- const roots = getFilesystemRoots(session);
238
- if (roots.length === 1) {
239
- return path.basename(roots[0]);
240
- }
241
- return void 0;
242
- }
243
-
244
268
  // src/mcp/storage/sqlite.ts
245
269
  import Database from "better-sqlite3";
246
- import path3 from "path";
247
- import fs3 from "fs";
270
+ import path4 from "path";
271
+ import fs4 from "fs";
248
272
  import os from "os";
249
273
 
250
274
  // src/mcp/storage/migrations.ts
@@ -1884,6 +1908,18 @@ var TaskEntity = class extends BaseEntity {
1884
1908
  const row = this.get(query, params);
1885
1909
  return (row?.count ?? 0) > 0;
1886
1910
  }
1911
+ getChildrenByParentId(id) {
1912
+ return this.all(
1913
+ "SELECT task_code, title, status FROM tasks WHERE parent_id = ? ORDER BY created_at ASC",
1914
+ [id]
1915
+ );
1916
+ }
1917
+ getDependedByTaskId(id) {
1918
+ return this.all(
1919
+ "SELECT task_code, title, status FROM tasks WHERE depends_on = ? ORDER BY created_at ASC",
1920
+ [id]
1921
+ );
1922
+ }
1887
1923
  getExistingTaskCodes(repo, codes) {
1888
1924
  if (codes.length === 0) return /* @__PURE__ */ new Set();
1889
1925
  const placeholders = codes.map(() => "?").join(",");
@@ -2888,8 +2924,8 @@ var HandoffEntity = class extends BaseEntity {
2888
2924
 
2889
2925
  // src/mcp/storage/write-lock.ts
2890
2926
  import lockfile from "proper-lockfile";
2891
- import path2 from "path";
2892
- import fs2 from "fs";
2927
+ import path3 from "path";
2928
+ import fs3 from "fs";
2893
2929
  var LOCK_STALE_MS = 3e4;
2894
2930
  var LOCK_RETRY_DELAY_MS = 200;
2895
2931
  var LOCK_RETRY_COUNT = 250;
@@ -2898,9 +2934,9 @@ var WriteLock = class {
2898
2934
  locked = false;
2899
2935
  constructor(dbPath) {
2900
2936
  this.lockTarget = dbPath;
2901
- if (!fs2.existsSync(dbPath)) {
2902
- fs2.mkdirSync(path2.dirname(dbPath), { recursive: true });
2903
- fs2.writeFileSync(dbPath, "");
2937
+ if (!fs3.existsSync(dbPath)) {
2938
+ fs3.mkdirSync(path3.dirname(dbPath), { recursive: true });
2939
+ fs3.writeFileSync(dbPath, "");
2904
2940
  }
2905
2941
  }
2906
2942
  /**
@@ -2952,13 +2988,13 @@ var WriteLock = class {
2952
2988
  // src/mcp/storage/sqlite.ts
2953
2989
  function resolveDbPath() {
2954
2990
  if (process.env.MEMORY_DB_PATH) return process.env.MEMORY_DB_PATH;
2955
- const standardConfigDir = process.platform === "win32" ? path3.join(os.homedir(), ".local-memory-mcp") : process.platform === "darwin" ? path3.join(os.homedir(), "Library", "Application Support", "local-memory-mcp") : path3.join(os.homedir(), ".config", "local-memory-mcp");
2956
- const standardPath = path3.join(standardConfigDir, "memory.db");
2957
- if (fs3.existsSync(standardPath)) return standardPath;
2958
- const legacyPath = path3.join(os.homedir(), ".config", "local-memory-mcp", "memory.db");
2959
- if (fs3.existsSync(legacyPath)) return legacyPath;
2960
- const localCwdFile = path3.join(process.cwd(), "storage", "memory.db");
2961
- if (fs3.existsSync(localCwdFile)) return localCwdFile;
2991
+ const standardConfigDir = process.platform === "win32" ? path4.join(os.homedir(), ".local-memory-mcp") : process.platform === "darwin" ? path4.join(os.homedir(), "Library", "Application Support", "local-memory-mcp") : path4.join(os.homedir(), ".config", "local-memory-mcp");
2992
+ const standardPath = path4.join(standardConfigDir, "memory.db");
2993
+ if (fs4.existsSync(standardPath)) return standardPath;
2994
+ const legacyPath = path4.join(os.homedir(), ".config", "local-memory-mcp", "memory.db");
2995
+ if (fs4.existsSync(legacyPath)) return legacyPath;
2996
+ const localCwdFile = path4.join(process.cwd(), "storage", "memory.db");
2997
+ if (fs4.existsSync(localCwdFile)) return localCwdFile;
2962
2998
  return standardPath;
2963
2999
  }
2964
3000
  var DB_PATH = resolveDbPath();
@@ -2981,9 +3017,9 @@ var SQLiteStore = class _SQLiteStore {
2981
3017
  const finalPath = dbPath ?? DB_PATH;
2982
3018
  this.dbPathInstance = finalPath;
2983
3019
  if (finalPath !== ":memory:") {
2984
- const dbDir = path3.dirname(finalPath);
2985
- if (!fs3.existsSync(dbDir)) {
2986
- fs3.mkdirSync(dbDir, { recursive: true });
3020
+ const dbDir = path4.dirname(finalPath);
3021
+ if (!fs4.existsSync(dbDir)) {
3022
+ fs4.mkdirSync(dbDir, { recursive: true });
2987
3023
  }
2988
3024
  }
2989
3025
  this.db = new Database(finalPath);
@@ -3042,12 +3078,12 @@ var SQLiteStore = class _SQLiteStore {
3042
3078
  */
3043
3079
  _attemptRecovery(dbPath) {
3044
3080
  const backupPath = dbPath + ".backup";
3045
- if (fs3.existsSync(backupPath)) {
3081
+ if (fs4.existsSync(backupPath)) {
3046
3082
  logger.warn("[SQLiteStore] Attempting recovery from backup", { backupPath });
3047
3083
  try {
3048
3084
  const corruptPath = `${dbPath}.corrupt_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").slice(0, 15)}`;
3049
- fs3.copyFileSync(dbPath, corruptPath);
3050
- fs3.copyFileSync(backupPath, dbPath);
3085
+ fs4.copyFileSync(dbPath, corruptPath);
3086
+ fs4.copyFileSync(backupPath, dbPath);
3051
3087
  logger.warn("[SQLiteStore] Recovery successful. Corrupt file saved to", { corruptPath });
3052
3088
  } catch (err) {
3053
3089
  logger.error("[SQLiteStore] Recovery failed", { error: String(err) });
@@ -3065,7 +3101,7 @@ var SQLiteStore = class _SQLiteStore {
3065
3101
  try {
3066
3102
  this.db.pragma("wal_checkpoint(PASSIVE)");
3067
3103
  const backupPath = this.dbPathInstance + ".backup";
3068
- fs3.copyFileSync(this.dbPathInstance, backupPath);
3104
+ fs4.copyFileSync(this.dbPathInstance, backupPath);
3069
3105
  } catch (err) {
3070
3106
  logger.warn("[SQLiteStore] Backup failed", { error: String(err) });
3071
3107
  }
@@ -3187,716 +3223,173 @@ var RealVectorStore = class {
3187
3223
  }
3188
3224
  };
3189
3225
 
3190
- // src/mcp/capabilities.ts
3191
- import { fileURLToPath as fileURLToPath3 } from "url";
3226
+ // src/mcp/session.ts
3192
3227
  import path5 from "path";
3193
-
3194
- // src/mcp/prompts/loader.ts
3195
- import fs4 from "fs";
3196
- import path4 from "path";
3197
- import { fileURLToPath as fileURLToPath2 } from "url";
3198
- import matter from "gray-matter";
3199
- var __filename = fileURLToPath2(import.meta.url);
3200
- var __dirname = path4.dirname(__filename);
3201
- function findPromptDir() {
3202
- const candidates = [
3203
- // Production if chunked into dist/
3204
- "./prompts",
3205
- // Production if inlined into dist/mcp/
3206
- "../prompts",
3207
- // Dev: /src/mcp/prompts/definitions (next to loader.ts)
3208
- "./definitions"
3209
- ].map((relPath) => path4.resolve(__dirname, relPath));
3210
- for (const dir of candidates) {
3211
- if (fs4.existsSync(dir)) {
3212
- const files = fs4.readdirSync(dir);
3213
- if (files.some((f) => f.endsWith(".md"))) {
3214
- return dir;
3215
- }
3216
- }
3217
- }
3218
- return path4.resolve(__dirname, "./definitions");
3219
- }
3220
- var PROMPT_DIR = findPromptDir();
3221
- function listPromptFiles() {
3222
- if (!fs4.existsSync(PROMPT_DIR)) return [];
3223
- return fs4.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
3224
- }
3225
- function loadPromptFromMarkdown(name) {
3226
- const filePath = path4.join(PROMPT_DIR, `${name}.md`);
3227
- if (!fs4.existsSync(filePath)) {
3228
- throw new Error(`Prompt file not found: ${filePath}`);
3229
- }
3230
- const fileContent = fs4.readFileSync(filePath, "utf-8");
3231
- const { data, content } = matter(fileContent);
3228
+ import { fileURLToPath as fileURLToPath3 } from "url";
3229
+ function createSessionContext() {
3232
3230
  return {
3233
- name: data.name || name,
3234
- description: data.description || "",
3235
- arguments: data.arguments || [],
3236
- agent: data.agent,
3237
- content: content.trim()
3238
- };
3239
- }
3240
- function findServerInstructionsDir() {
3241
- const candidates = [
3242
- // Production if chunked into dist/
3243
- "./prompts/server",
3244
- // Production if inlined into dist/mcp/
3245
- "../prompts/server",
3246
- // Dev: /src/mcp/prompts/server (next to loader.ts)
3247
- "./server"
3248
- ].map((relPath) => path4.resolve(__dirname, relPath));
3249
- for (const dir of candidates) {
3250
- if (fs4.existsSync(dir)) {
3251
- const filePath = path4.join(dir, "instructions.md");
3252
- if (fs4.existsSync(filePath)) {
3253
- return dir;
3254
- }
3255
- }
3231
+ roots: [],
3232
+ supportsRoots: false,
3233
+ supportsSampling: false,
3234
+ supportsSamplingTools: false,
3235
+ supportsElicitation: false,
3236
+ supportsElicitationForm: false,
3237
+ supportsElicitationUrl: false
3238
+ };
3239
+ }
3240
+ function updateSessionFromInitialize(session, params) {
3241
+ const capabilities = params?.capabilities || {};
3242
+ session.clientInfo = params?.clientInfo;
3243
+ session.clientCapabilities = capabilities;
3244
+ session.supportsRoots = Boolean(capabilities.roots);
3245
+ session.supportsSampling = Boolean(capabilities.sampling);
3246
+ const sampling = capabilities.sampling;
3247
+ session.supportsSamplingTools = Boolean(sampling?.tools);
3248
+ session.supportsElicitation = Boolean(capabilities.elicitation);
3249
+ session.supportsElicitationForm = supportsElicitationMode(capabilities.elicitation, "form");
3250
+ session.supportsElicitationUrl = supportsElicitationMode(capabilities.elicitation, "url");
3251
+ }
3252
+ function supportsElicitationMode(capability, mode) {
3253
+ if (!capability || typeof capability !== "object") {
3254
+ return false;
3255
+ }
3256
+ const cap = capability;
3257
+ if (mode === "form") {
3258
+ return Object.keys(cap).length === 0 || typeof cap.form === "object";
3256
3259
  }
3257
- return path4.resolve(__dirname, "./server");
3260
+ return typeof cap.url === "object";
3258
3261
  }
3259
- var SERVER_DIR = findServerInstructionsDir();
3260
- function loadServerInstructions() {
3261
- const filePath = path4.join(SERVER_DIR, "instructions.md");
3262
- if (!fs4.existsSync(filePath)) {
3263
- throw new Error(`Server instructions file not found: ${filePath}`);
3262
+ function updateSessionRoots(session, roots) {
3263
+ const normalized = normalizeRoots(roots);
3264
+ const previous = JSON.stringify(session.roots);
3265
+ const next = JSON.stringify(normalized);
3266
+ session.roots = normalized;
3267
+ return previous !== next;
3268
+ }
3269
+ function normalizeRoots(roots) {
3270
+ if (!Array.isArray(roots)) return [];
3271
+ const seen = /* @__PURE__ */ new Set();
3272
+ const normalized = [];
3273
+ for (const root of roots) {
3274
+ if (!root || typeof root !== "object") continue;
3275
+ const r = root;
3276
+ const uri = typeof r.uri === "string" ? r.uri : void 0;
3277
+ const name = typeof r.name === "string" ? r.name : void 0;
3278
+ if (!uri || seen.has(uri)) continue;
3279
+ seen.add(uri);
3280
+ normalized.push({ uri, name });
3264
3281
  }
3265
- const fileContent = fs4.readFileSync(filePath, "utf-8");
3266
- const { content } = matter(fileContent);
3267
- return content.trim();
3282
+ return normalized;
3268
3283
  }
3269
-
3270
- // src/mcp/capabilities.ts
3271
- var __dirname2 = path5.dirname(fileURLToPath3(import.meta.url));
3272
- var pkgVersion = "0.1.0";
3273
- if ("0.16.0") {
3274
- pkgVersion = "0.16.0";
3275
- } else {
3276
- let searchDir = __dirname2;
3277
- for (let i = 0; i < 5; i++) {
3278
- const candidate = path5.join(searchDir, "package.json");
3284
+ function extractRootsFromResult(result) {
3285
+ return normalizeRoots(result?.roots);
3286
+ }
3287
+ function getFilesystemRoots(session) {
3288
+ if (!session) return [];
3289
+ const resolved = [];
3290
+ for (const root of session.roots) {
3291
+ if (!root.uri.startsWith("file://")) continue;
3279
3292
  try {
3280
- if (fs.existsSync(candidate)) {
3281
- const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
3282
- if (pkg.name === "@vheins/local-memory-mcp" && pkg.version) {
3283
- pkgVersion = pkg.version;
3284
- break;
3285
- }
3286
- }
3293
+ resolved.push(path5.resolve(fileURLToPath3(root.uri)));
3287
3294
  } catch {
3288
3295
  }
3289
- searchDir = path5.dirname(searchDir);
3290
3296
  }
3297
+ return resolved;
3291
3298
  }
3292
- var MCP_PROTOCOL_VERSION = "2025-03-26";
3293
- var SERVER_INSTRUCTIONS = loadServerInstructions();
3294
- var CAPABILITIES = {
3295
- serverInfo: {
3296
- name: "local-memory-mcp",
3297
- version: pkgVersion,
3298
- instructions: SERVER_INSTRUCTIONS
3299
- },
3300
- capabilities: {
3301
- completions: {},
3302
- logging: {},
3303
- resources: {
3304
- subscribe: true,
3305
- listChanged: true
3306
- },
3307
- tools: {
3308
- listChanged: false
3309
- },
3310
- prompts: {
3311
- listChanged: true
3299
+ function isPathWithinRoots(targetPath, session) {
3300
+ const roots = getFilesystemRoots(session);
3301
+ if (roots.length === 0) return true;
3302
+ const normalizedTarget = path5.resolve(targetPath);
3303
+ return roots.some((rootPath) => {
3304
+ const relative = path5.relative(rootPath, normalizedTarget);
3305
+ return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
3306
+ });
3307
+ }
3308
+ function findContainingRoot(targetPath, session) {
3309
+ const roots = getFilesystemRoots(session);
3310
+ if (roots.length === 0) return null;
3311
+ const normalizedTarget = path5.resolve(targetPath);
3312
+ for (const rootPath of roots) {
3313
+ const relative = path5.relative(rootPath, normalizedTarget);
3314
+ if (relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative)) {
3315
+ return rootPath;
3312
3316
  }
3313
3317
  }
3314
- };
3315
-
3316
- // src/mcp/utils/pagination.ts
3317
- function encodeCursor(offset) {
3318
- return Buffer.from(String(offset), "utf8").toString("base64");
3318
+ return null;
3319
3319
  }
3320
- function decodeCursor(cursor) {
3321
- if (cursor === void 0 || cursor === null || cursor === "") {
3322
- return 0;
3323
- }
3324
- if (typeof cursor !== "string" || cursor.trim() === "") {
3325
- throw invalidPaginationParams("Invalid cursor");
3326
- }
3327
- let decoded;
3328
- try {
3329
- decoded = Buffer.from(cursor, "base64").toString("utf8");
3330
- } catch {
3331
- throw invalidPaginationParams("Invalid cursor");
3332
- }
3333
- if (!/^\d+$/.test(decoded)) {
3334
- throw invalidPaginationParams("Invalid cursor");
3335
- }
3336
- const offset = Number.parseInt(decoded, 10);
3337
- if (!Number.isFinite(offset) || offset < 0) {
3338
- throw invalidPaginationParams("Invalid cursor");
3320
+ function inferRepoFromSession(session) {
3321
+ const roots = getFilesystemRoots(session);
3322
+ if (roots.length === 1) {
3323
+ return path5.basename(roots[0]);
3339
3324
  }
3340
- return offset;
3341
- }
3342
- function invalidPaginationParams(message) {
3343
- const error = new Error(message);
3344
- error.code = -32602;
3345
- return error;
3325
+ return void 0;
3346
3326
  }
3347
3327
 
3348
- // src/mcp/utils/completion.ts
3349
- var MAX_COMPLETION_VALUES = 100;
3350
- function rankCompletionValues(candidates, input) {
3351
- const unique = [...new Set(candidates.filter(Boolean))];
3352
- const needle = input.trim().toLowerCase();
3353
- if (!needle) {
3354
- return unique.slice(0, MAX_COMPLETION_VALUES);
3355
- }
3356
- return unique.map((value) => ({ value, score: scoreCompletionValue(value, needle) })).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.value.localeCompare(b.value)).map((entry) => entry.value);
3357
- }
3358
- function scoreCompletionValue(value, needle) {
3359
- const haystack = value.toLowerCase();
3360
- if (haystack === needle) return 100;
3361
- if (haystack.startsWith(needle)) return 75;
3362
- if (haystack.includes(needle)) return 50;
3363
- const compactNeedle = needle.replace(/[\s_-]+/g, "");
3364
- const compactHaystack = haystack.replace(/[\s_-]+/g, "");
3365
- if (compactNeedle && compactHaystack.includes(compactNeedle)) return 25;
3366
- return 0;
3367
- }
3368
-
3369
- // src/mcp/resources/index.ts
3370
- var DEFAULT_PAGE_SIZE = 25;
3371
- var MAX_PAGE_SIZE = 100;
3372
- function listResources(session, params) {
3373
- const resources = [
3374
- {
3375
- uri: "repository://index",
3376
- name: "Repository Index",
3377
- title: "Repository Index",
3378
- description: "List of all known repositories with memory/task counts and last activity",
3379
- mimeType: "application/json",
3380
- annotations: {
3381
- audience: ["assistant"],
3382
- priority: 1,
3383
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3384
- }
3385
- },
3386
- {
3387
- uri: "session://roots",
3388
- name: "Session Roots",
3389
- title: "Session Roots",
3390
- description: session?.roots.length ? "Active workspace roots provided by the MCP client" : "No active workspace roots were provided by the MCP client",
3391
- mimeType: "application/json",
3392
- size: Buffer.byteLength(JSON.stringify({ roots: session?.roots ?? [] }), "utf8"),
3393
- annotations: {
3394
- audience: ["assistant"],
3395
- priority: 0.95,
3396
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3397
- }
3398
- }
3399
- ];
3400
- return paginateEntries("resources", resources, params);
3401
- }
3402
- function listResourceTemplates(params) {
3403
- const templates = [
3404
- // ── Memory ──────────────────────────────────────────────────────────────
3405
- {
3406
- uriTemplate: "repository://{name}/memories",
3407
- name: "Repository Memories",
3408
- title: "Repository Memories",
3409
- description: "All active memory entries for a specific repository",
3410
- mimeType: "application/json",
3411
- annotations: { audience: ["assistant"], priority: 0.85 }
3412
- },
3413
- {
3414
- uriTemplate: "repository://{name}/memories?search={search}&type={type}&tag={tag}",
3415
- name: "Filtered Repository Memories",
3416
- title: "Filtered Repository Memories",
3417
- description: "Filter or search memories within a repository by keyword, type, or tag",
3418
- mimeType: "application/json",
3419
- annotations: { audience: ["assistant"], priority: 0.8 }
3420
- },
3421
- {
3422
- uriTemplate: "memory://{id}",
3423
- name: "Memory Detail",
3424
- title: "Memory Detail",
3425
- description: "Full content and statistics for a specific memory UUID",
3426
- mimeType: "application/json",
3427
- annotations: { audience: ["assistant"], priority: 0.75 }
3428
- },
3429
- // ── Tasks ────────────────────────────────────────────────────────────────
3430
- {
3431
- uriTemplate: "repository://{name}/tasks",
3432
- name: "Repository Tasks",
3433
- title: "Repository Tasks",
3434
- description: "All active tasks for a specific repository",
3435
- mimeType: "application/json",
3436
- annotations: { audience: ["assistant"], priority: 0.9 }
3437
- },
3438
- {
3439
- uriTemplate: "repository://{name}/tasks?status={status}&priority={priority}",
3440
- name: "Filtered Repository Tasks",
3441
- title: "Filtered Repository Tasks",
3442
- description: "Filter tasks within a repository by status or priority level",
3443
- mimeType: "application/json",
3444
- annotations: { audience: ["assistant"], priority: 0.85 }
3445
- },
3446
- {
3447
- uriTemplate: "task://{id}",
3448
- name: "Task Detail",
3449
- title: "Task Detail",
3450
- description: "Full content and comments for a specific task UUID",
3451
- mimeType: "application/json",
3452
- annotations: { audience: ["assistant"], priority: 0.8 }
3453
- },
3454
- // ── Repository extras ────────────────────────────────────────────────────
3455
- {
3456
- uriTemplate: "repository://{name}/summary",
3457
- name: "Repository Summary",
3458
- title: "Repository Summary",
3459
- description: "High-level architectural summary for a repository",
3460
- mimeType: "text/plain",
3461
- annotations: { audience: ["assistant"], priority: 0.95 }
3462
- },
3463
- {
3464
- uriTemplate: "repository://{name}/actions",
3465
- name: "Repository Actions",
3466
- title: "Repository Actions",
3467
- description: "Audit log of agent tool actions scoped to a repository",
3468
- mimeType: "application/json",
3469
- annotations: { audience: ["assistant"], priority: 0.6 }
3470
- },
3471
- // ── Action detail ────────────────────────────────────────────────────────
3472
- {
3473
- uriTemplate: "action://{id}",
3474
- name: "Action Detail",
3475
- title: "Action Detail",
3476
- description: "Full details of a specific audit log entry by integer ID",
3477
- mimeType: "application/json",
3478
- annotations: { audience: ["assistant"], priority: 0.55 }
3479
- }
3480
- ];
3481
- return paginateEntries("resourceTemplates", templates, params);
3482
- }
3483
- function completeResourceArgument(resourceUri, argumentName, argumentValue, _contextArguments, dataSources) {
3484
- if (resourceUri === "repository://{name}/memories" || resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}" || resourceUri === "repository://{name}/tasks" || resourceUri === "repository://{name}/tasks?status={status}&priority={priority}" || resourceUri === "repository://{name}/summary" || resourceUri === "repository://{name}/actions") {
3485
- if (argumentName === "name") {
3486
- return rankCompletionValues(dataSources.repos, argumentValue);
3487
- }
3488
- }
3489
- if (resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}") {
3490
- if (argumentName === "tag") {
3491
- return rankCompletionValues(dataSources.tags, argumentValue);
3492
- }
3493
- }
3494
- throw invalidCompletionParams(`Unknown resource template or argument: ${resourceUri} (${argumentName})`);
3495
- }
3496
- function readResource(uri, db, session) {
3497
- logger.info("[Tool] resource.read", { uri });
3498
- if (uri === "repository://index") {
3499
- const repos = db.system.listRepoNavigation();
3500
- const payload = JSON.stringify(repos, null, 2);
3501
- return {
3502
- contents: [
3503
- {
3504
- uri,
3505
- mimeType: "application/json",
3506
- text: payload,
3507
- size: Buffer.byteLength(payload, "utf8"),
3508
- annotations: {
3509
- audience: ["assistant"],
3510
- priority: 1,
3511
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3512
- }
3513
- }
3514
- ]
3515
- };
3516
- }
3517
- if (uri === "session://roots") {
3518
- const payload = JSON.stringify({ roots: session?.roots ?? [] }, null, 2);
3519
- return {
3520
- contents: [
3521
- {
3522
- uri,
3523
- mimeType: "application/json",
3524
- text: payload,
3525
- size: Buffer.byteLength(payload, "utf8"),
3526
- annotations: {
3527
- audience: ["assistant"],
3528
- priority: 0.95,
3529
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3530
- }
3531
- }
3532
- ]
3533
- };
3534
- }
3535
- const memoryIdMatch = uri.match(/^memory:\/\/([0-9a-f-]{36})$/i);
3536
- if (memoryIdMatch) {
3537
- const id = memoryIdMatch[1];
3538
- const entry = db.memories.getByIdWithStats(id);
3539
- if (!entry) throw resourceNotFound(`Memory with ID ${id} not found.`, uri);
3540
- const payload = JSON.stringify(entry, null, 2);
3541
- return {
3542
- contents: [
3543
- {
3544
- uri,
3545
- mimeType: "application/json",
3546
- text: payload,
3547
- size: Buffer.byteLength(payload, "utf8"),
3548
- annotations: {
3549
- audience: ["assistant"],
3550
- priority: 0.75,
3551
- lastModified: entry.updated_at || entry.created_at
3552
- }
3553
- }
3554
- ]
3555
- };
3556
- }
3557
- const taskIdMatch = uri.match(/^task:\/\/([0-9a-f-]{36})$/i);
3558
- if (taskIdMatch) {
3559
- const id = taskIdMatch[1];
3560
- const task = db.tasks.getTaskById(id);
3561
- if (!task) throw resourceNotFound(`Task with ID ${id} not found.`, uri);
3562
- const payload = JSON.stringify(task, null, 2);
3563
- return {
3564
- contents: [
3565
- {
3566
- uri,
3567
- mimeType: "application/json",
3568
- text: payload,
3569
- size: Buffer.byteLength(payload, "utf8"),
3570
- annotations: {
3571
- audience: ["assistant"],
3572
- priority: 0.8,
3573
- lastModified: task.updated_at || task.created_at
3574
- }
3575
- }
3576
- ]
3577
- };
3578
- }
3579
- const repoBase = parseRepoUri(uri);
3580
- if (repoBase) {
3581
- const { name, path: repoPath, query } = repoBase;
3582
- if (repoPath === "summary") {
3583
- const summary = db.summaries.getSummary(name);
3584
- const text = summary?.summary || `No summary available for repository: ${name}`;
3585
- return {
3586
- contents: [
3587
- {
3588
- uri,
3589
- mimeType: "text/plain",
3590
- text,
3591
- size: Buffer.byteLength(text, "utf8"),
3592
- annotations: {
3593
- audience: ["assistant"],
3594
- priority: 0.95,
3595
- lastModified: summary?.updated_at || (/* @__PURE__ */ new Date()).toISOString()
3596
- }
3597
- }
3598
- ]
3599
- };
3600
- }
3601
- if (repoPath === "memories") {
3602
- const search = query.get("search") || "";
3603
- const type = query.get("type");
3604
- const tag = query.get("tag");
3605
- const result = db.memories.listMemoriesForDashboard({
3606
- repo: name,
3607
- type: type || void 0,
3608
- tag: tag || void 0,
3609
- search: search || void 0,
3610
- limit: 50
3611
- });
3612
- const entries = result.items;
3613
- const payload = JSON.stringify(entries, null, 2);
3614
- return {
3615
- contents: [
3616
- {
3617
- uri,
3618
- mimeType: "application/json",
3619
- text: payload,
3620
- size: Buffer.byteLength(payload, "utf8"),
3621
- annotations: {
3622
- audience: ["assistant"],
3623
- priority: 0.85,
3624
- lastModified: deriveLastModifiedFromCollection(
3625
- entries.map((e) => e.updated_at || e.created_at)
3626
- )
3627
- }
3628
- }
3629
- ]
3630
- };
3631
- }
3632
- if (repoPath === "tasks") {
3633
- const status = query.get("status");
3634
- const priority = query.get("priority");
3635
- let tasks;
3636
- if (status && status !== "all") {
3637
- const statuses = status.split(",").map((s) => s.trim());
3638
- tasks = db.tasks.getTasksByMultipleStatuses(name, statuses);
3639
- } else {
3640
- tasks = db.tasks.getTasksByMultipleStatuses(name, ["backlog", "pending", "in_progress", "blocked"]);
3641
- }
3642
- if (priority) {
3643
- const p = Number(priority);
3644
- if (!isNaN(p)) {
3645
- tasks = tasks.filter((t) => t.priority === p);
3646
- }
3647
- }
3648
- const payload = JSON.stringify(tasks, null, 2);
3649
- return {
3650
- contents: [
3651
- {
3652
- uri,
3653
- mimeType: "application/json",
3654
- text: payload,
3655
- size: Buffer.byteLength(payload, "utf8"),
3656
- annotations: {
3657
- audience: ["assistant"],
3658
- priority: 0.9,
3659
- lastModified: deriveLastModifiedFromCollection(tasks.map((t) => t.updated_at))
3660
- }
3661
- }
3662
- ]
3663
- };
3664
- }
3665
- if (repoPath === "actions") {
3666
- const actions = db.actions.getRecentActions(name, 100);
3667
- const payload = JSON.stringify(actions, null, 2);
3668
- return {
3669
- contents: [
3670
- {
3671
- uri,
3672
- mimeType: "application/json",
3673
- text: payload,
3674
- size: Buffer.byteLength(payload, "utf8"),
3675
- annotations: {
3676
- audience: ["assistant"],
3677
- priority: 0.6,
3678
- lastModified: deriveLastModifiedFromCollection(actions.map((a) => a.created_at))
3679
- }
3680
- }
3681
- ]
3682
- };
3683
- }
3684
- }
3685
- const actionIdMatch = uri.match(/^action:\/\/(\d+)$/);
3686
- if (actionIdMatch) {
3687
- const id = Number(actionIdMatch[1]);
3688
- const action = db.actions.getActionById(id);
3689
- if (!action) throw resourceNotFound(`Action with ID ${id} not found.`, uri);
3690
- const payload = JSON.stringify(action, null, 2);
3691
- return {
3692
- contents: [
3693
- {
3694
- uri,
3695
- mimeType: "application/json",
3696
- text: payload,
3697
- size: Buffer.byteLength(payload, "utf8"),
3698
- annotations: {
3699
- audience: ["assistant"],
3700
- priority: 0.55,
3701
- lastModified: action.created_at
3702
- }
3703
- }
3704
- ]
3705
- };
3706
- }
3707
- throw resourceNotFound(`Unknown resource URI: ${uri}`, uri);
3708
- }
3709
- function parseRepoUri(uri) {
3710
- const prefix = "repository://";
3711
- if (!uri.startsWith(prefix)) return null;
3712
- const rest = uri.slice(prefix.length);
3713
- const queryStart = rest.indexOf("?");
3714
- const withoutQuery = queryStart === -1 ? rest : rest.slice(0, queryStart);
3715
- const queryString = queryStart === -1 ? "" : rest.slice(queryStart + 1);
3716
- const slashIdx = withoutQuery.indexOf("/");
3717
- if (slashIdx === -1) return null;
3718
- const name = withoutQuery.slice(0, slashIdx);
3719
- const path6 = withoutQuery.slice(slashIdx + 1);
3720
- if (!name || !path6) return null;
3721
- return { name, path: path6, query: new URLSearchParams(queryString) };
3722
- }
3723
- function paginateEntries(key, entries, params) {
3724
- const limit = normalizeLimit(params?.limit);
3725
- const offset = decodeCursor(params?.cursor);
3726
- const sliced = entries.slice(offset, offset + limit);
3727
- const nextOffset = offset + sliced.length;
3728
- return {
3729
- [key]: sliced,
3730
- nextCursor: nextOffset < entries.length ? encodeCursor(nextOffset) : void 0
3731
- };
3732
- }
3733
- function normalizeLimit(limit) {
3734
- if (typeof limit !== "number" || !Number.isFinite(limit)) {
3735
- return DEFAULT_PAGE_SIZE;
3736
- }
3737
- return Math.min(MAX_PAGE_SIZE, Math.max(1, Math.trunc(limit)));
3738
- }
3739
- function deriveLastModifiedFromCollection(values) {
3740
- const normalized = values.filter((value) => typeof value === "string" && value.length > 0);
3741
- return normalized.sort().at(-1) ?? (/* @__PURE__ */ new Date()).toISOString();
3742
- }
3743
- function resourceNotFound(message, uri) {
3744
- const error = new Error(message);
3745
- error.code = -32002;
3746
- error.data = { uri };
3747
- return error;
3748
- }
3749
- function invalidCompletionParams(message) {
3750
- const error = new Error(message);
3751
- error.code = -32602;
3752
- return error;
3753
- }
3754
-
3755
- // src/mcp/prompts/registry.ts
3756
- function createPromptDefinition(loaded) {
3757
- return {
3758
- name: loaded.name,
3759
- description: loaded.description,
3760
- arguments: loaded.arguments,
3761
- agent: loaded.agent,
3762
- messages: [
3763
- {
3764
- role: "user",
3765
- content: {
3766
- type: "text",
3767
- text: loaded.content
3768
- }
3769
- }
3770
- ]
3771
- };
3772
- }
3773
- var PROMPTS = {};
3774
- var promptFiles = listPromptFiles();
3775
- for (const name of promptFiles) {
3776
- try {
3777
- PROMPTS[name] = createPromptDefinition(loadPromptFromMarkdown(name));
3778
- } catch (e) {
3779
- logger.warn(`Failed to load prompt ${name}: ${e}`);
3780
- }
3781
- }
3782
- async function listPrompts(db, session, params) {
3783
- const allPrompts = Object.values(PROMPTS).map((p) => ({
3784
- name: p.name,
3785
- description: p.description,
3786
- arguments: p.arguments,
3787
- metadata: p.agent ? { agent: p.agent } : void 0
3788
- }));
3789
- const rawLimit = typeof params?.limit === "number" && Number.isInteger(params?.limit) ? params.limit : 25;
3790
- const limit = Math.max(1, Math.min(100, Math.trunc(rawLimit)));
3791
- const offset = decodeCursor(params?.cursor);
3792
- const sliced = allPrompts.slice(offset, offset + limit);
3793
- const nextOffset = offset + sliced.length;
3794
- return {
3795
- prompts: sliced,
3796
- nextCursor: nextOffset < allPrompts.length ? encodeCursor(nextOffset) : void 0
3797
- };
3798
- }
3799
- async function getPrompt(name, args = {}, db, session) {
3800
- const prompt = PROMPTS[name];
3801
- if (!prompt) {
3802
- throw new Error(`Prompt not found: ${name}`);
3803
- }
3804
- const inferredRepo = inferRepoFromSession(session);
3805
- const messages = prompt.messages.map((m) => {
3806
- let text = m.content.text;
3807
- for (const [key, value] of Object.entries(args)) {
3808
- text = text.replace(new RegExp(`\\{{${key}\\}}`, "g"), value);
3809
- }
3810
- text = text.replace(/{{current_repo}}/g, inferredRepo || "unknown-repo");
3811
- return {
3812
- ...m,
3813
- content: {
3814
- ...m.content,
3815
- text
3816
- }
3817
- };
3818
- });
3819
- return {
3820
- description: prompt.description,
3821
- messages,
3822
- metadata: prompt.agent ? { agent: prompt.agent } : void 0
3823
- };
3824
- }
3825
- async function completePromptArgument(name, argName, value, contextArguments, dataSources) {
3826
- void name;
3827
- void contextArguments;
3828
- if (argName === "task_id") {
3829
- const values = dataSources.tasks.map((t) => t.id);
3830
- return rankCompletionValues(values, value);
3831
- }
3832
- return [];
3833
- }
3834
-
3835
- // src/mcp/tools/schemas.ts
3836
- import { z } from "zod";
3837
- var MemoryScopeSchema = z.object({
3838
- repo: z.string().min(1).transform(normalizeRepo),
3839
- branch: z.string().optional(),
3840
- folder: z.string().optional(),
3841
- language: z.string().optional()
3842
- });
3843
- var MemoryTypeSchema = z.enum(["code_fact", "decision", "mistake", "pattern", "task_archive"]);
3844
- var SingleMemorySchema = z.object({
3845
- code: z.string().max(20).optional(),
3846
- type: MemoryTypeSchema,
3847
- title: z.string().min(3).max(255),
3848
- content: z.string().min(10),
3849
- importance: z.number().min(1).max(5),
3850
- agent: z.string().min(1),
3851
- role: z.string().optional().default("unknown"),
3852
- model: z.string().min(1),
3853
- scope: MemoryScopeSchema,
3854
- ttlDays: z.number().min(1).optional(),
3855
- supersedes: z.string().optional(),
3856
- tags: z.array(z.string()).optional(),
3857
- metadata: z.record(z.string(), z.any()).optional(),
3858
- is_global: z.boolean().default(false)
3859
- });
3860
- var SingleStandardSchema = z.object({
3861
- name: z.string().min(3).max(255),
3862
- content: z.string().min(10),
3863
- parent_id: z.string().optional(),
3864
- context: z.string().optional(),
3865
- version: z.string().optional(),
3866
- language: z.string().optional(),
3867
- stack: z.array(z.string()).optional(),
3868
- is_global: z.boolean().optional(),
3869
- tags: z.array(z.string().min(1)).min(1),
3870
- metadata: z.record(z.string(), z.any()).refine((value) => Object.keys(value).length > 0, {
3871
- message: "metadata must contain at least one key"
3872
- }),
3873
- agent: z.string().optional(),
3874
- model: z.string().optional()
3875
- });
3876
- var MemoryStoreSchema = z.object({
3877
- code: z.string().max(20).optional(),
3878
- type: MemoryTypeSchema.optional(),
3879
- title: z.string().min(3).max(255).optional(),
3880
- content: z.string().min(10).optional(),
3881
- importance: z.number().min(1).max(5).optional(),
3882
- agent: z.string().min(1).optional(),
3883
- role: z.string().optional().default("unknown"),
3884
- model: z.string().min(1).optional(),
3885
- scope: MemoryScopeSchema.optional(),
3886
- ttlDays: z.number().min(1).optional(),
3887
- supersedes: z.string().optional(),
3888
- tags: z.array(z.string()).optional(),
3889
- metadata: z.record(z.string(), z.any()).optional(),
3890
- is_global: z.boolean().default(false),
3891
- structured: z.boolean().default(false),
3892
- memories: z.array(SingleMemorySchema).min(1).optional()
3893
- }).refine(
3894
- (data) => {
3895
- if (data.memories) return true;
3896
- return !!(data.type && data.title && data.content && data.importance && data.agent && data.model && data.scope);
3897
- },
3898
- {
3899
- message: "Either 'memories' array or single memory fields (type, title, content, importance, agent, model, scope) must be provided"
3328
+ // src/mcp/tools/schemas.ts
3329
+ import { z } from "zod";
3330
+ var MemoryScopeSchema = z.object({
3331
+ repo: z.string().min(1).transform(normalizeRepo),
3332
+ branch: z.string().optional(),
3333
+ folder: z.string().optional(),
3334
+ language: z.string().optional()
3335
+ });
3336
+ var MemoryTypeSchema = z.enum(["code_fact", "decision", "mistake", "pattern", "task_archive"]);
3337
+ var SingleMemorySchema = z.object({
3338
+ code: z.string().max(20).optional(),
3339
+ type: MemoryTypeSchema,
3340
+ title: z.string().min(3).max(255),
3341
+ content: z.string().min(10),
3342
+ importance: z.number().min(1).max(5),
3343
+ agent: z.string().min(1),
3344
+ role: z.string().optional().default("unknown"),
3345
+ model: z.string().min(1),
3346
+ scope: MemoryScopeSchema,
3347
+ ttlDays: z.number().min(1).optional(),
3348
+ supersedes: z.string().optional(),
3349
+ tags: z.array(z.string()).optional(),
3350
+ metadata: z.record(z.string(), z.any()).optional(),
3351
+ is_global: z.boolean().default(false)
3352
+ });
3353
+ var SingleStandardSchema = z.object({
3354
+ name: z.string().min(3).max(255),
3355
+ content: z.string().min(10),
3356
+ parent_id: z.string().optional(),
3357
+ context: z.string().optional(),
3358
+ version: z.string().optional(),
3359
+ language: z.string().optional(),
3360
+ stack: z.array(z.string()).optional(),
3361
+ is_global: z.boolean().optional(),
3362
+ tags: z.array(z.string().min(1)).min(1),
3363
+ metadata: z.record(z.string(), z.any()).refine((value) => Object.keys(value).length > 0, {
3364
+ message: "metadata must contain at least one key"
3365
+ }),
3366
+ agent: z.string().optional(),
3367
+ model: z.string().optional()
3368
+ });
3369
+ var MemoryStoreSchema = z.object({
3370
+ code: z.string().max(20).optional(),
3371
+ type: MemoryTypeSchema.optional(),
3372
+ title: z.string().min(3).max(255).optional(),
3373
+ content: z.string().min(10).optional(),
3374
+ importance: z.number().min(1).max(5).optional(),
3375
+ agent: z.string().min(1).optional(),
3376
+ role: z.string().optional().default("unknown"),
3377
+ model: z.string().min(1).optional(),
3378
+ scope: MemoryScopeSchema.optional(),
3379
+ ttlDays: z.number().min(1).optional(),
3380
+ supersedes: z.string().optional(),
3381
+ tags: z.array(z.string()).optional(),
3382
+ metadata: z.record(z.string(), z.any()).optional(),
3383
+ is_global: z.boolean().default(false),
3384
+ structured: z.boolean().default(false),
3385
+ memories: z.array(SingleMemorySchema).min(1).optional()
3386
+ }).refine(
3387
+ (data) => {
3388
+ if (data.memories) return true;
3389
+ return !!(data.type && data.title && data.content && data.importance && data.agent && data.model && data.scope);
3390
+ },
3391
+ {
3392
+ message: "Either 'memories' array or single memory fields (type, title, content, importance, agent, model, scope) must be provided"
3900
3393
  }
3901
3394
  );
3902
3395
  var MemoryUpdateSchema = z.object({
@@ -3982,6 +3475,27 @@ var MemorySynthesizeSchema = z.object({
3982
3475
  });
3983
3476
  var TaskStatusSchema = z.enum(["backlog", "pending", "in_progress", "completed", "canceled", "blocked"]);
3984
3477
  var TaskPrioritySchema = z.number().min(1).max(5);
3478
+ var TaskMetadataSchema = z.record(z.string(), z.any()).optional().superRefine((metadata, ctx) => {
3479
+ if (!metadata) return;
3480
+ if (metadata.required_skills !== void 0) {
3481
+ if (!Array.isArray(metadata.required_skills)) {
3482
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "metadata.required_skills must be an array of strings", path: ["metadata", "required_skills"] });
3483
+ } else if (metadata.required_skills.length === 0) {
3484
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "metadata.required_skills must not be empty when present", path: ["metadata", "required_skills"] });
3485
+ } else if (!metadata.required_skills.every((s) => typeof s === "string" && s.length > 0)) {
3486
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "metadata.required_skills must be an array of non-empty strings", path: ["metadata", "required_skills"] });
3487
+ }
3488
+ }
3489
+ if (metadata.fsm_gates !== void 0) {
3490
+ if (!Array.isArray(metadata.fsm_gates)) {
3491
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "metadata.fsm_gates must be an array of strings", path: ["metadata", "fsm_gates"] });
3492
+ } else if (metadata.fsm_gates.length === 0) {
3493
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "metadata.fsm_gates must not be empty when present", path: ["metadata", "fsm_gates"] });
3494
+ } else if (!metadata.fsm_gates.every((s) => typeof s === "string" && s.length > 0)) {
3495
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "metadata.fsm_gates must be an array of non-empty strings", path: ["metadata", "fsm_gates"] });
3496
+ }
3497
+ }
3498
+ });
3985
3499
  var SingleTaskCreateSchema = z.object({
3986
3500
  task_code: z.string().min(1).optional(),
3987
3501
  phase: z.string().min(1),
@@ -3994,7 +3508,7 @@ var SingleTaskCreateSchema = z.object({
3994
3508
  doc_path: z.string().optional(),
3995
3509
  tags: z.array(z.string()).optional(),
3996
3510
  suggested_skills: z.array(z.string()).optional(),
3997
- metadata: z.record(z.string(), z.any()).optional(),
3511
+ metadata: TaskMetadataSchema,
3998
3512
  parent_id: z.string().optional(),
3999
3513
  depends_on: z.string().optional(),
4000
3514
  est_tokens: z.number().int().min(0).optional()
@@ -5642,100 +5156,619 @@ var TOOL_DEFINITIONS = [
5642
5156
  destructiveHint: false,
5643
5157
  openWorldHint: false
5644
5158
  },
5645
- inputSchema: {
5646
- type: "object",
5647
- properties: {
5648
- id: { type: "string", format: "uuid", description: "Standard ID to update. Optional if code is provided." },
5649
- code: { type: "string", maxLength: 20, description: "Short standard code. Optional if id is provided." },
5650
- name: { type: "string", minLength: 3, maxLength: 255 },
5651
- content: { type: "string", minLength: 10 },
5652
- parent_id: { type: "string", nullable: true },
5653
- context: { type: "string" },
5654
- version: { type: "string" },
5655
- language: { type: "string" },
5656
- stack: { type: "array", items: { type: "string" } },
5657
- repo: { type: "string" },
5658
- is_global: { type: "boolean" },
5659
- tags: { type: "array", items: { type: "string" } },
5660
- metadata: { type: "object" },
5661
- agent: { type: "string" },
5662
- model: { type: "string" },
5663
- structured: { type: "boolean", default: false }
5664
- }
5159
+ inputSchema: {
5160
+ type: "object",
5161
+ properties: {
5162
+ id: { type: "string", format: "uuid", description: "Standard ID to update. Optional if code is provided." },
5163
+ code: { type: "string", maxLength: 20, description: "Short standard code. Optional if id is provided." },
5164
+ name: { type: "string", minLength: 3, maxLength: 255 },
5165
+ content: { type: "string", minLength: 10 },
5166
+ parent_id: { type: "string", nullable: true },
5167
+ context: { type: "string" },
5168
+ version: { type: "string" },
5169
+ language: { type: "string" },
5170
+ stack: { type: "array", items: { type: "string" } },
5171
+ repo: { type: "string" },
5172
+ is_global: { type: "boolean" },
5173
+ tags: { type: "array", items: { type: "string" } },
5174
+ metadata: { type: "object" },
5175
+ agent: { type: "string" },
5176
+ model: { type: "string" },
5177
+ structured: { type: "boolean", default: false }
5178
+ }
5179
+ },
5180
+ outputSchema: {
5181
+ type: "object",
5182
+ properties: {
5183
+ success: { type: "boolean" },
5184
+ id: { type: "string" },
5185
+ updatedFields: { type: "array", items: { type: "string" } }
5186
+ },
5187
+ required: ["success", "id", "updatedFields"]
5188
+ }
5189
+ },
5190
+ {
5191
+ name: "standard-search",
5192
+ title: "Standard Search",
5193
+ description: "MANDATORY PRE-IMPLEMENTATION CHECK: Call before any code edit, test edit, refactor, migration, or implementation decision to find applicable coding standards. Returns a compact pointer table; use `standard-detail` for relevant results. If no relevant standards are returned, continue and state that no applicable standards were found.",
5194
+ annotations: {
5195
+ readOnlyHint: true,
5196
+ idempotentHint: true,
5197
+ openWorldHint: false
5198
+ },
5199
+ inputSchema: {
5200
+ type: "object",
5201
+ properties: {
5202
+ query: { type: "string", description: "Search query (optional, searches title/content)" },
5203
+ stack: {
5204
+ type: "array",
5205
+ items: { type: "string" },
5206
+ description: "Technology stack to filter by (e.g., ['react', 'nextjs'])"
5207
+ },
5208
+ tags: {
5209
+ type: "array",
5210
+ items: { type: "string" },
5211
+ description: "Tag filter"
5212
+ },
5213
+ language: { type: "string", description: "Programming language filter" },
5214
+ context: { type: "string", description: "Context/category filter" },
5215
+ version: { type: "string", description: "Version filter" },
5216
+ repo: { type: "string", description: "Repository filter (optional)" },
5217
+ is_global: { type: "boolean", description: "Filter by global/repo-specific" },
5218
+ limit: { type: "number", minimum: 1, maximum: 100, default: 20 },
5219
+ offset: { type: "number", minimum: 0, default: 0 },
5220
+ structured: { type: "boolean", default: false }
5221
+ },
5222
+ required: []
5223
+ },
5224
+ outputSchema: {
5225
+ type: "object",
5226
+ properties: {
5227
+ schema: { type: "string", enum: ["standard-search"] },
5228
+ query: { type: "string" },
5229
+ count: { type: "number", description: "Number of rows returned" },
5230
+ total: { type: "number", description: "Total number of matches before pagination" },
5231
+ offset: { type: "number" },
5232
+ limit: { type: "number" },
5233
+ results: {
5234
+ type: "object",
5235
+ properties: {
5236
+ columns: {
5237
+ type: "array",
5238
+ items: { type: "string" }
5239
+ },
5240
+ rows: {
5241
+ type: "array",
5242
+ items: { type: "array" },
5243
+ description: "Each row includes standard id and pointer metadata. Fetch full content via standard-detail."
5244
+ }
5245
+ },
5246
+ required: ["columns", "rows"]
5247
+ }
5248
+ },
5249
+ required: ["schema", "query", "count", "total", "offset", "limit", "results"]
5250
+ }
5251
+ }
5252
+ ];
5253
+
5254
+ // src/mcp/utils/pagination.ts
5255
+ function encodeCursor(offset) {
5256
+ return Buffer.from(String(offset), "utf8").toString("base64");
5257
+ }
5258
+ function decodeCursor(cursor) {
5259
+ if (cursor === void 0 || cursor === null || cursor === "") {
5260
+ return 0;
5261
+ }
5262
+ if (typeof cursor !== "string" || cursor.trim() === "") {
5263
+ throw invalidPaginationParams("Invalid cursor");
5264
+ }
5265
+ let decoded;
5266
+ try {
5267
+ decoded = Buffer.from(cursor, "base64").toString("utf8");
5268
+ } catch {
5269
+ throw invalidPaginationParams("Invalid cursor");
5270
+ }
5271
+ if (!/^\d+$/.test(decoded)) {
5272
+ throw invalidPaginationParams("Invalid cursor");
5273
+ }
5274
+ const offset = Number.parseInt(decoded, 10);
5275
+ if (!Number.isFinite(offset) || offset < 0) {
5276
+ throw invalidPaginationParams("Invalid cursor");
5277
+ }
5278
+ return offset;
5279
+ }
5280
+ function invalidPaginationParams(message) {
5281
+ const error = new Error(message);
5282
+ error.code = -32602;
5283
+ return error;
5284
+ }
5285
+
5286
+ // src/mcp/utils/completion.ts
5287
+ var MAX_COMPLETION_VALUES = 100;
5288
+ function rankCompletionValues(candidates, input) {
5289
+ const unique = [...new Set(candidates.filter(Boolean))];
5290
+ const needle = input.trim().toLowerCase();
5291
+ if (!needle) {
5292
+ return unique.slice(0, MAX_COMPLETION_VALUES);
5293
+ }
5294
+ return unique.map((value) => ({ value, score: scoreCompletionValue(value, needle) })).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.value.localeCompare(b.value)).map((entry) => entry.value);
5295
+ }
5296
+ function scoreCompletionValue(value, needle) {
5297
+ const haystack = value.toLowerCase();
5298
+ if (haystack === needle) return 100;
5299
+ if (haystack.startsWith(needle)) return 75;
5300
+ if (haystack.includes(needle)) return 50;
5301
+ const compactNeedle = needle.replace(/[\s_-]+/g, "");
5302
+ const compactHaystack = haystack.replace(/[\s_-]+/g, "");
5303
+ if (compactNeedle && compactHaystack.includes(compactNeedle)) return 25;
5304
+ return 0;
5305
+ }
5306
+
5307
+ // src/mcp/resources/index.ts
5308
+ var DEFAULT_PAGE_SIZE = 25;
5309
+ var MAX_PAGE_SIZE = 100;
5310
+ function listResources(session, params) {
5311
+ const resources = [
5312
+ {
5313
+ uri: "repository://index",
5314
+ name: "Repository Index",
5315
+ title: "Repository Index",
5316
+ description: "List of all known repositories with memory/task counts and last activity",
5317
+ mimeType: "application/json",
5318
+ annotations: {
5319
+ audience: ["assistant"],
5320
+ priority: 1,
5321
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
5322
+ }
5323
+ },
5324
+ {
5325
+ uri: "session://roots",
5326
+ name: "Session Roots",
5327
+ title: "Session Roots",
5328
+ description: session?.roots.length ? "Active workspace roots provided by the MCP client" : "No active workspace roots were provided by the MCP client",
5329
+ mimeType: "application/json",
5330
+ size: Buffer.byteLength(JSON.stringify({ roots: session?.roots ?? [] }), "utf8"),
5331
+ annotations: {
5332
+ audience: ["assistant"],
5333
+ priority: 0.95,
5334
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
5335
+ }
5336
+ }
5337
+ ];
5338
+ return paginateEntries("resources", resources, params);
5339
+ }
5340
+ function listResourceTemplates(params) {
5341
+ const templates = [
5342
+ // ── Memory ──────────────────────────────────────────────────────────────
5343
+ {
5344
+ uriTemplate: "repository://{name}/memories",
5345
+ name: "Repository Memories",
5346
+ title: "Repository Memories",
5347
+ description: "All active memory entries for a specific repository",
5348
+ mimeType: "application/json",
5349
+ annotations: { audience: ["assistant"], priority: 0.85 }
5350
+ },
5351
+ {
5352
+ uriTemplate: "repository://{name}/memories?search={search}&type={type}&tag={tag}",
5353
+ name: "Filtered Repository Memories",
5354
+ title: "Filtered Repository Memories",
5355
+ description: "Filter or search memories within a repository by keyword, type, or tag",
5356
+ mimeType: "application/json",
5357
+ annotations: { audience: ["assistant"], priority: 0.8 }
5358
+ },
5359
+ {
5360
+ uriTemplate: "memory://{id}",
5361
+ name: "Memory Detail",
5362
+ title: "Memory Detail",
5363
+ description: "Full content and statistics for a specific memory UUID",
5364
+ mimeType: "application/json",
5365
+ annotations: { audience: ["assistant"], priority: 0.75 }
5366
+ },
5367
+ // ── Tasks ────────────────────────────────────────────────────────────────
5368
+ {
5369
+ uriTemplate: "repository://{name}/tasks",
5370
+ name: "Repository Tasks",
5371
+ title: "Repository Tasks",
5372
+ description: "All active tasks for a specific repository",
5373
+ mimeType: "application/json",
5374
+ annotations: { audience: ["assistant"], priority: 0.9 }
5375
+ },
5376
+ {
5377
+ uriTemplate: "repository://{name}/tasks?status={status}&priority={priority}",
5378
+ name: "Filtered Repository Tasks",
5379
+ title: "Filtered Repository Tasks",
5380
+ description: "Filter tasks within a repository by status or priority level",
5381
+ mimeType: "application/json",
5382
+ annotations: { audience: ["assistant"], priority: 0.85 }
5383
+ },
5384
+ {
5385
+ uriTemplate: "task://{id}",
5386
+ name: "Task Detail",
5387
+ title: "Task Detail",
5388
+ description: "Full content and comments for a specific task UUID",
5389
+ mimeType: "application/json",
5390
+ annotations: { audience: ["assistant"], priority: 0.8 }
5665
5391
  },
5666
- outputSchema: {
5667
- type: "object",
5668
- properties: {
5669
- success: { type: "boolean" },
5670
- id: { type: "string" },
5671
- updatedFields: { type: "array", items: { type: "string" } }
5672
- },
5673
- required: ["success", "id", "updatedFields"]
5674
- }
5675
- },
5676
- {
5677
- name: "standard-search",
5678
- title: "Standard Search",
5679
- description: "MANDATORY PRE-IMPLEMENTATION CHECK: Call before any code edit, test edit, refactor, migration, or implementation decision to find applicable coding standards. Returns a compact pointer table; use `standard-detail` for relevant results. If no relevant standards are returned, continue and state that no applicable standards were found.",
5680
- annotations: {
5681
- readOnlyHint: true,
5682
- idempotentHint: true,
5683
- openWorldHint: false
5392
+ // ── Repository extras ────────────────────────────────────────────────────
5393
+ {
5394
+ uriTemplate: "repository://{name}/summary",
5395
+ name: "Repository Summary",
5396
+ title: "Repository Summary",
5397
+ description: "High-level architectural summary for a repository",
5398
+ mimeType: "text/plain",
5399
+ annotations: { audience: ["assistant"], priority: 0.95 }
5684
5400
  },
5685
- inputSchema: {
5686
- type: "object",
5687
- properties: {
5688
- query: { type: "string", description: "Search query (optional, searches title/content)" },
5689
- stack: {
5690
- type: "array",
5691
- items: { type: "string" },
5692
- description: "Technology stack to filter by (e.g., ['react', 'nextjs'])"
5693
- },
5694
- tags: {
5695
- type: "array",
5696
- items: { type: "string" },
5697
- description: "Tag filter"
5698
- },
5699
- language: { type: "string", description: "Programming language filter" },
5700
- context: { type: "string", description: "Context/category filter" },
5701
- version: { type: "string", description: "Version filter" },
5702
- repo: { type: "string", description: "Repository filter (optional)" },
5703
- is_global: { type: "boolean", description: "Filter by global/repo-specific" },
5704
- limit: { type: "number", minimum: 1, maximum: 100, default: 20 },
5705
- offset: { type: "number", minimum: 0, default: 0 },
5706
- structured: { type: "boolean", default: false }
5707
- },
5708
- required: []
5401
+ {
5402
+ uriTemplate: "repository://{name}/actions",
5403
+ name: "Repository Actions",
5404
+ title: "Repository Actions",
5405
+ description: "Audit log of agent tool actions scoped to a repository",
5406
+ mimeType: "application/json",
5407
+ annotations: { audience: ["assistant"], priority: 0.6 }
5709
5408
  },
5710
- outputSchema: {
5711
- type: "object",
5712
- properties: {
5713
- schema: { type: "string", enum: ["standard-search"] },
5714
- query: { type: "string" },
5715
- count: { type: "number", description: "Number of rows returned" },
5716
- total: { type: "number", description: "Total number of matches before pagination" },
5717
- offset: { type: "number" },
5718
- limit: { type: "number" },
5719
- results: {
5720
- type: "object",
5721
- properties: {
5722
- columns: {
5723
- type: "array",
5724
- items: { type: "string" }
5725
- },
5726
- rows: {
5727
- type: "array",
5728
- items: { type: "array" },
5729
- description: "Each row includes standard id and pointer metadata. Fetch full content via standard-detail."
5409
+ // ── Action detail ────────────────────────────────────────────────────────
5410
+ {
5411
+ uriTemplate: "action://{id}",
5412
+ name: "Action Detail",
5413
+ title: "Action Detail",
5414
+ description: "Full details of a specific audit log entry by integer ID",
5415
+ mimeType: "application/json",
5416
+ annotations: { audience: ["assistant"], priority: 0.55 }
5417
+ }
5418
+ ];
5419
+ return paginateEntries("resourceTemplates", templates, params);
5420
+ }
5421
+ function completeResourceArgument(resourceUri, argumentName, argumentValue, _contextArguments, dataSources) {
5422
+ if (resourceUri === "repository://{name}/memories" || resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}" || resourceUri === "repository://{name}/tasks" || resourceUri === "repository://{name}/tasks?status={status}&priority={priority}" || resourceUri === "repository://{name}/summary" || resourceUri === "repository://{name}/actions") {
5423
+ if (argumentName === "name") {
5424
+ return rankCompletionValues(dataSources.repos, argumentValue);
5425
+ }
5426
+ }
5427
+ if (resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}") {
5428
+ if (argumentName === "tag") {
5429
+ return rankCompletionValues(dataSources.tags, argumentValue);
5430
+ }
5431
+ }
5432
+ throw invalidCompletionParams(`Unknown resource template or argument: ${resourceUri} (${argumentName})`);
5433
+ }
5434
+ function readResource(uri, db, session) {
5435
+ logger.info("[Tool] resource.read", { uri });
5436
+ if (uri === "repository://index") {
5437
+ const repos = db.system.listRepoNavigation();
5438
+ const payload = JSON.stringify(repos, null, 2);
5439
+ return {
5440
+ contents: [
5441
+ {
5442
+ uri,
5443
+ mimeType: "application/json",
5444
+ text: payload,
5445
+ size: Buffer.byteLength(payload, "utf8"),
5446
+ annotations: {
5447
+ audience: ["assistant"],
5448
+ priority: 1,
5449
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
5450
+ }
5451
+ }
5452
+ ]
5453
+ };
5454
+ }
5455
+ if (uri === "session://roots") {
5456
+ const payload = JSON.stringify({ roots: session?.roots ?? [] }, null, 2);
5457
+ return {
5458
+ contents: [
5459
+ {
5460
+ uri,
5461
+ mimeType: "application/json",
5462
+ text: payload,
5463
+ size: Buffer.byteLength(payload, "utf8"),
5464
+ annotations: {
5465
+ audience: ["assistant"],
5466
+ priority: 0.95,
5467
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
5468
+ }
5469
+ }
5470
+ ]
5471
+ };
5472
+ }
5473
+ const memoryIdMatch = uri.match(/^memory:\/\/([0-9a-f-]{36})$/i);
5474
+ if (memoryIdMatch) {
5475
+ const id = memoryIdMatch[1];
5476
+ const entry = db.memories.getByIdWithStats(id);
5477
+ if (!entry) throw resourceNotFound(`Memory with ID ${id} not found.`, uri);
5478
+ const payload = JSON.stringify(entry, null, 2);
5479
+ return {
5480
+ contents: [
5481
+ {
5482
+ uri,
5483
+ mimeType: "application/json",
5484
+ text: payload,
5485
+ size: Buffer.byteLength(payload, "utf8"),
5486
+ annotations: {
5487
+ audience: ["assistant"],
5488
+ priority: 0.75,
5489
+ lastModified: entry.updated_at || entry.created_at
5490
+ }
5491
+ }
5492
+ ]
5493
+ };
5494
+ }
5495
+ const taskIdMatch = uri.match(/^task:\/\/([0-9a-f-]{36})$/i);
5496
+ if (taskIdMatch) {
5497
+ const id = taskIdMatch[1];
5498
+ const task = db.tasks.getTaskById(id);
5499
+ if (!task) throw resourceNotFound(`Task with ID ${id} not found.`, uri);
5500
+ const payload = JSON.stringify(task, null, 2);
5501
+ return {
5502
+ contents: [
5503
+ {
5504
+ uri,
5505
+ mimeType: "application/json",
5506
+ text: payload,
5507
+ size: Buffer.byteLength(payload, "utf8"),
5508
+ annotations: {
5509
+ audience: ["assistant"],
5510
+ priority: 0.8,
5511
+ lastModified: task.updated_at || task.created_at
5512
+ }
5513
+ }
5514
+ ]
5515
+ };
5516
+ }
5517
+ const repoBase = parseRepoUri(uri);
5518
+ if (repoBase) {
5519
+ const { name, path: repoPath, query } = repoBase;
5520
+ if (repoPath === "summary") {
5521
+ const summary = db.summaries.getSummary(name);
5522
+ const text = summary?.summary || `No summary available for repository: ${name}`;
5523
+ return {
5524
+ contents: [
5525
+ {
5526
+ uri,
5527
+ mimeType: "text/plain",
5528
+ text,
5529
+ size: Buffer.byteLength(text, "utf8"),
5530
+ annotations: {
5531
+ audience: ["assistant"],
5532
+ priority: 0.95,
5533
+ lastModified: summary?.updated_at || (/* @__PURE__ */ new Date()).toISOString()
5534
+ }
5535
+ }
5536
+ ]
5537
+ };
5538
+ }
5539
+ if (repoPath === "memories") {
5540
+ const search = query.get("search") || "";
5541
+ const type = query.get("type");
5542
+ const tag = query.get("tag");
5543
+ const result = db.memories.listMemoriesForDashboard({
5544
+ repo: name,
5545
+ type: type || void 0,
5546
+ tag: tag || void 0,
5547
+ search: search || void 0,
5548
+ limit: 50
5549
+ });
5550
+ const entries = result.items;
5551
+ const payload = JSON.stringify(entries, null, 2);
5552
+ return {
5553
+ contents: [
5554
+ {
5555
+ uri,
5556
+ mimeType: "application/json",
5557
+ text: payload,
5558
+ size: Buffer.byteLength(payload, "utf8"),
5559
+ annotations: {
5560
+ audience: ["assistant"],
5561
+ priority: 0.85,
5562
+ lastModified: deriveLastModifiedFromCollection(
5563
+ entries.map((e) => e.updated_at || e.created_at)
5564
+ )
5730
5565
  }
5731
- },
5732
- required: ["columns", "rows"]
5566
+ }
5567
+ ]
5568
+ };
5569
+ }
5570
+ if (repoPath === "tasks") {
5571
+ const status = query.get("status");
5572
+ const priority = query.get("priority");
5573
+ let tasks;
5574
+ if (status && status !== "all") {
5575
+ const statuses = status.split(",").map((s) => s.trim());
5576
+ tasks = db.tasks.getTasksByMultipleStatuses(name, statuses);
5577
+ } else {
5578
+ tasks = db.tasks.getTasksByMultipleStatuses(name, ["backlog", "pending", "in_progress", "blocked"]);
5579
+ }
5580
+ if (priority) {
5581
+ const p = Number(priority);
5582
+ if (!isNaN(p)) {
5583
+ tasks = tasks.filter((t) => t.priority === p);
5733
5584
  }
5734
- },
5735
- required: ["schema", "query", "count", "total", "offset", "limit", "results"]
5585
+ }
5586
+ const payload = JSON.stringify(tasks, null, 2);
5587
+ return {
5588
+ contents: [
5589
+ {
5590
+ uri,
5591
+ mimeType: "application/json",
5592
+ text: payload,
5593
+ size: Buffer.byteLength(payload, "utf8"),
5594
+ annotations: {
5595
+ audience: ["assistant"],
5596
+ priority: 0.9,
5597
+ lastModified: deriveLastModifiedFromCollection(tasks.map((t) => t.updated_at))
5598
+ }
5599
+ }
5600
+ ]
5601
+ };
5602
+ }
5603
+ if (repoPath === "actions") {
5604
+ const actions = db.actions.getRecentActions(name, 100);
5605
+ const payload = JSON.stringify(actions, null, 2);
5606
+ return {
5607
+ contents: [
5608
+ {
5609
+ uri,
5610
+ mimeType: "application/json",
5611
+ text: payload,
5612
+ size: Buffer.byteLength(payload, "utf8"),
5613
+ annotations: {
5614
+ audience: ["assistant"],
5615
+ priority: 0.6,
5616
+ lastModified: deriveLastModifiedFromCollection(actions.map((a) => a.created_at))
5617
+ }
5618
+ }
5619
+ ]
5620
+ };
5736
5621
  }
5737
5622
  }
5738
- ];
5623
+ const actionIdMatch = uri.match(/^action:\/\/(\d+)$/);
5624
+ if (actionIdMatch) {
5625
+ const id = Number(actionIdMatch[1]);
5626
+ const action = db.actions.getActionById(id);
5627
+ if (!action) throw resourceNotFound(`Action with ID ${id} not found.`, uri);
5628
+ const payload = JSON.stringify(action, null, 2);
5629
+ return {
5630
+ contents: [
5631
+ {
5632
+ uri,
5633
+ mimeType: "application/json",
5634
+ text: payload,
5635
+ size: Buffer.byteLength(payload, "utf8"),
5636
+ annotations: {
5637
+ audience: ["assistant"],
5638
+ priority: 0.55,
5639
+ lastModified: action.created_at
5640
+ }
5641
+ }
5642
+ ]
5643
+ };
5644
+ }
5645
+ throw resourceNotFound(`Unknown resource URI: ${uri}`, uri);
5646
+ }
5647
+ function parseRepoUri(uri) {
5648
+ const prefix = "repository://";
5649
+ if (!uri.startsWith(prefix)) return null;
5650
+ const rest = uri.slice(prefix.length);
5651
+ const queryStart = rest.indexOf("?");
5652
+ const withoutQuery = queryStart === -1 ? rest : rest.slice(0, queryStart);
5653
+ const queryString = queryStart === -1 ? "" : rest.slice(queryStart + 1);
5654
+ const slashIdx = withoutQuery.indexOf("/");
5655
+ if (slashIdx === -1) return null;
5656
+ const name = withoutQuery.slice(0, slashIdx);
5657
+ const path6 = withoutQuery.slice(slashIdx + 1);
5658
+ if (!name || !path6) return null;
5659
+ return { name, path: path6, query: new URLSearchParams(queryString) };
5660
+ }
5661
+ function paginateEntries(key, entries, params) {
5662
+ const limit = normalizeLimit(params?.limit);
5663
+ const offset = decodeCursor(params?.cursor);
5664
+ const sliced = entries.slice(offset, offset + limit);
5665
+ const nextOffset = offset + sliced.length;
5666
+ return {
5667
+ [key]: sliced,
5668
+ nextCursor: nextOffset < entries.length ? encodeCursor(nextOffset) : void 0
5669
+ };
5670
+ }
5671
+ function normalizeLimit(limit) {
5672
+ if (typeof limit !== "number" || !Number.isFinite(limit)) {
5673
+ return DEFAULT_PAGE_SIZE;
5674
+ }
5675
+ return Math.min(MAX_PAGE_SIZE, Math.max(1, Math.trunc(limit)));
5676
+ }
5677
+ function deriveLastModifiedFromCollection(values) {
5678
+ const normalized = values.filter((value) => typeof value === "string" && value.length > 0);
5679
+ return normalized.sort().at(-1) ?? (/* @__PURE__ */ new Date()).toISOString();
5680
+ }
5681
+ function resourceNotFound(message, uri) {
5682
+ const error = new Error(message);
5683
+ error.code = -32002;
5684
+ error.data = { uri };
5685
+ return error;
5686
+ }
5687
+ function invalidCompletionParams(message) {
5688
+ const error = new Error(message);
5689
+ error.code = -32602;
5690
+ return error;
5691
+ }
5692
+
5693
+ // src/mcp/prompts/registry.ts
5694
+ function createPromptDefinition(loaded) {
5695
+ return {
5696
+ name: loaded.name,
5697
+ description: loaded.description,
5698
+ arguments: loaded.arguments,
5699
+ agent: loaded.agent,
5700
+ messages: [
5701
+ {
5702
+ role: "user",
5703
+ content: {
5704
+ type: "text",
5705
+ text: loaded.content
5706
+ }
5707
+ }
5708
+ ]
5709
+ };
5710
+ }
5711
+ var PROMPTS = {};
5712
+ var promptFiles = listPromptFiles();
5713
+ for (const name of promptFiles) {
5714
+ try {
5715
+ PROMPTS[name] = createPromptDefinition(loadPromptFromMarkdown(name));
5716
+ } catch (e) {
5717
+ logger.warn(`Failed to load prompt ${name}: ${e}`);
5718
+ }
5719
+ }
5720
+ async function listPrompts(db, session, params) {
5721
+ const allPrompts = Object.values(PROMPTS).map((p) => ({
5722
+ name: p.name,
5723
+ description: p.description,
5724
+ arguments: p.arguments,
5725
+ metadata: p.agent ? { agent: p.agent } : void 0
5726
+ }));
5727
+ const rawLimit = typeof params?.limit === "number" && Number.isInteger(params?.limit) ? params.limit : 50;
5728
+ const limit = Math.max(1, Math.min(100, Math.trunc(rawLimit)));
5729
+ const offset = decodeCursor(params?.cursor);
5730
+ const sliced = allPrompts.slice(offset, offset + limit);
5731
+ const nextOffset = offset + sliced.length;
5732
+ return {
5733
+ prompts: sliced,
5734
+ nextCursor: nextOffset < allPrompts.length ? encodeCursor(nextOffset) : void 0
5735
+ };
5736
+ }
5737
+ async function getPrompt(name, args = {}, db, session) {
5738
+ const prompt = PROMPTS[name];
5739
+ if (!prompt) {
5740
+ throw new Error(`Prompt not found: ${name}`);
5741
+ }
5742
+ const inferredRepo = inferRepoFromSession(session);
5743
+ const messages = prompt.messages.map((m) => {
5744
+ let text = m.content.text;
5745
+ for (const [key, value] of Object.entries(args)) {
5746
+ text = text.replace(new RegExp(`\\{{${key}\\}}`, "g"), value);
5747
+ }
5748
+ text = text.replace(/{{current_repo}}/g, inferredRepo || "unknown-repo");
5749
+ return {
5750
+ ...m,
5751
+ content: {
5752
+ ...m.content,
5753
+ text
5754
+ }
5755
+ };
5756
+ });
5757
+ return {
5758
+ description: prompt.description,
5759
+ messages,
5760
+ metadata: prompt.agent ? { agent: prompt.agent } : void 0
5761
+ };
5762
+ }
5763
+ async function completePromptArgument(name, argName, value, contextArguments, dataSources) {
5764
+ void name;
5765
+ void contextArguments;
5766
+ if (argName === "task_id") {
5767
+ const values = dataSources.tasks.map((t) => t.id);
5768
+ return rankCompletionValues(values, value);
5769
+ }
5770
+ return [];
5771
+ }
5739
5772
 
5740
5773
  // src/mcp/utils/mcp-response.ts
5741
5774
  import { z as z2 } from "zod";
@@ -6121,31 +6154,17 @@ function buildStandardVectorText(standard) {
6121
6154
  }
6122
6155
 
6123
6156
  export {
6157
+ MCP_PROTOCOL_VERSION,
6158
+ CAPABILITIES,
6124
6159
  logger,
6125
6160
  setLogLevel,
6126
6161
  getLogLevel,
6127
6162
  addLogSink,
6128
6163
  LOG_LEVEL_VALUES,
6129
6164
  createFileSink,
6130
- encodeCursor,
6131
- decodeCursor,
6132
- listResources,
6133
- listResourceTemplates,
6134
- completeResourceArgument,
6135
- readResource,
6136
- createSessionContext,
6137
- updateSessionFromInitialize,
6138
- updateSessionRoots,
6139
- extractRootsFromResult,
6140
- getFilesystemRoots,
6141
- isPathWithinRoots,
6142
- findContainingRoot,
6143
- inferRepoFromSession,
6144
- PROMPTS,
6145
- listPrompts,
6146
- getPrompt,
6147
- completePromptArgument,
6148
6165
  normalizeRepo,
6166
+ SQLiteStore,
6167
+ RealVectorStore,
6149
6168
  MemoryStoreSchema,
6150
6169
  MemoryUpdateSchema,
6151
6170
  MemorySearchSchema,
@@ -6168,6 +6187,24 @@ export {
6168
6187
  StandardUpdateSchema,
6169
6188
  StandardSearchSchema,
6170
6189
  TOOL_DEFINITIONS,
6190
+ encodeCursor,
6191
+ decodeCursor,
6192
+ listResources,
6193
+ listResourceTemplates,
6194
+ completeResourceArgument,
6195
+ readResource,
6196
+ createSessionContext,
6197
+ updateSessionFromInitialize,
6198
+ updateSessionRoots,
6199
+ extractRootsFromResult,
6200
+ getFilesystemRoots,
6201
+ isPathWithinRoots,
6202
+ findContainingRoot,
6203
+ inferRepoFromSession,
6204
+ PROMPTS,
6205
+ listPrompts,
6206
+ getPrompt,
6207
+ completePromptArgument,
6171
6208
  createMcpResponse,
6172
6209
  getPrimaryTextContent,
6173
6210
  handleHandoffCreate,
@@ -6177,9 +6214,5 @@ export {
6177
6214
  handleClaimList,
6178
6215
  handleClaimRelease,
6179
6216
  toContextSlug,
6180
- buildStandardVectorText,
6181
- SQLiteStore,
6182
- RealVectorStore,
6183
- MCP_PROTOCOL_VERSION,
6184
- CAPABILITIES
6217
+ buildStandardVectorText
6185
6218
  };