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