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