@vheins/local-memory-mcp 0.8.11 → 0.8.13

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,41 +1,5 @@
1
- // src/mcp/capabilities.ts
2
- import { fileURLToPath } from "url";
3
- import path from "path";
4
- import fs from "fs";
5
- var __dirname = path.dirname(fileURLToPath(import.meta.url));
6
- var pkgPath = path.join(__dirname, "../../package.json");
7
- var pkgVersion = "0.1.0";
8
- try {
9
- if (fs.existsSync(pkgPath)) {
10
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
11
- pkgVersion = pkg.version;
12
- }
13
- } catch {
14
- }
15
- var MCP_PROTOCOL_VERSION = "2025-11-25";
16
- var CAPABILITIES = {
17
- serverInfo: {
18
- name: "mcp-memory-local",
19
- version: pkgVersion
20
- },
21
- capabilities: {
22
- completions: {},
23
- logging: {},
24
- resources: {
25
- subscribe: true,
26
- listChanged: true
27
- },
28
- tools: {
29
- listChanged: false
30
- },
31
- prompts: {
32
- listChanged: true
33
- }
34
- }
35
- };
36
-
37
1
  // src/mcp/utils/logger.ts
38
- import fs2 from "fs";
2
+ import fs from "fs";
39
3
  var LEVELS = {
40
4
  debug: 0,
41
5
  info: 1,
@@ -155,11 +119,11 @@ function addLogSink(sink) {
155
119
  }
156
120
  var LOG_LEVEL_VALUES = Object.keys(LEVELS);
157
121
  function createFileSink(logDir, maxFiles = 5) {
158
- fs2.mkdirSync(logDir, { recursive: true });
159
- const existing = fs2.readdirSync(logDir).filter((f) => f.startsWith("mcp-") && f.endsWith(".log")).sort();
122
+ fs.mkdirSync(logDir, { recursive: true });
123
+ const existing = fs.readdirSync(logDir).filter((f) => f.startsWith("mcp-") && f.endsWith(".log")).sort();
160
124
  while (existing.length >= maxFiles) {
161
125
  try {
162
- fs2.unlinkSync(`${logDir}/${existing.shift()}`);
126
+ fs.unlinkSync(`${logDir}/${existing.shift()}`);
163
127
  } catch {
164
128
  }
165
129
  }
@@ -169,16 +133,118 @@ function createFileSink(logDir, maxFiles = 5) {
169
133
  const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${payload.level.toUpperCase()}] [pid:${process.pid}] ${JSON.stringify(payload.data)}
170
134
  `;
171
135
  try {
172
- fs2.appendFileSync(logFile, line);
136
+ fs.appendFileSync(logFile, line);
173
137
  } catch {
174
138
  }
175
139
  };
176
140
  }
177
141
 
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
+
178
244
  // src/mcp/storage/sqlite.ts
179
245
  import Database from "better-sqlite3";
180
246
  import path3 from "path";
181
- import fs4 from "fs";
247
+ import fs3 from "fs";
182
248
  import os from "os";
183
249
 
184
250
  // src/mcp/storage/migrations.ts
@@ -1900,7 +1966,7 @@ var SummaryEntity = class extends BaseEntity {
1900
1966
  // src/mcp/storage/write-lock.ts
1901
1967
  import lockfile from "proper-lockfile";
1902
1968
  import path2 from "path";
1903
- import fs3 from "fs";
1969
+ import fs2 from "fs";
1904
1970
  var LOCK_STALE_MS = 15e3;
1905
1971
  var LOCK_RETRY_DELAY_MS = 100;
1906
1972
  var LOCK_RETRY_COUNT = 150;
@@ -1909,9 +1975,9 @@ var WriteLock = class {
1909
1975
  locked = false;
1910
1976
  constructor(dbPath) {
1911
1977
  this.lockTarget = dbPath;
1912
- if (!fs3.existsSync(dbPath)) {
1913
- fs3.mkdirSync(path2.dirname(dbPath), { recursive: true });
1914
- fs3.writeFileSync(dbPath, "");
1978
+ if (!fs2.existsSync(dbPath)) {
1979
+ fs2.mkdirSync(path2.dirname(dbPath), { recursive: true });
1980
+ fs2.writeFileSync(dbPath, "");
1915
1981
  }
1916
1982
  }
1917
1983
  /**
@@ -1965,11 +2031,11 @@ function resolveDbPath() {
1965
2031
  if (process.env.MEMORY_DB_PATH) return process.env.MEMORY_DB_PATH;
1966
2032
  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");
1967
2033
  const standardPath = path3.join(standardConfigDir, "memory.db");
1968
- if (fs4.existsSync(standardPath)) return standardPath;
2034
+ if (fs3.existsSync(standardPath)) return standardPath;
1969
2035
  const legacyPath = path3.join(os.homedir(), ".config", "local-memory-mcp", "memory.db");
1970
- if (fs4.existsSync(legacyPath)) return legacyPath;
2036
+ if (fs3.existsSync(legacyPath)) return legacyPath;
1971
2037
  const localCwdFile = path3.join(process.cwd(), "storage", "memory.db");
1972
- if (fs4.existsSync(localCwdFile)) return localCwdFile;
2038
+ if (fs3.existsSync(localCwdFile)) return localCwdFile;
1973
2039
  return standardPath;
1974
2040
  }
1975
2041
  var DB_PATH = resolveDbPath();
@@ -1987,8 +2053,8 @@ var SQLiteStore = class _SQLiteStore {
1987
2053
  this.dbPathInstance = finalPath;
1988
2054
  if (finalPath !== ":memory:") {
1989
2055
  const dbDir = path3.dirname(finalPath);
1990
- if (!fs4.existsSync(dbDir)) {
1991
- fs4.mkdirSync(dbDir, { recursive: true });
2056
+ if (!fs3.existsSync(dbDir)) {
2057
+ fs3.mkdirSync(dbDir, { recursive: true });
1992
2058
  }
1993
2059
  }
1994
2060
  this.db = new Database(finalPath);
@@ -2040,12 +2106,12 @@ var SQLiteStore = class _SQLiteStore {
2040
2106
  */
2041
2107
  _attemptRecovery(dbPath) {
2042
2108
  const backupPath = dbPath + ".backup";
2043
- if (fs4.existsSync(backupPath)) {
2109
+ if (fs3.existsSync(backupPath)) {
2044
2110
  logger.warn("[SQLiteStore] Attempting recovery from backup", { backupPath });
2045
2111
  try {
2046
2112
  const corruptPath = `${dbPath}.corrupt_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").slice(0, 15)}`;
2047
- fs4.copyFileSync(dbPath, corruptPath);
2048
- fs4.copyFileSync(backupPath, dbPath);
2113
+ fs3.copyFileSync(dbPath, corruptPath);
2114
+ fs3.copyFileSync(backupPath, dbPath);
2049
2115
  logger.warn("[SQLiteStore] Recovery successful. Corrupt file saved to", { corruptPath });
2050
2116
  } catch (err) {
2051
2117
  logger.error("[SQLiteStore] Recovery failed", { error: String(err) });
@@ -2063,7 +2129,7 @@ var SQLiteStore = class _SQLiteStore {
2063
2129
  try {
2064
2130
  this.db.pragma("wal_checkpoint(PASSIVE)");
2065
2131
  const backupPath = this.dbPathInstance + ".backup";
2066
- fs4.copyFileSync(this.dbPathInstance, backupPath);
2132
+ fs3.copyFileSync(this.dbPathInstance, backupPath);
2067
2133
  } catch (err) {
2068
2134
  logger.warn("[SQLiteStore] Backup failed", { error: String(err) });
2069
2135
  }
@@ -2108,1721 +2174,1635 @@ var SQLiteStore = class _SQLiteStore {
2108
2174
  }
2109
2175
  };
2110
2176
 
2111
- // src/mcp/session.ts
2112
- import path4 from "path";
2177
+ // src/mcp/capabilities.ts
2113
2178
  import { fileURLToPath as fileURLToPath2 } from "url";
2114
- function createSessionContext() {
2115
- return {
2116
- roots: [],
2117
- supportsRoots: false,
2118
- supportsSampling: false,
2119
- supportsSamplingTools: false,
2120
- supportsElicitation: false,
2121
- supportsElicitationForm: false,
2122
- supportsElicitationUrl: false
2123
- };
2179
+ import path4 from "path";
2180
+ import fs4 from "fs";
2181
+ var __dirname = path4.dirname(fileURLToPath2(import.meta.url));
2182
+ var pkgPath = path4.join(__dirname, "../../package.json");
2183
+ var pkgVersion = "0.1.0";
2184
+ try {
2185
+ if (fs4.existsSync(pkgPath)) {
2186
+ const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf8"));
2187
+ pkgVersion = pkg.version;
2188
+ }
2189
+ } catch {
2124
2190
  }
2125
- function updateSessionFromInitialize(session, params) {
2126
- const capabilities = params?.capabilities || {};
2127
- session.clientInfo = params?.clientInfo;
2128
- session.clientCapabilities = capabilities;
2129
- session.supportsRoots = Boolean(capabilities.roots);
2130
- session.supportsSampling = Boolean(capabilities.sampling);
2131
- const sampling = capabilities.sampling;
2132
- session.supportsSamplingTools = Boolean(sampling?.tools);
2133
- session.supportsElicitation = Boolean(capabilities.elicitation);
2134
- session.supportsElicitationForm = supportsElicitationMode(capabilities.elicitation, "form");
2135
- session.supportsElicitationUrl = supportsElicitationMode(capabilities.elicitation, "url");
2191
+ var MCP_PROTOCOL_VERSION = "2025-11-25";
2192
+ var CAPABILITIES = {
2193
+ serverInfo: {
2194
+ name: "mcp-memory-local",
2195
+ version: pkgVersion
2196
+ },
2197
+ capabilities: {
2198
+ completions: {},
2199
+ logging: {},
2200
+ resources: {
2201
+ subscribe: true,
2202
+ listChanged: true
2203
+ },
2204
+ tools: {
2205
+ listChanged: false
2206
+ },
2207
+ prompts: {
2208
+ listChanged: true
2209
+ }
2210
+ }
2211
+ };
2212
+
2213
+ // src/mcp/utils/pagination.ts
2214
+ function encodeCursor(offset) {
2215
+ return Buffer.from(String(offset), "utf8").toString("base64");
2136
2216
  }
2137
- function supportsElicitationMode(capability, mode) {
2138
- if (!capability || typeof capability !== "object") {
2139
- return false;
2217
+ function decodeCursor(cursor) {
2218
+ if (cursor === void 0 || cursor === null || cursor === "") {
2219
+ return 0;
2140
2220
  }
2141
- const cap = capability;
2142
- if (mode === "form") {
2143
- return Object.keys(cap).length === 0 || typeof cap.form === "object";
2221
+ if (typeof cursor !== "string" || cursor.trim() === "") {
2222
+ throw invalidPaginationParams("Invalid cursor");
2144
2223
  }
2145
- return typeof cap.url === "object";
2146
- }
2147
- function updateSessionRoots(session, roots) {
2148
- const normalized = normalizeRoots(roots);
2149
- const previous = JSON.stringify(session.roots);
2150
- const next = JSON.stringify(normalized);
2151
- session.roots = normalized;
2152
- return previous !== next;
2153
- }
2154
- function normalizeRoots(roots) {
2155
- if (!Array.isArray(roots)) return [];
2156
- const seen = /* @__PURE__ */ new Set();
2157
- const normalized = [];
2158
- for (const root of roots) {
2159
- if (!root || typeof root !== "object") continue;
2160
- const r = root;
2161
- const uri = typeof r.uri === "string" ? r.uri : void 0;
2162
- const name = typeof r.name === "string" ? r.name : void 0;
2163
- if (!uri || seen.has(uri)) continue;
2164
- seen.add(uri);
2165
- normalized.push({ uri, name });
2224
+ let decoded;
2225
+ try {
2226
+ decoded = Buffer.from(cursor, "base64").toString("utf8");
2227
+ } catch {
2228
+ throw invalidPaginationParams("Invalid cursor");
2166
2229
  }
2167
- return normalized;
2168
- }
2169
- function extractRootsFromResult(result) {
2170
- return normalizeRoots(result?.roots);
2171
- }
2172
- function getFilesystemRoots(session) {
2173
- if (!session) return [];
2174
- const resolved = [];
2175
- for (const root of session.roots) {
2176
- if (!root.uri.startsWith("file://")) continue;
2177
- try {
2178
- resolved.push(path4.resolve(fileURLToPath2(root.uri)));
2179
- } catch {
2180
- }
2230
+ if (!/^\d+$/.test(decoded)) {
2231
+ throw invalidPaginationParams("Invalid cursor");
2181
2232
  }
2182
- return resolved;
2233
+ const offset = Number.parseInt(decoded, 10);
2234
+ if (!Number.isFinite(offset) || offset < 0) {
2235
+ throw invalidPaginationParams("Invalid cursor");
2236
+ }
2237
+ return offset;
2183
2238
  }
2184
- function isPathWithinRoots(targetPath, session) {
2185
- const roots = getFilesystemRoots(session);
2186
- if (roots.length === 0) return true;
2187
- const normalizedTarget = path4.resolve(targetPath);
2188
- return roots.some((rootPath) => {
2189
- const relative = path4.relative(rootPath, normalizedTarget);
2190
- return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
2191
- });
2239
+ function invalidPaginationParams(message) {
2240
+ const error = new Error(message);
2241
+ error.code = -32602;
2242
+ return error;
2192
2243
  }
2193
- function findContainingRoot(targetPath, session) {
2194
- const roots = getFilesystemRoots(session);
2195
- if (roots.length === 0) return null;
2196
- const normalizedTarget = path4.resolve(targetPath);
2197
- for (const rootPath of roots) {
2198
- const relative = path4.relative(rootPath, normalizedTarget);
2199
- if (relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative)) {
2200
- return rootPath;
2201
- }
2244
+
2245
+ // src/mcp/utils/completion.ts
2246
+ var MAX_COMPLETION_VALUES = 100;
2247
+ function rankCompletionValues(candidates, input) {
2248
+ const unique = [...new Set(candidates.filter(Boolean))];
2249
+ const needle = input.trim().toLowerCase();
2250
+ if (!needle) {
2251
+ return unique.slice(0, MAX_COMPLETION_VALUES);
2202
2252
  }
2203
- return null;
2253
+ 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);
2204
2254
  }
2205
- function inferRepoFromSession(session) {
2206
- const roots = getFilesystemRoots(session);
2207
- if (roots.length === 1) {
2208
- return path4.basename(roots[0]);
2209
- }
2210
- return void 0;
2255
+ function scoreCompletionValue(value, needle) {
2256
+ const haystack = value.toLowerCase();
2257
+ if (haystack === needle) return 100;
2258
+ if (haystack.startsWith(needle)) return 75;
2259
+ if (haystack.includes(needle)) return 50;
2260
+ const compactNeedle = needle.replace(/[\s_-]+/g, "");
2261
+ const compactHaystack = haystack.replace(/[\s_-]+/g, "");
2262
+ if (compactNeedle && compactHaystack.includes(compactNeedle)) return 25;
2263
+ return 0;
2211
2264
  }
2212
2265
 
2213
- // src/mcp/tools/schemas.ts
2214
- import { z } from "zod";
2215
- var MemoryScopeSchema = z.object({
2216
- repo: z.string().min(1).transform(normalizeRepo),
2217
- branch: z.string().optional(),
2218
- folder: z.string().optional(),
2219
- language: z.string().optional()
2220
- });
2221
- var MemoryTypeSchema = z.enum([
2222
- "code_fact",
2223
- "decision",
2224
- "mistake",
2225
- "pattern",
2226
- "agent_handoff",
2227
- "agent_registered",
2228
- "file_claim",
2229
- "task_archive"
2230
- ]);
2231
- var MemoryStoreSchema = z.object({
2232
- code: z.string().max(20).optional(),
2233
- type: MemoryTypeSchema,
2234
- title: z.string().min(3).max(255),
2235
- content: z.string().min(10),
2236
- importance: z.number().min(1).max(5),
2237
- agent: z.string().min(1),
2238
- role: z.string().optional().default("unknown"),
2239
- model: z.string().min(1),
2240
- scope: MemoryScopeSchema,
2241
- ttlDays: z.number().min(1).optional(),
2242
- supersedes: z.string().uuid().optional(),
2243
- tags: z.array(z.string()).optional(),
2244
- metadata: z.record(z.string(), z.any()).optional(),
2245
- is_global: z.boolean().default(false),
2246
- structured: z.boolean().default(false)
2247
- });
2248
- var MemoryUpdateSchema = z.object({
2249
- id: z.string().uuid(),
2250
- type: MemoryTypeSchema.optional(),
2251
- title: z.string().min(3).max(255).optional(),
2252
- content: z.string().min(10).optional(),
2253
- importance: z.number().min(1).max(5).optional(),
2254
- agent: z.string().optional(),
2255
- role: z.string().optional(),
2256
- status: z.enum(["active", "archived"]).optional(),
2257
- supersedes: z.string().uuid().optional(),
2258
- tags: z.array(z.string()).optional(),
2259
- metadata: z.record(z.string(), z.any()).optional(),
2260
- is_global: z.boolean().optional(),
2261
- completed_at: z.string().optional(),
2262
- structured: z.boolean().default(false)
2263
- }).refine(
2264
- (data) => data.type !== void 0 || data.content !== void 0 || data.title !== void 0 || data.importance !== void 0 || data.status !== void 0 || data.supersedes !== void 0 || data.tags !== void 0 || data.metadata !== void 0 || data.is_global !== void 0 || data.agent !== void 0 || data.role !== void 0 || data.completed_at !== void 0,
2265
- { message: "At least one field must be provided for update" }
2266
- );
2267
- var MemorySearchSchema = z.object({
2268
- query: z.string().min(3),
2269
- prompt: z.string().optional(),
2270
- repo: z.string().min(1).transform(normalizeRepo),
2271
- types: z.array(MemoryTypeSchema).optional(),
2272
- minImportance: z.number().min(1).max(5).optional(),
2273
- limit: z.number().min(1).max(100).default(5),
2274
- offset: z.number().min(0).default(0),
2275
- includeRecap: z.boolean().default(false),
2276
- current_file_path: z.string().optional(),
2277
- include_archived: z.boolean().default(false),
2278
- current_tags: z.array(z.string()).optional(),
2279
- scope: MemoryScopeSchema.partial().optional(),
2280
- structured: z.boolean().default(false)
2281
- });
2282
- var MemoryAcknowledgeSchema = z.object({
2283
- memory_id: z.string().uuid(),
2284
- status: z.enum(["used", "irrelevant", "contradictory"]),
2285
- application_context: z.string().min(10).optional(),
2286
- structured: z.boolean().default(false)
2287
- });
2288
- var MemoryRecapSchema = z.object({
2289
- repo: z.string().min(1).transform(normalizeRepo),
2290
- limit: z.number().min(1).max(50).default(20),
2291
- offset: z.number().min(0).default(0),
2292
- structured: z.boolean().default(false)
2293
- });
2294
- var MemoryDeleteSchema = z.object({
2295
- repo: z.string().min(1).transform(normalizeRepo).optional(),
2296
- id: z.string().uuid().optional(),
2297
- ids: z.array(z.string().uuid()).min(1).optional(),
2298
- structured: z.boolean().default(false)
2299
- }).refine((data) => data.id !== void 0 || data.ids !== void 0, {
2300
- message: "Either 'id' or 'ids' must be provided for deletion"
2301
- });
2302
- var MemorySummarizeSchema = z.object({
2303
- repo: z.string().min(1).transform(normalizeRepo),
2304
- signals: z.array(z.string().max(200)).min(1),
2305
- structured: z.boolean().default(false)
2306
- });
2307
- var MemorySynthesizeSchema = z.object({
2308
- repo: z.string().min(1).transform(normalizeRepo).optional(),
2309
- objective: z.string().min(5),
2310
- current_file_path: z.string().optional(),
2311
- include_summary: z.boolean().default(true),
2312
- include_tasks: z.boolean().default(true),
2313
- use_tools: z.boolean().default(true),
2314
- max_iterations: z.number().int().min(1).max(5).default(3),
2315
- max_tokens: z.number().int().min(128).max(4e3).default(1200),
2316
- structured: z.boolean().default(false)
2317
- });
2318
- var TaskStatusSchema = z.enum(["backlog", "pending", "in_progress", "completed", "canceled", "blocked"]);
2319
- var TaskPrioritySchema = z.number().min(1).max(5);
2320
- var SingleTaskCreateSchema = z.object({
2321
- task_code: z.string().min(1),
2322
- phase: z.string().min(1),
2323
- title: z.string().min(3).max(100),
2324
- description: z.string().min(1),
2325
- status: TaskStatusSchema.default("backlog"),
2326
- priority: TaskPrioritySchema.default(3),
2327
- agent: z.string().optional(),
2328
- role: z.string().optional(),
2329
- doc_path: z.string().optional(),
2330
- tags: z.array(z.string()).optional(),
2331
- metadata: z.record(z.string(), z.any()).optional(),
2332
- parent_id: z.string().uuid().optional(),
2333
- depends_on: z.string().uuid().optional(),
2334
- est_tokens: z.number().int().min(0).optional()
2335
- });
2336
- var TaskCreateSchema = z.object({
2337
- repo: z.string().min(1).transform(normalizeRepo),
2338
- // Allow single task fields at top level (backward compatibility & single use)
2339
- task_code: z.string().min(1).optional(),
2340
- phase: z.string().min(1).optional(),
2341
- title: z.string().min(3).max(100).optional(),
2342
- description: z.string().min(1).optional(),
2343
- status: TaskStatusSchema.optional(),
2344
- priority: TaskPrioritySchema.optional(),
2345
- agent: z.string().optional(),
2346
- role: z.string().optional(),
2347
- doc_path: z.string().optional(),
2348
- tags: z.array(z.string()).optional(),
2349
- metadata: z.record(z.string(), z.any()).optional(),
2350
- parent_id: z.string().uuid().optional(),
2351
- depends_on: z.string().uuid().optional(),
2352
- est_tokens: z.number().int().min(0).optional(),
2353
- // Allow bulk tasks
2354
- tasks: z.array(SingleTaskCreateSchema).min(1).optional(),
2355
- structured: z.boolean().default(false)
2356
- }).refine(
2357
- (data) => {
2358
- if (data.tasks) return true;
2359
- return !!(data.task_code && data.phase && data.title && data.description);
2360
- },
2361
- { message: "Either 'tasks' array or single task fields (task_code, phase, title, description) must be provided" }
2362
- );
2363
- var TaskCreateInteractiveSchema = SingleTaskCreateSchema.partial().extend({
2364
- repo: z.string().min(1).transform(normalizeRepo).optional(),
2365
- structured: z.boolean().default(false)
2366
- });
2367
- var TaskUpdateSchema = z.object({
2368
- repo: z.string().min(1).transform(normalizeRepo),
2369
- id: z.string().uuid().optional(),
2370
- ids: z.array(z.string().uuid()).min(1).optional(),
2371
- task_code: z.string().optional(),
2372
- phase: z.string().optional(),
2373
- title: z.string().min(3).max(100).optional(),
2374
- description: z.string().optional(),
2375
- status: TaskStatusSchema.optional(),
2376
- priority: TaskPrioritySchema.optional(),
2377
- agent: z.string().min(1, "agent name is required").optional(),
2378
- role: z.string().min(1, "agent role is required").optional(),
2379
- model: z.string().optional(),
2380
- comment: z.string().min(1).optional(),
2381
- doc_path: z.string().optional(),
2382
- tags: z.array(z.string()).optional(),
2383
- metadata: z.record(z.string(), z.any()).optional(),
2384
- parent_id: z.string().uuid().optional(),
2385
- depends_on: z.string().uuid().optional(),
2386
- est_tokens: z.number().int().min(0).optional(),
2387
- force: z.boolean().optional(),
2388
- structured: z.boolean().default(false)
2389
- }).refine((data) => data.id !== void 0 || data.ids !== void 0, {
2390
- message: "Either 'id' or 'ids' must be provided for update"
2391
- }).refine((data) => Object.keys(data).length > 2, {
2392
- message: "At least one field besides repo and id/ids must be provided for update"
2393
- });
2394
- var TaskListSchema = z.object({
2395
- repo: z.string().min(1).transform(normalizeRepo),
2396
- status: z.string().optional(),
2397
- phase: z.string().optional(),
2398
- query: z.string().optional(),
2399
- limit: z.number().min(1).max(100).default(15),
2400
- offset: z.number().min(0).default(0),
2401
- structured: z.boolean().default(false)
2402
- });
2403
- var TaskSearchSchema = z.object({
2404
- repo: z.string().min(1).transform(normalizeRepo),
2405
- query: z.string().min(1),
2406
- status: z.string().optional(),
2407
- limit: z.number().min(1).max(100).default(10),
2408
- offset: z.number().min(0).default(0),
2409
- structured: z.boolean().default(false)
2410
- });
2411
- var TaskDeleteSchema = z.object({
2412
- repo: z.string().min(1).transform(normalizeRepo),
2413
- id: z.string().uuid().optional(),
2414
- ids: z.array(z.string().uuid()).min(1).optional(),
2415
- structured: z.boolean().default(false)
2416
- }).refine((data) => data.id !== void 0 || data.ids !== void 0, {
2417
- message: "Either 'id' or 'ids' must be provided for deletion"
2418
- });
2419
- var MemoryDetailSchema = z.object({
2420
- id: z.string().uuid().optional(),
2421
- code: z.string().max(20).optional(),
2422
- structured: z.boolean().default(false)
2423
- }).refine((data) => data.id !== void 0 || data.code !== void 0, {
2424
- message: "Either id or code must be provided"
2425
- });
2426
- var TaskGetSchema = z.object({
2427
- repo: z.string().min(1).transform(normalizeRepo),
2428
- id: z.string().uuid().optional(),
2429
- task_code: z.string().optional(),
2430
- structured: z.boolean().default(false)
2431
- }).refine((data) => data.id !== void 0 || data.task_code !== void 0, {
2432
- message: "Either id or task_code must be provided"
2433
- });
2434
- var TOOL_DEFINITIONS = [
2435
- {
2436
- name: "memory-synthesize",
2437
- title: "Memory Synthesize",
2438
- description: "Use client sampling to synthesize a grounded answer from local memory and tasks. Best for project briefings, tradeoff summaries, and context-aware answers.",
2439
- annotations: {
2440
- readOnlyHint: true,
2441
- idempotentHint: true,
2442
- openWorldHint: false
2266
+ // src/mcp/resources/index.ts
2267
+ var DEFAULT_PAGE_SIZE = 25;
2268
+ var MAX_PAGE_SIZE = 100;
2269
+ function listResources(session, params) {
2270
+ const resources = [
2271
+ {
2272
+ uri: "repository://index",
2273
+ name: "Repository Index",
2274
+ title: "Repository Index",
2275
+ description: "List of all known repositories with memory/task counts and last activity",
2276
+ mimeType: "application/json",
2277
+ annotations: {
2278
+ audience: ["assistant"],
2279
+ priority: 1,
2280
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
2281
+ }
2443
2282
  },
2444
- execution: {
2445
- taskSupport: "optional"
2283
+ {
2284
+ uri: "session://roots",
2285
+ name: "Session Roots",
2286
+ title: "Session Roots",
2287
+ description: session?.roots.length ? "Active workspace roots provided by the MCP client" : "No active workspace roots were provided by the MCP client",
2288
+ mimeType: "application/json",
2289
+ size: Buffer.byteLength(JSON.stringify({ roots: session?.roots ?? [] }), "utf8"),
2290
+ annotations: {
2291
+ audience: ["assistant"],
2292
+ priority: 0.95,
2293
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
2294
+ }
2295
+ }
2296
+ ];
2297
+ return paginateEntries("resources", resources, params);
2298
+ }
2299
+ function listResourceTemplates(params) {
2300
+ const templates = [
2301
+ // ── Memory ──────────────────────────────────────────────────────────────
2302
+ {
2303
+ uriTemplate: "repository://{name}/memories",
2304
+ name: "Repository Memories",
2305
+ title: "Repository Memories",
2306
+ description: "All active memory entries for a specific repository",
2307
+ mimeType: "application/json",
2308
+ annotations: { audience: ["assistant"], priority: 0.85 }
2446
2309
  },
2447
- inputSchema: {
2448
- type: "object",
2449
- properties: {
2450
- repo: { type: "string", description: "Repository name. Optional when a single MCP root is active." },
2451
- objective: { type: "string", minLength: 5, description: "Question or synthesis objective." },
2452
- current_file_path: {
2453
- type: "string",
2454
- description: "Optional absolute file path for workspace-local grounding."
2455
- },
2456
- include_summary: { type: "boolean", default: true },
2457
- include_tasks: { type: "boolean", default: true },
2458
- use_tools: {
2459
- type: "boolean",
2460
- default: true,
2461
- description: "Allow the sampled model to call local memory/task tools during synthesis when the client supports sampling.tools."
2462
- },
2463
- max_iterations: { type: "number", minimum: 1, maximum: 5, default: 3 },
2464
- max_tokens: { type: "number", minimum: 128, maximum: 4e3, default: 1200 },
2465
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON results." }
2466
- },
2467
- required: ["objective"]
2310
+ {
2311
+ uriTemplate: "repository://{name}/memories?search={search}&type={type}&tag={tag}",
2312
+ name: "Filtered Repository Memories",
2313
+ title: "Filtered Repository Memories",
2314
+ description: "Filter or search memories within a repository by keyword, type, or tag",
2315
+ mimeType: "application/json",
2316
+ annotations: { audience: ["assistant"], priority: 0.8 }
2468
2317
  },
2469
- outputSchema: {
2470
- type: "object",
2471
- properties: {
2472
- repo: { type: "string" },
2473
- objective: { type: "string" },
2474
- answer: { type: "string" },
2475
- model: { type: "string" },
2476
- stopReason: { type: "string" },
2477
- iterations: { type: "number" },
2478
- toolCalls: { type: "number" }
2479
- },
2480
- required: ["repo", "objective", "answer", "iterations", "toolCalls"]
2481
- }
2482
- },
2483
- {
2484
- name: "task-create-interactive",
2485
- title: "Interactive Task Create",
2486
- description: "Create a task with MCP elicitation fallback for any missing required fields. Best when an agent knows a task is needed but still needs user confirmation for repo, title, or phase.",
2487
- annotations: {
2488
- readOnlyHint: false,
2489
- idempotentHint: false,
2490
- destructiveHint: false,
2491
- openWorldHint: false
2318
+ {
2319
+ uriTemplate: "memory://{id}",
2320
+ name: "Memory Detail",
2321
+ title: "Memory Detail",
2322
+ description: "Full content and statistics for a specific memory UUID",
2323
+ mimeType: "application/json",
2324
+ annotations: { audience: ["assistant"], priority: 0.75 }
2492
2325
  },
2493
- inputSchema: {
2494
- type: "object",
2495
- properties: {
2496
- repo: {
2497
- type: "string",
2498
- description: "Repository name. Optional when it can be inferred from MCP roots or elicited from the user."
2499
- },
2500
- task_code: { type: "string" },
2501
- phase: { type: "string" },
2502
- title: { type: "string", minLength: 3, maxLength: 100 },
2503
- description: { type: "string", minLength: 1 },
2504
- status: { type: "string", enum: ["backlog", "pending"], default: "backlog" },
2505
- priority: { type: "number", minimum: 1, maximum: 5, default: 3 },
2506
- agent: { type: "string" },
2507
- role: { type: "string" },
2508
- doc_path: { type: "string" },
2509
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
2510
- }
2326
+ // ── Tasks ────────────────────────────────────────────────────────────────
2327
+ {
2328
+ uriTemplate: "repository://{name}/tasks",
2329
+ name: "Repository Tasks",
2330
+ title: "Repository Tasks",
2331
+ description: "All active tasks for a specific repository",
2332
+ mimeType: "application/json",
2333
+ annotations: { audience: ["assistant"], priority: 0.9 }
2511
2334
  },
2512
- outputSchema: {
2513
- type: "object",
2514
- properties: {
2515
- repo: { type: "string" },
2516
- task_code: { type: "string" },
2517
- phase: { type: "string" },
2518
- title: { type: "string" },
2519
- status: { type: "string" },
2520
- priority: { type: "number" }
2521
- },
2522
- required: ["repo", "task_code", "phase", "title", "status", "priority"]
2335
+ {
2336
+ uriTemplate: "repository://{name}/tasks?status={status}&priority={priority}",
2337
+ name: "Filtered Repository Tasks",
2338
+ title: "Filtered Repository Tasks",
2339
+ description: "Filter tasks within a repository by status or priority level",
2340
+ mimeType: "application/json",
2341
+ annotations: { audience: ["assistant"], priority: 0.85 }
2342
+ },
2343
+ {
2344
+ uriTemplate: "task://{id}",
2345
+ name: "Task Detail",
2346
+ title: "Task Detail",
2347
+ description: "Full content and comments for a specific task UUID",
2348
+ mimeType: "application/json",
2349
+ annotations: { audience: ["assistant"], priority: 0.8 }
2350
+ },
2351
+ // ── Repository extras ────────────────────────────────────────────────────
2352
+ {
2353
+ uriTemplate: "repository://{name}/summary",
2354
+ name: "Repository Summary",
2355
+ title: "Repository Summary",
2356
+ description: "High-level architectural summary for a repository",
2357
+ mimeType: "text/plain",
2358
+ annotations: { audience: ["assistant"], priority: 0.95 }
2359
+ },
2360
+ {
2361
+ uriTemplate: "repository://{name}/actions",
2362
+ name: "Repository Actions",
2363
+ title: "Repository Actions",
2364
+ description: "Audit log of agent tool actions scoped to a repository",
2365
+ mimeType: "application/json",
2366
+ annotations: { audience: ["assistant"], priority: 0.6 }
2367
+ },
2368
+ // ── Action detail ────────────────────────────────────────────────────────
2369
+ {
2370
+ uriTemplate: "action://{id}",
2371
+ name: "Action Detail",
2372
+ title: "Action Detail",
2373
+ description: "Full details of a specific audit log entry by integer ID",
2374
+ mimeType: "application/json",
2375
+ annotations: { audience: ["assistant"], priority: 0.55 }
2523
2376
  }
2524
- },
2525
- {
2526
- name: "memory-detail",
2527
- title: "Memory Detail",
2528
- description: "Fetch full details of a specific memory by ID. Use this when you have a memory ID (e.g. from search results) and need to read the full content.",
2529
- inputSchema: {
2530
- type: "object",
2531
- properties: {
2532
- id: { type: "string", format: "uuid", description: "Memory entry ID" },
2533
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON details." }
2534
- },
2535
- required: ["id"]
2377
+ ];
2378
+ return paginateEntries("resourceTemplates", templates, params);
2379
+ }
2380
+ function completeResourceArgument(resourceUri, argumentName, argumentValue, _contextArguments, dataSources) {
2381
+ 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") {
2382
+ if (argumentName === "name") {
2383
+ return rankCompletionValues(dataSources.repos, argumentValue);
2536
2384
  }
2537
- },
2538
- {
2539
- name: "task-detail",
2540
- title: "Task Detail",
2541
- description: "Fetch full details of a specific task by ID or task code. Use this when you have a task ID or code and need to read the full description and comments.",
2542
- inputSchema: {
2543
- type: "object",
2544
- properties: {
2545
- repo: { type: "string", description: "Repository name" },
2546
- id: { type: "string", format: "uuid", description: "Task ID (optional if task_code is provided)" },
2547
- task_code: { type: "string", description: "Task code (e.g. TASK-001) (optional if id is provided)" },
2548
- structured: {
2549
- type: "boolean",
2550
- default: false,
2551
- description: "If true, returns structured JSON without the text content details."
2552
- }
2553
- },
2554
- required: ["repo"]
2385
+ }
2386
+ if (resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}") {
2387
+ if (argumentName === "tag") {
2388
+ return rankCompletionValues(dataSources.tags, argumentValue);
2555
2389
  }
2556
- },
2557
- {
2558
- name: "memory-store",
2559
- title: "Memory Store",
2560
- description: "Store a new memory entry. Keep 'title' concise and human-readable; do not embed agent/role/date metadata in the title. Put auxiliary context into 'metadata'. Use 'tags' for tech-stack and 'is_global' for universal rules.",
2561
- annotations: {
2562
- readOnlyHint: false,
2563
- idempotentHint: false,
2564
- destructiveHint: false,
2565
- openWorldHint: false
2566
- },
2567
- inputSchema: {
2568
- type: "object",
2569
- properties: {
2570
- type: {
2571
- type: "string",
2572
- enum: [
2573
- "code_fact",
2574
- "decision",
2575
- "mistake",
2576
- "pattern",
2577
- "agent_handoff",
2578
- "agent_registered",
2579
- "file_claim",
2580
- "task_archive"
2581
- ],
2582
- description: "Type of memory being stored"
2583
- },
2584
- title: {
2585
- type: "string",
2586
- minLength: 3,
2587
- maxLength: 100,
2588
- description: "Short human-readable title for the memory. Do not embed bracketed metadata like agent/role/date prefixes here."
2589
- },
2590
- content: {
2591
- type: "string",
2592
- minLength: 10,
2593
- description: "The memory content"
2594
- },
2595
- importance: {
2596
- type: "number",
2597
- minimum: 1,
2598
- maximum: 5,
2599
- description: "Importance score (1-5)"
2600
- },
2601
- agent: {
2602
- type: "string",
2603
- description: "Name of the agent creating this memory"
2604
- },
2605
- role: {
2606
- type: "string",
2607
- description: "Role of the agent creating this memory"
2608
- },
2609
- model: {
2610
- type: "string",
2611
- description: "AI model used by the agent"
2612
- },
2613
- scope: {
2614
- type: "object",
2615
- properties: {
2616
- repo: { type: "string", description: "Repository name" },
2617
- branch: { type: "string" },
2618
- folder: { type: "string" },
2619
- language: { type: "string" }
2620
- },
2621
- required: ["repo"]
2622
- },
2623
- tags: {
2624
- type: "array",
2625
- items: { type: "string" },
2626
- description: "Technology stack tags (e.g., ['filament', 'laravel'])"
2627
- },
2628
- metadata: {
2629
- type: "object",
2630
- description: "Structured metadata for non-title context such as source agent, claim fields, or timestamps"
2631
- },
2632
- is_global: {
2633
- type: "boolean",
2634
- description: "If true, this memory is shared across all repositories"
2635
- },
2636
- ttlDays: { type: "number", minimum: 1 },
2637
- supersedes: { type: "string", format: "uuid" },
2638
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON of the stored memory." }
2639
- },
2640
- required: ["type", "title", "content", "importance", "scope", "agent", "model"]
2641
- },
2642
- outputSchema: {
2643
- type: "object",
2644
- properties: {
2645
- success: { type: "boolean" },
2646
- id: { type: "string" },
2647
- code: { type: "string" },
2648
- repo: { type: "string" },
2649
- type: { type: "string" },
2650
- title: { type: "string" },
2651
- error: { type: "string" },
2652
- message: { type: "string" }
2653
- },
2654
- required: ["success"]
2655
- }
2656
- },
2657
- {
2658
- name: "memory-acknowledge",
2659
- title: "Memory Acknowledge",
2660
- description: "Acknowledge the use of a memory or report its irrelevance/contradiction. Mandatory after using memory to generate code.",
2661
- annotations: {
2662
- readOnlyHint: false,
2663
- idempotentHint: false,
2664
- openWorldHint: false
2665
- },
2666
- inputSchema: {
2667
- type: "object",
2668
- properties: {
2669
- memory_id: { type: "string", format: "uuid" },
2670
- status: { type: "string", enum: ["used", "irrelevant", "contradictory"] },
2671
- application_context: { type: "string", minLength: 10 },
2672
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
2673
- },
2674
- required: ["memory_id", "status"]
2675
- },
2676
- outputSchema: {
2677
- type: "object",
2678
- properties: {
2679
- success: { type: "boolean" },
2680
- id: { type: "string" },
2681
- status: { type: "string" }
2682
- },
2683
- required: ["success", "id", "status"]
2684
- }
2685
- },
2686
- {
2687
- name: "memory-update",
2688
- title: "Memory Update",
2689
- description: "Update an existing memory entry. Keep 'title' concise and move agent/role/date or claim context into 'metadata' instead of the title.",
2690
- annotations: {
2691
- readOnlyHint: false,
2692
- idempotentHint: false,
2693
- destructiveHint: false,
2694
- openWorldHint: false
2695
- },
2696
- inputSchema: {
2697
- type: "object",
2698
- properties: {
2699
- id: { type: "string", format: "uuid" },
2700
- type: {
2701
- type: "string",
2702
- enum: [
2703
- "code_fact",
2704
- "decision",
2705
- "mistake",
2706
- "pattern",
2707
- "agent_handoff",
2708
- "agent_registered",
2709
- "file_claim",
2710
- "task_archive"
2711
- ]
2712
- },
2713
- title: { type: "string", minLength: 3, maxLength: 100 },
2714
- content: { type: "string", minLength: 10 },
2715
- importance: { type: "number", minimum: 1, maximum: 5 },
2716
- agent: { type: "string" },
2717
- role: { type: "string" },
2718
- status: { type: "string", enum: ["active", "archived"] },
2719
- supersedes: { type: "string", format: "uuid" },
2720
- tags: { type: "array", items: { type: "string" } },
2721
- metadata: { type: "object" },
2722
- is_global: { type: "boolean" },
2723
- completed_at: { type: "string" },
2724
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON of the updated memory." }
2725
- },
2726
- required: ["id"]
2727
- },
2728
- outputSchema: {
2729
- type: "object",
2730
- properties: {
2731
- success: { type: "boolean" },
2732
- id: { type: "string" },
2733
- repo: { type: "string" },
2734
- updatedFields: {
2735
- type: "array",
2736
- items: { type: "string" }
2737
- }
2738
- },
2739
- required: ["success", "id", "repo", "updatedFields"]
2740
- }
2741
- },
2742
- {
2743
- name: "memory-search",
2744
- title: "Memory Search",
2745
- description: "NAVIGATION LAYER: Returns a pointer table of matching memory IDs only. Returns columns [id, title, type, importance] \u2014 NO content. Retrieve full memory via memory-detail. Use 'current_tags' to find tech-stack specific knowledge from other projects.",
2746
- annotations: {
2747
- readOnlyHint: true,
2748
- idempotentHint: true,
2749
- openWorldHint: false
2750
- },
2751
- inputSchema: {
2752
- type: "object",
2753
- properties: {
2754
- query: { type: "string", minLength: 3 },
2755
- prompt: { type: "string" },
2756
- repo: { type: "string" },
2757
- current_tags: {
2758
- type: "array",
2759
- items: { type: "string" },
2760
- description: "Active tech stack tags (e.g., ['filament', 'react'])"
2761
- },
2762
- types: {
2763
- type: "array",
2764
- items: {
2765
- type: "string",
2766
- enum: [
2767
- "code_fact",
2768
- "decision",
2769
- "mistake",
2770
- "pattern",
2771
- "agent_handoff",
2772
- "agent_registered",
2773
- "file_claim",
2774
- "task_archive"
2775
- ]
2390
+ }
2391
+ throw invalidCompletionParams(`Unknown resource template or argument: ${resourceUri} (${argumentName})`);
2392
+ }
2393
+ function readResource(uri, db, session) {
2394
+ logger.info("[Tool] resource.read", { uri });
2395
+ if (uri === "repository://index") {
2396
+ const repos = db.system.listRepoNavigation();
2397
+ const payload = JSON.stringify(repos, null, 2);
2398
+ return {
2399
+ contents: [
2400
+ {
2401
+ uri,
2402
+ mimeType: "application/json",
2403
+ text: payload,
2404
+ size: Buffer.byteLength(payload, "utf8"),
2405
+ annotations: {
2406
+ audience: ["assistant"],
2407
+ priority: 1,
2408
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
2776
2409
  }
2777
- },
2778
- minImportance: { type: "number", minimum: 1, maximum: 5 },
2779
- limit: { type: "number", minimum: 1, maximum: 100, default: 5 },
2780
- offset: { type: "number", minimum: 0, default: 0 },
2781
- includeRecap: { type: "boolean", default: false },
2782
- current_file_path: { type: "string" },
2783
- include_archived: { type: "boolean", default: false },
2784
- scope: {
2785
- type: "object",
2786
- properties: {
2787
- repo: { type: "string" },
2788
- branch: { type: "string" },
2789
- folder: { type: "string" },
2790
- language: { type: "string" }
2410
+ }
2411
+ ]
2412
+ };
2413
+ }
2414
+ if (uri === "session://roots") {
2415
+ const payload = JSON.stringify({ roots: session?.roots ?? [] }, null, 2);
2416
+ return {
2417
+ contents: [
2418
+ {
2419
+ uri,
2420
+ mimeType: "application/json",
2421
+ text: payload,
2422
+ size: Buffer.byteLength(payload, "utf8"),
2423
+ annotations: {
2424
+ audience: ["assistant"],
2425
+ priority: 0.95,
2426
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
2791
2427
  }
2792
- },
2793
- structured: {
2794
- type: "boolean",
2795
- default: false,
2796
- description: "If true, returns structured JSON without the text content summary."
2797
2428
  }
2798
- },
2799
- required: ["query", "repo"]
2800
- },
2801
- outputSchema: {
2802
- type: "object",
2803
- properties: {
2804
- schema: { type: "string", enum: ["memory-search"] },
2805
- query: { type: "string" },
2806
- count: { type: "number", description: "Number of rows returned" },
2807
- total: { type: "number", description: "Total matching memories" },
2808
- offset: { type: "number" },
2809
- limit: { type: "number" },
2810
- results: {
2811
- type: "object",
2812
- properties: {
2813
- columns: {
2814
- type: "array",
2815
- items: { type: "string" },
2816
- description: "Column names: [id, title, type, importance]"
2817
- },
2818
- rows: {
2819
- type: "array",
2820
- items: { type: "array" },
2821
- description: "Each row: [id, title, type, importance]. Fetch full content via memory-detail"
2822
- }
2823
- },
2824
- required: ["columns", "rows"]
2429
+ ]
2430
+ };
2431
+ }
2432
+ const memoryIdMatch = uri.match(/^memory:\/\/([0-9a-f-]{36})$/i);
2433
+ if (memoryIdMatch) {
2434
+ const id = memoryIdMatch[1];
2435
+ const entry = db.memories.getByIdWithStats(id);
2436
+ if (!entry) throw resourceNotFound(`Memory with ID ${id} not found.`, uri);
2437
+ const payload = JSON.stringify(entry, null, 2);
2438
+ return {
2439
+ contents: [
2440
+ {
2441
+ uri,
2442
+ mimeType: "application/json",
2443
+ text: payload,
2444
+ size: Buffer.byteLength(payload, "utf8"),
2445
+ annotations: {
2446
+ audience: ["assistant"],
2447
+ priority: 0.75,
2448
+ lastModified: entry.updated_at || entry.created_at
2449
+ }
2825
2450
  }
2826
- },
2827
- required: ["schema", "query", "count", "total", "offset", "limit", "results"]
2451
+ ]
2452
+ };
2453
+ }
2454
+ const taskIdMatch = uri.match(/^task:\/\/([0-9a-f-]{36})$/i);
2455
+ if (taskIdMatch) {
2456
+ const id = taskIdMatch[1];
2457
+ const task = db.tasks.getTaskById(id);
2458
+ if (!task) throw resourceNotFound(`Task with ID ${id} not found.`, uri);
2459
+ const payload = JSON.stringify(task, null, 2);
2460
+ return {
2461
+ contents: [
2462
+ {
2463
+ uri,
2464
+ mimeType: "application/json",
2465
+ text: payload,
2466
+ size: Buffer.byteLength(payload, "utf8"),
2467
+ annotations: {
2468
+ audience: ["assistant"],
2469
+ priority: 0.8,
2470
+ lastModified: task.updated_at || task.created_at
2471
+ }
2472
+ }
2473
+ ]
2474
+ };
2475
+ }
2476
+ const repoBase = parseRepoUri(uri);
2477
+ if (repoBase) {
2478
+ const { name, path: repoPath, query } = repoBase;
2479
+ if (repoPath === "summary") {
2480
+ const summary = db.summaries.getSummary(name);
2481
+ const text = summary?.summary || `No summary available for repository: ${name}`;
2482
+ return {
2483
+ contents: [
2484
+ {
2485
+ uri,
2486
+ mimeType: "text/plain",
2487
+ text,
2488
+ size: Buffer.byteLength(text, "utf8"),
2489
+ annotations: {
2490
+ audience: ["assistant"],
2491
+ priority: 0.95,
2492
+ lastModified: summary?.updated_at || (/* @__PURE__ */ new Date()).toISOString()
2493
+ }
2494
+ }
2495
+ ]
2496
+ };
2828
2497
  }
2829
- },
2830
- {
2831
- name: "memory-summarize",
2832
- title: "Memory Summarize",
2833
- description: "Update the summary for a repository",
2834
- annotations: {
2835
- readOnlyHint: false,
2836
- idempotentHint: false,
2837
- openWorldHint: false
2838
- },
2839
- inputSchema: {
2840
- type: "object",
2841
- properties: {
2842
- repo: { type: "string", description: "Repository name" },
2843
- signals: {
2844
- type: "array",
2845
- items: { type: "string", maxLength: 200 },
2846
- minItems: 1,
2847
- description: "High-level signals to include in summary"
2848
- },
2849
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON of the summary." }
2850
- },
2851
- required: ["repo", "signals"]
2852
- },
2853
- outputSchema: {
2854
- type: "object",
2855
- properties: {
2856
- success: { type: "boolean" },
2857
- repo: { type: "string" },
2858
- summary: { type: "string" },
2859
- signalCount: { type: "number" }
2860
- },
2861
- required: ["success", "repo", "summary", "signalCount"]
2498
+ if (repoPath === "memories") {
2499
+ const search = query.get("search") || "";
2500
+ const type = query.get("type");
2501
+ const tag = query.get("tag");
2502
+ const result = db.memories.listMemoriesForDashboard({
2503
+ repo: name,
2504
+ type: type || void 0,
2505
+ tag: tag || void 0,
2506
+ search: search || void 0,
2507
+ limit: 50
2508
+ });
2509
+ const entries = result.items;
2510
+ const payload = JSON.stringify(entries, null, 2);
2511
+ return {
2512
+ contents: [
2513
+ {
2514
+ uri,
2515
+ mimeType: "application/json",
2516
+ text: payload,
2517
+ size: Buffer.byteLength(payload, "utf8"),
2518
+ annotations: {
2519
+ audience: ["assistant"],
2520
+ priority: 0.85,
2521
+ lastModified: deriveLastModifiedFromCollection(
2522
+ entries.map((e) => e.updated_at || e.created_at)
2523
+ )
2524
+ }
2525
+ }
2526
+ ]
2527
+ };
2862
2528
  }
2863
- },
2864
- {
2865
- name: "memory-delete",
2866
- title: "Memory Delete",
2867
- description: "Soft-delete one or more memory entries. Supports single 'id' or bulk 'ids'.",
2868
- annotations: {
2869
- readOnlyHint: false,
2870
- idempotentHint: false,
2871
- destructiveHint: true,
2872
- openWorldHint: false
2873
- },
2874
- inputSchema: {
2875
- type: "object",
2876
- properties: {
2877
- repo: { type: "string", description: "Repository name (optional for single id)" },
2878
- id: { type: "string", format: "uuid", description: "Memory entry ID to delete" },
2879
- ids: {
2880
- type: "array",
2881
- items: { type: "string", format: "uuid" },
2882
- minItems: 1,
2883
- description: "Array of memory IDs to delete"
2884
- },
2885
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
2529
+ if (repoPath === "tasks") {
2530
+ const status = query.get("status");
2531
+ const priority = query.get("priority");
2532
+ let tasks;
2533
+ if (status && status !== "all") {
2534
+ const statuses = status.split(",").map((s) => s.trim());
2535
+ tasks = db.tasks.getTasksByMultipleStatuses(name, statuses);
2536
+ } else {
2537
+ tasks = db.tasks.getTasksByMultipleStatuses(name, ["backlog", "pending", "in_progress", "blocked"]);
2886
2538
  }
2887
- },
2888
- outputSchema: {
2889
- type: "object",
2890
- properties: {
2891
- success: { type: "boolean" },
2892
- id: { type: "string" },
2893
- ids: { type: "array", items: { type: "string" } },
2894
- repo: { type: "string" },
2895
- deletedCount: { type: "number" }
2896
- },
2897
- required: ["success"]
2539
+ if (priority) {
2540
+ const p = Number(priority);
2541
+ if (!isNaN(p)) {
2542
+ tasks = tasks.filter((t) => t.priority === p);
2543
+ }
2544
+ }
2545
+ const payload = JSON.stringify(tasks, null, 2);
2546
+ return {
2547
+ contents: [
2548
+ {
2549
+ uri,
2550
+ mimeType: "application/json",
2551
+ text: payload,
2552
+ size: Buffer.byteLength(payload, "utf8"),
2553
+ annotations: {
2554
+ audience: ["assistant"],
2555
+ priority: 0.9,
2556
+ lastModified: deriveLastModifiedFromCollection(tasks.map((t) => t.updated_at))
2557
+ }
2558
+ }
2559
+ ]
2560
+ };
2898
2561
  }
2899
- },
2562
+ if (repoPath === "actions") {
2563
+ const actions = db.actions.getRecentActions(name, 100);
2564
+ const payload = JSON.stringify(actions, null, 2);
2565
+ return {
2566
+ contents: [
2567
+ {
2568
+ uri,
2569
+ mimeType: "application/json",
2570
+ text: payload,
2571
+ size: Buffer.byteLength(payload, "utf8"),
2572
+ annotations: {
2573
+ audience: ["assistant"],
2574
+ priority: 0.6,
2575
+ lastModified: deriveLastModifiedFromCollection(actions.map((a) => a.created_at))
2576
+ }
2577
+ }
2578
+ ]
2579
+ };
2580
+ }
2581
+ }
2582
+ const actionIdMatch = uri.match(/^action:\/\/(\d+)$/);
2583
+ if (actionIdMatch) {
2584
+ const id = Number(actionIdMatch[1]);
2585
+ const action = db.actions.getActionById(id);
2586
+ if (!action) throw resourceNotFound(`Action with ID ${id} not found.`, uri);
2587
+ const payload = JSON.stringify(action, null, 2);
2588
+ return {
2589
+ contents: [
2590
+ {
2591
+ uri,
2592
+ mimeType: "application/json",
2593
+ text: payload,
2594
+ size: Buffer.byteLength(payload, "utf8"),
2595
+ annotations: {
2596
+ audience: ["assistant"],
2597
+ priority: 0.55,
2598
+ lastModified: action.created_at
2599
+ }
2600
+ }
2601
+ ]
2602
+ };
2603
+ }
2604
+ throw resourceNotFound(`Unknown resource URI: ${uri}`, uri);
2605
+ }
2606
+ function parseRepoUri(uri) {
2607
+ const prefix = "repository://";
2608
+ if (!uri.startsWith(prefix)) return null;
2609
+ const rest = uri.slice(prefix.length);
2610
+ const queryStart = rest.indexOf("?");
2611
+ const withoutQuery = queryStart === -1 ? rest : rest.slice(0, queryStart);
2612
+ const queryString = queryStart === -1 ? "" : rest.slice(queryStart + 1);
2613
+ const slashIdx = withoutQuery.indexOf("/");
2614
+ if (slashIdx === -1) return null;
2615
+ const name = withoutQuery.slice(0, slashIdx);
2616
+ const path6 = withoutQuery.slice(slashIdx + 1);
2617
+ if (!name || !path6) return null;
2618
+ return { name, path: path6, query: new URLSearchParams(queryString) };
2619
+ }
2620
+ function paginateEntries(key, entries, params) {
2621
+ const limit = normalizeLimit(params?.limit);
2622
+ const offset = decodeCursor(params?.cursor);
2623
+ const sliced = entries.slice(offset, offset + limit);
2624
+ const nextOffset = offset + sliced.length;
2625
+ return {
2626
+ [key]: sliced,
2627
+ nextCursor: nextOffset < entries.length ? encodeCursor(nextOffset) : void 0
2628
+ };
2629
+ }
2630
+ function normalizeLimit(limit) {
2631
+ if (typeof limit !== "number" || !Number.isFinite(limit)) {
2632
+ return DEFAULT_PAGE_SIZE;
2633
+ }
2634
+ return Math.min(MAX_PAGE_SIZE, Math.max(1, Math.trunc(limit)));
2635
+ }
2636
+ function deriveLastModifiedFromCollection(values) {
2637
+ const normalized = values.filter((value) => typeof value === "string" && value.length > 0);
2638
+ return normalized.sort().at(-1) ?? (/* @__PURE__ */ new Date()).toISOString();
2639
+ }
2640
+ function resourceNotFound(message, uri) {
2641
+ const error = new Error(message);
2642
+ error.code = -32002;
2643
+ error.data = { uri };
2644
+ return error;
2645
+ }
2646
+ function invalidCompletionParams(message) {
2647
+ const error = new Error(message);
2648
+ error.code = -32602;
2649
+ return error;
2650
+ }
2651
+
2652
+ // src/mcp/prompts/loader.ts
2653
+ import fs5 from "fs";
2654
+ import path5 from "path";
2655
+ import { fileURLToPath as fileURLToPath3 } from "url";
2656
+ import matter from "gray-matter";
2657
+ var __filename = fileURLToPath3(import.meta.url);
2658
+ var __dirname2 = path5.dirname(__filename);
2659
+ function findPromptDir() {
2660
+ const candidates = [
2661
+ // Production if chunked into dist/
2662
+ "./prompts",
2663
+ // Production if inlined into dist/mcp/
2664
+ "../prompts",
2665
+ // Dev: /src/mcp/prompts/definitions (next to loader.ts)
2666
+ "./definitions"
2667
+ ].map((relPath) => path5.resolve(__dirname2, relPath));
2668
+ for (const dir of candidates) {
2669
+ if (fs5.existsSync(dir)) {
2670
+ const files = fs5.readdirSync(dir);
2671
+ if (files.some((f) => f.endsWith(".md"))) {
2672
+ return dir;
2673
+ }
2674
+ }
2675
+ }
2676
+ return path5.resolve(__dirname2, "./definitions");
2677
+ }
2678
+ var PROMPT_DIR = findPromptDir();
2679
+ function listPromptFiles() {
2680
+ if (!fs5.existsSync(PROMPT_DIR)) return [];
2681
+ return fs5.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
2682
+ }
2683
+ function loadPromptFromMarkdown(name) {
2684
+ const filePath = path5.join(PROMPT_DIR, `${name}.md`);
2685
+ if (!fs5.existsSync(filePath)) {
2686
+ throw new Error(`Prompt file not found: ${filePath}`);
2687
+ }
2688
+ const fileContent = fs5.readFileSync(filePath, "utf-8");
2689
+ const { data, content } = matter(fileContent);
2690
+ return {
2691
+ name: data.name || name,
2692
+ description: data.description || "",
2693
+ arguments: data.arguments || [],
2694
+ agent: data.agent,
2695
+ content: content.trim()
2696
+ };
2697
+ }
2698
+
2699
+ // src/mcp/prompts/registry.ts
2700
+ function createPromptDefinition(loaded) {
2701
+ return {
2702
+ name: loaded.name,
2703
+ description: loaded.description,
2704
+ arguments: loaded.arguments,
2705
+ agent: loaded.agent,
2706
+ messages: [
2707
+ {
2708
+ role: "user",
2709
+ content: {
2710
+ type: "text",
2711
+ text: loaded.content
2712
+ }
2713
+ }
2714
+ ]
2715
+ };
2716
+ }
2717
+ var PROMPTS = {};
2718
+ var promptFiles = listPromptFiles();
2719
+ for (const name of promptFiles) {
2720
+ try {
2721
+ PROMPTS[name] = createPromptDefinition(loadPromptFromMarkdown(name));
2722
+ } catch (e) {
2723
+ logger.warn(`Failed to load prompt ${name}: ${e}`);
2724
+ }
2725
+ }
2726
+ async function listPrompts(db, session, params) {
2727
+ const allPrompts = Object.values(PROMPTS).map((p) => ({
2728
+ name: p.name,
2729
+ description: p.description,
2730
+ arguments: p.arguments,
2731
+ metadata: p.agent ? { agent: p.agent } : void 0
2732
+ }));
2733
+ const rawLimit = typeof params?.limit === "number" && Number.isInteger(params?.limit) ? params.limit : 25;
2734
+ const limit = Math.max(1, Math.min(100, Math.trunc(rawLimit)));
2735
+ const offset = decodeCursor(params?.cursor);
2736
+ const sliced = allPrompts.slice(offset, offset + limit);
2737
+ const nextOffset = offset + sliced.length;
2738
+ return {
2739
+ prompts: sliced,
2740
+ nextCursor: nextOffset < allPrompts.length ? encodeCursor(nextOffset) : void 0
2741
+ };
2742
+ }
2743
+ async function getPrompt(name, args = {}, db, session) {
2744
+ const prompt = PROMPTS[name];
2745
+ if (!prompt) {
2746
+ throw new Error(`Prompt not found: ${name}`);
2747
+ }
2748
+ const inferredRepo = inferRepoFromSession(session);
2749
+ const messages = prompt.messages.map((m) => {
2750
+ let text = m.content.text;
2751
+ for (const [key, value] of Object.entries(args)) {
2752
+ text = text.replace(new RegExp(`\\{{${key}\\}}`, "g"), value);
2753
+ }
2754
+ text = text.replace(/{{current_repo}}/g, inferredRepo || "unknown-repo");
2755
+ return {
2756
+ ...m,
2757
+ content: {
2758
+ ...m.content,
2759
+ text
2760
+ }
2761
+ };
2762
+ });
2763
+ return {
2764
+ description: prompt.description,
2765
+ messages,
2766
+ metadata: prompt.agent ? { agent: prompt.agent } : void 0
2767
+ };
2768
+ }
2769
+ async function completePromptArgument(name, argName, value, contextArguments, dataSources) {
2770
+ void name;
2771
+ void contextArguments;
2772
+ if (argName === "task_id") {
2773
+ const values = dataSources.tasks.map((t) => t.id);
2774
+ return rankCompletionValues(values, value);
2775
+ }
2776
+ return [];
2777
+ }
2778
+
2779
+ // src/mcp/tools/schemas.ts
2780
+ import { z } from "zod";
2781
+ var MemoryScopeSchema = z.object({
2782
+ repo: z.string().min(1).transform(normalizeRepo),
2783
+ branch: z.string().optional(),
2784
+ folder: z.string().optional(),
2785
+ language: z.string().optional()
2786
+ });
2787
+ var MemoryTypeSchema = z.enum([
2788
+ "code_fact",
2789
+ "decision",
2790
+ "mistake",
2791
+ "pattern",
2792
+ "agent_handoff",
2793
+ "agent_registered",
2794
+ "file_claim",
2795
+ "task_archive"
2796
+ ]);
2797
+ var MemoryStoreSchema = z.object({
2798
+ code: z.string().max(20).optional(),
2799
+ type: MemoryTypeSchema,
2800
+ title: z.string().min(3).max(255),
2801
+ content: z.string().min(10),
2802
+ importance: z.number().min(1).max(5),
2803
+ agent: z.string().min(1),
2804
+ role: z.string().optional().default("unknown"),
2805
+ model: z.string().min(1),
2806
+ scope: MemoryScopeSchema,
2807
+ ttlDays: z.number().min(1).optional(),
2808
+ supersedes: z.string().uuid().optional(),
2809
+ tags: z.array(z.string()).optional(),
2810
+ metadata: z.record(z.string(), z.any()).optional(),
2811
+ is_global: z.boolean().default(false),
2812
+ structured: z.boolean().default(false)
2813
+ });
2814
+ var MemoryUpdateSchema = z.object({
2815
+ id: z.string().uuid(),
2816
+ type: MemoryTypeSchema.optional(),
2817
+ title: z.string().min(3).max(255).optional(),
2818
+ content: z.string().min(10).optional(),
2819
+ importance: z.number().min(1).max(5).optional(),
2820
+ agent: z.string().optional(),
2821
+ role: z.string().optional(),
2822
+ status: z.enum(["active", "archived"]).optional(),
2823
+ supersedes: z.string().uuid().optional(),
2824
+ tags: z.array(z.string()).optional(),
2825
+ metadata: z.record(z.string(), z.any()).optional(),
2826
+ is_global: z.boolean().optional(),
2827
+ completed_at: z.string().optional(),
2828
+ structured: z.boolean().default(false)
2829
+ }).refine(
2830
+ (data) => data.type !== void 0 || data.content !== void 0 || data.title !== void 0 || data.importance !== void 0 || data.status !== void 0 || data.supersedes !== void 0 || data.tags !== void 0 || data.metadata !== void 0 || data.is_global !== void 0 || data.agent !== void 0 || data.role !== void 0 || data.completed_at !== void 0,
2831
+ { message: "At least one field must be provided for update" }
2832
+ );
2833
+ var MemorySearchSchema = z.object({
2834
+ query: z.string().min(3),
2835
+ prompt: z.string().optional(),
2836
+ repo: z.string().min(1).transform(normalizeRepo),
2837
+ types: z.array(MemoryTypeSchema).optional(),
2838
+ minImportance: z.number().min(1).max(5).optional(),
2839
+ limit: z.number().min(1).max(100).default(5),
2840
+ offset: z.number().min(0).default(0),
2841
+ includeRecap: z.boolean().default(false),
2842
+ current_file_path: z.string().optional(),
2843
+ include_archived: z.boolean().default(false),
2844
+ current_tags: z.array(z.string()).optional(),
2845
+ scope: MemoryScopeSchema.partial().optional(),
2846
+ structured: z.boolean().default(false)
2847
+ });
2848
+ var MemoryAcknowledgeSchema = z.object({
2849
+ memory_id: z.string().uuid(),
2850
+ status: z.enum(["used", "irrelevant", "contradictory"]),
2851
+ application_context: z.string().min(10).optional(),
2852
+ structured: z.boolean().default(false)
2853
+ });
2854
+ var MemoryRecapSchema = z.object({
2855
+ repo: z.string().min(1).transform(normalizeRepo),
2856
+ limit: z.number().min(1).max(50).default(20),
2857
+ offset: z.number().min(0).default(0),
2858
+ structured: z.boolean().default(false)
2859
+ });
2860
+ var MemoryDeleteSchema = z.object({
2861
+ repo: z.string().min(1).transform(normalizeRepo).optional(),
2862
+ id: z.string().uuid().optional(),
2863
+ ids: z.array(z.string().uuid()).min(1).optional(),
2864
+ structured: z.boolean().default(false)
2865
+ }).refine((data) => data.id !== void 0 || data.ids !== void 0, {
2866
+ message: "Either 'id' or 'ids' must be provided for deletion"
2867
+ });
2868
+ var MemorySummarizeSchema = z.object({
2869
+ repo: z.string().min(1).transform(normalizeRepo),
2870
+ signals: z.array(z.string().max(200)).min(1),
2871
+ structured: z.boolean().default(false)
2872
+ });
2873
+ var MemorySynthesizeSchema = z.object({
2874
+ repo: z.string().min(1).transform(normalizeRepo).optional(),
2875
+ objective: z.string().min(5),
2876
+ current_file_path: z.string().optional(),
2877
+ include_summary: z.boolean().default(true),
2878
+ include_tasks: z.boolean().default(true),
2879
+ use_tools: z.boolean().default(true),
2880
+ max_iterations: z.number().int().min(1).max(5).default(3),
2881
+ max_tokens: z.number().int().min(128).max(4e3).default(1200),
2882
+ structured: z.boolean().default(false)
2883
+ });
2884
+ var TaskStatusSchema = z.enum(["backlog", "pending", "in_progress", "completed", "canceled", "blocked"]);
2885
+ var TaskPrioritySchema = z.number().min(1).max(5);
2886
+ var SingleTaskCreateSchema = z.object({
2887
+ task_code: z.string().min(1),
2888
+ phase: z.string().min(1),
2889
+ title: z.string().min(3).max(100),
2890
+ description: z.string().min(1),
2891
+ status: TaskStatusSchema.default("backlog"),
2892
+ priority: TaskPrioritySchema.default(3),
2893
+ agent: z.string().optional(),
2894
+ role: z.string().optional(),
2895
+ doc_path: z.string().optional(),
2896
+ tags: z.array(z.string()).optional(),
2897
+ metadata: z.record(z.string(), z.any()).optional(),
2898
+ parent_id: z.string().uuid().optional(),
2899
+ depends_on: z.string().uuid().optional(),
2900
+ est_tokens: z.number().int().min(0).optional()
2901
+ });
2902
+ var TaskCreateSchema = z.object({
2903
+ repo: z.string().min(1).transform(normalizeRepo),
2904
+ // Allow single task fields at top level (backward compatibility & single use)
2905
+ task_code: z.string().min(1).optional(),
2906
+ phase: z.string().min(1).optional(),
2907
+ title: z.string().min(3).max(100).optional(),
2908
+ description: z.string().min(1).optional(),
2909
+ status: TaskStatusSchema.optional(),
2910
+ priority: TaskPrioritySchema.optional(),
2911
+ agent: z.string().optional(),
2912
+ role: z.string().optional(),
2913
+ doc_path: z.string().optional(),
2914
+ tags: z.array(z.string()).optional(),
2915
+ metadata: z.record(z.string(), z.any()).optional(),
2916
+ parent_id: z.string().uuid().optional(),
2917
+ depends_on: z.string().uuid().optional(),
2918
+ est_tokens: z.number().int().min(0).optional(),
2919
+ // Allow bulk tasks
2920
+ tasks: z.array(SingleTaskCreateSchema).min(1).optional(),
2921
+ structured: z.boolean().default(false)
2922
+ }).refine(
2923
+ (data) => {
2924
+ if (data.tasks) return true;
2925
+ return !!(data.task_code && data.phase && data.title && data.description);
2926
+ },
2927
+ { message: "Either 'tasks' array or single task fields (task_code, phase, title, description) must be provided" }
2928
+ );
2929
+ var TaskCreateInteractiveSchema = SingleTaskCreateSchema.partial().extend({
2930
+ repo: z.string().min(1).transform(normalizeRepo).optional(),
2931
+ structured: z.boolean().default(false)
2932
+ });
2933
+ var TaskUpdateSchema = z.object({
2934
+ repo: z.string().min(1).transform(normalizeRepo),
2935
+ id: z.string().uuid().optional(),
2936
+ ids: z.array(z.string().uuid()).min(1).optional(),
2937
+ task_code: z.string().optional(),
2938
+ phase: z.string().optional(),
2939
+ title: z.string().min(3).max(100).optional(),
2940
+ description: z.string().optional(),
2941
+ status: TaskStatusSchema.optional(),
2942
+ priority: TaskPrioritySchema.optional(),
2943
+ agent: z.string().min(1, "agent name is required").optional(),
2944
+ role: z.string().min(1, "agent role is required").optional(),
2945
+ model: z.string().optional(),
2946
+ comment: z.string().min(1).optional(),
2947
+ doc_path: z.string().optional(),
2948
+ tags: z.array(z.string()).optional(),
2949
+ metadata: z.record(z.string(), z.any()).optional(),
2950
+ parent_id: z.string().uuid().optional(),
2951
+ depends_on: z.string().uuid().optional(),
2952
+ est_tokens: z.number().int().min(0).optional(),
2953
+ force: z.boolean().optional(),
2954
+ structured: z.boolean().default(false)
2955
+ }).refine((data) => data.id !== void 0 || data.ids !== void 0, {
2956
+ message: "Either 'id' or 'ids' must be provided for update"
2957
+ }).refine((data) => Object.keys(data).length > 2, {
2958
+ message: "At least one field besides repo and id/ids must be provided for update"
2959
+ });
2960
+ var TaskListSchema = z.object({
2961
+ repo: z.string().min(1).transform(normalizeRepo),
2962
+ status: z.string().optional(),
2963
+ phase: z.string().optional(),
2964
+ query: z.string().optional(),
2965
+ limit: z.number().min(1).max(100).default(15),
2966
+ offset: z.number().min(0).default(0),
2967
+ structured: z.boolean().default(false)
2968
+ });
2969
+ var TaskSearchSchema = z.object({
2970
+ repo: z.string().min(1).transform(normalizeRepo),
2971
+ query: z.string().min(1),
2972
+ status: z.string().optional(),
2973
+ limit: z.number().min(1).max(100).default(10),
2974
+ offset: z.number().min(0).default(0),
2975
+ structured: z.boolean().default(false)
2976
+ });
2977
+ var TaskDeleteSchema = z.object({
2978
+ repo: z.string().min(1).transform(normalizeRepo),
2979
+ id: z.string().uuid().optional(),
2980
+ ids: z.array(z.string().uuid()).min(1).optional(),
2981
+ structured: z.boolean().default(false)
2982
+ }).refine((data) => data.id !== void 0 || data.ids !== void 0, {
2983
+ message: "Either 'id' or 'ids' must be provided for deletion"
2984
+ });
2985
+ var MemoryDetailSchema = z.object({
2986
+ id: z.string().uuid().optional(),
2987
+ code: z.string().max(20).optional(),
2988
+ structured: z.boolean().default(false)
2989
+ }).refine((data) => data.id !== void 0 || data.code !== void 0, {
2990
+ message: "Either id or code must be provided"
2991
+ });
2992
+ var TaskGetSchema = z.object({
2993
+ repo: z.string().min(1).transform(normalizeRepo),
2994
+ id: z.string().uuid().optional(),
2995
+ task_code: z.string().optional(),
2996
+ structured: z.boolean().default(false)
2997
+ }).refine((data) => data.id !== void 0 || data.task_code !== void 0, {
2998
+ message: "Either id or task_code must be provided"
2999
+ });
3000
+ var TOOL_DEFINITIONS = [
2900
3001
  {
2901
- name: "memory-recap",
2902
- title: "Memory Recap",
2903
- description: "AGGREGATED OVERVIEW LAYER: Returns stats (counts by type) and a pointer table of top memories [id, title, type, importance]. NO content. Use for orientation only \u2014 retrieve full memory via memory-detail.",
3002
+ name: "memory-synthesize",
3003
+ title: "Memory Synthesize",
3004
+ description: "Use client sampling to synthesize a grounded answer from local memory and tasks. Best for project briefings, tradeoff summaries, and context-aware answers.",
2904
3005
  annotations: {
2905
3006
  readOnlyHint: true,
2906
3007
  idempotentHint: true,
2907
3008
  openWorldHint: false
2908
3009
  },
3010
+ execution: {
3011
+ taskSupport: "optional"
3012
+ },
2909
3013
  inputSchema: {
2910
3014
  type: "object",
2911
3015
  properties: {
2912
- repo: { type: "string", description: "Repository name (required)" },
2913
- limit: {
2914
- type: "number",
2915
- minimum: 1,
2916
- maximum: 50,
2917
- default: 20,
2918
- description: "Maximum number of top memories to return in the pointer table"
2919
- },
2920
- offset: {
2921
- type: "number",
2922
- minimum: 0,
2923
- default: 0,
2924
- description: "Number of memories to skip for pagination (optional, default 0)"
3016
+ repo: { type: "string", description: "Repository name. Optional when a single MCP root is active." },
3017
+ objective: { type: "string", minLength: 5, description: "Question or synthesis objective." },
3018
+ current_file_path: {
3019
+ type: "string",
3020
+ description: "Optional absolute file path for workspace-local grounding."
2925
3021
  },
2926
- structured: {
3022
+ include_summary: { type: "boolean", default: true },
3023
+ include_tasks: { type: "boolean", default: true },
3024
+ use_tools: {
2927
3025
  type: "boolean",
2928
- default: false,
2929
- description: "If true, returns structured JSON without the text content summary."
2930
- }
3026
+ default: true,
3027
+ description: "Allow the sampled model to call local memory/task tools during synthesis when the client supports sampling.tools."
3028
+ },
3029
+ max_iterations: { type: "number", minimum: 1, maximum: 5, default: 3 },
3030
+ max_tokens: { type: "number", minimum: 128, maximum: 4e3, default: 1200 },
3031
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON results." }
2931
3032
  },
2932
- required: ["repo"]
3033
+ required: ["objective"]
2933
3034
  },
2934
3035
  outputSchema: {
2935
3036
  type: "object",
2936
3037
  properties: {
2937
- schema: { type: "string", enum: ["memory-recap"] },
2938
3038
  repo: { type: "string" },
2939
- count: { type: "number", description: "Number of rows in the top pointer table" },
2940
- total: { type: "number", description: "Total active memories in repo" },
2941
- offset: { type: "number" },
2942
- limit: { type: "number" },
2943
- stats: {
2944
- type: "object",
2945
- properties: {
2946
- by_type: {
2947
- type: "object",
2948
- description: "Count of active memories per type (e.g. { decision: 3, code_fact: 7 })"
2949
- }
2950
- },
2951
- required: ["by_type"]
2952
- },
2953
- top: {
2954
- type: "object",
2955
- properties: {
2956
- columns: {
2957
- type: "array",
2958
- items: { type: "string" },
2959
- description: "Column names: [id, title, type, importance]"
2960
- },
2961
- rows: {
2962
- type: "array",
2963
- items: { type: "array" },
2964
- description: "Each row: [id, title, type, importance]. Fetch full content via memory-detail"
2965
- }
2966
- },
2967
- required: ["columns", "rows"]
2968
- }
3039
+ objective: { type: "string" },
3040
+ answer: { type: "string" },
3041
+ model: { type: "string" },
3042
+ stopReason: { type: "string" },
3043
+ iterations: { type: "number" },
3044
+ toolCalls: { type: "number" }
2969
3045
  },
2970
- required: ["schema", "repo", "count", "total", "offset", "limit", "stats", "top"]
3046
+ required: ["repo", "objective", "answer", "iterations", "toolCalls"]
2971
3047
  }
2972
3048
  },
2973
3049
  {
2974
- name: "task-create",
2975
- title: "Task Create",
2976
- description: "Register one or more new tasks in a repository. task_code must be unique within the repository. Supports single task object or an array of tasks for bulk creation.",
3050
+ name: "task-create-interactive",
3051
+ title: "Interactive Task Create",
3052
+ description: "Create a task with MCP elicitation fallback for any missing required fields. Best when an agent knows a task is needed but still needs user confirmation for repo, title, or phase.",
2977
3053
  annotations: {
2978
3054
  readOnlyHint: false,
2979
3055
  idempotentHint: false,
3056
+ destructiveHint: false,
2980
3057
  openWorldHint: false
2981
3058
  },
2982
3059
  inputSchema: {
2983
3060
  type: "object",
2984
3061
  properties: {
2985
- repo: { type: "string", description: "Repository name" },
2986
- task_code: { type: "string", description: "Unique task code (e.g. TASK-001) (Required for single task)" },
2987
- phase: { type: "string", description: "Project phase (Required for single task)" },
2988
- title: {
2989
- type: "string",
2990
- minLength: 3,
2991
- maxLength: 100,
2992
- description: "Task objective (Required for single task)"
2993
- },
2994
- description: { type: "string", description: "Detailed description (Required for single task)" },
2995
- status: {
3062
+ repo: {
2996
3063
  type: "string",
2997
- enum: ["backlog", "pending"],
2998
- default: "backlog",
2999
- description: "New tasks MUST start in 'backlog' if there are already 10 pending tasks. Otherwise can start in 'pending'."
3064
+ description: "Repository name. Optional when it can be inferred from MCP roots or elicited from the user."
3000
3065
  },
3066
+ task_code: { type: "string" },
3067
+ phase: { type: "string" },
3068
+ title: { type: "string", minLength: 3, maxLength: 100 },
3069
+ description: { type: "string", minLength: 1 },
3070
+ status: { type: "string", enum: ["backlog", "pending"], default: "backlog" },
3001
3071
  priority: { type: "number", minimum: 1, maximum: 5, default: 3 },
3002
3072
  agent: { type: "string" },
3003
3073
  role: { type: "string" },
3004
3074
  doc_path: { type: "string" },
3005
- tags: { type: "array", items: { type: "string" } },
3006
- metadata: { type: "object" },
3007
- parent_id: { type: "string", format: "uuid" },
3008
- depends_on: { type: "string", format: "uuid" },
3009
- est_tokens: { type: "number", minimum: 0, description: "Estimated tokens budget for this task" },
3010
- tasks: {
3011
- type: "array",
3012
- items: {
3013
- type: "object",
3014
- properties: {
3015
- task_code: { type: "string" },
3016
- phase: { type: "string" },
3017
- title: { type: "string", minLength: 3, maxLength: 100 },
3018
- description: { type: "string" },
3019
- status: { type: "string", enum: ["backlog", "pending"], default: "backlog" },
3020
- priority: { type: "number", minimum: 1, maximum: 5, default: 3 },
3021
- agent: { type: "string" },
3022
- role: { type: "string" },
3023
- doc_path: { type: "string" },
3024
- tags: { type: "array", items: { type: "string" } },
3025
- metadata: { type: "object" },
3026
- parent_id: { type: "string", format: "uuid" },
3027
- depends_on: { type: "string", format: "uuid" },
3028
- est_tokens: { type: "number", minimum: 0 }
3029
- },
3030
- required: ["task_code", "phase", "title", "description"]
3031
- },
3032
- description: "Array of tasks for bulk creation"
3033
- },
3034
3075
  structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3035
- },
3036
- required: ["repo"]
3076
+ }
3037
3077
  },
3038
3078
  outputSchema: {
3039
3079
  type: "object",
3040
3080
  properties: {
3041
- success: { type: "boolean" },
3042
- id: { type: "string" },
3043
- task_code: { type: "string" },
3044
3081
  repo: { type: "string" },
3082
+ task_code: { type: "string" },
3045
3083
  phase: { type: "string" },
3046
3084
  title: { type: "string" },
3047
3085
  status: { type: "string" },
3048
- priority: { type: "number" },
3049
- createdCount: { type: "number" },
3050
- taskCodes: { type: "array", items: { type: "string" } }
3086
+ priority: { type: "number" }
3051
3087
  },
3052
- required: ["success", "repo"]
3088
+ required: ["repo", "task_code", "phase", "title", "status", "priority"]
3053
3089
  }
3054
3090
  },
3055
3091
  {
3056
- name: "task-update",
3057
- title: "Task Update",
3058
- description: "Update one or more tasks. Supports single update via 'id' or bulk update via 'ids'. Provide only the fields that need to be changed. MANDATORY WORKFLOW: You cannot move a task from 'pending' or 'blocked' directly to 'completed'. You MUST move it to 'in_progress' first. When changing status to 'completed', include 'est_tokens' with the estimated total tokens actually used for the task.",
3059
- annotations: {
3060
- readOnlyHint: false,
3061
- idempotentHint: false,
3062
- openWorldHint: false
3063
- },
3092
+ name: "memory-detail",
3093
+ title: "Memory Detail",
3094
+ description: "Fetch full details of a specific memory by ID. Use this when you have a memory ID (e.g. from search results) and need to read the full content.",
3064
3095
  inputSchema: {
3065
3096
  type: "object",
3066
3097
  properties: {
3067
- repo: { type: "string", description: "Repository name" },
3068
- id: { type: "string", format: "uuid", description: "Task ID (for single update)" },
3069
- ids: { type: "array", items: { type: "string", format: "uuid" }, description: "Task IDs (for bulk update)" },
3070
- task_code: { type: "string" },
3071
- phase: { type: "string" },
3072
- title: { type: "string", minLength: 3, maxLength: 100 },
3073
- description: { type: "string" },
3074
- status: {
3075
- type: "string",
3076
- enum: ["backlog", "pending", "in_progress", "completed", "canceled", "blocked"],
3077
- description: "New status. Transitions from 'backlog', 'pending' or 'blocked' to 'completed' are NOT allowed."
3078
- },
3079
- priority: { type: "number", minimum: 1, maximum: 5 },
3080
- agent: { type: "string" },
3081
- role: { type: "string" },
3082
- model: { type: "string" },
3083
- comment: {
3084
- type: "string",
3085
- description: "REQUIRED when changing task status. Explain WHY the status is changing (e.g., 'Starting implementation', 'Blocked by missing API docs', 'Verified fix')."
3086
- },
3087
- doc_path: { type: "string" },
3088
- tags: { type: "array", items: { type: "string" } },
3089
- metadata: { type: "object" },
3090
- parent_id: { type: "string", format: "uuid" },
3091
- depends_on: { type: "string", format: "uuid" },
3092
- est_tokens: {
3093
- type: "number",
3094
- minimum: 0,
3095
- description: "Estimated total tokens actually used for this task. Required when status changes to 'completed'."
3096
- },
3097
- force: {
3098
- type: "boolean",
3099
- description: "If true, bypasses status transition validation (e.g. pending -> completed)."
3100
- },
3101
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3102
- },
3103
- required: ["repo"]
3104
- },
3105
- outputSchema: {
3106
- type: "object",
3107
- properties: {
3108
- success: { type: "boolean" },
3109
- id: { type: "string" },
3110
- ids: { type: "array", items: { type: "string" } },
3111
- repo: { type: "string" },
3112
- status: { type: "string" },
3113
- archivedToMemory: { type: "boolean" },
3114
- updatedFields: {
3115
- type: "array",
3116
- items: { type: "string" }
3117
- },
3118
- updatedCount: { type: "number" }
3098
+ id: { type: "string", format: "uuid", description: "Memory entry ID" },
3099
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON details." }
3119
3100
  },
3120
- required: ["success", "repo"]
3101
+ required: ["id"]
3121
3102
  }
3122
3103
  },
3123
3104
  {
3124
- name: "task-delete",
3125
- title: "Task Delete",
3126
- description: "Delete one or more tasks from a repository. Supports single 'id' or bulk 'ids'.",
3127
- annotations: {
3128
- readOnlyHint: false,
3129
- idempotentHint: false,
3130
- destructiveHint: true,
3131
- openWorldHint: false
3132
- },
3105
+ name: "task-detail",
3106
+ title: "Task Detail",
3107
+ description: "Fetch full details of a specific task by ID or task code. Use this when you have a task ID or code and need to read the full description and comments.",
3133
3108
  inputSchema: {
3134
3109
  type: "object",
3135
3110
  properties: {
3136
3111
  repo: { type: "string", description: "Repository name" },
3137
- id: { type: "string", format: "uuid", description: "Task ID (for single deletion)" },
3138
- ids: { type: "array", items: { type: "string", format: "uuid" }, description: "Task IDs (for bulk deletion)" },
3139
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3140
- },
3141
- required: ["repo"]
3142
- },
3143
- outputSchema: {
3144
- type: "object",
3145
- properties: {
3146
- success: { type: "boolean" },
3147
- id: { type: "string" },
3148
- ids: { type: "array", items: { type: "string" } },
3149
- repo: { type: "string" },
3150
- deletedCount: { type: "number" }
3112
+ id: { type: "string", format: "uuid", description: "Task ID (optional if task_code is provided)" },
3113
+ task_code: { type: "string", description: "Task code (e.g. TASK-001) (optional if id is provided)" },
3114
+ structured: {
3115
+ type: "boolean",
3116
+ default: false,
3117
+ description: "If true, returns structured JSON without the text content details."
3118
+ }
3151
3119
  },
3152
- required: ["success", "repo"]
3120
+ required: ["repo"]
3153
3121
  }
3154
3122
  },
3155
3123
  {
3156
- name: "task-list",
3157
- title: "Task List",
3158
- description: "PRIMARY navigation and search tool for tasks. Returns a compact tabular list of tasks (id, task_code, title, status, priority). Defaults to in_progress and pending tasks. Use 'query' to filter by code, title, or description. Use 'status' (comma-separated) for specific filters. AGENTS: call this once at start, pick ONE task, then call task-detail.",
3124
+ name: "memory-store",
3125
+ title: "Memory Store",
3126
+ description: "Store a new memory entry. Keep 'title' concise and human-readable; do not embed agent/role/date metadata in the title. Put auxiliary context into 'metadata'. Use 'tags' for tech-stack and 'is_global' for universal rules.",
3159
3127
  annotations: {
3160
- readOnlyHint: true,
3161
- idempotentHint: true,
3128
+ readOnlyHint: false,
3129
+ idempotentHint: false,
3130
+ destructiveHint: false,
3162
3131
  openWorldHint: false
3163
3132
  },
3164
3133
  inputSchema: {
3165
3134
  type: "object",
3166
3135
  properties: {
3167
- repo: {
3168
- type: "string",
3169
- description: "Repository name"
3170
- },
3171
- status: {
3136
+ type: {
3172
3137
  type: "string",
3173
- default: "in_progress,pending",
3174
- description: "Comma-separated status filter (backlog, pending, in_progress, completed, canceled, blocked). Defaults to 'in_progress,pending'."
3138
+ enum: [
3139
+ "code_fact",
3140
+ "decision",
3141
+ "mistake",
3142
+ "pattern",
3143
+ "agent_handoff",
3144
+ "agent_registered",
3145
+ "file_claim",
3146
+ "task_archive"
3147
+ ],
3148
+ description: "Type of memory being stored"
3175
3149
  },
3176
- phase: {
3150
+ title: {
3177
3151
  type: "string",
3178
- description: "Filter by phase (e.g., 'research', 'implementation')"
3152
+ minLength: 3,
3153
+ maxLength: 100,
3154
+ description: "Short human-readable title for the memory. Do not embed bracketed metadata like agent/role/date prefixes here."
3179
3155
  },
3180
- query: {
3156
+ content: {
3181
3157
  type: "string",
3182
- description: "Search keyword matching task code, title, or description"
3158
+ minLength: 10,
3159
+ description: "The memory content"
3183
3160
  },
3184
- limit: {
3161
+ importance: {
3185
3162
  type: "number",
3186
3163
  minimum: 1,
3187
- maximum: 100,
3188
- default: 5,
3189
- description: "Maximum rows to return (default 5)"
3164
+ maximum: 5,
3165
+ description: "Importance score (1-5)"
3190
3166
  },
3191
- offset: {
3192
- type: "number",
3193
- minimum: 0,
3194
- default: 0,
3195
- description: "Offset for pagination"
3167
+ agent: {
3168
+ type: "string",
3169
+ description: "Name of the agent creating this memory"
3196
3170
  },
3197
- structured: {
3198
- type: "boolean",
3199
- default: false,
3200
- description: "If true, returns structured JSON without the text content summary."
3201
- }
3202
- },
3203
- required: ["repo"]
3204
- },
3205
- outputSchema: {
3206
- type: "object",
3207
- properties: {
3208
- schema: { type: "string", enum: ["task-list"] },
3209
- tasks: {
3171
+ role: {
3172
+ type: "string",
3173
+ description: "Role of the agent creating this memory"
3174
+ },
3175
+ model: {
3176
+ type: "string",
3177
+ description: "AI model used by the agent"
3178
+ },
3179
+ scope: {
3210
3180
  type: "object",
3211
3181
  properties: {
3212
- columns: {
3213
- type: "array",
3214
- items: { type: "string" },
3215
- description: "Column names in order: id, task_code, title, status, priority, comments_count"
3216
- },
3217
- rows: {
3218
- type: "array",
3219
- items: { type: "array" },
3220
- description: "Each row: [id, task_code, title, status, priority, comments_count]. Use task-detail to fetch full task."
3221
- }
3182
+ repo: { type: "string", description: "Repository name" },
3183
+ branch: { type: "string" },
3184
+ folder: { type: "string" },
3185
+ language: { type: "string" }
3222
3186
  },
3223
- required: ["columns", "rows"]
3187
+ required: ["repo"]
3224
3188
  },
3225
- count: { type: "number" },
3226
- offset: { type: "number" }
3227
- },
3228
- required: ["schema", "tasks", "count"]
3229
- }
3230
- }
3231
- ];
3232
-
3233
- // src/mcp/utils/pagination.ts
3234
- function encodeCursor(offset) {
3235
- return Buffer.from(String(offset), "utf8").toString("base64");
3236
- }
3237
- function decodeCursor(cursor) {
3238
- if (cursor === void 0 || cursor === null || cursor === "") {
3239
- return 0;
3240
- }
3241
- if (typeof cursor !== "string" || cursor.trim() === "") {
3242
- throw invalidPaginationParams("Invalid cursor");
3243
- }
3244
- let decoded;
3245
- try {
3246
- decoded = Buffer.from(cursor, "base64").toString("utf8");
3247
- } catch {
3248
- throw invalidPaginationParams("Invalid cursor");
3249
- }
3250
- if (!/^\d+$/.test(decoded)) {
3251
- throw invalidPaginationParams("Invalid cursor");
3252
- }
3253
- const offset = Number.parseInt(decoded, 10);
3254
- if (!Number.isFinite(offset) || offset < 0) {
3255
- throw invalidPaginationParams("Invalid cursor");
3256
- }
3257
- return offset;
3258
- }
3259
- function invalidPaginationParams(message) {
3260
- const error = new Error(message);
3261
- error.code = -32602;
3262
- return error;
3263
- }
3264
-
3265
- // src/mcp/utils/completion.ts
3266
- var MAX_COMPLETION_VALUES = 100;
3267
- function rankCompletionValues(candidates, input) {
3268
- const unique = [...new Set(candidates.filter(Boolean))];
3269
- const needle = input.trim().toLowerCase();
3270
- if (!needle) {
3271
- return unique.slice(0, MAX_COMPLETION_VALUES);
3272
- }
3273
- 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);
3274
- }
3275
- function scoreCompletionValue(value, needle) {
3276
- const haystack = value.toLowerCase();
3277
- if (haystack === needle) return 100;
3278
- if (haystack.startsWith(needle)) return 75;
3279
- if (haystack.includes(needle)) return 50;
3280
- const compactNeedle = needle.replace(/[\s_-]+/g, "");
3281
- const compactHaystack = haystack.replace(/[\s_-]+/g, "");
3282
- if (compactNeedle && compactHaystack.includes(compactNeedle)) return 25;
3283
- return 0;
3284
- }
3285
-
3286
- // src/mcp/resources/index.ts
3287
- var DEFAULT_PAGE_SIZE = 25;
3288
- var MAX_PAGE_SIZE = 100;
3289
- function listResources(session, params) {
3290
- const resources = [
3291
- {
3292
- uri: "repository://index",
3293
- name: "Repository Index",
3294
- title: "Repository Index",
3295
- description: "List of all known repositories with memory/task counts and last activity",
3296
- mimeType: "application/json",
3297
- annotations: {
3298
- audience: ["assistant"],
3299
- priority: 1,
3300
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3301
- }
3302
- },
3303
- {
3304
- uri: "session://roots",
3305
- name: "Session Roots",
3306
- title: "Session Roots",
3307
- description: session?.roots.length ? "Active workspace roots provided by the MCP client" : "No active workspace roots were provided by the MCP client",
3308
- mimeType: "application/json",
3309
- size: Buffer.byteLength(JSON.stringify({ roots: session?.roots ?? [] }), "utf8"),
3310
- annotations: {
3311
- audience: ["assistant"],
3312
- priority: 0.95,
3313
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3314
- }
3315
- }
3316
- ];
3317
- return paginateEntries("resources", resources, params);
3318
- }
3319
- function listResourceTemplates(params) {
3320
- const templates = [
3321
- // ── Memory ──────────────────────────────────────────────────────────────
3322
- {
3323
- uriTemplate: "repository://{name}/memories",
3324
- name: "Repository Memories",
3325
- title: "Repository Memories",
3326
- description: "All active memory entries for a specific repository",
3327
- mimeType: "application/json",
3328
- annotations: { audience: ["assistant"], priority: 0.85 }
3329
- },
3330
- {
3331
- uriTemplate: "repository://{name}/memories?search={search}&type={type}&tag={tag}",
3332
- name: "Filtered Repository Memories",
3333
- title: "Filtered Repository Memories",
3334
- description: "Filter or search memories within a repository by keyword, type, or tag",
3335
- mimeType: "application/json",
3336
- annotations: { audience: ["assistant"], priority: 0.8 }
3337
- },
3338
- {
3339
- uriTemplate: "memory://{id}",
3340
- name: "Memory Detail",
3341
- title: "Memory Detail",
3342
- description: "Full content and statistics for a specific memory UUID",
3343
- mimeType: "application/json",
3344
- annotations: { audience: ["assistant"], priority: 0.75 }
3345
- },
3346
- // ── Tasks ────────────────────────────────────────────────────────────────
3347
- {
3348
- uriTemplate: "repository://{name}/tasks",
3349
- name: "Repository Tasks",
3350
- title: "Repository Tasks",
3351
- description: "All active tasks for a specific repository",
3352
- mimeType: "application/json",
3353
- annotations: { audience: ["assistant"], priority: 0.9 }
3354
- },
3355
- {
3356
- uriTemplate: "repository://{name}/tasks?status={status}&priority={priority}",
3357
- name: "Filtered Repository Tasks",
3358
- title: "Filtered Repository Tasks",
3359
- description: "Filter tasks within a repository by status or priority level",
3360
- mimeType: "application/json",
3361
- annotations: { audience: ["assistant"], priority: 0.85 }
3362
- },
3363
- {
3364
- uriTemplate: "task://{id}",
3365
- name: "Task Detail",
3366
- title: "Task Detail",
3367
- description: "Full content and comments for a specific task UUID",
3368
- mimeType: "application/json",
3369
- annotations: { audience: ["assistant"], priority: 0.8 }
3189
+ tags: {
3190
+ type: "array",
3191
+ items: { type: "string" },
3192
+ description: "Technology stack tags (e.g., ['filament', 'laravel'])"
3193
+ },
3194
+ metadata: {
3195
+ type: "object",
3196
+ description: "Structured metadata for non-title context such as source agent, claim fields, or timestamps"
3197
+ },
3198
+ is_global: {
3199
+ type: "boolean",
3200
+ description: "If true, this memory is shared across all repositories"
3201
+ },
3202
+ ttlDays: { type: "number", minimum: 1 },
3203
+ supersedes: { type: "string", format: "uuid" },
3204
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON of the stored memory." }
3205
+ },
3206
+ required: ["type", "title", "content", "importance", "scope", "agent", "model"]
3370
3207
  },
3371
- // ── Repository extras ────────────────────────────────────────────────────
3372
- {
3373
- uriTemplate: "repository://{name}/summary",
3374
- name: "Repository Summary",
3375
- title: "Repository Summary",
3376
- description: "High-level architectural summary for a repository",
3377
- mimeType: "text/plain",
3378
- annotations: { audience: ["assistant"], priority: 0.95 }
3208
+ outputSchema: {
3209
+ type: "object",
3210
+ properties: {
3211
+ success: { type: "boolean" },
3212
+ id: { type: "string" },
3213
+ code: { type: "string" },
3214
+ repo: { type: "string" },
3215
+ type: { type: "string" },
3216
+ title: { type: "string" },
3217
+ error: { type: "string" },
3218
+ message: { type: "string" }
3219
+ },
3220
+ required: ["success"]
3221
+ }
3222
+ },
3223
+ {
3224
+ name: "memory-acknowledge",
3225
+ title: "Memory Acknowledge",
3226
+ description: "Acknowledge the use of a memory or report its irrelevance/contradiction. Mandatory after using memory to generate code.",
3227
+ annotations: {
3228
+ readOnlyHint: false,
3229
+ idempotentHint: false,
3230
+ openWorldHint: false
3379
3231
  },
3380
- {
3381
- uriTemplate: "repository://{name}/actions",
3382
- name: "Repository Actions",
3383
- title: "Repository Actions",
3384
- description: "Audit log of agent tool actions scoped to a repository",
3385
- mimeType: "application/json",
3386
- annotations: { audience: ["assistant"], priority: 0.6 }
3232
+ inputSchema: {
3233
+ type: "object",
3234
+ properties: {
3235
+ memory_id: { type: "string", format: "uuid" },
3236
+ status: { type: "string", enum: ["used", "irrelevant", "contradictory"] },
3237
+ application_context: { type: "string", minLength: 10 },
3238
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3239
+ },
3240
+ required: ["memory_id", "status"]
3387
3241
  },
3388
- // ── Action detail ────────────────────────────────────────────────────────
3389
- {
3390
- uriTemplate: "action://{id}",
3391
- name: "Action Detail",
3392
- title: "Action Detail",
3393
- description: "Full details of a specific audit log entry by integer ID",
3394
- mimeType: "application/json",
3395
- annotations: { audience: ["assistant"], priority: 0.55 }
3396
- }
3397
- ];
3398
- return paginateEntries("resourceTemplates", templates, params);
3399
- }
3400
- function completeResourceArgument(resourceUri, argumentName, argumentValue, _contextArguments, dataSources) {
3401
- 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") {
3402
- if (argumentName === "name") {
3403
- return rankCompletionValues(dataSources.repos, argumentValue);
3404
- }
3405
- }
3406
- if (resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}") {
3407
- if (argumentName === "tag") {
3408
- return rankCompletionValues(dataSources.tags, argumentValue);
3242
+ outputSchema: {
3243
+ type: "object",
3244
+ properties: {
3245
+ success: { type: "boolean" },
3246
+ id: { type: "string" },
3247
+ status: { type: "string" }
3248
+ },
3249
+ required: ["success", "id", "status"]
3409
3250
  }
3410
- }
3411
- throw invalidCompletionParams(`Unknown resource template or argument: ${resourceUri} (${argumentName})`);
3412
- }
3413
- function readResource(uri, db, session) {
3414
- logger.info("[Tool] resource.read", { uri });
3415
- if (uri === "repository://index") {
3416
- const repos = db.system.listRepoNavigation();
3417
- const payload = JSON.stringify(repos, null, 2);
3418
- return {
3419
- contents: [
3420
- {
3421
- uri,
3422
- mimeType: "application/json",
3423
- text: payload,
3424
- size: Buffer.byteLength(payload, "utf8"),
3425
- annotations: {
3426
- audience: ["assistant"],
3427
- priority: 1,
3428
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3429
- }
3430
- }
3431
- ]
3432
- };
3433
- }
3434
- if (uri === "session://roots") {
3435
- const payload = JSON.stringify({ roots: session?.roots ?? [] }, null, 2);
3436
- return {
3437
- contents: [
3438
- {
3439
- uri,
3440
- mimeType: "application/json",
3441
- text: payload,
3442
- size: Buffer.byteLength(payload, "utf8"),
3443
- annotations: {
3444
- audience: ["assistant"],
3445
- priority: 0.95,
3446
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3447
- }
3251
+ },
3252
+ {
3253
+ name: "memory-update",
3254
+ title: "Memory Update",
3255
+ description: "Update an existing memory entry. Keep 'title' concise and move agent/role/date or claim context into 'metadata' instead of the title.",
3256
+ annotations: {
3257
+ readOnlyHint: false,
3258
+ idempotentHint: false,
3259
+ destructiveHint: false,
3260
+ openWorldHint: false
3261
+ },
3262
+ inputSchema: {
3263
+ type: "object",
3264
+ properties: {
3265
+ id: { type: "string", format: "uuid" },
3266
+ type: {
3267
+ type: "string",
3268
+ enum: [
3269
+ "code_fact",
3270
+ "decision",
3271
+ "mistake",
3272
+ "pattern",
3273
+ "agent_handoff",
3274
+ "agent_registered",
3275
+ "file_claim",
3276
+ "task_archive"
3277
+ ]
3278
+ },
3279
+ title: { type: "string", minLength: 3, maxLength: 100 },
3280
+ content: { type: "string", minLength: 10 },
3281
+ importance: { type: "number", minimum: 1, maximum: 5 },
3282
+ agent: { type: "string" },
3283
+ role: { type: "string" },
3284
+ status: { type: "string", enum: ["active", "archived"] },
3285
+ supersedes: { type: "string", format: "uuid" },
3286
+ tags: { type: "array", items: { type: "string" } },
3287
+ metadata: { type: "object" },
3288
+ is_global: { type: "boolean" },
3289
+ completed_at: { type: "string" },
3290
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON of the updated memory." }
3291
+ },
3292
+ required: ["id"]
3293
+ },
3294
+ outputSchema: {
3295
+ type: "object",
3296
+ properties: {
3297
+ success: { type: "boolean" },
3298
+ id: { type: "string" },
3299
+ repo: { type: "string" },
3300
+ updatedFields: {
3301
+ type: "array",
3302
+ items: { type: "string" }
3448
3303
  }
3449
- ]
3450
- };
3451
- }
3452
- const memoryIdMatch = uri.match(/^memory:\/\/([0-9a-f-]{36})$/i);
3453
- if (memoryIdMatch) {
3454
- const id = memoryIdMatch[1];
3455
- const entry = db.memories.getByIdWithStats(id);
3456
- if (!entry) throw resourceNotFound(`Memory with ID ${id} not found.`, uri);
3457
- const payload = JSON.stringify(entry, null, 2);
3458
- return {
3459
- contents: [
3460
- {
3461
- uri,
3462
- mimeType: "application/json",
3463
- text: payload,
3464
- size: Buffer.byteLength(payload, "utf8"),
3465
- annotations: {
3466
- audience: ["assistant"],
3467
- priority: 0.75,
3468
- lastModified: entry.updated_at || entry.created_at
3304
+ },
3305
+ required: ["success", "id", "repo", "updatedFields"]
3306
+ }
3307
+ },
3308
+ {
3309
+ name: "memory-search",
3310
+ title: "Memory Search",
3311
+ description: "NAVIGATION LAYER: Returns a pointer table of matching memory IDs only. Returns columns [id, title, type, importance] \u2014 NO content. Retrieve full memory via memory-detail. Use 'current_tags' to find tech-stack specific knowledge from other projects.",
3312
+ annotations: {
3313
+ readOnlyHint: true,
3314
+ idempotentHint: true,
3315
+ openWorldHint: false
3316
+ },
3317
+ inputSchema: {
3318
+ type: "object",
3319
+ properties: {
3320
+ query: { type: "string", minLength: 3 },
3321
+ prompt: { type: "string" },
3322
+ repo: { type: "string" },
3323
+ current_tags: {
3324
+ type: "array",
3325
+ items: { type: "string" },
3326
+ description: "Active tech stack tags (e.g., ['filament', 'react'])"
3327
+ },
3328
+ types: {
3329
+ type: "array",
3330
+ items: {
3331
+ type: "string",
3332
+ enum: [
3333
+ "code_fact",
3334
+ "decision",
3335
+ "mistake",
3336
+ "pattern",
3337
+ "agent_handoff",
3338
+ "agent_registered",
3339
+ "file_claim",
3340
+ "task_archive"
3341
+ ]
3469
3342
  }
3470
- }
3471
- ]
3472
- };
3473
- }
3474
- const taskIdMatch = uri.match(/^task:\/\/([0-9a-f-]{36})$/i);
3475
- if (taskIdMatch) {
3476
- const id = taskIdMatch[1];
3477
- const task = db.tasks.getTaskById(id);
3478
- if (!task) throw resourceNotFound(`Task with ID ${id} not found.`, uri);
3479
- const payload = JSON.stringify(task, null, 2);
3480
- return {
3481
- contents: [
3482
- {
3483
- uri,
3484
- mimeType: "application/json",
3485
- text: payload,
3486
- size: Buffer.byteLength(payload, "utf8"),
3487
- annotations: {
3488
- audience: ["assistant"],
3489
- priority: 0.8,
3490
- lastModified: task.updated_at || task.created_at
3343
+ },
3344
+ minImportance: { type: "number", minimum: 1, maximum: 5 },
3345
+ limit: { type: "number", minimum: 1, maximum: 100, default: 5 },
3346
+ offset: { type: "number", minimum: 0, default: 0 },
3347
+ includeRecap: { type: "boolean", default: false },
3348
+ current_file_path: { type: "string" },
3349
+ include_archived: { type: "boolean", default: false },
3350
+ scope: {
3351
+ type: "object",
3352
+ properties: {
3353
+ repo: { type: "string" },
3354
+ branch: { type: "string" },
3355
+ folder: { type: "string" },
3356
+ language: { type: "string" }
3491
3357
  }
3358
+ },
3359
+ structured: {
3360
+ type: "boolean",
3361
+ default: false,
3362
+ description: "If true, returns structured JSON without the text content summary."
3492
3363
  }
3493
- ]
3494
- };
3495
- }
3496
- const repoBase = parseRepoUri(uri);
3497
- if (repoBase) {
3498
- const { name, path: repoPath, query } = repoBase;
3499
- if (repoPath === "summary") {
3500
- const summary = db.summaries.getSummary(name);
3501
- const text = summary?.summary || `No summary available for repository: ${name}`;
3502
- return {
3503
- contents: [
3504
- {
3505
- uri,
3506
- mimeType: "text/plain",
3507
- text,
3508
- size: Buffer.byteLength(text, "utf8"),
3509
- annotations: {
3510
- audience: ["assistant"],
3511
- priority: 0.95,
3512
- lastModified: summary?.updated_at || (/* @__PURE__ */ new Date()).toISOString()
3364
+ },
3365
+ required: ["query", "repo"]
3366
+ },
3367
+ outputSchema: {
3368
+ type: "object",
3369
+ properties: {
3370
+ schema: { type: "string", enum: ["memory-search"] },
3371
+ query: { type: "string" },
3372
+ count: { type: "number", description: "Number of rows returned" },
3373
+ total: { type: "number", description: "Total matching memories" },
3374
+ offset: { type: "number" },
3375
+ limit: { type: "number" },
3376
+ results: {
3377
+ type: "object",
3378
+ properties: {
3379
+ columns: {
3380
+ type: "array",
3381
+ items: { type: "string" },
3382
+ description: "Column names: [id, title, type, importance]"
3383
+ },
3384
+ rows: {
3385
+ type: "array",
3386
+ items: { type: "array" },
3387
+ description: "Each row: [id, title, type, importance]. Fetch full content via memory-detail"
3513
3388
  }
3514
- }
3515
- ]
3516
- };
3389
+ },
3390
+ required: ["columns", "rows"]
3391
+ }
3392
+ },
3393
+ required: ["schema", "query", "count", "total", "offset", "limit", "results"]
3517
3394
  }
3518
- if (repoPath === "memories") {
3519
- const search = query.get("search") || "";
3520
- const type = query.get("type");
3521
- const tag = query.get("tag");
3522
- const result = db.memories.listMemoriesForDashboard({
3523
- repo: name,
3524
- type: type || void 0,
3525
- tag: tag || void 0,
3526
- search: search || void 0,
3527
- limit: 50
3528
- });
3529
- const entries = result.items;
3530
- const payload = JSON.stringify(entries, null, 2);
3531
- return {
3532
- contents: [
3533
- {
3534
- uri,
3535
- mimeType: "application/json",
3536
- text: payload,
3537
- size: Buffer.byteLength(payload, "utf8"),
3538
- annotations: {
3539
- audience: ["assistant"],
3540
- priority: 0.85,
3541
- lastModified: deriveLastModifiedFromCollection(
3542
- entries.map((e) => e.updated_at || e.created_at)
3543
- )
3544
- }
3545
- }
3546
- ]
3547
- };
3395
+ },
3396
+ {
3397
+ name: "memory-summarize",
3398
+ title: "Memory Summarize",
3399
+ description: "Update the summary for a repository",
3400
+ annotations: {
3401
+ readOnlyHint: false,
3402
+ idempotentHint: false,
3403
+ openWorldHint: false
3404
+ },
3405
+ inputSchema: {
3406
+ type: "object",
3407
+ properties: {
3408
+ repo: { type: "string", description: "Repository name" },
3409
+ signals: {
3410
+ type: "array",
3411
+ items: { type: "string", maxLength: 200 },
3412
+ minItems: 1,
3413
+ description: "High-level signals to include in summary"
3414
+ },
3415
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON of the summary." }
3416
+ },
3417
+ required: ["repo", "signals"]
3418
+ },
3419
+ outputSchema: {
3420
+ type: "object",
3421
+ properties: {
3422
+ success: { type: "boolean" },
3423
+ repo: { type: "string" },
3424
+ summary: { type: "string" },
3425
+ signalCount: { type: "number" }
3426
+ },
3427
+ required: ["success", "repo", "summary", "signalCount"]
3548
3428
  }
3549
- if (repoPath === "tasks") {
3550
- const status = query.get("status");
3551
- const priority = query.get("priority");
3552
- let tasks;
3553
- if (status && status !== "all") {
3554
- const statuses = status.split(",").map((s) => s.trim());
3555
- tasks = db.tasks.getTasksByMultipleStatuses(name, statuses);
3556
- } else {
3557
- tasks = db.tasks.getTasksByMultipleStatuses(name, ["backlog", "pending", "in_progress", "blocked"]);
3429
+ },
3430
+ {
3431
+ name: "memory-delete",
3432
+ title: "Memory Delete",
3433
+ description: "Soft-delete one or more memory entries. Supports single 'id' or bulk 'ids'.",
3434
+ annotations: {
3435
+ readOnlyHint: false,
3436
+ idempotentHint: false,
3437
+ destructiveHint: true,
3438
+ openWorldHint: false
3439
+ },
3440
+ inputSchema: {
3441
+ type: "object",
3442
+ properties: {
3443
+ repo: { type: "string", description: "Repository name (optional for single id)" },
3444
+ id: { type: "string", format: "uuid", description: "Memory entry ID to delete" },
3445
+ ids: {
3446
+ type: "array",
3447
+ items: { type: "string", format: "uuid" },
3448
+ minItems: 1,
3449
+ description: "Array of memory IDs to delete"
3450
+ },
3451
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3558
3452
  }
3559
- if (priority) {
3560
- const p = Number(priority);
3561
- if (!isNaN(p)) {
3562
- tasks = tasks.filter((t) => t.priority === p);
3453
+ },
3454
+ outputSchema: {
3455
+ type: "object",
3456
+ properties: {
3457
+ success: { type: "boolean" },
3458
+ id: { type: "string" },
3459
+ ids: { type: "array", items: { type: "string" } },
3460
+ repo: { type: "string" },
3461
+ deletedCount: { type: "number" }
3462
+ },
3463
+ required: ["success"]
3464
+ }
3465
+ },
3466
+ {
3467
+ name: "memory-recap",
3468
+ title: "Memory Recap",
3469
+ description: "AGGREGATED OVERVIEW LAYER: Returns stats (counts by type) and a pointer table of top memories [id, title, type, importance]. NO content. Use for orientation only \u2014 retrieve full memory via memory-detail.",
3470
+ annotations: {
3471
+ readOnlyHint: true,
3472
+ idempotentHint: true,
3473
+ openWorldHint: false
3474
+ },
3475
+ inputSchema: {
3476
+ type: "object",
3477
+ properties: {
3478
+ repo: { type: "string", description: "Repository name (required)" },
3479
+ limit: {
3480
+ type: "number",
3481
+ minimum: 1,
3482
+ maximum: 50,
3483
+ default: 20,
3484
+ description: "Maximum number of top memories to return in the pointer table"
3485
+ },
3486
+ offset: {
3487
+ type: "number",
3488
+ minimum: 0,
3489
+ default: 0,
3490
+ description: "Number of memories to skip for pagination (optional, default 0)"
3491
+ },
3492
+ structured: {
3493
+ type: "boolean",
3494
+ default: false,
3495
+ description: "If true, returns structured JSON without the text content summary."
3563
3496
  }
3564
- }
3565
- const payload = JSON.stringify(tasks, null, 2);
3566
- return {
3567
- contents: [
3568
- {
3569
- uri,
3570
- mimeType: "application/json",
3571
- text: payload,
3572
- size: Buffer.byteLength(payload, "utf8"),
3573
- annotations: {
3574
- audience: ["assistant"],
3575
- priority: 0.9,
3576
- lastModified: deriveLastModifiedFromCollection(tasks.map((t) => t.updated_at))
3497
+ },
3498
+ required: ["repo"]
3499
+ },
3500
+ outputSchema: {
3501
+ type: "object",
3502
+ properties: {
3503
+ schema: { type: "string", enum: ["memory-recap"] },
3504
+ repo: { type: "string" },
3505
+ count: { type: "number", description: "Number of rows in the top pointer table" },
3506
+ total: { type: "number", description: "Total active memories in repo" },
3507
+ offset: { type: "number" },
3508
+ limit: { type: "number" },
3509
+ stats: {
3510
+ type: "object",
3511
+ properties: {
3512
+ by_type: {
3513
+ type: "object",
3514
+ description: "Count of active memories per type (e.g. { decision: 3, code_fact: 7 })"
3515
+ }
3516
+ },
3517
+ required: ["by_type"]
3518
+ },
3519
+ top: {
3520
+ type: "object",
3521
+ properties: {
3522
+ columns: {
3523
+ type: "array",
3524
+ items: { type: "string" },
3525
+ description: "Column names: [id, title, type, importance]"
3526
+ },
3527
+ rows: {
3528
+ type: "array",
3529
+ items: { type: "array" },
3530
+ description: "Each row: [id, title, type, importance]. Fetch full content via memory-detail"
3577
3531
  }
3578
- }
3579
- ]
3580
- };
3532
+ },
3533
+ required: ["columns", "rows"]
3534
+ }
3535
+ },
3536
+ required: ["schema", "repo", "count", "total", "offset", "limit", "stats", "top"]
3581
3537
  }
3582
- if (repoPath === "actions") {
3583
- const actions = db.actions.getRecentActions(name, 100);
3584
- const payload = JSON.stringify(actions, null, 2);
3585
- return {
3586
- contents: [
3587
- {
3588
- uri,
3589
- mimeType: "application/json",
3590
- text: payload,
3591
- size: Buffer.byteLength(payload, "utf8"),
3592
- annotations: {
3593
- audience: ["assistant"],
3594
- priority: 0.6,
3595
- lastModified: deriveLastModifiedFromCollection(actions.map((a) => a.created_at))
3596
- }
3597
- }
3598
- ]
3599
- };
3538
+ },
3539
+ {
3540
+ name: "task-create",
3541
+ title: "Task Create",
3542
+ description: "Register one or more new tasks in a repository. task_code must be unique within the repository. Supports single task object or an array of tasks for bulk creation.",
3543
+ annotations: {
3544
+ readOnlyHint: false,
3545
+ idempotentHint: false,
3546
+ openWorldHint: false
3547
+ },
3548
+ inputSchema: {
3549
+ type: "object",
3550
+ properties: {
3551
+ repo: { type: "string", description: "Repository name" },
3552
+ task_code: { type: "string", description: "Unique task code (e.g. TASK-001) (Required for single task)" },
3553
+ phase: { type: "string", description: "Project phase (Required for single task)" },
3554
+ title: {
3555
+ type: "string",
3556
+ minLength: 3,
3557
+ maxLength: 100,
3558
+ description: "Task objective (Required for single task)"
3559
+ },
3560
+ description: { type: "string", description: "Detailed description (Required for single task)" },
3561
+ status: {
3562
+ type: "string",
3563
+ enum: ["backlog", "pending"],
3564
+ default: "backlog",
3565
+ description: "New tasks MUST start in 'backlog' if there are already 10 pending tasks. Otherwise can start in 'pending'."
3566
+ },
3567
+ priority: { type: "number", minimum: 1, maximum: 5, default: 3 },
3568
+ agent: { type: "string" },
3569
+ role: { type: "string" },
3570
+ doc_path: { type: "string" },
3571
+ tags: { type: "array", items: { type: "string" } },
3572
+ metadata: { type: "object" },
3573
+ parent_id: { type: "string", format: "uuid" },
3574
+ depends_on: { type: "string", format: "uuid" },
3575
+ est_tokens: { type: "number", minimum: 0, description: "Estimated tokens budget for this task" },
3576
+ tasks: {
3577
+ type: "array",
3578
+ items: {
3579
+ type: "object",
3580
+ properties: {
3581
+ task_code: { type: "string" },
3582
+ phase: { type: "string" },
3583
+ title: { type: "string", minLength: 3, maxLength: 100 },
3584
+ description: { type: "string" },
3585
+ status: { type: "string", enum: ["backlog", "pending"], default: "backlog" },
3586
+ priority: { type: "number", minimum: 1, maximum: 5, default: 3 },
3587
+ agent: { type: "string" },
3588
+ role: { type: "string" },
3589
+ doc_path: { type: "string" },
3590
+ tags: { type: "array", items: { type: "string" } },
3591
+ metadata: { type: "object" },
3592
+ parent_id: { type: "string", format: "uuid" },
3593
+ depends_on: { type: "string", format: "uuid" },
3594
+ est_tokens: { type: "number", minimum: 0 }
3595
+ },
3596
+ required: ["task_code", "phase", "title", "description"]
3597
+ },
3598
+ description: "Array of tasks for bulk creation"
3599
+ },
3600
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3601
+ },
3602
+ required: ["repo"]
3603
+ },
3604
+ outputSchema: {
3605
+ type: "object",
3606
+ properties: {
3607
+ success: { type: "boolean" },
3608
+ id: { type: "string" },
3609
+ task_code: { type: "string" },
3610
+ repo: { type: "string" },
3611
+ phase: { type: "string" },
3612
+ title: { type: "string" },
3613
+ status: { type: "string" },
3614
+ priority: { type: "number" },
3615
+ createdCount: { type: "number" },
3616
+ taskCodes: { type: "array", items: { type: "string" } }
3617
+ },
3618
+ required: ["success", "repo"]
3600
3619
  }
3601
- }
3602
- const actionIdMatch = uri.match(/^action:\/\/(\d+)$/);
3603
- if (actionIdMatch) {
3604
- const id = Number(actionIdMatch[1]);
3605
- const action = db.actions.getActionById(id);
3606
- if (!action) throw resourceNotFound(`Action with ID ${id} not found.`, uri);
3607
- const payload = JSON.stringify(action, null, 2);
3608
- return {
3609
- contents: [
3610
- {
3611
- uri,
3612
- mimeType: "application/json",
3613
- text: payload,
3614
- size: Buffer.byteLength(payload, "utf8"),
3615
- annotations: {
3616
- audience: ["assistant"],
3617
- priority: 0.55,
3618
- lastModified: action.created_at
3619
- }
3620
- }
3621
- ]
3622
- };
3623
- }
3624
- throw resourceNotFound(`Unknown resource URI: ${uri}`, uri);
3625
- }
3626
- function parseRepoUri(uri) {
3627
- const prefix = "repository://";
3628
- if (!uri.startsWith(prefix)) return null;
3629
- const rest = uri.slice(prefix.length);
3630
- const queryStart = rest.indexOf("?");
3631
- const withoutQuery = queryStart === -1 ? rest : rest.slice(0, queryStart);
3632
- const queryString = queryStart === -1 ? "" : rest.slice(queryStart + 1);
3633
- const slashIdx = withoutQuery.indexOf("/");
3634
- if (slashIdx === -1) return null;
3635
- const name = withoutQuery.slice(0, slashIdx);
3636
- const path6 = withoutQuery.slice(slashIdx + 1);
3637
- if (!name || !path6) return null;
3638
- return { name, path: path6, query: new URLSearchParams(queryString) };
3639
- }
3640
- function paginateEntries(key, entries, params) {
3641
- const limit = normalizeLimit(params?.limit);
3642
- const offset = decodeCursor(params?.cursor);
3643
- const sliced = entries.slice(offset, offset + limit);
3644
- const nextOffset = offset + sliced.length;
3645
- return {
3646
- [key]: sliced,
3647
- nextCursor: nextOffset < entries.length ? encodeCursor(nextOffset) : void 0
3648
- };
3649
- }
3650
- function normalizeLimit(limit) {
3651
- if (typeof limit !== "number" || !Number.isFinite(limit)) {
3652
- return DEFAULT_PAGE_SIZE;
3653
- }
3654
- return Math.min(MAX_PAGE_SIZE, Math.max(1, Math.trunc(limit)));
3655
- }
3656
- function deriveLastModifiedFromCollection(values) {
3657
- const normalized = values.filter((value) => typeof value === "string" && value.length > 0);
3658
- return normalized.sort().at(-1) ?? (/* @__PURE__ */ new Date()).toISOString();
3659
- }
3660
- function resourceNotFound(message, uri) {
3661
- const error = new Error(message);
3662
- error.code = -32002;
3663
- error.data = { uri };
3664
- return error;
3665
- }
3666
- function invalidCompletionParams(message) {
3667
- const error = new Error(message);
3668
- error.code = -32602;
3669
- return error;
3670
- }
3671
-
3672
- // src/mcp/prompts/loader.ts
3673
- import fs5 from "fs";
3674
- import path5 from "path";
3675
- import { fileURLToPath as fileURLToPath3 } from "url";
3676
- import matter from "gray-matter";
3677
- var __filename = fileURLToPath3(import.meta.url);
3678
- var __dirname2 = path5.dirname(__filename);
3679
- function findPromptDir() {
3680
- const candidates = [
3681
- // Production if chunked into dist/
3682
- "./prompts",
3683
- // Production if inlined into dist/mcp/
3684
- "../prompts",
3685
- // Dev: /src/mcp/prompts/definitions (next to loader.ts)
3686
- "./definitions"
3687
- ].map((relPath) => path5.resolve(__dirname2, relPath));
3688
- for (const dir of candidates) {
3689
- if (fs5.existsSync(dir)) {
3690
- const files = fs5.readdirSync(dir);
3691
- if (files.some((f) => f.endsWith(".md"))) {
3692
- return dir;
3693
- }
3620
+ },
3621
+ {
3622
+ name: "task-update",
3623
+ title: "Task Update",
3624
+ description: "Update one or more tasks. Supports single update via 'id' or bulk update via 'ids'. Provide only the fields that need to be changed. MANDATORY WORKFLOW: You cannot move a task from 'pending' or 'blocked' directly to 'completed'. You MUST move it to 'in_progress' first. When changing status to 'completed', include 'est_tokens' with the estimated total tokens actually used for the task.",
3625
+ annotations: {
3626
+ readOnlyHint: false,
3627
+ idempotentHint: false,
3628
+ openWorldHint: false
3629
+ },
3630
+ inputSchema: {
3631
+ type: "object",
3632
+ properties: {
3633
+ repo: { type: "string", description: "Repository name" },
3634
+ id: { type: "string", format: "uuid", description: "Task ID (for single update)" },
3635
+ ids: { type: "array", items: { type: "string", format: "uuid" }, description: "Task IDs (for bulk update)" },
3636
+ task_code: { type: "string" },
3637
+ phase: { type: "string" },
3638
+ title: { type: "string", minLength: 3, maxLength: 100 },
3639
+ description: { type: "string" },
3640
+ status: {
3641
+ type: "string",
3642
+ enum: ["backlog", "pending", "in_progress", "completed", "canceled", "blocked"],
3643
+ description: "New status. Transitions from 'backlog', 'pending' or 'blocked' to 'completed' are NOT allowed."
3644
+ },
3645
+ priority: { type: "number", minimum: 1, maximum: 5 },
3646
+ agent: { type: "string" },
3647
+ role: { type: "string" },
3648
+ model: { type: "string" },
3649
+ comment: {
3650
+ type: "string",
3651
+ description: "REQUIRED when changing task status. Explain WHY the status is changing (e.g., 'Starting implementation', 'Blocked by missing API docs', 'Verified fix')."
3652
+ },
3653
+ doc_path: { type: "string" },
3654
+ tags: { type: "array", items: { type: "string" } },
3655
+ metadata: { type: "object" },
3656
+ parent_id: { type: "string", format: "uuid" },
3657
+ depends_on: { type: "string", format: "uuid" },
3658
+ est_tokens: {
3659
+ type: "number",
3660
+ minimum: 0,
3661
+ description: "Estimated total tokens actually used for this task. Required when status changes to 'completed'."
3662
+ },
3663
+ force: {
3664
+ type: "boolean",
3665
+ description: "If true, bypasses status transition validation (e.g. pending -> completed)."
3666
+ },
3667
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3668
+ },
3669
+ required: ["repo"]
3670
+ },
3671
+ outputSchema: {
3672
+ type: "object",
3673
+ properties: {
3674
+ success: { type: "boolean" },
3675
+ id: { type: "string" },
3676
+ ids: { type: "array", items: { type: "string" } },
3677
+ repo: { type: "string" },
3678
+ status: { type: "string" },
3679
+ archivedToMemory: { type: "boolean" },
3680
+ updatedFields: {
3681
+ type: "array",
3682
+ items: { type: "string" }
3683
+ },
3684
+ updatedCount: { type: "number" }
3685
+ },
3686
+ required: ["success", "repo"]
3694
3687
  }
3695
- }
3696
- return path5.resolve(__dirname2, "./definitions");
3697
- }
3698
- var PROMPT_DIR = findPromptDir();
3699
- function listPromptFiles() {
3700
- if (!fs5.existsSync(PROMPT_DIR)) return [];
3701
- return fs5.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
3702
- }
3703
- function loadPromptFromMarkdown(name) {
3704
- const filePath = path5.join(PROMPT_DIR, `${name}.md`);
3705
- if (!fs5.existsSync(filePath)) {
3706
- throw new Error(`Prompt file not found: ${filePath}`);
3707
- }
3708
- const fileContent = fs5.readFileSync(filePath, "utf-8");
3709
- const { data, content } = matter(fileContent);
3710
- return {
3711
- name: data.name || name,
3712
- description: data.description || "",
3713
- arguments: data.arguments || [],
3714
- agent: data.agent,
3715
- content: content.trim()
3716
- };
3717
- }
3718
-
3719
- // src/mcp/prompts/registry.ts
3720
- function createPromptDefinition(loaded) {
3721
- return {
3722
- name: loaded.name,
3723
- description: loaded.description,
3724
- arguments: loaded.arguments,
3725
- agent: loaded.agent,
3726
- messages: [
3727
- {
3728
- role: "user",
3729
- content: {
3730
- type: "text",
3731
- text: loaded.content
3688
+ },
3689
+ {
3690
+ name: "task-delete",
3691
+ title: "Task Delete",
3692
+ description: "Delete one or more tasks from a repository. Supports single 'id' or bulk 'ids'.",
3693
+ annotations: {
3694
+ readOnlyHint: false,
3695
+ idempotentHint: false,
3696
+ destructiveHint: true,
3697
+ openWorldHint: false
3698
+ },
3699
+ inputSchema: {
3700
+ type: "object",
3701
+ properties: {
3702
+ repo: { type: "string", description: "Repository name" },
3703
+ id: { type: "string", format: "uuid", description: "Task ID (for single deletion)" },
3704
+ ids: { type: "array", items: { type: "string", format: "uuid" }, description: "Task IDs (for bulk deletion)" },
3705
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3706
+ },
3707
+ required: ["repo"]
3708
+ },
3709
+ outputSchema: {
3710
+ type: "object",
3711
+ properties: {
3712
+ success: { type: "boolean" },
3713
+ id: { type: "string" },
3714
+ ids: { type: "array", items: { type: "string" } },
3715
+ repo: { type: "string" },
3716
+ deletedCount: { type: "number" }
3717
+ },
3718
+ required: ["success", "repo"]
3719
+ }
3720
+ },
3721
+ {
3722
+ name: "task-list",
3723
+ title: "Task List",
3724
+ description: "PRIMARY navigation and search tool for tasks. Returns a compact tabular list of tasks (id, task_code, title, status, priority). Defaults to in_progress and pending tasks. Use 'query' to filter by code, title, or description. Use 'status' (comma-separated) for specific filters. AGENTS: call this once at start, pick ONE task, then call task-detail.",
3725
+ annotations: {
3726
+ readOnlyHint: true,
3727
+ idempotentHint: true,
3728
+ openWorldHint: false
3729
+ },
3730
+ inputSchema: {
3731
+ type: "object",
3732
+ properties: {
3733
+ repo: {
3734
+ type: "string",
3735
+ description: "Repository name"
3736
+ },
3737
+ status: {
3738
+ type: "string",
3739
+ default: "in_progress,pending",
3740
+ description: "Comma-separated status filter (backlog, pending, in_progress, completed, canceled, blocked). Defaults to 'in_progress,pending'."
3741
+ },
3742
+ phase: {
3743
+ type: "string",
3744
+ description: "Filter by phase (e.g., 'research', 'implementation')"
3745
+ },
3746
+ query: {
3747
+ type: "string",
3748
+ description: "Search keyword matching task code, title, or description"
3749
+ },
3750
+ limit: {
3751
+ type: "number",
3752
+ minimum: 1,
3753
+ maximum: 100,
3754
+ default: 5,
3755
+ description: "Maximum rows to return (default 5)"
3756
+ },
3757
+ offset: {
3758
+ type: "number",
3759
+ minimum: 0,
3760
+ default: 0,
3761
+ description: "Offset for pagination"
3762
+ },
3763
+ structured: {
3764
+ type: "boolean",
3765
+ default: false,
3766
+ description: "If true, returns structured JSON without the text content summary."
3732
3767
  }
3733
- }
3734
- ]
3735
- };
3736
- }
3737
- var PROMPTS = {};
3738
- var promptFiles = listPromptFiles();
3739
- for (const name of promptFiles) {
3740
- try {
3741
- PROMPTS[name] = createPromptDefinition(loadPromptFromMarkdown(name));
3742
- } catch (e) {
3743
- logger.warn(`Failed to load prompt ${name}: ${e}`);
3744
- }
3745
- }
3746
- async function listPrompts(db, session, params) {
3747
- const allPrompts = Object.values(PROMPTS).map((p) => ({
3748
- name: p.name,
3749
- description: p.description,
3750
- arguments: p.arguments,
3751
- metadata: p.agent ? { agent: p.agent } : void 0
3752
- }));
3753
- const rawLimit = typeof params?.limit === "number" && Number.isInteger(params?.limit) ? params.limit : 25;
3754
- const limit = Math.max(1, Math.min(100, Math.trunc(rawLimit)));
3755
- const offset = decodeCursor(params?.cursor);
3756
- const sliced = allPrompts.slice(offset, offset + limit);
3757
- const nextOffset = offset + sliced.length;
3758
- return {
3759
- prompts: sliced,
3760
- nextCursor: nextOffset < allPrompts.length ? encodeCursor(nextOffset) : void 0
3761
- };
3762
- }
3763
- async function getPrompt(name, args = {}, db, session) {
3764
- const prompt = PROMPTS[name];
3765
- if (!prompt) {
3766
- throw new Error(`Prompt not found: ${name}`);
3767
- }
3768
- const inferredRepo = inferRepoFromSession(session);
3769
- const messages = prompt.messages.map((m) => {
3770
- let text = m.content.text;
3771
- for (const [key, value] of Object.entries(args)) {
3772
- text = text.replace(new RegExp(`\\{{${key}\\}}`, "g"), value);
3768
+ },
3769
+ required: ["repo"]
3770
+ },
3771
+ outputSchema: {
3772
+ type: "object",
3773
+ properties: {
3774
+ schema: { type: "string", enum: ["task-list"] },
3775
+ tasks: {
3776
+ type: "object",
3777
+ properties: {
3778
+ columns: {
3779
+ type: "array",
3780
+ items: { type: "string" },
3781
+ description: "Column names in order: id, task_code, title, status, priority, comments_count"
3782
+ },
3783
+ rows: {
3784
+ type: "array",
3785
+ items: { type: "array" },
3786
+ description: "Each row: [id, task_code, title, status, priority, comments_count]. Use task-detail to fetch full task."
3787
+ }
3788
+ },
3789
+ required: ["columns", "rows"]
3790
+ },
3791
+ count: { type: "number" },
3792
+ offset: { type: "number" }
3793
+ },
3794
+ required: ["schema", "tasks", "count"]
3773
3795
  }
3774
- text = text.replace(/{{current_repo}}/g, inferredRepo || "unknown-repo");
3775
- return {
3776
- ...m,
3777
- content: {
3778
- ...m.content,
3779
- text
3780
- }
3781
- };
3782
- });
3783
- return {
3784
- description: prompt.description,
3785
- messages,
3786
- metadata: prompt.agent ? { agent: prompt.agent } : void 0
3787
- };
3788
- }
3789
- async function completePromptArgument(name, argName, value, contextArguments, dataSources) {
3790
- void name;
3791
- void contextArguments;
3792
- if (argName === "task_id") {
3793
- const values = dataSources.tasks.map((t) => t.id);
3794
- return rankCompletionValues(values, value);
3795
3796
  }
3796
- return [];
3797
- }
3797
+ ];
3798
3798
 
3799
3799
  export {
3800
- MCP_PROTOCOL_VERSION,
3801
- CAPABILITIES,
3802
3800
  logger,
3803
3801
  setLogLevel,
3804
3802
  getLogLevel,
3805
3803
  addLogSink,
3806
3804
  LOG_LEVEL_VALUES,
3807
3805
  createFileSink,
3808
- normalizeRepo,
3809
- SQLiteStore,
3810
- MemoryStoreSchema,
3811
- MemoryUpdateSchema,
3812
- MemorySearchSchema,
3813
- MemoryAcknowledgeSchema,
3814
- MemoryRecapSchema,
3815
- MemoryDeleteSchema,
3816
- MemorySummarizeSchema,
3817
- MemorySynthesizeSchema,
3818
- TaskCreateSchema,
3819
- TaskCreateInteractiveSchema,
3820
- TaskUpdateSchema,
3821
- TaskListSchema,
3822
- TaskDeleteSchema,
3823
- MemoryDetailSchema,
3824
- TaskGetSchema,
3825
- TOOL_DEFINITIONS,
3826
3806
  encodeCursor,
3827
3807
  decodeCursor,
3828
3808
  listResources,
@@ -3840,5 +3820,25 @@ export {
3840
3820
  PROMPTS,
3841
3821
  listPrompts,
3842
3822
  getPrompt,
3843
- completePromptArgument
3823
+ completePromptArgument,
3824
+ normalizeRepo,
3825
+ MemoryStoreSchema,
3826
+ MemoryUpdateSchema,
3827
+ MemorySearchSchema,
3828
+ MemoryAcknowledgeSchema,
3829
+ MemoryRecapSchema,
3830
+ MemoryDeleteSchema,
3831
+ MemorySummarizeSchema,
3832
+ MemorySynthesizeSchema,
3833
+ TaskCreateSchema,
3834
+ TaskCreateInteractiveSchema,
3835
+ TaskUpdateSchema,
3836
+ TaskListSchema,
3837
+ TaskDeleteSchema,
3838
+ MemoryDetailSchema,
3839
+ TaskGetSchema,
3840
+ TOOL_DEFINITIONS,
3841
+ SQLiteStore,
3842
+ MCP_PROTOCOL_VERSION,
3843
+ CAPABILITIES
3844
3844
  };