@vheins/local-memory-mcp 0.16.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,5 +1,131 @@
|
|
|
1
|
-
// src/mcp/
|
|
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.1") {
|
|
85
|
+
pkgVersion = "0.16.1";
|
|
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
|
|
@@ -2888,8 +2912,8 @@ var HandoffEntity = class extends BaseEntity {
|
|
|
2888
2912
|
|
|
2889
2913
|
// src/mcp/storage/write-lock.ts
|
|
2890
2914
|
import lockfile from "proper-lockfile";
|
|
2891
|
-
import
|
|
2892
|
-
import
|
|
2915
|
+
import path3 from "path";
|
|
2916
|
+
import fs3 from "fs";
|
|
2893
2917
|
var LOCK_STALE_MS = 3e4;
|
|
2894
2918
|
var LOCK_RETRY_DELAY_MS = 200;
|
|
2895
2919
|
var LOCK_RETRY_COUNT = 250;
|
|
@@ -2898,9 +2922,9 @@ var WriteLock = class {
|
|
|
2898
2922
|
locked = false;
|
|
2899
2923
|
constructor(dbPath) {
|
|
2900
2924
|
this.lockTarget = dbPath;
|
|
2901
|
-
if (!
|
|
2902
|
-
|
|
2903
|
-
|
|
2925
|
+
if (!fs3.existsSync(dbPath)) {
|
|
2926
|
+
fs3.mkdirSync(path3.dirname(dbPath), { recursive: true });
|
|
2927
|
+
fs3.writeFileSync(dbPath, "");
|
|
2904
2928
|
}
|
|
2905
2929
|
}
|
|
2906
2930
|
/**
|
|
@@ -2952,13 +2976,13 @@ var WriteLock = class {
|
|
|
2952
2976
|
// src/mcp/storage/sqlite.ts
|
|
2953
2977
|
function resolveDbPath() {
|
|
2954
2978
|
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 (
|
|
2979
|
+
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");
|
|
2980
|
+
const standardPath = path4.join(standardConfigDir, "memory.db");
|
|
2981
|
+
if (fs4.existsSync(standardPath)) return standardPath;
|
|
2982
|
+
const legacyPath = path4.join(os.homedir(), ".config", "local-memory-mcp", "memory.db");
|
|
2983
|
+
if (fs4.existsSync(legacyPath)) return legacyPath;
|
|
2984
|
+
const localCwdFile = path4.join(process.cwd(), "storage", "memory.db");
|
|
2985
|
+
if (fs4.existsSync(localCwdFile)) return localCwdFile;
|
|
2962
2986
|
return standardPath;
|
|
2963
2987
|
}
|
|
2964
2988
|
var DB_PATH = resolveDbPath();
|
|
@@ -2981,9 +3005,9 @@ var SQLiteStore = class _SQLiteStore {
|
|
|
2981
3005
|
const finalPath = dbPath ?? DB_PATH;
|
|
2982
3006
|
this.dbPathInstance = finalPath;
|
|
2983
3007
|
if (finalPath !== ":memory:") {
|
|
2984
|
-
const dbDir =
|
|
2985
|
-
if (!
|
|
2986
|
-
|
|
3008
|
+
const dbDir = path4.dirname(finalPath);
|
|
3009
|
+
if (!fs4.existsSync(dbDir)) {
|
|
3010
|
+
fs4.mkdirSync(dbDir, { recursive: true });
|
|
2987
3011
|
}
|
|
2988
3012
|
}
|
|
2989
3013
|
this.db = new Database(finalPath);
|
|
@@ -3042,12 +3066,12 @@ var SQLiteStore = class _SQLiteStore {
|
|
|
3042
3066
|
*/
|
|
3043
3067
|
_attemptRecovery(dbPath) {
|
|
3044
3068
|
const backupPath = dbPath + ".backup";
|
|
3045
|
-
if (
|
|
3069
|
+
if (fs4.existsSync(backupPath)) {
|
|
3046
3070
|
logger.warn("[SQLiteStore] Attempting recovery from backup", { backupPath });
|
|
3047
3071
|
try {
|
|
3048
3072
|
const corruptPath = `${dbPath}.corrupt_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").slice(0, 15)}`;
|
|
3049
|
-
|
|
3050
|
-
|
|
3073
|
+
fs4.copyFileSync(dbPath, corruptPath);
|
|
3074
|
+
fs4.copyFileSync(backupPath, dbPath);
|
|
3051
3075
|
logger.warn("[SQLiteStore] Recovery successful. Corrupt file saved to", { corruptPath });
|
|
3052
3076
|
} catch (err) {
|
|
3053
3077
|
logger.error("[SQLiteStore] Recovery failed", { error: String(err) });
|
|
@@ -3065,7 +3089,7 @@ var SQLiteStore = class _SQLiteStore {
|
|
|
3065
3089
|
try {
|
|
3066
3090
|
this.db.pragma("wal_checkpoint(PASSIVE)");
|
|
3067
3091
|
const backupPath = this.dbPathInstance + ".backup";
|
|
3068
|
-
|
|
3092
|
+
fs4.copyFileSync(this.dbPathInstance, backupPath);
|
|
3069
3093
|
} catch (err) {
|
|
3070
3094
|
logger.warn("[SQLiteStore] Backup failed", { error: String(err) });
|
|
3071
3095
|
}
|
|
@@ -3187,716 +3211,173 @@ var RealVectorStore = class {
|
|
|
3187
3211
|
}
|
|
3188
3212
|
};
|
|
3189
3213
|
|
|
3190
|
-
// src/mcp/
|
|
3191
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3214
|
+
// src/mcp/session.ts
|
|
3192
3215
|
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);
|
|
3216
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3217
|
+
function createSessionContext() {
|
|
3232
3218
|
return {
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3219
|
+
roots: [],
|
|
3220
|
+
supportsRoots: false,
|
|
3221
|
+
supportsSampling: false,
|
|
3222
|
+
supportsSamplingTools: false,
|
|
3223
|
+
supportsElicitation: false,
|
|
3224
|
+
supportsElicitationForm: false,
|
|
3225
|
+
supportsElicitationUrl: false
|
|
3238
3226
|
};
|
|
3239
3227
|
}
|
|
3240
|
-
function
|
|
3241
|
-
const
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3228
|
+
function updateSessionFromInitialize(session, params) {
|
|
3229
|
+
const capabilities = params?.capabilities || {};
|
|
3230
|
+
session.clientInfo = params?.clientInfo;
|
|
3231
|
+
session.clientCapabilities = capabilities;
|
|
3232
|
+
session.supportsRoots = Boolean(capabilities.roots);
|
|
3233
|
+
session.supportsSampling = Boolean(capabilities.sampling);
|
|
3234
|
+
const sampling = capabilities.sampling;
|
|
3235
|
+
session.supportsSamplingTools = Boolean(sampling?.tools);
|
|
3236
|
+
session.supportsElicitation = Boolean(capabilities.elicitation);
|
|
3237
|
+
session.supportsElicitationForm = supportsElicitationMode(capabilities.elicitation, "form");
|
|
3238
|
+
session.supportsElicitationUrl = supportsElicitationMode(capabilities.elicitation, "url");
|
|
3239
|
+
}
|
|
3240
|
+
function supportsElicitationMode(capability, mode) {
|
|
3241
|
+
if (!capability || typeof capability !== "object") {
|
|
3242
|
+
return false;
|
|
3243
|
+
}
|
|
3244
|
+
const cap = capability;
|
|
3245
|
+
if (mode === "form") {
|
|
3246
|
+
return Object.keys(cap).length === 0 || typeof cap.form === "object";
|
|
3256
3247
|
}
|
|
3257
|
-
return
|
|
3248
|
+
return typeof cap.url === "object";
|
|
3258
3249
|
}
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
const
|
|
3262
|
-
|
|
3263
|
-
|
|
3250
|
+
function updateSessionRoots(session, roots) {
|
|
3251
|
+
const normalized = normalizeRoots(roots);
|
|
3252
|
+
const previous = JSON.stringify(session.roots);
|
|
3253
|
+
const next = JSON.stringify(normalized);
|
|
3254
|
+
session.roots = normalized;
|
|
3255
|
+
return previous !== next;
|
|
3256
|
+
}
|
|
3257
|
+
function normalizeRoots(roots) {
|
|
3258
|
+
if (!Array.isArray(roots)) return [];
|
|
3259
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3260
|
+
const normalized = [];
|
|
3261
|
+
for (const root of roots) {
|
|
3262
|
+
if (!root || typeof root !== "object") continue;
|
|
3263
|
+
const r = root;
|
|
3264
|
+
const uri = typeof r.uri === "string" ? r.uri : void 0;
|
|
3265
|
+
const name = typeof r.name === "string" ? r.name : void 0;
|
|
3266
|
+
if (!uri || seen.has(uri)) continue;
|
|
3267
|
+
seen.add(uri);
|
|
3268
|
+
normalized.push({ uri, name });
|
|
3264
3269
|
}
|
|
3265
|
-
|
|
3266
|
-
const { content } = matter(fileContent);
|
|
3267
|
-
return content.trim();
|
|
3270
|
+
return normalized;
|
|
3268
3271
|
}
|
|
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");
|
|
3272
|
+
function extractRootsFromResult(result) {
|
|
3273
|
+
return normalizeRoots(result?.roots);
|
|
3274
|
+
}
|
|
3275
|
+
function getFilesystemRoots(session) {
|
|
3276
|
+
if (!session) return [];
|
|
3277
|
+
const resolved = [];
|
|
3278
|
+
for (const root of session.roots) {
|
|
3279
|
+
if (!root.uri.startsWith("file://")) continue;
|
|
3279
3280
|
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
|
-
}
|
|
3281
|
+
resolved.push(path5.resolve(fileURLToPath3(root.uri)));
|
|
3287
3282
|
} catch {
|
|
3288
3283
|
}
|
|
3289
|
-
searchDir = path5.dirname(searchDir);
|
|
3290
3284
|
}
|
|
3285
|
+
return resolved;
|
|
3291
3286
|
}
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
}
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
},
|
|
3310
|
-
prompts: {
|
|
3311
|
-
listChanged: true
|
|
3287
|
+
function isPathWithinRoots(targetPath, session) {
|
|
3288
|
+
const roots = getFilesystemRoots(session);
|
|
3289
|
+
if (roots.length === 0) return true;
|
|
3290
|
+
const normalizedTarget = path5.resolve(targetPath);
|
|
3291
|
+
return roots.some((rootPath) => {
|
|
3292
|
+
const relative = path5.relative(rootPath, normalizedTarget);
|
|
3293
|
+
return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
|
|
3294
|
+
});
|
|
3295
|
+
}
|
|
3296
|
+
function findContainingRoot(targetPath, session) {
|
|
3297
|
+
const roots = getFilesystemRoots(session);
|
|
3298
|
+
if (roots.length === 0) return null;
|
|
3299
|
+
const normalizedTarget = path5.resolve(targetPath);
|
|
3300
|
+
for (const rootPath of roots) {
|
|
3301
|
+
const relative = path5.relative(rootPath, normalizedTarget);
|
|
3302
|
+
if (relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative)) {
|
|
3303
|
+
return rootPath;
|
|
3312
3304
|
}
|
|
3313
3305
|
}
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
// src/mcp/utils/pagination.ts
|
|
3317
|
-
function encodeCursor(offset) {
|
|
3318
|
-
return Buffer.from(String(offset), "utf8").toString("base64");
|
|
3306
|
+
return null;
|
|
3319
3307
|
}
|
|
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");
|
|
3308
|
+
function inferRepoFromSession(session) {
|
|
3309
|
+
const roots = getFilesystemRoots(session);
|
|
3310
|
+
if (roots.length === 1) {
|
|
3311
|
+
return path5.basename(roots[0]);
|
|
3339
3312
|
}
|
|
3340
|
-
return
|
|
3341
|
-
}
|
|
3342
|
-
function invalidPaginationParams(message) {
|
|
3343
|
-
const error = new Error(message);
|
|
3344
|
-
error.code = -32602;
|
|
3345
|
-
return error;
|
|
3313
|
+
return void 0;
|
|
3346
3314
|
}
|
|
3347
3315
|
|
|
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"
|
|
3316
|
+
// src/mcp/tools/schemas.ts
|
|
3317
|
+
import { z } from "zod";
|
|
3318
|
+
var MemoryScopeSchema = z.object({
|
|
3319
|
+
repo: z.string().min(1).transform(normalizeRepo),
|
|
3320
|
+
branch: z.string().optional(),
|
|
3321
|
+
folder: z.string().optional(),
|
|
3322
|
+
language: z.string().optional()
|
|
3323
|
+
});
|
|
3324
|
+
var MemoryTypeSchema = z.enum(["code_fact", "decision", "mistake", "pattern", "task_archive"]);
|
|
3325
|
+
var SingleMemorySchema = z.object({
|
|
3326
|
+
code: z.string().max(20).optional(),
|
|
3327
|
+
type: MemoryTypeSchema,
|
|
3328
|
+
title: z.string().min(3).max(255),
|
|
3329
|
+
content: z.string().min(10),
|
|
3330
|
+
importance: z.number().min(1).max(5),
|
|
3331
|
+
agent: z.string().min(1),
|
|
3332
|
+
role: z.string().optional().default("unknown"),
|
|
3333
|
+
model: z.string().min(1),
|
|
3334
|
+
scope: MemoryScopeSchema,
|
|
3335
|
+
ttlDays: z.number().min(1).optional(),
|
|
3336
|
+
supersedes: z.string().optional(),
|
|
3337
|
+
tags: z.array(z.string()).optional(),
|
|
3338
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
3339
|
+
is_global: z.boolean().default(false)
|
|
3340
|
+
});
|
|
3341
|
+
var SingleStandardSchema = z.object({
|
|
3342
|
+
name: z.string().min(3).max(255),
|
|
3343
|
+
content: z.string().min(10),
|
|
3344
|
+
parent_id: z.string().optional(),
|
|
3345
|
+
context: z.string().optional(),
|
|
3346
|
+
version: z.string().optional(),
|
|
3347
|
+
language: z.string().optional(),
|
|
3348
|
+
stack: z.array(z.string()).optional(),
|
|
3349
|
+
is_global: z.boolean().optional(),
|
|
3350
|
+
tags: z.array(z.string().min(1)).min(1),
|
|
3351
|
+
metadata: z.record(z.string(), z.any()).refine((value) => Object.keys(value).length > 0, {
|
|
3352
|
+
message: "metadata must contain at least one key"
|
|
3353
|
+
}),
|
|
3354
|
+
agent: z.string().optional(),
|
|
3355
|
+
model: z.string().optional()
|
|
3356
|
+
});
|
|
3357
|
+
var MemoryStoreSchema = z.object({
|
|
3358
|
+
code: z.string().max(20).optional(),
|
|
3359
|
+
type: MemoryTypeSchema.optional(),
|
|
3360
|
+
title: z.string().min(3).max(255).optional(),
|
|
3361
|
+
content: z.string().min(10).optional(),
|
|
3362
|
+
importance: z.number().min(1).max(5).optional(),
|
|
3363
|
+
agent: z.string().min(1).optional(),
|
|
3364
|
+
role: z.string().optional().default("unknown"),
|
|
3365
|
+
model: z.string().min(1).optional(),
|
|
3366
|
+
scope: MemoryScopeSchema.optional(),
|
|
3367
|
+
ttlDays: z.number().min(1).optional(),
|
|
3368
|
+
supersedes: z.string().optional(),
|
|
3369
|
+
tags: z.array(z.string()).optional(),
|
|
3370
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
3371
|
+
is_global: z.boolean().default(false),
|
|
3372
|
+
structured: z.boolean().default(false),
|
|
3373
|
+
memories: z.array(SingleMemorySchema).min(1).optional()
|
|
3374
|
+
}).refine(
|
|
3375
|
+
(data) => {
|
|
3376
|
+
if (data.memories) return true;
|
|
3377
|
+
return !!(data.type && data.title && data.content && data.importance && data.agent && data.model && data.scope);
|
|
3378
|
+
},
|
|
3379
|
+
{
|
|
3380
|
+
message: "Either 'memories' array or single memory fields (type, title, content, importance, agent, model, scope) must be provided"
|
|
3900
3381
|
}
|
|
3901
3382
|
);
|
|
3902
3383
|
var MemoryUpdateSchema = z.object({
|
|
@@ -5642,100 +5123,619 @@ var TOOL_DEFINITIONS = [
|
|
|
5642
5123
|
destructiveHint: false,
|
|
5643
5124
|
openWorldHint: false
|
|
5644
5125
|
},
|
|
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
|
-
}
|
|
5126
|
+
inputSchema: {
|
|
5127
|
+
type: "object",
|
|
5128
|
+
properties: {
|
|
5129
|
+
id: { type: "string", format: "uuid", description: "Standard ID to update. Optional if code is provided." },
|
|
5130
|
+
code: { type: "string", maxLength: 20, description: "Short standard code. Optional if id is provided." },
|
|
5131
|
+
name: { type: "string", minLength: 3, maxLength: 255 },
|
|
5132
|
+
content: { type: "string", minLength: 10 },
|
|
5133
|
+
parent_id: { type: "string", nullable: true },
|
|
5134
|
+
context: { type: "string" },
|
|
5135
|
+
version: { type: "string" },
|
|
5136
|
+
language: { type: "string" },
|
|
5137
|
+
stack: { type: "array", items: { type: "string" } },
|
|
5138
|
+
repo: { type: "string" },
|
|
5139
|
+
is_global: { type: "boolean" },
|
|
5140
|
+
tags: { type: "array", items: { type: "string" } },
|
|
5141
|
+
metadata: { type: "object" },
|
|
5142
|
+
agent: { type: "string" },
|
|
5143
|
+
model: { type: "string" },
|
|
5144
|
+
structured: { type: "boolean", default: false }
|
|
5145
|
+
}
|
|
5146
|
+
},
|
|
5147
|
+
outputSchema: {
|
|
5148
|
+
type: "object",
|
|
5149
|
+
properties: {
|
|
5150
|
+
success: { type: "boolean" },
|
|
5151
|
+
id: { type: "string" },
|
|
5152
|
+
updatedFields: { type: "array", items: { type: "string" } }
|
|
5153
|
+
},
|
|
5154
|
+
required: ["success", "id", "updatedFields"]
|
|
5155
|
+
}
|
|
5156
|
+
},
|
|
5157
|
+
{
|
|
5158
|
+
name: "standard-search",
|
|
5159
|
+
title: "Standard Search",
|
|
5160
|
+
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.",
|
|
5161
|
+
annotations: {
|
|
5162
|
+
readOnlyHint: true,
|
|
5163
|
+
idempotentHint: true,
|
|
5164
|
+
openWorldHint: false
|
|
5165
|
+
},
|
|
5166
|
+
inputSchema: {
|
|
5167
|
+
type: "object",
|
|
5168
|
+
properties: {
|
|
5169
|
+
query: { type: "string", description: "Search query (optional, searches title/content)" },
|
|
5170
|
+
stack: {
|
|
5171
|
+
type: "array",
|
|
5172
|
+
items: { type: "string" },
|
|
5173
|
+
description: "Technology stack to filter by (e.g., ['react', 'nextjs'])"
|
|
5174
|
+
},
|
|
5175
|
+
tags: {
|
|
5176
|
+
type: "array",
|
|
5177
|
+
items: { type: "string" },
|
|
5178
|
+
description: "Tag filter"
|
|
5179
|
+
},
|
|
5180
|
+
language: { type: "string", description: "Programming language filter" },
|
|
5181
|
+
context: { type: "string", description: "Context/category filter" },
|
|
5182
|
+
version: { type: "string", description: "Version filter" },
|
|
5183
|
+
repo: { type: "string", description: "Repository filter (optional)" },
|
|
5184
|
+
is_global: { type: "boolean", description: "Filter by global/repo-specific" },
|
|
5185
|
+
limit: { type: "number", minimum: 1, maximum: 100, default: 20 },
|
|
5186
|
+
offset: { type: "number", minimum: 0, default: 0 },
|
|
5187
|
+
structured: { type: "boolean", default: false }
|
|
5188
|
+
},
|
|
5189
|
+
required: []
|
|
5190
|
+
},
|
|
5191
|
+
outputSchema: {
|
|
5192
|
+
type: "object",
|
|
5193
|
+
properties: {
|
|
5194
|
+
schema: { type: "string", enum: ["standard-search"] },
|
|
5195
|
+
query: { type: "string" },
|
|
5196
|
+
count: { type: "number", description: "Number of rows returned" },
|
|
5197
|
+
total: { type: "number", description: "Total number of matches before pagination" },
|
|
5198
|
+
offset: { type: "number" },
|
|
5199
|
+
limit: { type: "number" },
|
|
5200
|
+
results: {
|
|
5201
|
+
type: "object",
|
|
5202
|
+
properties: {
|
|
5203
|
+
columns: {
|
|
5204
|
+
type: "array",
|
|
5205
|
+
items: { type: "string" }
|
|
5206
|
+
},
|
|
5207
|
+
rows: {
|
|
5208
|
+
type: "array",
|
|
5209
|
+
items: { type: "array" },
|
|
5210
|
+
description: "Each row includes standard id and pointer metadata. Fetch full content via standard-detail."
|
|
5211
|
+
}
|
|
5212
|
+
},
|
|
5213
|
+
required: ["columns", "rows"]
|
|
5214
|
+
}
|
|
5215
|
+
},
|
|
5216
|
+
required: ["schema", "query", "count", "total", "offset", "limit", "results"]
|
|
5217
|
+
}
|
|
5218
|
+
}
|
|
5219
|
+
];
|
|
5220
|
+
|
|
5221
|
+
// src/mcp/utils/pagination.ts
|
|
5222
|
+
function encodeCursor(offset) {
|
|
5223
|
+
return Buffer.from(String(offset), "utf8").toString("base64");
|
|
5224
|
+
}
|
|
5225
|
+
function decodeCursor(cursor) {
|
|
5226
|
+
if (cursor === void 0 || cursor === null || cursor === "") {
|
|
5227
|
+
return 0;
|
|
5228
|
+
}
|
|
5229
|
+
if (typeof cursor !== "string" || cursor.trim() === "") {
|
|
5230
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
5231
|
+
}
|
|
5232
|
+
let decoded;
|
|
5233
|
+
try {
|
|
5234
|
+
decoded = Buffer.from(cursor, "base64").toString("utf8");
|
|
5235
|
+
} catch {
|
|
5236
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
5237
|
+
}
|
|
5238
|
+
if (!/^\d+$/.test(decoded)) {
|
|
5239
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
5240
|
+
}
|
|
5241
|
+
const offset = Number.parseInt(decoded, 10);
|
|
5242
|
+
if (!Number.isFinite(offset) || offset < 0) {
|
|
5243
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
5244
|
+
}
|
|
5245
|
+
return offset;
|
|
5246
|
+
}
|
|
5247
|
+
function invalidPaginationParams(message) {
|
|
5248
|
+
const error = new Error(message);
|
|
5249
|
+
error.code = -32602;
|
|
5250
|
+
return error;
|
|
5251
|
+
}
|
|
5252
|
+
|
|
5253
|
+
// src/mcp/utils/completion.ts
|
|
5254
|
+
var MAX_COMPLETION_VALUES = 100;
|
|
5255
|
+
function rankCompletionValues(candidates, input) {
|
|
5256
|
+
const unique = [...new Set(candidates.filter(Boolean))];
|
|
5257
|
+
const needle = input.trim().toLowerCase();
|
|
5258
|
+
if (!needle) {
|
|
5259
|
+
return unique.slice(0, MAX_COMPLETION_VALUES);
|
|
5260
|
+
}
|
|
5261
|
+
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);
|
|
5262
|
+
}
|
|
5263
|
+
function scoreCompletionValue(value, needle) {
|
|
5264
|
+
const haystack = value.toLowerCase();
|
|
5265
|
+
if (haystack === needle) return 100;
|
|
5266
|
+
if (haystack.startsWith(needle)) return 75;
|
|
5267
|
+
if (haystack.includes(needle)) return 50;
|
|
5268
|
+
const compactNeedle = needle.replace(/[\s_-]+/g, "");
|
|
5269
|
+
const compactHaystack = haystack.replace(/[\s_-]+/g, "");
|
|
5270
|
+
if (compactNeedle && compactHaystack.includes(compactNeedle)) return 25;
|
|
5271
|
+
return 0;
|
|
5272
|
+
}
|
|
5273
|
+
|
|
5274
|
+
// src/mcp/resources/index.ts
|
|
5275
|
+
var DEFAULT_PAGE_SIZE = 25;
|
|
5276
|
+
var MAX_PAGE_SIZE = 100;
|
|
5277
|
+
function listResources(session, params) {
|
|
5278
|
+
const resources = [
|
|
5279
|
+
{
|
|
5280
|
+
uri: "repository://index",
|
|
5281
|
+
name: "Repository Index",
|
|
5282
|
+
title: "Repository Index",
|
|
5283
|
+
description: "List of all known repositories with memory/task counts and last activity",
|
|
5284
|
+
mimeType: "application/json",
|
|
5285
|
+
annotations: {
|
|
5286
|
+
audience: ["assistant"],
|
|
5287
|
+
priority: 1,
|
|
5288
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
5289
|
+
}
|
|
5290
|
+
},
|
|
5291
|
+
{
|
|
5292
|
+
uri: "session://roots",
|
|
5293
|
+
name: "Session Roots",
|
|
5294
|
+
title: "Session Roots",
|
|
5295
|
+
description: session?.roots.length ? "Active workspace roots provided by the MCP client" : "No active workspace roots were provided by the MCP client",
|
|
5296
|
+
mimeType: "application/json",
|
|
5297
|
+
size: Buffer.byteLength(JSON.stringify({ roots: session?.roots ?? [] }), "utf8"),
|
|
5298
|
+
annotations: {
|
|
5299
|
+
audience: ["assistant"],
|
|
5300
|
+
priority: 0.95,
|
|
5301
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
5304
|
+
];
|
|
5305
|
+
return paginateEntries("resources", resources, params);
|
|
5306
|
+
}
|
|
5307
|
+
function listResourceTemplates(params) {
|
|
5308
|
+
const templates = [
|
|
5309
|
+
// ── Memory ──────────────────────────────────────────────────────────────
|
|
5310
|
+
{
|
|
5311
|
+
uriTemplate: "repository://{name}/memories",
|
|
5312
|
+
name: "Repository Memories",
|
|
5313
|
+
title: "Repository Memories",
|
|
5314
|
+
description: "All active memory entries for a specific repository",
|
|
5315
|
+
mimeType: "application/json",
|
|
5316
|
+
annotations: { audience: ["assistant"], priority: 0.85 }
|
|
5317
|
+
},
|
|
5318
|
+
{
|
|
5319
|
+
uriTemplate: "repository://{name}/memories?search={search}&type={type}&tag={tag}",
|
|
5320
|
+
name: "Filtered Repository Memories",
|
|
5321
|
+
title: "Filtered Repository Memories",
|
|
5322
|
+
description: "Filter or search memories within a repository by keyword, type, or tag",
|
|
5323
|
+
mimeType: "application/json",
|
|
5324
|
+
annotations: { audience: ["assistant"], priority: 0.8 }
|
|
5325
|
+
},
|
|
5326
|
+
{
|
|
5327
|
+
uriTemplate: "memory://{id}",
|
|
5328
|
+
name: "Memory Detail",
|
|
5329
|
+
title: "Memory Detail",
|
|
5330
|
+
description: "Full content and statistics for a specific memory UUID",
|
|
5331
|
+
mimeType: "application/json",
|
|
5332
|
+
annotations: { audience: ["assistant"], priority: 0.75 }
|
|
5333
|
+
},
|
|
5334
|
+
// ── Tasks ────────────────────────────────────────────────────────────────
|
|
5335
|
+
{
|
|
5336
|
+
uriTemplate: "repository://{name}/tasks",
|
|
5337
|
+
name: "Repository Tasks",
|
|
5338
|
+
title: "Repository Tasks",
|
|
5339
|
+
description: "All active tasks for a specific repository",
|
|
5340
|
+
mimeType: "application/json",
|
|
5341
|
+
annotations: { audience: ["assistant"], priority: 0.9 }
|
|
5342
|
+
},
|
|
5343
|
+
{
|
|
5344
|
+
uriTemplate: "repository://{name}/tasks?status={status}&priority={priority}",
|
|
5345
|
+
name: "Filtered Repository Tasks",
|
|
5346
|
+
title: "Filtered Repository Tasks",
|
|
5347
|
+
description: "Filter tasks within a repository by status or priority level",
|
|
5348
|
+
mimeType: "application/json",
|
|
5349
|
+
annotations: { audience: ["assistant"], priority: 0.85 }
|
|
5350
|
+
},
|
|
5351
|
+
{
|
|
5352
|
+
uriTemplate: "task://{id}",
|
|
5353
|
+
name: "Task Detail",
|
|
5354
|
+
title: "Task Detail",
|
|
5355
|
+
description: "Full content and comments for a specific task UUID",
|
|
5356
|
+
mimeType: "application/json",
|
|
5357
|
+
annotations: { audience: ["assistant"], priority: 0.8 }
|
|
5665
5358
|
},
|
|
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
|
|
5359
|
+
// ── Repository extras ────────────────────────────────────────────────────
|
|
5360
|
+
{
|
|
5361
|
+
uriTemplate: "repository://{name}/summary",
|
|
5362
|
+
name: "Repository Summary",
|
|
5363
|
+
title: "Repository Summary",
|
|
5364
|
+
description: "High-level architectural summary for a repository",
|
|
5365
|
+
mimeType: "text/plain",
|
|
5366
|
+
annotations: { audience: ["assistant"], priority: 0.95 }
|
|
5684
5367
|
},
|
|
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: []
|
|
5368
|
+
{
|
|
5369
|
+
uriTemplate: "repository://{name}/actions",
|
|
5370
|
+
name: "Repository Actions",
|
|
5371
|
+
title: "Repository Actions",
|
|
5372
|
+
description: "Audit log of agent tool actions scoped to a repository",
|
|
5373
|
+
mimeType: "application/json",
|
|
5374
|
+
annotations: { audience: ["assistant"], priority: 0.6 }
|
|
5709
5375
|
},
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5376
|
+
// ── Action detail ────────────────────────────────────────────────────────
|
|
5377
|
+
{
|
|
5378
|
+
uriTemplate: "action://{id}",
|
|
5379
|
+
name: "Action Detail",
|
|
5380
|
+
title: "Action Detail",
|
|
5381
|
+
description: "Full details of a specific audit log entry by integer ID",
|
|
5382
|
+
mimeType: "application/json",
|
|
5383
|
+
annotations: { audience: ["assistant"], priority: 0.55 }
|
|
5384
|
+
}
|
|
5385
|
+
];
|
|
5386
|
+
return paginateEntries("resourceTemplates", templates, params);
|
|
5387
|
+
}
|
|
5388
|
+
function completeResourceArgument(resourceUri, argumentName, argumentValue, _contextArguments, dataSources) {
|
|
5389
|
+
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") {
|
|
5390
|
+
if (argumentName === "name") {
|
|
5391
|
+
return rankCompletionValues(dataSources.repos, argumentValue);
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
if (resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}") {
|
|
5395
|
+
if (argumentName === "tag") {
|
|
5396
|
+
return rankCompletionValues(dataSources.tags, argumentValue);
|
|
5397
|
+
}
|
|
5398
|
+
}
|
|
5399
|
+
throw invalidCompletionParams(`Unknown resource template or argument: ${resourceUri} (${argumentName})`);
|
|
5400
|
+
}
|
|
5401
|
+
function readResource(uri, db, session) {
|
|
5402
|
+
logger.info("[Tool] resource.read", { uri });
|
|
5403
|
+
if (uri === "repository://index") {
|
|
5404
|
+
const repos = db.system.listRepoNavigation();
|
|
5405
|
+
const payload = JSON.stringify(repos, null, 2);
|
|
5406
|
+
return {
|
|
5407
|
+
contents: [
|
|
5408
|
+
{
|
|
5409
|
+
uri,
|
|
5410
|
+
mimeType: "application/json",
|
|
5411
|
+
text: payload,
|
|
5412
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
5413
|
+
annotations: {
|
|
5414
|
+
audience: ["assistant"],
|
|
5415
|
+
priority: 1,
|
|
5416
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
5417
|
+
}
|
|
5418
|
+
}
|
|
5419
|
+
]
|
|
5420
|
+
};
|
|
5421
|
+
}
|
|
5422
|
+
if (uri === "session://roots") {
|
|
5423
|
+
const payload = JSON.stringify({ roots: session?.roots ?? [] }, null, 2);
|
|
5424
|
+
return {
|
|
5425
|
+
contents: [
|
|
5426
|
+
{
|
|
5427
|
+
uri,
|
|
5428
|
+
mimeType: "application/json",
|
|
5429
|
+
text: payload,
|
|
5430
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
5431
|
+
annotations: {
|
|
5432
|
+
audience: ["assistant"],
|
|
5433
|
+
priority: 0.95,
|
|
5434
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5437
|
+
]
|
|
5438
|
+
};
|
|
5439
|
+
}
|
|
5440
|
+
const memoryIdMatch = uri.match(/^memory:\/\/([0-9a-f-]{36})$/i);
|
|
5441
|
+
if (memoryIdMatch) {
|
|
5442
|
+
const id = memoryIdMatch[1];
|
|
5443
|
+
const entry = db.memories.getByIdWithStats(id);
|
|
5444
|
+
if (!entry) throw resourceNotFound(`Memory with ID ${id} not found.`, uri);
|
|
5445
|
+
const payload = JSON.stringify(entry, null, 2);
|
|
5446
|
+
return {
|
|
5447
|
+
contents: [
|
|
5448
|
+
{
|
|
5449
|
+
uri,
|
|
5450
|
+
mimeType: "application/json",
|
|
5451
|
+
text: payload,
|
|
5452
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
5453
|
+
annotations: {
|
|
5454
|
+
audience: ["assistant"],
|
|
5455
|
+
priority: 0.75,
|
|
5456
|
+
lastModified: entry.updated_at || entry.created_at
|
|
5457
|
+
}
|
|
5458
|
+
}
|
|
5459
|
+
]
|
|
5460
|
+
};
|
|
5461
|
+
}
|
|
5462
|
+
const taskIdMatch = uri.match(/^task:\/\/([0-9a-f-]{36})$/i);
|
|
5463
|
+
if (taskIdMatch) {
|
|
5464
|
+
const id = taskIdMatch[1];
|
|
5465
|
+
const task = db.tasks.getTaskById(id);
|
|
5466
|
+
if (!task) throw resourceNotFound(`Task with ID ${id} not found.`, uri);
|
|
5467
|
+
const payload = JSON.stringify(task, null, 2);
|
|
5468
|
+
return {
|
|
5469
|
+
contents: [
|
|
5470
|
+
{
|
|
5471
|
+
uri,
|
|
5472
|
+
mimeType: "application/json",
|
|
5473
|
+
text: payload,
|
|
5474
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
5475
|
+
annotations: {
|
|
5476
|
+
audience: ["assistant"],
|
|
5477
|
+
priority: 0.8,
|
|
5478
|
+
lastModified: task.updated_at || task.created_at
|
|
5479
|
+
}
|
|
5480
|
+
}
|
|
5481
|
+
]
|
|
5482
|
+
};
|
|
5483
|
+
}
|
|
5484
|
+
const repoBase = parseRepoUri(uri);
|
|
5485
|
+
if (repoBase) {
|
|
5486
|
+
const { name, path: repoPath, query } = repoBase;
|
|
5487
|
+
if (repoPath === "summary") {
|
|
5488
|
+
const summary = db.summaries.getSummary(name);
|
|
5489
|
+
const text = summary?.summary || `No summary available for repository: ${name}`;
|
|
5490
|
+
return {
|
|
5491
|
+
contents: [
|
|
5492
|
+
{
|
|
5493
|
+
uri,
|
|
5494
|
+
mimeType: "text/plain",
|
|
5495
|
+
text,
|
|
5496
|
+
size: Buffer.byteLength(text, "utf8"),
|
|
5497
|
+
annotations: {
|
|
5498
|
+
audience: ["assistant"],
|
|
5499
|
+
priority: 0.95,
|
|
5500
|
+
lastModified: summary?.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
]
|
|
5504
|
+
};
|
|
5505
|
+
}
|
|
5506
|
+
if (repoPath === "memories") {
|
|
5507
|
+
const search = query.get("search") || "";
|
|
5508
|
+
const type = query.get("type");
|
|
5509
|
+
const tag = query.get("tag");
|
|
5510
|
+
const result = db.memories.listMemoriesForDashboard({
|
|
5511
|
+
repo: name,
|
|
5512
|
+
type: type || void 0,
|
|
5513
|
+
tag: tag || void 0,
|
|
5514
|
+
search: search || void 0,
|
|
5515
|
+
limit: 50
|
|
5516
|
+
});
|
|
5517
|
+
const entries = result.items;
|
|
5518
|
+
const payload = JSON.stringify(entries, null, 2);
|
|
5519
|
+
return {
|
|
5520
|
+
contents: [
|
|
5521
|
+
{
|
|
5522
|
+
uri,
|
|
5523
|
+
mimeType: "application/json",
|
|
5524
|
+
text: payload,
|
|
5525
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
5526
|
+
annotations: {
|
|
5527
|
+
audience: ["assistant"],
|
|
5528
|
+
priority: 0.85,
|
|
5529
|
+
lastModified: deriveLastModifiedFromCollection(
|
|
5530
|
+
entries.map((e) => e.updated_at || e.created_at)
|
|
5531
|
+
)
|
|
5730
5532
|
}
|
|
5731
|
-
}
|
|
5732
|
-
|
|
5533
|
+
}
|
|
5534
|
+
]
|
|
5535
|
+
};
|
|
5536
|
+
}
|
|
5537
|
+
if (repoPath === "tasks") {
|
|
5538
|
+
const status = query.get("status");
|
|
5539
|
+
const priority = query.get("priority");
|
|
5540
|
+
let tasks;
|
|
5541
|
+
if (status && status !== "all") {
|
|
5542
|
+
const statuses = status.split(",").map((s) => s.trim());
|
|
5543
|
+
tasks = db.tasks.getTasksByMultipleStatuses(name, statuses);
|
|
5544
|
+
} else {
|
|
5545
|
+
tasks = db.tasks.getTasksByMultipleStatuses(name, ["backlog", "pending", "in_progress", "blocked"]);
|
|
5546
|
+
}
|
|
5547
|
+
if (priority) {
|
|
5548
|
+
const p = Number(priority);
|
|
5549
|
+
if (!isNaN(p)) {
|
|
5550
|
+
tasks = tasks.filter((t) => t.priority === p);
|
|
5733
5551
|
}
|
|
5734
|
-
}
|
|
5735
|
-
|
|
5552
|
+
}
|
|
5553
|
+
const payload = JSON.stringify(tasks, null, 2);
|
|
5554
|
+
return {
|
|
5555
|
+
contents: [
|
|
5556
|
+
{
|
|
5557
|
+
uri,
|
|
5558
|
+
mimeType: "application/json",
|
|
5559
|
+
text: payload,
|
|
5560
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
5561
|
+
annotations: {
|
|
5562
|
+
audience: ["assistant"],
|
|
5563
|
+
priority: 0.9,
|
|
5564
|
+
lastModified: deriveLastModifiedFromCollection(tasks.map((t) => t.updated_at))
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5567
|
+
]
|
|
5568
|
+
};
|
|
5569
|
+
}
|
|
5570
|
+
if (repoPath === "actions") {
|
|
5571
|
+
const actions = db.actions.getRecentActions(name, 100);
|
|
5572
|
+
const payload = JSON.stringify(actions, null, 2);
|
|
5573
|
+
return {
|
|
5574
|
+
contents: [
|
|
5575
|
+
{
|
|
5576
|
+
uri,
|
|
5577
|
+
mimeType: "application/json",
|
|
5578
|
+
text: payload,
|
|
5579
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
5580
|
+
annotations: {
|
|
5581
|
+
audience: ["assistant"],
|
|
5582
|
+
priority: 0.6,
|
|
5583
|
+
lastModified: deriveLastModifiedFromCollection(actions.map((a) => a.created_at))
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5586
|
+
]
|
|
5587
|
+
};
|
|
5736
5588
|
}
|
|
5737
5589
|
}
|
|
5738
|
-
|
|
5590
|
+
const actionIdMatch = uri.match(/^action:\/\/(\d+)$/);
|
|
5591
|
+
if (actionIdMatch) {
|
|
5592
|
+
const id = Number(actionIdMatch[1]);
|
|
5593
|
+
const action = db.actions.getActionById(id);
|
|
5594
|
+
if (!action) throw resourceNotFound(`Action with ID ${id} not found.`, uri);
|
|
5595
|
+
const payload = JSON.stringify(action, null, 2);
|
|
5596
|
+
return {
|
|
5597
|
+
contents: [
|
|
5598
|
+
{
|
|
5599
|
+
uri,
|
|
5600
|
+
mimeType: "application/json",
|
|
5601
|
+
text: payload,
|
|
5602
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
5603
|
+
annotations: {
|
|
5604
|
+
audience: ["assistant"],
|
|
5605
|
+
priority: 0.55,
|
|
5606
|
+
lastModified: action.created_at
|
|
5607
|
+
}
|
|
5608
|
+
}
|
|
5609
|
+
]
|
|
5610
|
+
};
|
|
5611
|
+
}
|
|
5612
|
+
throw resourceNotFound(`Unknown resource URI: ${uri}`, uri);
|
|
5613
|
+
}
|
|
5614
|
+
function parseRepoUri(uri) {
|
|
5615
|
+
const prefix = "repository://";
|
|
5616
|
+
if (!uri.startsWith(prefix)) return null;
|
|
5617
|
+
const rest = uri.slice(prefix.length);
|
|
5618
|
+
const queryStart = rest.indexOf("?");
|
|
5619
|
+
const withoutQuery = queryStart === -1 ? rest : rest.slice(0, queryStart);
|
|
5620
|
+
const queryString = queryStart === -1 ? "" : rest.slice(queryStart + 1);
|
|
5621
|
+
const slashIdx = withoutQuery.indexOf("/");
|
|
5622
|
+
if (slashIdx === -1) return null;
|
|
5623
|
+
const name = withoutQuery.slice(0, slashIdx);
|
|
5624
|
+
const path6 = withoutQuery.slice(slashIdx + 1);
|
|
5625
|
+
if (!name || !path6) return null;
|
|
5626
|
+
return { name, path: path6, query: new URLSearchParams(queryString) };
|
|
5627
|
+
}
|
|
5628
|
+
function paginateEntries(key, entries, params) {
|
|
5629
|
+
const limit = normalizeLimit(params?.limit);
|
|
5630
|
+
const offset = decodeCursor(params?.cursor);
|
|
5631
|
+
const sliced = entries.slice(offset, offset + limit);
|
|
5632
|
+
const nextOffset = offset + sliced.length;
|
|
5633
|
+
return {
|
|
5634
|
+
[key]: sliced,
|
|
5635
|
+
nextCursor: nextOffset < entries.length ? encodeCursor(nextOffset) : void 0
|
|
5636
|
+
};
|
|
5637
|
+
}
|
|
5638
|
+
function normalizeLimit(limit) {
|
|
5639
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
5640
|
+
return DEFAULT_PAGE_SIZE;
|
|
5641
|
+
}
|
|
5642
|
+
return Math.min(MAX_PAGE_SIZE, Math.max(1, Math.trunc(limit)));
|
|
5643
|
+
}
|
|
5644
|
+
function deriveLastModifiedFromCollection(values) {
|
|
5645
|
+
const normalized = values.filter((value) => typeof value === "string" && value.length > 0);
|
|
5646
|
+
return normalized.sort().at(-1) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5647
|
+
}
|
|
5648
|
+
function resourceNotFound(message, uri) {
|
|
5649
|
+
const error = new Error(message);
|
|
5650
|
+
error.code = -32002;
|
|
5651
|
+
error.data = { uri };
|
|
5652
|
+
return error;
|
|
5653
|
+
}
|
|
5654
|
+
function invalidCompletionParams(message) {
|
|
5655
|
+
const error = new Error(message);
|
|
5656
|
+
error.code = -32602;
|
|
5657
|
+
return error;
|
|
5658
|
+
}
|
|
5659
|
+
|
|
5660
|
+
// src/mcp/prompts/registry.ts
|
|
5661
|
+
function createPromptDefinition(loaded) {
|
|
5662
|
+
return {
|
|
5663
|
+
name: loaded.name,
|
|
5664
|
+
description: loaded.description,
|
|
5665
|
+
arguments: loaded.arguments,
|
|
5666
|
+
agent: loaded.agent,
|
|
5667
|
+
messages: [
|
|
5668
|
+
{
|
|
5669
|
+
role: "user",
|
|
5670
|
+
content: {
|
|
5671
|
+
type: "text",
|
|
5672
|
+
text: loaded.content
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5675
|
+
]
|
|
5676
|
+
};
|
|
5677
|
+
}
|
|
5678
|
+
var PROMPTS = {};
|
|
5679
|
+
var promptFiles = listPromptFiles();
|
|
5680
|
+
for (const name of promptFiles) {
|
|
5681
|
+
try {
|
|
5682
|
+
PROMPTS[name] = createPromptDefinition(loadPromptFromMarkdown(name));
|
|
5683
|
+
} catch (e) {
|
|
5684
|
+
logger.warn(`Failed to load prompt ${name}: ${e}`);
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
async function listPrompts(db, session, params) {
|
|
5688
|
+
const allPrompts = Object.values(PROMPTS).map((p) => ({
|
|
5689
|
+
name: p.name,
|
|
5690
|
+
description: p.description,
|
|
5691
|
+
arguments: p.arguments,
|
|
5692
|
+
metadata: p.agent ? { agent: p.agent } : void 0
|
|
5693
|
+
}));
|
|
5694
|
+
const rawLimit = typeof params?.limit === "number" && Number.isInteger(params?.limit) ? params.limit : 50;
|
|
5695
|
+
const limit = Math.max(1, Math.min(100, Math.trunc(rawLimit)));
|
|
5696
|
+
const offset = decodeCursor(params?.cursor);
|
|
5697
|
+
const sliced = allPrompts.slice(offset, offset + limit);
|
|
5698
|
+
const nextOffset = offset + sliced.length;
|
|
5699
|
+
return {
|
|
5700
|
+
prompts: sliced,
|
|
5701
|
+
nextCursor: nextOffset < allPrompts.length ? encodeCursor(nextOffset) : void 0
|
|
5702
|
+
};
|
|
5703
|
+
}
|
|
5704
|
+
async function getPrompt(name, args = {}, db, session) {
|
|
5705
|
+
const prompt = PROMPTS[name];
|
|
5706
|
+
if (!prompt) {
|
|
5707
|
+
throw new Error(`Prompt not found: ${name}`);
|
|
5708
|
+
}
|
|
5709
|
+
const inferredRepo = inferRepoFromSession(session);
|
|
5710
|
+
const messages = prompt.messages.map((m) => {
|
|
5711
|
+
let text = m.content.text;
|
|
5712
|
+
for (const [key, value] of Object.entries(args)) {
|
|
5713
|
+
text = text.replace(new RegExp(`\\{{${key}\\}}`, "g"), value);
|
|
5714
|
+
}
|
|
5715
|
+
text = text.replace(/{{current_repo}}/g, inferredRepo || "unknown-repo");
|
|
5716
|
+
return {
|
|
5717
|
+
...m,
|
|
5718
|
+
content: {
|
|
5719
|
+
...m.content,
|
|
5720
|
+
text
|
|
5721
|
+
}
|
|
5722
|
+
};
|
|
5723
|
+
});
|
|
5724
|
+
return {
|
|
5725
|
+
description: prompt.description,
|
|
5726
|
+
messages,
|
|
5727
|
+
metadata: prompt.agent ? { agent: prompt.agent } : void 0
|
|
5728
|
+
};
|
|
5729
|
+
}
|
|
5730
|
+
async function completePromptArgument(name, argName, value, contextArguments, dataSources) {
|
|
5731
|
+
void name;
|
|
5732
|
+
void contextArguments;
|
|
5733
|
+
if (argName === "task_id") {
|
|
5734
|
+
const values = dataSources.tasks.map((t) => t.id);
|
|
5735
|
+
return rankCompletionValues(values, value);
|
|
5736
|
+
}
|
|
5737
|
+
return [];
|
|
5738
|
+
}
|
|
5739
5739
|
|
|
5740
5740
|
// src/mcp/utils/mcp-response.ts
|
|
5741
5741
|
import { z as z2 } from "zod";
|
|
@@ -6121,31 +6121,17 @@ function buildStandardVectorText(standard) {
|
|
|
6121
6121
|
}
|
|
6122
6122
|
|
|
6123
6123
|
export {
|
|
6124
|
+
MCP_PROTOCOL_VERSION,
|
|
6125
|
+
CAPABILITIES,
|
|
6124
6126
|
logger,
|
|
6125
6127
|
setLogLevel,
|
|
6126
6128
|
getLogLevel,
|
|
6127
6129
|
addLogSink,
|
|
6128
6130
|
LOG_LEVEL_VALUES,
|
|
6129
6131
|
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
6132
|
normalizeRepo,
|
|
6133
|
+
SQLiteStore,
|
|
6134
|
+
RealVectorStore,
|
|
6149
6135
|
MemoryStoreSchema,
|
|
6150
6136
|
MemoryUpdateSchema,
|
|
6151
6137
|
MemorySearchSchema,
|
|
@@ -6168,6 +6154,24 @@ export {
|
|
|
6168
6154
|
StandardUpdateSchema,
|
|
6169
6155
|
StandardSearchSchema,
|
|
6170
6156
|
TOOL_DEFINITIONS,
|
|
6157
|
+
encodeCursor,
|
|
6158
|
+
decodeCursor,
|
|
6159
|
+
listResources,
|
|
6160
|
+
listResourceTemplates,
|
|
6161
|
+
completeResourceArgument,
|
|
6162
|
+
readResource,
|
|
6163
|
+
createSessionContext,
|
|
6164
|
+
updateSessionFromInitialize,
|
|
6165
|
+
updateSessionRoots,
|
|
6166
|
+
extractRootsFromResult,
|
|
6167
|
+
getFilesystemRoots,
|
|
6168
|
+
isPathWithinRoots,
|
|
6169
|
+
findContainingRoot,
|
|
6170
|
+
inferRepoFromSession,
|
|
6171
|
+
PROMPTS,
|
|
6172
|
+
listPrompts,
|
|
6173
|
+
getPrompt,
|
|
6174
|
+
completePromptArgument,
|
|
6171
6175
|
createMcpResponse,
|
|
6172
6176
|
getPrimaryTextContent,
|
|
6173
6177
|
handleHandoffCreate,
|
|
@@ -6177,9 +6181,5 @@ export {
|
|
|
6177
6181
|
handleClaimList,
|
|
6178
6182
|
handleClaimRelease,
|
|
6179
6183
|
toContextSlug,
|
|
6180
|
-
buildStandardVectorText
|
|
6181
|
-
SQLiteStore,
|
|
6182
|
-
RealVectorStore,
|
|
6183
|
-
MCP_PROTOCOL_VERSION,
|
|
6184
|
-
CAPABILITIES
|
|
6184
|
+
buildStandardVectorText
|
|
6185
6185
|
};
|