@vheins/local-memory-mcp 0.8.3 → 0.8.4

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