@vheins/local-memory-mcp 0.18.13 → 0.19.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.
- package/README.id.md +9 -1
- package/README.md +32 -17
- package/dist/{chunk-BK2QIQXS.js → chunk-AKFMCVQ4.js} +857 -680
- package/dist/dashboard/public/assets/index-BEmIBViV.js +150 -0
- package/dist/dashboard/public/assets/index-DGiNkigj.css +1 -0
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/server.js +227 -171
- package/dist/mcp/server.js +1919 -727
- package/dist/prompts/create-task.md +10 -2
- package/dist/prompts/documentation-sync.md +1 -1
- package/dist/prompts/learning-retrospective.md +1 -1
- package/dist/prompts/memory-agent-core.md +8 -2
- package/dist/prompts/project-briefing.md +2 -2
- package/dist/prompts/review-and-audit.md +2 -2
- package/dist/prompts/review-and-post-issue.md +3 -3
- package/dist/prompts/scrum-master.md +1 -1
- package/dist/prompts/sentinel-issue-resolver.md +1 -1
- package/dist/prompts/server/instructions.md +10 -2
- package/dist/prompts/session-planner.md +1 -1
- package/dist/prompts/task-memory-executor.md +2 -2
- package/dist/prompts/tool-usage-guidelines.md +9 -2
- package/package.json +5 -1
- package/dist/dashboard/public/assets/index-DWRMdSsg.js +0 -150
- package/dist/dashboard/public/assets/index-wAYh22Zy.css +0 -1
|
@@ -1,131 +1,5 @@
|
|
|
1
|
-
// src/mcp/capabilities.ts
|
|
2
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3
|
-
import path2 from "path";
|
|
4
|
-
|
|
5
|
-
// src/mcp/prompts/loader.ts
|
|
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.18.13") {
|
|
85
|
-
pkgVersion = "0.18.13";
|
|
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
1
|
// src/mcp/utils/logger.ts
|
|
128
|
-
import
|
|
2
|
+
import fs from "fs";
|
|
129
3
|
var LEVELS = {
|
|
130
4
|
debug: 0,
|
|
131
5
|
info: 1,
|
|
@@ -225,31 +99,17 @@ var logger = {
|
|
|
225
99
|
log("emergency", message, context);
|
|
226
100
|
}
|
|
227
101
|
};
|
|
228
|
-
function setLogLevel(level) {
|
|
229
|
-
const raw = level.toLowerCase();
|
|
230
|
-
const normalized = LEVEL_ALIASES[raw] || raw;
|
|
231
|
-
if (!(normalized in LEVELS)) {
|
|
232
|
-
const error = new Error(`Invalid log level: ${level}`);
|
|
233
|
-
error.code = -32602;
|
|
234
|
-
throw error;
|
|
235
|
-
}
|
|
236
|
-
currentLevel = normalized;
|
|
237
|
-
return currentLevel;
|
|
238
|
-
}
|
|
239
|
-
function getLogLevel() {
|
|
240
|
-
return currentLevel;
|
|
241
|
-
}
|
|
242
102
|
function addLogSink(sink) {
|
|
243
103
|
sinks.add(sink);
|
|
244
104
|
return () => sinks.delete(sink);
|
|
245
105
|
}
|
|
246
106
|
var LOG_LEVEL_VALUES = Object.keys(LEVELS);
|
|
247
107
|
function createFileSink(logDir, maxFiles = 5) {
|
|
248
|
-
|
|
249
|
-
const existing =
|
|
108
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
109
|
+
const existing = fs.readdirSync(logDir).filter((f) => f.startsWith("mcp-") && f.endsWith(".log")).sort();
|
|
250
110
|
while (existing.length >= maxFiles) {
|
|
251
111
|
try {
|
|
252
|
-
|
|
112
|
+
fs.unlinkSync(`${logDir}/${existing.shift()}`);
|
|
253
113
|
} catch {
|
|
254
114
|
}
|
|
255
115
|
}
|
|
@@ -259,7 +119,7 @@ function createFileSink(logDir, maxFiles = 5) {
|
|
|
259
119
|
const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${payload.level.toUpperCase()}] [pid:${process.pid}] ${JSON.stringify(payload.data)}
|
|
260
120
|
`;
|
|
261
121
|
try {
|
|
262
|
-
|
|
122
|
+
fs.appendFileSync(logFile, line);
|
|
263
123
|
} catch {
|
|
264
124
|
}
|
|
265
125
|
};
|
|
@@ -267,8 +127,8 @@ function createFileSink(logDir, maxFiles = 5) {
|
|
|
267
127
|
|
|
268
128
|
// src/mcp/storage/sqlite.ts
|
|
269
129
|
import Database from "better-sqlite3";
|
|
270
|
-
import
|
|
271
|
-
import
|
|
130
|
+
import path2 from "path";
|
|
131
|
+
import fs3 from "fs";
|
|
272
132
|
import os from "os";
|
|
273
133
|
|
|
274
134
|
// src/mcp/storage/migrations.ts
|
|
@@ -504,6 +364,50 @@ var MigrationManager = class {
|
|
|
504
364
|
CREATE INDEX IF NOT EXISTS idx_claims_task_id ON claims(task_id);
|
|
505
365
|
CREATE INDEX IF NOT EXISTS idx_claims_agent ON claims(agent);
|
|
506
366
|
CREATE INDEX IF NOT EXISTS idx_claims_claimed_at ON claims(claimed_at);
|
|
367
|
+
|
|
368
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
369
|
+
name TEXT PRIMARY KEY,
|
|
370
|
+
type TEXT NOT NULL DEFAULT 'unknown',
|
|
371
|
+
description TEXT,
|
|
372
|
+
repo TEXT NOT NULL DEFAULT '',
|
|
373
|
+
owner TEXT NOT NULL DEFAULT '',
|
|
374
|
+
created_at TEXT NOT NULL,
|
|
375
|
+
updated_at TEXT NOT NULL
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
|
|
379
|
+
CREATE INDEX IF NOT EXISTS idx_entities_repo ON entities(repo);
|
|
380
|
+
|
|
381
|
+
CREATE TABLE IF NOT EXISTS relations (
|
|
382
|
+
from_entity TEXT NOT NULL,
|
|
383
|
+
to_entity TEXT NOT NULL,
|
|
384
|
+
relation_type TEXT NOT NULL,
|
|
385
|
+
repo TEXT NOT NULL DEFAULT '',
|
|
386
|
+
owner TEXT NOT NULL DEFAULT '',
|
|
387
|
+
created_at TEXT NOT NULL,
|
|
388
|
+
PRIMARY KEY (from_entity, to_entity, relation_type),
|
|
389
|
+
FOREIGN KEY (from_entity) REFERENCES entities(name) ON DELETE CASCADE,
|
|
390
|
+
FOREIGN KEY (to_entity) REFERENCES entities(name) ON DELETE CASCADE
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
CREATE INDEX IF NOT EXISTS idx_relations_from ON relations(from_entity);
|
|
394
|
+
CREATE INDEX IF NOT EXISTS idx_relations_to ON relations(to_entity);
|
|
395
|
+
CREATE INDEX IF NOT EXISTS idx_relations_type ON relations(relation_type);
|
|
396
|
+
CREATE INDEX IF NOT EXISTS idx_relations_repo ON relations(repo);
|
|
397
|
+
|
|
398
|
+
CREATE TABLE IF NOT EXISTS observations (
|
|
399
|
+
id TEXT PRIMARY KEY,
|
|
400
|
+
entity_name TEXT NOT NULL,
|
|
401
|
+
observation TEXT NOT NULL,
|
|
402
|
+
repo TEXT NOT NULL DEFAULT '',
|
|
403
|
+
owner TEXT NOT NULL DEFAULT '',
|
|
404
|
+
created_at TEXT NOT NULL,
|
|
405
|
+
FOREIGN KEY (entity_name) REFERENCES entities(name) ON DELETE CASCADE
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
CREATE INDEX IF NOT EXISTS idx_observations_entity ON observations(entity_name);
|
|
409
|
+
CREATE INDEX IF NOT EXISTS idx_observations_repo ON observations(repo);
|
|
410
|
+
CREATE INDEX IF NOT EXISTS idx_observations_created_at ON observations(created_at);
|
|
507
411
|
`);
|
|
508
412
|
const columnsToAdd = [
|
|
509
413
|
{ name: "title", table: "memories", definition: "ALTER TABLE memories ADD COLUMN title TEXT" },
|
|
@@ -1561,7 +1465,7 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1561
1465
|
);
|
|
1562
1466
|
}
|
|
1563
1467
|
listMemoriesForDashboard(options) {
|
|
1564
|
-
|
|
1468
|
+
const {
|
|
1565
1469
|
owner,
|
|
1566
1470
|
repo,
|
|
1567
1471
|
type,
|
|
@@ -1572,9 +1476,9 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1572
1476
|
search,
|
|
1573
1477
|
offset = 0,
|
|
1574
1478
|
limit = 50,
|
|
1575
|
-
sortBy = "created_at",
|
|
1576
1479
|
sortOrder = "DESC"
|
|
1577
1480
|
} = options;
|
|
1481
|
+
let sortBy = options.sortBy ?? "created_at";
|
|
1578
1482
|
const ALLOWED_SORT_COLUMNS = /* @__PURE__ */ new Set([
|
|
1579
1483
|
"created_at",
|
|
1580
1484
|
"updated_at",
|
|
@@ -3199,8 +3103,8 @@ var HandoffEntity = class extends BaseEntity {
|
|
|
3199
3103
|
|
|
3200
3104
|
// src/mcp/storage/write-lock.ts
|
|
3201
3105
|
import lockfile from "proper-lockfile";
|
|
3202
|
-
import
|
|
3203
|
-
import
|
|
3106
|
+
import path from "path";
|
|
3107
|
+
import fs2 from "fs";
|
|
3204
3108
|
var LOCK_STALE_MS = 3e4;
|
|
3205
3109
|
var LOCK_RETRY_DELAY_MS = 200;
|
|
3206
3110
|
var LOCK_RETRY_COUNT = 250;
|
|
@@ -3209,9 +3113,9 @@ var WriteLock = class {
|
|
|
3209
3113
|
locked = false;
|
|
3210
3114
|
constructor(dbPath) {
|
|
3211
3115
|
this.lockTarget = dbPath;
|
|
3212
|
-
if (!
|
|
3213
|
-
|
|
3214
|
-
|
|
3116
|
+
if (!fs2.existsSync(dbPath)) {
|
|
3117
|
+
fs2.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
3118
|
+
fs2.writeFileSync(dbPath, "");
|
|
3215
3119
|
}
|
|
3216
3120
|
}
|
|
3217
3121
|
/**
|
|
@@ -3263,13 +3167,13 @@ var WriteLock = class {
|
|
|
3263
3167
|
// src/mcp/storage/sqlite.ts
|
|
3264
3168
|
function resolveDbPath() {
|
|
3265
3169
|
if (process.env.MEMORY_DB_PATH) return process.env.MEMORY_DB_PATH;
|
|
3266
|
-
const standardConfigDir = process.platform === "win32" ?
|
|
3267
|
-
const standardPath =
|
|
3268
|
-
if (
|
|
3269
|
-
const legacyPath =
|
|
3270
|
-
if (
|
|
3271
|
-
const localCwdFile =
|
|
3272
|
-
if (
|
|
3170
|
+
const standardConfigDir = process.platform === "win32" ? path2.join(os.homedir(), ".local-memory-mcp") : process.platform === "darwin" ? path2.join(os.homedir(), "Library", "Application Support", "local-memory-mcp") : path2.join(os.homedir(), ".config", "local-memory-mcp");
|
|
3171
|
+
const standardPath = path2.join(standardConfigDir, "memory.db");
|
|
3172
|
+
if (fs3.existsSync(standardPath)) return standardPath;
|
|
3173
|
+
const legacyPath = path2.join(os.homedir(), ".config", "local-memory-mcp", "memory.db");
|
|
3174
|
+
if (fs3.existsSync(legacyPath)) return legacyPath;
|
|
3175
|
+
const localCwdFile = path2.join(process.cwd(), "storage", "memory.db");
|
|
3176
|
+
if (fs3.existsSync(localCwdFile)) return localCwdFile;
|
|
3273
3177
|
return standardPath;
|
|
3274
3178
|
}
|
|
3275
3179
|
var DB_PATH = resolveDbPath();
|
|
@@ -3292,9 +3196,9 @@ var SQLiteStore = class _SQLiteStore {
|
|
|
3292
3196
|
const finalPath = dbPath ?? DB_PATH;
|
|
3293
3197
|
this.dbPathInstance = finalPath;
|
|
3294
3198
|
if (finalPath !== ":memory:") {
|
|
3295
|
-
const dbDir =
|
|
3296
|
-
if (!
|
|
3297
|
-
|
|
3199
|
+
const dbDir = path2.dirname(finalPath);
|
|
3200
|
+
if (!fs3.existsSync(dbDir)) {
|
|
3201
|
+
fs3.mkdirSync(dbDir, { recursive: true });
|
|
3298
3202
|
}
|
|
3299
3203
|
}
|
|
3300
3204
|
this.db = new Database(finalPath);
|
|
@@ -3353,12 +3257,12 @@ var SQLiteStore = class _SQLiteStore {
|
|
|
3353
3257
|
*/
|
|
3354
3258
|
_attemptRecovery(dbPath) {
|
|
3355
3259
|
const backupPath = dbPath + ".backup";
|
|
3356
|
-
if (
|
|
3260
|
+
if (fs3.existsSync(backupPath)) {
|
|
3357
3261
|
logger.warn("[SQLiteStore] Attempting recovery from backup", { backupPath });
|
|
3358
3262
|
try {
|
|
3359
3263
|
const corruptPath = `${dbPath}.corrupt_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").slice(0, 15)}`;
|
|
3360
|
-
|
|
3361
|
-
|
|
3264
|
+
fs3.copyFileSync(dbPath, corruptPath);
|
|
3265
|
+
fs3.copyFileSync(backupPath, dbPath);
|
|
3362
3266
|
logger.warn("[SQLiteStore] Recovery successful. Corrupt file saved to", { corruptPath });
|
|
3363
3267
|
} catch (err) {
|
|
3364
3268
|
logger.error("[SQLiteStore] Recovery failed", { error: String(err) });
|
|
@@ -3376,7 +3280,7 @@ var SQLiteStore = class _SQLiteStore {
|
|
|
3376
3280
|
try {
|
|
3377
3281
|
this.db.pragma("wal_checkpoint(PASSIVE)");
|
|
3378
3282
|
const backupPath = this.dbPathInstance + ".backup";
|
|
3379
|
-
|
|
3283
|
+
fs3.copyFileSync(this.dbPathInstance, backupPath);
|
|
3380
3284
|
} catch (err) {
|
|
3381
3285
|
logger.warn("[SQLiteStore] Backup failed", { error: String(err) });
|
|
3382
3286
|
}
|
|
@@ -3499,8 +3403,8 @@ var RealVectorStore = class {
|
|
|
3499
3403
|
};
|
|
3500
3404
|
|
|
3501
3405
|
// src/mcp/session.ts
|
|
3502
|
-
import
|
|
3503
|
-
import { fileURLToPath
|
|
3406
|
+
import path3 from "path";
|
|
3407
|
+
import { fileURLToPath } from "url";
|
|
3504
3408
|
function createSessionContext() {
|
|
3505
3409
|
return {
|
|
3506
3410
|
roots: [],
|
|
@@ -3512,60 +3416,13 @@ function createSessionContext() {
|
|
|
3512
3416
|
supportsElicitationUrl: false
|
|
3513
3417
|
};
|
|
3514
3418
|
}
|
|
3515
|
-
function updateSessionFromInitialize(session, params) {
|
|
3516
|
-
const capabilities = params?.capabilities || {};
|
|
3517
|
-
session.clientInfo = params?.clientInfo;
|
|
3518
|
-
session.clientCapabilities = capabilities;
|
|
3519
|
-
session.supportsRoots = Boolean(capabilities.roots);
|
|
3520
|
-
session.supportsSampling = Boolean(capabilities.sampling);
|
|
3521
|
-
const sampling = capabilities.sampling;
|
|
3522
|
-
session.supportsSamplingTools = Boolean(sampling?.tools);
|
|
3523
|
-
session.supportsElicitation = Boolean(capabilities.elicitation);
|
|
3524
|
-
session.supportsElicitationForm = supportsElicitationMode(capabilities.elicitation, "form");
|
|
3525
|
-
session.supportsElicitationUrl = supportsElicitationMode(capabilities.elicitation, "url");
|
|
3526
|
-
}
|
|
3527
|
-
function supportsElicitationMode(capability, mode) {
|
|
3528
|
-
if (!capability || typeof capability !== "object") {
|
|
3529
|
-
return false;
|
|
3530
|
-
}
|
|
3531
|
-
const cap = capability;
|
|
3532
|
-
if (mode === "form") {
|
|
3533
|
-
return Object.keys(cap).length === 0 || typeof cap.form === "object";
|
|
3534
|
-
}
|
|
3535
|
-
return typeof cap.url === "object";
|
|
3536
|
-
}
|
|
3537
|
-
function updateSessionRoots(session, roots) {
|
|
3538
|
-
const normalized = normalizeRoots(roots);
|
|
3539
|
-
const previous = JSON.stringify(session.roots);
|
|
3540
|
-
const next = JSON.stringify(normalized);
|
|
3541
|
-
session.roots = normalized;
|
|
3542
|
-
return previous !== next;
|
|
3543
|
-
}
|
|
3544
|
-
function normalizeRoots(roots) {
|
|
3545
|
-
if (!Array.isArray(roots)) return [];
|
|
3546
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3547
|
-
const normalized = [];
|
|
3548
|
-
for (const root of roots) {
|
|
3549
|
-
if (!root || typeof root !== "object") continue;
|
|
3550
|
-
const r = root;
|
|
3551
|
-
const uri = typeof r.uri === "string" ? r.uri : void 0;
|
|
3552
|
-
const name = typeof r.name === "string" ? r.name : void 0;
|
|
3553
|
-
if (!uri || seen.has(uri)) continue;
|
|
3554
|
-
seen.add(uri);
|
|
3555
|
-
normalized.push({ uri, name });
|
|
3556
|
-
}
|
|
3557
|
-
return normalized;
|
|
3558
|
-
}
|
|
3559
|
-
function extractRootsFromResult(result) {
|
|
3560
|
-
return normalizeRoots(result?.roots);
|
|
3561
|
-
}
|
|
3562
3419
|
function getFilesystemRoots(session) {
|
|
3563
3420
|
if (!session) return [];
|
|
3564
3421
|
const resolved = [];
|
|
3565
3422
|
for (const root of session.roots) {
|
|
3566
3423
|
if (!root.uri.startsWith("file://")) continue;
|
|
3567
3424
|
try {
|
|
3568
|
-
resolved.push(
|
|
3425
|
+
resolved.push(path3.resolve(fileURLToPath(root.uri)));
|
|
3569
3426
|
} catch {
|
|
3570
3427
|
}
|
|
3571
3428
|
}
|
|
@@ -3574,19 +3431,19 @@ function getFilesystemRoots(session) {
|
|
|
3574
3431
|
function isPathWithinRoots(targetPath, session) {
|
|
3575
3432
|
const roots = getFilesystemRoots(session);
|
|
3576
3433
|
if (roots.length === 0) return true;
|
|
3577
|
-
const normalizedTarget =
|
|
3434
|
+
const normalizedTarget = path3.resolve(targetPath);
|
|
3578
3435
|
return roots.some((rootPath) => {
|
|
3579
|
-
const relative =
|
|
3580
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
3436
|
+
const relative = path3.relative(rootPath, normalizedTarget);
|
|
3437
|
+
return relative === "" || !relative.startsWith("..") && !path3.isAbsolute(relative);
|
|
3581
3438
|
});
|
|
3582
3439
|
}
|
|
3583
3440
|
function findContainingRoot(targetPath, session) {
|
|
3584
3441
|
const roots = getFilesystemRoots(session);
|
|
3585
3442
|
if (roots.length === 0) return null;
|
|
3586
|
-
const normalizedTarget =
|
|
3443
|
+
const normalizedTarget = path3.resolve(targetPath);
|
|
3587
3444
|
for (const rootPath of roots) {
|
|
3588
|
-
const relative =
|
|
3589
|
-
if (relative === "" || !relative.startsWith("..") && !
|
|
3445
|
+
const relative = path3.relative(rootPath, normalizedTarget);
|
|
3446
|
+
if (relative === "" || !relative.startsWith("..") && !path3.isAbsolute(relative)) {
|
|
3590
3447
|
return rootPath;
|
|
3591
3448
|
}
|
|
3592
3449
|
}
|
|
@@ -3595,14 +3452,14 @@ function findContainingRoot(targetPath, session) {
|
|
|
3595
3452
|
function inferRepoFromSession(session) {
|
|
3596
3453
|
const roots = getFilesystemRoots(session);
|
|
3597
3454
|
if (roots.length === 1) {
|
|
3598
|
-
return
|
|
3455
|
+
return path3.basename(roots[0]);
|
|
3599
3456
|
}
|
|
3600
3457
|
return void 0;
|
|
3601
3458
|
}
|
|
3602
3459
|
function inferOwnerFromSession(session) {
|
|
3603
3460
|
const roots = getFilesystemRoots(session);
|
|
3604
3461
|
if (roots.length === 1) {
|
|
3605
|
-
const parts = roots[0].split(
|
|
3462
|
+
const parts = roots[0].split(path3.sep).filter(Boolean);
|
|
3606
3463
|
if (parts.length >= 2) {
|
|
3607
3464
|
return parts[parts.length - 2];
|
|
3608
3465
|
}
|
|
@@ -3610,6 +3467,82 @@ function inferOwnerFromSession(session) {
|
|
|
3610
3467
|
return void 0;
|
|
3611
3468
|
}
|
|
3612
3469
|
|
|
3470
|
+
// src/mcp/prompts/loader.ts
|
|
3471
|
+
import fs4 from "fs";
|
|
3472
|
+
import path4 from "path";
|
|
3473
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3474
|
+
import matter from "gray-matter";
|
|
3475
|
+
var __filename = fileURLToPath2(import.meta.url);
|
|
3476
|
+
var __dirname = path4.dirname(__filename);
|
|
3477
|
+
function findPromptDir() {
|
|
3478
|
+
const candidates = [
|
|
3479
|
+
// Production if chunked into dist/
|
|
3480
|
+
"./prompts",
|
|
3481
|
+
// Production if inlined into dist/mcp/
|
|
3482
|
+
"../prompts",
|
|
3483
|
+
// Dev: /src/mcp/prompts/definitions (next to loader.ts)
|
|
3484
|
+
"./definitions"
|
|
3485
|
+
].map((relPath) => path4.resolve(__dirname, relPath));
|
|
3486
|
+
for (const dir of candidates) {
|
|
3487
|
+
if (fs4.existsSync(dir)) {
|
|
3488
|
+
const files = fs4.readdirSync(dir);
|
|
3489
|
+
if (files.some((f) => f.endsWith(".md"))) {
|
|
3490
|
+
return dir;
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
return path4.resolve(__dirname, "./definitions");
|
|
3495
|
+
}
|
|
3496
|
+
var PROMPT_DIR = findPromptDir();
|
|
3497
|
+
function listPromptFiles() {
|
|
3498
|
+
if (!fs4.existsSync(PROMPT_DIR)) return [];
|
|
3499
|
+
return fs4.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
|
|
3500
|
+
}
|
|
3501
|
+
function loadPromptFromMarkdown(name) {
|
|
3502
|
+
const filePath = path4.join(PROMPT_DIR, `${name}.md`);
|
|
3503
|
+
if (!fs4.existsSync(filePath)) {
|
|
3504
|
+
throw new Error(`Prompt file not found: ${filePath}`);
|
|
3505
|
+
}
|
|
3506
|
+
const fileContent = fs4.readFileSync(filePath, "utf-8");
|
|
3507
|
+
const { data, content } = matter(fileContent);
|
|
3508
|
+
return {
|
|
3509
|
+
name: data.name || name,
|
|
3510
|
+
description: data.description || "",
|
|
3511
|
+
arguments: data.arguments || [],
|
|
3512
|
+
agent: data.agent,
|
|
3513
|
+
content: content.trim()
|
|
3514
|
+
};
|
|
3515
|
+
}
|
|
3516
|
+
function findServerInstructionsDir() {
|
|
3517
|
+
const candidates = [
|
|
3518
|
+
// Production if chunked into dist/
|
|
3519
|
+
"./prompts/server",
|
|
3520
|
+
// Production if inlined into dist/mcp/
|
|
3521
|
+
"../prompts/server",
|
|
3522
|
+
// Dev: /src/mcp/prompts/server (next to loader.ts)
|
|
3523
|
+
"./server"
|
|
3524
|
+
].map((relPath) => path4.resolve(__dirname, relPath));
|
|
3525
|
+
for (const dir of candidates) {
|
|
3526
|
+
if (fs4.existsSync(dir)) {
|
|
3527
|
+
const filePath = path4.join(dir, "instructions.md");
|
|
3528
|
+
if (fs4.existsSync(filePath)) {
|
|
3529
|
+
return dir;
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
return path4.resolve(__dirname, "./server");
|
|
3534
|
+
}
|
|
3535
|
+
var SERVER_DIR = findServerInstructionsDir();
|
|
3536
|
+
function loadServerInstructions() {
|
|
3537
|
+
const filePath = path4.join(SERVER_DIR, "instructions.md");
|
|
3538
|
+
if (!fs4.existsSync(filePath)) {
|
|
3539
|
+
throw new Error(`Server instructions file not found: ${filePath}`);
|
|
3540
|
+
}
|
|
3541
|
+
const fileContent = fs4.readFileSync(filePath, "utf-8");
|
|
3542
|
+
const { content } = matter(fileContent);
|
|
3543
|
+
return content.trim();
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3613
3546
|
// src/mcp/tools/definitions/memory.ts
|
|
3614
3547
|
var MEMORY_TOOL_DEFINITIONS = [
|
|
3615
3548
|
{
|
|
@@ -5595,180 +5528,640 @@ var STANDARD_TOOL_DEFINITIONS = [
|
|
|
5595
5528
|
}
|
|
5596
5529
|
];
|
|
5597
5530
|
|
|
5598
|
-
// src/mcp/tools/definitions/
|
|
5599
|
-
var
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
return Buffer.from(String(offset), "utf8").toString("base64");
|
|
5609
|
-
}
|
|
5610
|
-
function decodeCursor(cursor) {
|
|
5611
|
-
if (cursor === void 0 || cursor === null || cursor === "") {
|
|
5612
|
-
return 0;
|
|
5613
|
-
}
|
|
5614
|
-
if (typeof cursor !== "string" || cursor.trim() === "") {
|
|
5615
|
-
throw invalidPaginationParams("Invalid cursor");
|
|
5616
|
-
}
|
|
5617
|
-
let decoded;
|
|
5618
|
-
try {
|
|
5619
|
-
decoded = Buffer.from(cursor, "base64").toString("utf8");
|
|
5620
|
-
} catch {
|
|
5621
|
-
throw invalidPaginationParams("Invalid cursor");
|
|
5622
|
-
}
|
|
5623
|
-
if (!/^\d+$/.test(decoded)) {
|
|
5624
|
-
throw invalidPaginationParams("Invalid cursor");
|
|
5625
|
-
}
|
|
5626
|
-
const offset = Number.parseInt(decoded, 10);
|
|
5627
|
-
if (!Number.isFinite(offset) || offset < 0) {
|
|
5628
|
-
throw invalidPaginationParams("Invalid cursor");
|
|
5629
|
-
}
|
|
5630
|
-
return offset;
|
|
5631
|
-
}
|
|
5632
|
-
function invalidPaginationParams(message) {
|
|
5633
|
-
const error = new Error(message);
|
|
5634
|
-
error.code = -32602;
|
|
5635
|
-
return error;
|
|
5636
|
-
}
|
|
5637
|
-
|
|
5638
|
-
// src/mcp/utils/completion.ts
|
|
5639
|
-
var MAX_COMPLETION_VALUES = 100;
|
|
5640
|
-
function rankCompletionValues(candidates, input) {
|
|
5641
|
-
const unique = [...new Set(candidates.filter(Boolean))];
|
|
5642
|
-
const needle = input.trim().toLowerCase();
|
|
5643
|
-
if (!needle) {
|
|
5644
|
-
return unique.slice(0, MAX_COMPLETION_VALUES);
|
|
5645
|
-
}
|
|
5646
|
-
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);
|
|
5647
|
-
}
|
|
5648
|
-
function scoreCompletionValue(value, needle) {
|
|
5649
|
-
const haystack = value.toLowerCase();
|
|
5650
|
-
if (haystack === needle) return 100;
|
|
5651
|
-
if (haystack.startsWith(needle)) return 75;
|
|
5652
|
-
if (haystack.includes(needle)) return 50;
|
|
5653
|
-
const compactNeedle = needle.replace(/[\s_-]+/g, "");
|
|
5654
|
-
const compactHaystack = haystack.replace(/[\s_-]+/g, "");
|
|
5655
|
-
if (compactNeedle && compactHaystack.includes(compactNeedle)) return 25;
|
|
5656
|
-
return 0;
|
|
5657
|
-
}
|
|
5658
|
-
|
|
5659
|
-
// src/mcp/resources/index.ts
|
|
5660
|
-
var DEFAULT_PAGE_SIZE = 25;
|
|
5661
|
-
var MAX_PAGE_SIZE = 100;
|
|
5662
|
-
function listResources(session, params) {
|
|
5663
|
-
const resources = [
|
|
5664
|
-
{
|
|
5665
|
-
uri: "repository://index",
|
|
5666
|
-
name: "Repository Index",
|
|
5667
|
-
title: "Repository Index",
|
|
5668
|
-
description: "List of all known repositories with memory/task counts and last activity",
|
|
5669
|
-
mimeType: "application/json",
|
|
5670
|
-
annotations: {
|
|
5671
|
-
audience: ["assistant"],
|
|
5672
|
-
priority: 1,
|
|
5673
|
-
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
5674
|
-
}
|
|
5531
|
+
// src/mcp/tools/definitions/agent.ts
|
|
5532
|
+
var AGENT_TOOL_DEFINITIONS = [
|
|
5533
|
+
{
|
|
5534
|
+
name: "agent-context",
|
|
5535
|
+
title: "Agent Context Recall",
|
|
5536
|
+
description: "Returns relevant context for the current agent session. Auto-detects active repo, searches relevant memories, lists active tasks, and surfaces recent decisions formatted as a single context block ready for injection into agent prompts.",
|
|
5537
|
+
annotations: {
|
|
5538
|
+
readOnlyHint: true,
|
|
5539
|
+
idempotentHint: true,
|
|
5540
|
+
openWorldHint: false
|
|
5675
5541
|
},
|
|
5676
|
-
{
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5542
|
+
inputSchema: {
|
|
5543
|
+
type: "object",
|
|
5544
|
+
properties: {
|
|
5545
|
+
owner: {
|
|
5546
|
+
type: "string",
|
|
5547
|
+
description: "Organization/namespace (e.g., GitHub org or username). Auto-detected from session when omitted."
|
|
5548
|
+
},
|
|
5549
|
+
repo: {
|
|
5550
|
+
type: "string",
|
|
5551
|
+
description: "Repository/project name. Auto-detected from session when omitted."
|
|
5552
|
+
},
|
|
5553
|
+
objective: {
|
|
5554
|
+
type: "string",
|
|
5555
|
+
description: "Current agent objective to search relevant memories around."
|
|
5556
|
+
},
|
|
5557
|
+
type_filter: {
|
|
5558
|
+
type: "string",
|
|
5559
|
+
description: "Memory type to filter by (e.g., 'decision', 'code_fact', 'pattern', 'mistake'). Default: all."
|
|
5560
|
+
},
|
|
5561
|
+
limit: {
|
|
5562
|
+
type: "number",
|
|
5563
|
+
minimum: 1,
|
|
5564
|
+
maximum: 100,
|
|
5565
|
+
default: 5,
|
|
5566
|
+
description: "Maximum number of memories to return."
|
|
5567
|
+
},
|
|
5568
|
+
structured: {
|
|
5569
|
+
type: "boolean",
|
|
5570
|
+
default: false,
|
|
5571
|
+
description: "If true, returns structured JSON results."
|
|
5572
|
+
}
|
|
5687
5573
|
}
|
|
5574
|
+
},
|
|
5575
|
+
outputSchema: {
|
|
5576
|
+
type: "object",
|
|
5577
|
+
properties: {
|
|
5578
|
+
schema: { type: "string", enum: ["agent-context"] },
|
|
5579
|
+
repo: { type: "string" },
|
|
5580
|
+
objective: { type: "string" },
|
|
5581
|
+
memories: {
|
|
5582
|
+
type: "array",
|
|
5583
|
+
items: {
|
|
5584
|
+
type: "object",
|
|
5585
|
+
properties: {
|
|
5586
|
+
id: { type: "string" },
|
|
5587
|
+
code: { type: "string" },
|
|
5588
|
+
title: { type: "string" },
|
|
5589
|
+
type: { type: "string" },
|
|
5590
|
+
importance: { type: "number" }
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
},
|
|
5594
|
+
decisions: {
|
|
5595
|
+
type: "array",
|
|
5596
|
+
items: {
|
|
5597
|
+
type: "object",
|
|
5598
|
+
properties: {
|
|
5599
|
+
id: { type: "string" },
|
|
5600
|
+
code: { type: "string" },
|
|
5601
|
+
title: { type: "string" },
|
|
5602
|
+
importance: { type: "number" }
|
|
5603
|
+
}
|
|
5604
|
+
}
|
|
5605
|
+
},
|
|
5606
|
+
tasks: {
|
|
5607
|
+
type: "array",
|
|
5608
|
+
items: {
|
|
5609
|
+
type: "object",
|
|
5610
|
+
properties: {
|
|
5611
|
+
task_code: { type: "string" },
|
|
5612
|
+
title: { type: "string" },
|
|
5613
|
+
status: { type: "string" },
|
|
5614
|
+
priority: { type: "number" }
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5617
|
+
}
|
|
5618
|
+
},
|
|
5619
|
+
required: ["schema", "repo", "memories", "decisions", "tasks"]
|
|
5688
5620
|
}
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
title: "Repository Memories",
|
|
5699
|
-
description: "All active memory entries for a specific repository",
|
|
5700
|
-
mimeType: "application/json",
|
|
5701
|
-
annotations: { audience: ["assistant"], priority: 0.85 }
|
|
5621
|
+
},
|
|
5622
|
+
{
|
|
5623
|
+
name: "decision-log",
|
|
5624
|
+
title: "Decision Logger",
|
|
5625
|
+
description: "Logs a structured decision as a memory entry. Accepts a summary of what was decided, the context/situation, the rationale, and optional alternatives considered. Internally reuses the memory-store handler with type='decision' and importance=4. Owner and repo are auto-inferred from session when omitted.",
|
|
5626
|
+
annotations: {
|
|
5627
|
+
readOnlyHint: false,
|
|
5628
|
+
idempotentHint: false,
|
|
5629
|
+
openWorldHint: false
|
|
5702
5630
|
},
|
|
5703
|
-
{
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5631
|
+
inputSchema: {
|
|
5632
|
+
type: "object",
|
|
5633
|
+
properties: {
|
|
5634
|
+
summary: {
|
|
5635
|
+
type: "string",
|
|
5636
|
+
description: "What was decided (used as the memory title). Max 255 characters."
|
|
5637
|
+
},
|
|
5638
|
+
context: {
|
|
5639
|
+
type: "string",
|
|
5640
|
+
description: "The situation or context surrounding the decision. Min 10 characters."
|
|
5641
|
+
},
|
|
5642
|
+
rationale: {
|
|
5643
|
+
type: "string",
|
|
5644
|
+
description: "Why this decision was made. Min 10 characters."
|
|
5645
|
+
},
|
|
5646
|
+
alternatives: {
|
|
5647
|
+
type: "array",
|
|
5648
|
+
items: { type: "string" },
|
|
5649
|
+
description: "Alternatives that were considered (optional)."
|
|
5650
|
+
},
|
|
5651
|
+
tags: {
|
|
5652
|
+
type: "array",
|
|
5653
|
+
items: { type: "string" },
|
|
5654
|
+
description: "Additional tags to apply (auto-includes 'decision')."
|
|
5655
|
+
},
|
|
5656
|
+
owner: {
|
|
5657
|
+
type: "string",
|
|
5658
|
+
description: "Organization/namespace. Auto-detected from session when omitted."
|
|
5659
|
+
},
|
|
5660
|
+
repo: {
|
|
5661
|
+
type: "string",
|
|
5662
|
+
description: "Repository/project name. Auto-detected from session when omitted."
|
|
5663
|
+
},
|
|
5664
|
+
structured: {
|
|
5665
|
+
type: "boolean",
|
|
5666
|
+
default: false,
|
|
5667
|
+
description: "If true, returns structured JSON results."
|
|
5668
|
+
}
|
|
5669
|
+
},
|
|
5670
|
+
required: ["summary", "context", "rationale"]
|
|
5710
5671
|
},
|
|
5711
|
-
{
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5672
|
+
outputSchema: {
|
|
5673
|
+
type: "object",
|
|
5674
|
+
properties: {
|
|
5675
|
+
success: { type: "boolean" },
|
|
5676
|
+
id: { type: "string" },
|
|
5677
|
+
code: { type: "string" },
|
|
5678
|
+
repo: { type: "string" },
|
|
5679
|
+
type: { type: "string", enum: ["decision"] },
|
|
5680
|
+
title: { type: "string" }
|
|
5681
|
+
}
|
|
5682
|
+
}
|
|
5683
|
+
},
|
|
5684
|
+
{
|
|
5685
|
+
name: "session-summarize",
|
|
5686
|
+
title: "Session Summarizer",
|
|
5687
|
+
description: "Persists a session summary as a task_archive memory entry. Accepts a session summary, optional key decisions made, optional next steps, and optional tags. Internally reuses the memory-store handler with type='task_archive' and importance=3. Owner and repo are auto-inferred from session when omitted.",
|
|
5688
|
+
annotations: {
|
|
5689
|
+
readOnlyHint: false,
|
|
5690
|
+
idempotentHint: false,
|
|
5691
|
+
openWorldHint: false
|
|
5718
5692
|
},
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5693
|
+
inputSchema: {
|
|
5694
|
+
type: "object",
|
|
5695
|
+
properties: {
|
|
5696
|
+
summary: {
|
|
5697
|
+
type: "string",
|
|
5698
|
+
description: "Session summary text. Min 10 characters."
|
|
5699
|
+
},
|
|
5700
|
+
key_decisions: {
|
|
5701
|
+
type: "array",
|
|
5702
|
+
items: { type: "string" },
|
|
5703
|
+
description: "Key decisions made during this session (optional)."
|
|
5704
|
+
},
|
|
5705
|
+
next_steps: {
|
|
5706
|
+
type: "array",
|
|
5707
|
+
items: { type: "string" },
|
|
5708
|
+
description: "Next steps or follow-up actions (optional)."
|
|
5709
|
+
},
|
|
5710
|
+
tags: {
|
|
5711
|
+
type: "array",
|
|
5712
|
+
items: { type: "string" },
|
|
5713
|
+
description: "Additional tags to apply (auto-includes 'session-summary')."
|
|
5714
|
+
},
|
|
5715
|
+
owner: {
|
|
5716
|
+
type: "string",
|
|
5717
|
+
description: "Organization/namespace. Auto-detected from session when omitted."
|
|
5718
|
+
},
|
|
5719
|
+
repo: {
|
|
5720
|
+
type: "string",
|
|
5721
|
+
description: "Repository/project name. Auto-detected from session when omitted."
|
|
5722
|
+
},
|
|
5723
|
+
structured: {
|
|
5724
|
+
type: "boolean",
|
|
5725
|
+
default: false,
|
|
5726
|
+
description: "If true, returns structured JSON results."
|
|
5727
|
+
}
|
|
5728
|
+
},
|
|
5729
|
+
required: ["summary"]
|
|
5730
|
+
},
|
|
5731
|
+
outputSchema: {
|
|
5732
|
+
type: "object",
|
|
5733
|
+
properties: {
|
|
5734
|
+
success: { type: "boolean" },
|
|
5735
|
+
id: { type: "string" },
|
|
5736
|
+
code: { type: "string" },
|
|
5737
|
+
repo: { type: "string" },
|
|
5738
|
+
type: { type: "string", enum: ["task_archive"] },
|
|
5739
|
+
title: { type: "string" }
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
5742
|
+
},
|
|
5743
|
+
// ── Upstream compatibility aliases ──────────────────────────────────────
|
|
5744
|
+
{
|
|
5745
|
+
name: "remember_fact",
|
|
5746
|
+
title: "Remember Fact (Upstream)",
|
|
5747
|
+
description: "Upstream-compatible alias for memory-store. Stores a single fact as a memory entry. Accepts the same arguments as memory-store. Owner and repo are auto-inferred from session when omitted.",
|
|
5748
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
|
|
5749
|
+
inputSchema: {
|
|
5750
|
+
type: "object",
|
|
5751
|
+
properties: {
|
|
5752
|
+
owner: { type: "string", description: "Auto-detected from session." },
|
|
5753
|
+
repo: { type: "string", description: "Auto-detected from session." },
|
|
5754
|
+
title: { type: "string", description: "Title for the fact/memory." },
|
|
5755
|
+
content: { type: "string", description: "The fact content to store." },
|
|
5756
|
+
type: { type: "string", default: "code_fact" },
|
|
5757
|
+
importance: { type: "number", default: 3 },
|
|
5758
|
+
tags: { type: "array", items: { type: "string" } }
|
|
5759
|
+
},
|
|
5760
|
+
required: ["content"]
|
|
5761
|
+
},
|
|
5762
|
+
outputSchema: {
|
|
5763
|
+
type: "object",
|
|
5764
|
+
properties: { success: { type: "boolean" }, id: { type: "string" }, code: { type: "string" } }
|
|
5765
|
+
}
|
|
5766
|
+
},
|
|
5767
|
+
{
|
|
5768
|
+
name: "remember_facts",
|
|
5769
|
+
title: "Remember Facts (Upstream Bulk)",
|
|
5770
|
+
description: "Upstream-compatible alias for bulk memory-store. Stores multiple facts at once.",
|
|
5771
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
|
|
5772
|
+
inputSchema: {
|
|
5773
|
+
type: "object",
|
|
5774
|
+
properties: {
|
|
5775
|
+
owner: { type: "string" },
|
|
5776
|
+
repo: { type: "string" },
|
|
5777
|
+
facts: { type: "array", items: { type: "object" }, description: "Array of fact objects." }
|
|
5778
|
+
},
|
|
5779
|
+
required: ["facts"]
|
|
5780
|
+
},
|
|
5781
|
+
outputSchema: { type: "object", properties: { success: { type: "boolean" }, count: { type: "number" } } }
|
|
5782
|
+
},
|
|
5783
|
+
{
|
|
5784
|
+
name: "recall",
|
|
5785
|
+
title: "Recall (Upstream)",
|
|
5786
|
+
description: "Upstream-compatible alias for memory-search. Searches stored memories by query.",
|
|
5787
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
5788
|
+
inputSchema: {
|
|
5789
|
+
type: "object",
|
|
5790
|
+
properties: {
|
|
5791
|
+
owner: { type: "string" },
|
|
5792
|
+
repo: { type: "string" },
|
|
5793
|
+
query: { type: "string", description: "Search query." },
|
|
5794
|
+
type: { type: "string", description: "Filter by memory type." },
|
|
5795
|
+
limit: { type: "number", default: 10 }
|
|
5796
|
+
},
|
|
5797
|
+
required: ["query"]
|
|
5798
|
+
},
|
|
5799
|
+
outputSchema: { type: "object", properties: { memories: { type: "array", items: { type: "object" } } } }
|
|
5800
|
+
},
|
|
5801
|
+
{
|
|
5802
|
+
name: "forget",
|
|
5803
|
+
title: "Forget (Upstream)",
|
|
5804
|
+
description: "Upstream-compatible alias for memory-delete. Deletes a stored memory by id or code.",
|
|
5805
|
+
annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true, openWorldHint: true },
|
|
5806
|
+
inputSchema: {
|
|
5807
|
+
type: "object",
|
|
5808
|
+
properties: {
|
|
5809
|
+
owner: { type: "string" },
|
|
5810
|
+
repo: { type: "string" },
|
|
5811
|
+
id: { type: "string", description: "Memory ID to delete." },
|
|
5812
|
+
code: { type: "string", description: "Memory code to delete." }
|
|
5813
|
+
},
|
|
5814
|
+
required: []
|
|
5815
|
+
},
|
|
5816
|
+
outputSchema: { type: "object", properties: { success: { type: "boolean" } } }
|
|
5817
|
+
}
|
|
5818
|
+
];
|
|
5819
|
+
|
|
5820
|
+
// src/mcp/tools/definitions/knowledge-graph.ts
|
|
5821
|
+
var KG_TOOL_DEFINITIONS = [
|
|
5822
|
+
{
|
|
5823
|
+
name: "create_entity",
|
|
5824
|
+
title: "KG Create Entity",
|
|
5825
|
+
description: "Creates a new knowledge graph entity. Entities represent nodes in the knowledge graph (e.g., concepts, people, systems). Name must be unique per repo. Owner and repo are auto-inferred from session when omitted.",
|
|
5826
|
+
annotations: {
|
|
5827
|
+
readOnlyHint: false,
|
|
5828
|
+
idempotentHint: false,
|
|
5829
|
+
destructiveHint: false,
|
|
5830
|
+
openWorldHint: false
|
|
5831
|
+
},
|
|
5832
|
+
inputSchema: {
|
|
5833
|
+
type: "object",
|
|
5834
|
+
properties: {
|
|
5835
|
+
name: {
|
|
5836
|
+
type: "string",
|
|
5837
|
+
description: "Unique entity name (acts as primary key per repo)."
|
|
5838
|
+
},
|
|
5839
|
+
type: {
|
|
5840
|
+
type: "string",
|
|
5841
|
+
default: "unknown",
|
|
5842
|
+
description: "Entity type for categorization (e.g., 'person', 'system', 'concept')."
|
|
5843
|
+
},
|
|
5844
|
+
description: {
|
|
5845
|
+
type: "string",
|
|
5846
|
+
description: "Optional description of the entity."
|
|
5847
|
+
},
|
|
5848
|
+
owner: {
|
|
5849
|
+
type: "string",
|
|
5850
|
+
description: "Organization/namespace. Auto-detected from session when omitted."
|
|
5851
|
+
},
|
|
5852
|
+
repo: {
|
|
5853
|
+
type: "string",
|
|
5854
|
+
description: "Repository/project name. Auto-detected from session when omitted."
|
|
5855
|
+
},
|
|
5856
|
+
structured: {
|
|
5857
|
+
type: "boolean",
|
|
5858
|
+
default: false,
|
|
5859
|
+
description: "If true, returns structured JSON results."
|
|
5860
|
+
}
|
|
5861
|
+
},
|
|
5862
|
+
required: ["name"]
|
|
5863
|
+
},
|
|
5864
|
+
outputSchema: {
|
|
5865
|
+
type: "object",
|
|
5866
|
+
properties: {
|
|
5867
|
+
success: { type: "boolean" },
|
|
5868
|
+
entity: {
|
|
5869
|
+
type: "object",
|
|
5870
|
+
properties: {
|
|
5871
|
+
name: { type: "string" },
|
|
5872
|
+
type: { type: "string" },
|
|
5873
|
+
description: { type: "string" },
|
|
5874
|
+
repo: { type: "string" },
|
|
5875
|
+
owner: { type: "string" }
|
|
5876
|
+
}
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5880
|
+
},
|
|
5881
|
+
{
|
|
5882
|
+
name: "delete_entity",
|
|
5883
|
+
title: "KG Delete Entity",
|
|
5884
|
+
description: "Deletes a knowledge graph entity by name. All related relations and observations are automatically removed via CASCADE. Owner and repo are auto-inferred from session when omitted.",
|
|
5885
|
+
annotations: {
|
|
5886
|
+
readOnlyHint: false,
|
|
5887
|
+
idempotentHint: false,
|
|
5888
|
+
destructiveHint: true,
|
|
5889
|
+
openWorldHint: false
|
|
5890
|
+
},
|
|
5891
|
+
inputSchema: {
|
|
5892
|
+
type: "object",
|
|
5893
|
+
properties: {
|
|
5894
|
+
name: {
|
|
5895
|
+
type: "string",
|
|
5896
|
+
description: "Name of the entity to delete."
|
|
5897
|
+
},
|
|
5898
|
+
owner: {
|
|
5899
|
+
type: "string",
|
|
5900
|
+
description: "Organization/namespace. Auto-detected from session when omitted."
|
|
5901
|
+
},
|
|
5902
|
+
repo: {
|
|
5903
|
+
type: "string",
|
|
5904
|
+
description: "Repository/project name. Auto-detected from session when omitted."
|
|
5905
|
+
},
|
|
5906
|
+
structured: {
|
|
5907
|
+
type: "boolean",
|
|
5908
|
+
default: false,
|
|
5909
|
+
description: "If true, returns structured JSON results."
|
|
5910
|
+
}
|
|
5911
|
+
},
|
|
5912
|
+
required: ["name"]
|
|
5913
|
+
},
|
|
5914
|
+
outputSchema: {
|
|
5915
|
+
type: "object",
|
|
5916
|
+
properties: {
|
|
5917
|
+
success: { type: "boolean" },
|
|
5918
|
+
deletedCount: { type: "number" }
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5921
|
+
},
|
|
5922
|
+
{
|
|
5923
|
+
name: "create_relation",
|
|
5924
|
+
title: "KG Create Relation",
|
|
5925
|
+
description: "Creates a directed relation between two existing knowledge graph entities. Both from_entity and to_entity must already exist. The composite key (from_entity, to_entity, relation_type) must be unique. Owner and repo are auto-inferred from session when omitted.",
|
|
5926
|
+
annotations: {
|
|
5927
|
+
readOnlyHint: false,
|
|
5928
|
+
idempotentHint: false,
|
|
5929
|
+
destructiveHint: false,
|
|
5930
|
+
openWorldHint: false
|
|
5931
|
+
},
|
|
5932
|
+
inputSchema: {
|
|
5933
|
+
type: "object",
|
|
5934
|
+
properties: {
|
|
5935
|
+
from_entity: {
|
|
5936
|
+
type: "string",
|
|
5937
|
+
description: "Source entity name (must exist)."
|
|
5938
|
+
},
|
|
5939
|
+
to_entity: {
|
|
5940
|
+
type: "string",
|
|
5941
|
+
description: "Target entity name (must exist)."
|
|
5942
|
+
},
|
|
5943
|
+
relation_type: {
|
|
5944
|
+
type: "string",
|
|
5945
|
+
description: "Type of relation (e.g., 'depends_on', 'implements', 'part_of')."
|
|
5946
|
+
},
|
|
5947
|
+
owner: {
|
|
5948
|
+
type: "string",
|
|
5949
|
+
description: "Organization/namespace. Auto-detected from session when omitted."
|
|
5950
|
+
},
|
|
5951
|
+
repo: {
|
|
5952
|
+
type: "string",
|
|
5953
|
+
description: "Repository/project name. Auto-detected from session when omitted."
|
|
5954
|
+
},
|
|
5955
|
+
structured: {
|
|
5956
|
+
type: "boolean",
|
|
5957
|
+
default: false,
|
|
5958
|
+
description: "If true, returns structured JSON results."
|
|
5959
|
+
}
|
|
5960
|
+
},
|
|
5961
|
+
required: ["from_entity", "to_entity", "relation_type"]
|
|
5962
|
+
},
|
|
5963
|
+
outputSchema: {
|
|
5964
|
+
type: "object",
|
|
5965
|
+
properties: {
|
|
5966
|
+
success: { type: "boolean" },
|
|
5967
|
+
relation: {
|
|
5968
|
+
type: "object",
|
|
5969
|
+
properties: {
|
|
5970
|
+
from_entity: { type: "string" },
|
|
5971
|
+
to_entity: { type: "string" },
|
|
5972
|
+
relation_type: { type: "string" }
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5976
|
+
}
|
|
5977
|
+
},
|
|
5978
|
+
{
|
|
5979
|
+
name: "delete_relation",
|
|
5980
|
+
title: "KG Delete Relation",
|
|
5981
|
+
description: "Deletes a relation by its composite key (from_entity, to_entity, relation_type). Owner and repo are auto-inferred from session when omitted.",
|
|
5982
|
+
annotations: {
|
|
5983
|
+
readOnlyHint: false,
|
|
5984
|
+
idempotentHint: false,
|
|
5985
|
+
destructiveHint: true,
|
|
5986
|
+
openWorldHint: false
|
|
5727
5987
|
},
|
|
5728
|
-
{
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5988
|
+
inputSchema: {
|
|
5989
|
+
type: "object",
|
|
5990
|
+
properties: {
|
|
5991
|
+
from_entity: {
|
|
5992
|
+
type: "string",
|
|
5993
|
+
description: "Source entity name."
|
|
5994
|
+
},
|
|
5995
|
+
to_entity: {
|
|
5996
|
+
type: "string",
|
|
5997
|
+
description: "Target entity name."
|
|
5998
|
+
},
|
|
5999
|
+
relation_type: {
|
|
6000
|
+
type: "string",
|
|
6001
|
+
description: "Relation type."
|
|
6002
|
+
},
|
|
6003
|
+
owner: {
|
|
6004
|
+
type: "string",
|
|
6005
|
+
description: "Organization/namespace. Auto-detected from session when omitted."
|
|
6006
|
+
},
|
|
6007
|
+
repo: {
|
|
6008
|
+
type: "string",
|
|
6009
|
+
description: "Repository/project name. Auto-detected from session when omitted."
|
|
6010
|
+
},
|
|
6011
|
+
structured: {
|
|
6012
|
+
type: "boolean",
|
|
6013
|
+
default: false,
|
|
6014
|
+
description: "If true, returns structured JSON results."
|
|
6015
|
+
}
|
|
6016
|
+
},
|
|
6017
|
+
required: ["from_entity", "to_entity", "relation_type"]
|
|
5735
6018
|
},
|
|
5736
|
-
{
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
6019
|
+
outputSchema: {
|
|
6020
|
+
type: "object",
|
|
6021
|
+
properties: {
|
|
6022
|
+
success: { type: "boolean" },
|
|
6023
|
+
deletedCount: { type: "number" }
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
},
|
|
6027
|
+
{
|
|
6028
|
+
name: "delete_observation",
|
|
6029
|
+
title: "KG Delete Observation",
|
|
6030
|
+
description: "Deletes an observation by its ID. Observation IDs are UUIDs returned when observations are created. Owner and repo are auto-inferred from session when omitted.",
|
|
6031
|
+
annotations: {
|
|
6032
|
+
readOnlyHint: false,
|
|
6033
|
+
idempotentHint: false,
|
|
6034
|
+
destructiveHint: true,
|
|
6035
|
+
openWorldHint: false
|
|
5743
6036
|
},
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
6037
|
+
inputSchema: {
|
|
6038
|
+
type: "object",
|
|
6039
|
+
properties: {
|
|
6040
|
+
id: {
|
|
6041
|
+
type: "string",
|
|
6042
|
+
description: "UUID of the observation to delete."
|
|
6043
|
+
},
|
|
6044
|
+
owner: {
|
|
6045
|
+
type: "string",
|
|
6046
|
+
description: "Organization/namespace. Auto-detected from session when omitted."
|
|
6047
|
+
},
|
|
6048
|
+
repo: {
|
|
6049
|
+
type: "string",
|
|
6050
|
+
description: "Repository/project name. Auto-detected from session when omitted."
|
|
6051
|
+
},
|
|
6052
|
+
structured: {
|
|
6053
|
+
type: "boolean",
|
|
6054
|
+
default: false,
|
|
6055
|
+
description: "If true, returns structured JSON results."
|
|
6056
|
+
}
|
|
6057
|
+
},
|
|
6058
|
+
required: ["id"]
|
|
5752
6059
|
},
|
|
6060
|
+
outputSchema: {
|
|
6061
|
+
type: "object",
|
|
6062
|
+
properties: {
|
|
6063
|
+
success: { type: "boolean" },
|
|
6064
|
+
deletedCount: { type: "number" }
|
|
6065
|
+
}
|
|
6066
|
+
}
|
|
6067
|
+
}
|
|
6068
|
+
];
|
|
6069
|
+
|
|
6070
|
+
// src/mcp/tools/definitions/index.ts
|
|
6071
|
+
var TOOL_DEFINITIONS = [
|
|
6072
|
+
...MEMORY_TOOL_DEFINITIONS,
|
|
6073
|
+
...TASK_TOOL_DEFINITIONS,
|
|
6074
|
+
...HANDOFF_TOOL_DEFINITIONS,
|
|
6075
|
+
...STANDARD_TOOL_DEFINITIONS,
|
|
6076
|
+
...AGENT_TOOL_DEFINITIONS,
|
|
6077
|
+
...KG_TOOL_DEFINITIONS
|
|
6078
|
+
];
|
|
6079
|
+
|
|
6080
|
+
// src/mcp/utils/completion.ts
|
|
6081
|
+
var MAX_COMPLETION_VALUES = 100;
|
|
6082
|
+
function rankCompletionValues(candidates, input) {
|
|
6083
|
+
const unique = [...new Set(candidates.filter(Boolean))];
|
|
6084
|
+
const needle = input.trim().toLowerCase();
|
|
6085
|
+
if (!needle) {
|
|
6086
|
+
return unique.slice(0, MAX_COMPLETION_VALUES);
|
|
6087
|
+
}
|
|
6088
|
+
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);
|
|
6089
|
+
}
|
|
6090
|
+
function scoreCompletionValue(value, needle) {
|
|
6091
|
+
const haystack = value.toLowerCase();
|
|
6092
|
+
if (haystack === needle) return 100;
|
|
6093
|
+
if (haystack.startsWith(needle)) return 75;
|
|
6094
|
+
if (haystack.includes(needle)) return 50;
|
|
6095
|
+
const compactNeedle = needle.replace(/[\s_-]+/g, "");
|
|
6096
|
+
const compactHaystack = haystack.replace(/[\s_-]+/g, "");
|
|
6097
|
+
if (compactNeedle && compactHaystack.includes(compactNeedle)) return 25;
|
|
6098
|
+
return 0;
|
|
6099
|
+
}
|
|
6100
|
+
|
|
6101
|
+
// src/mcp/utils/pagination.ts
|
|
6102
|
+
function encodeCursor(offset) {
|
|
6103
|
+
return Buffer.from(String(offset), "utf8").toString("base64");
|
|
6104
|
+
}
|
|
6105
|
+
function decodeCursor(cursor) {
|
|
6106
|
+
if (cursor === void 0 || cursor === null || cursor === "") {
|
|
6107
|
+
return 0;
|
|
6108
|
+
}
|
|
6109
|
+
if (typeof cursor !== "string" || cursor.trim() === "") {
|
|
6110
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
6111
|
+
}
|
|
6112
|
+
let decoded;
|
|
6113
|
+
try {
|
|
6114
|
+
decoded = Buffer.from(cursor, "base64").toString("utf8");
|
|
6115
|
+
} catch {
|
|
6116
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
6117
|
+
}
|
|
6118
|
+
if (!/^\d+$/.test(decoded)) {
|
|
6119
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
6120
|
+
}
|
|
6121
|
+
const offset = Number.parseInt(decoded, 10);
|
|
6122
|
+
if (!Number.isFinite(offset) || offset < 0) {
|
|
6123
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
6124
|
+
}
|
|
6125
|
+
return offset;
|
|
6126
|
+
}
|
|
6127
|
+
function invalidPaginationParams(message) {
|
|
6128
|
+
const error = new Error(message);
|
|
6129
|
+
error.code = -32602;
|
|
6130
|
+
return error;
|
|
6131
|
+
}
|
|
6132
|
+
|
|
6133
|
+
// src/mcp/resources/index.ts
|
|
6134
|
+
var DEFAULT_PAGE_SIZE = 25;
|
|
6135
|
+
var MAX_PAGE_SIZE = 100;
|
|
6136
|
+
function listResources(session, params) {
|
|
6137
|
+
const resources = [
|
|
5753
6138
|
{
|
|
5754
|
-
|
|
5755
|
-
name: "Repository
|
|
5756
|
-
title: "Repository
|
|
5757
|
-
description: "
|
|
6139
|
+
uri: "repository://index",
|
|
6140
|
+
name: "Repository Index",
|
|
6141
|
+
title: "Repository Index",
|
|
6142
|
+
description: "List of all known repositories with memory/task counts and last activity",
|
|
5758
6143
|
mimeType: "application/json",
|
|
5759
|
-
annotations: {
|
|
6144
|
+
annotations: {
|
|
6145
|
+
audience: ["assistant"],
|
|
6146
|
+
priority: 1,
|
|
6147
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
6148
|
+
}
|
|
5760
6149
|
},
|
|
5761
|
-
// ── Action detail ────────────────────────────────────────────────────────
|
|
5762
6150
|
{
|
|
5763
|
-
|
|
5764
|
-
name: "
|
|
5765
|
-
title: "
|
|
5766
|
-
description: "
|
|
6151
|
+
uri: "session://roots",
|
|
6152
|
+
name: "Session Roots",
|
|
6153
|
+
title: "Session Roots",
|
|
6154
|
+
description: session?.roots.length ? "Active workspace roots provided by the MCP client" : "No active workspace roots were provided by the MCP client",
|
|
5767
6155
|
mimeType: "application/json",
|
|
5768
|
-
|
|
6156
|
+
size: Buffer.byteLength(JSON.stringify({ roots: session?.roots ?? [] }), "utf8"),
|
|
6157
|
+
annotations: {
|
|
6158
|
+
audience: ["assistant"],
|
|
6159
|
+
priority: 0.95,
|
|
6160
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
6161
|
+
}
|
|
5769
6162
|
}
|
|
5770
6163
|
];
|
|
5771
|
-
return paginateEntries("
|
|
6164
|
+
return paginateEntries("resources", resources, params);
|
|
5772
6165
|
}
|
|
5773
6166
|
function completeResourceArgument(resourceUri, argumentName, argumentValue, _contextArguments, dataSources) {
|
|
5774
6167
|
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") {
|
|
@@ -5783,234 +6176,6 @@ function completeResourceArgument(resourceUri, argumentName, argumentValue, _con
|
|
|
5783
6176
|
}
|
|
5784
6177
|
throw invalidCompletionParams(`Unknown resource template or argument: ${resourceUri} (${argumentName})`);
|
|
5785
6178
|
}
|
|
5786
|
-
function readResource(uri, db, session) {
|
|
5787
|
-
logger.info("[Tool] resource.read", { uri });
|
|
5788
|
-
if (uri === "repository://index") {
|
|
5789
|
-
const repos = db.system.listRepoNavigation();
|
|
5790
|
-
const payload = JSON.stringify(repos, null, 2);
|
|
5791
|
-
return {
|
|
5792
|
-
contents: [
|
|
5793
|
-
{
|
|
5794
|
-
uri,
|
|
5795
|
-
mimeType: "application/json",
|
|
5796
|
-
text: payload,
|
|
5797
|
-
size: Buffer.byteLength(payload, "utf8"),
|
|
5798
|
-
annotations: {
|
|
5799
|
-
audience: ["assistant"],
|
|
5800
|
-
priority: 1,
|
|
5801
|
-
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
5802
|
-
}
|
|
5803
|
-
}
|
|
5804
|
-
]
|
|
5805
|
-
};
|
|
5806
|
-
}
|
|
5807
|
-
if (uri === "session://roots") {
|
|
5808
|
-
const payload = JSON.stringify({ roots: session?.roots ?? [] }, null, 2);
|
|
5809
|
-
return {
|
|
5810
|
-
contents: [
|
|
5811
|
-
{
|
|
5812
|
-
uri,
|
|
5813
|
-
mimeType: "application/json",
|
|
5814
|
-
text: payload,
|
|
5815
|
-
size: Buffer.byteLength(payload, "utf8"),
|
|
5816
|
-
annotations: {
|
|
5817
|
-
audience: ["assistant"],
|
|
5818
|
-
priority: 0.95,
|
|
5819
|
-
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
5820
|
-
}
|
|
5821
|
-
}
|
|
5822
|
-
]
|
|
5823
|
-
};
|
|
5824
|
-
}
|
|
5825
|
-
const memoryIdMatch = uri.match(/^memory:\/\/([0-9a-f-]{36})$/i);
|
|
5826
|
-
if (memoryIdMatch) {
|
|
5827
|
-
const id = memoryIdMatch[1];
|
|
5828
|
-
const entry = db.memories.getByIdWithStats(id);
|
|
5829
|
-
if (!entry) throw resourceNotFound(`Memory with ID ${id} not found.`, uri);
|
|
5830
|
-
const payload = JSON.stringify(entry, null, 2);
|
|
5831
|
-
return {
|
|
5832
|
-
contents: [
|
|
5833
|
-
{
|
|
5834
|
-
uri,
|
|
5835
|
-
mimeType: "application/json",
|
|
5836
|
-
text: payload,
|
|
5837
|
-
size: Buffer.byteLength(payload, "utf8"),
|
|
5838
|
-
annotations: {
|
|
5839
|
-
audience: ["assistant"],
|
|
5840
|
-
priority: 0.75,
|
|
5841
|
-
lastModified: entry.updated_at || entry.created_at
|
|
5842
|
-
}
|
|
5843
|
-
}
|
|
5844
|
-
]
|
|
5845
|
-
};
|
|
5846
|
-
}
|
|
5847
|
-
const taskIdMatch = uri.match(/^task:\/\/([0-9a-f-]{36})$/i);
|
|
5848
|
-
if (taskIdMatch) {
|
|
5849
|
-
const id = taskIdMatch[1];
|
|
5850
|
-
const task = db.tasks.getTaskById(id);
|
|
5851
|
-
if (!task) throw resourceNotFound(`Task with ID ${id} not found.`, uri);
|
|
5852
|
-
const payload = JSON.stringify(task, null, 2);
|
|
5853
|
-
return {
|
|
5854
|
-
contents: [
|
|
5855
|
-
{
|
|
5856
|
-
uri,
|
|
5857
|
-
mimeType: "application/json",
|
|
5858
|
-
text: payload,
|
|
5859
|
-
size: Buffer.byteLength(payload, "utf8"),
|
|
5860
|
-
annotations: {
|
|
5861
|
-
audience: ["assistant"],
|
|
5862
|
-
priority: 0.8,
|
|
5863
|
-
lastModified: task.updated_at || task.created_at
|
|
5864
|
-
}
|
|
5865
|
-
}
|
|
5866
|
-
]
|
|
5867
|
-
};
|
|
5868
|
-
}
|
|
5869
|
-
const repoBase = parseRepoUri(uri);
|
|
5870
|
-
if (repoBase) {
|
|
5871
|
-
const { name, path: repoPath, query } = repoBase;
|
|
5872
|
-
if (repoPath === "summary") {
|
|
5873
|
-
const summary = db.summaries.getSummary("", name);
|
|
5874
|
-
const text = summary?.summary || `No summary available for repository: ${name}`;
|
|
5875
|
-
return {
|
|
5876
|
-
contents: [
|
|
5877
|
-
{
|
|
5878
|
-
uri,
|
|
5879
|
-
mimeType: "text/plain",
|
|
5880
|
-
text,
|
|
5881
|
-
size: Buffer.byteLength(text, "utf8"),
|
|
5882
|
-
annotations: {
|
|
5883
|
-
audience: ["assistant"],
|
|
5884
|
-
priority: 0.95,
|
|
5885
|
-
lastModified: summary?.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
5886
|
-
}
|
|
5887
|
-
}
|
|
5888
|
-
]
|
|
5889
|
-
};
|
|
5890
|
-
}
|
|
5891
|
-
if (repoPath === "memories") {
|
|
5892
|
-
const search = query.get("search") || "";
|
|
5893
|
-
const type = query.get("type");
|
|
5894
|
-
const tag = query.get("tag");
|
|
5895
|
-
const result = db.memories.listMemoriesForDashboard({
|
|
5896
|
-
repo: name,
|
|
5897
|
-
type: type || void 0,
|
|
5898
|
-
tag: tag || void 0,
|
|
5899
|
-
search: search || void 0,
|
|
5900
|
-
limit: 50
|
|
5901
|
-
});
|
|
5902
|
-
const entries = result.items;
|
|
5903
|
-
const payload = JSON.stringify(entries, null, 2);
|
|
5904
|
-
return {
|
|
5905
|
-
contents: [
|
|
5906
|
-
{
|
|
5907
|
-
uri,
|
|
5908
|
-
mimeType: "application/json",
|
|
5909
|
-
text: payload,
|
|
5910
|
-
size: Buffer.byteLength(payload, "utf8"),
|
|
5911
|
-
annotations: {
|
|
5912
|
-
audience: ["assistant"],
|
|
5913
|
-
priority: 0.85,
|
|
5914
|
-
lastModified: deriveLastModifiedFromCollection(
|
|
5915
|
-
entries.map((e) => e.updated_at || e.created_at)
|
|
5916
|
-
)
|
|
5917
|
-
}
|
|
5918
|
-
}
|
|
5919
|
-
]
|
|
5920
|
-
};
|
|
5921
|
-
}
|
|
5922
|
-
if (repoPath === "tasks") {
|
|
5923
|
-
const status = query.get("status");
|
|
5924
|
-
const priority = query.get("priority");
|
|
5925
|
-
const owner = parseRepoInput(name).owner;
|
|
5926
|
-
let tasks;
|
|
5927
|
-
if (status && status !== "all") {
|
|
5928
|
-
const statuses = status.split(",").map((s) => s.trim());
|
|
5929
|
-
tasks = db.tasks.getTasksByMultipleStatuses(owner, name, statuses);
|
|
5930
|
-
} else {
|
|
5931
|
-
tasks = db.tasks.getTasksByMultipleStatuses(owner, name, ["backlog", "pending", "in_progress", "blocked"]);
|
|
5932
|
-
}
|
|
5933
|
-
if (priority) {
|
|
5934
|
-
const p = Number(priority);
|
|
5935
|
-
if (!isNaN(p)) {
|
|
5936
|
-
tasks = tasks.filter((t) => t.priority === p);
|
|
5937
|
-
}
|
|
5938
|
-
}
|
|
5939
|
-
const payload = JSON.stringify(tasks, null, 2);
|
|
5940
|
-
return {
|
|
5941
|
-
contents: [
|
|
5942
|
-
{
|
|
5943
|
-
uri,
|
|
5944
|
-
mimeType: "application/json",
|
|
5945
|
-
text: payload,
|
|
5946
|
-
size: Buffer.byteLength(payload, "utf8"),
|
|
5947
|
-
annotations: {
|
|
5948
|
-
audience: ["assistant"],
|
|
5949
|
-
priority: 0.9,
|
|
5950
|
-
lastModified: deriveLastModifiedFromCollection(tasks.map((t) => t.updated_at))
|
|
5951
|
-
}
|
|
5952
|
-
}
|
|
5953
|
-
]
|
|
5954
|
-
};
|
|
5955
|
-
}
|
|
5956
|
-
if (repoPath === "actions") {
|
|
5957
|
-
const actions = db.actions.getRecentActions("", name, 100);
|
|
5958
|
-
const payload = JSON.stringify(actions, null, 2);
|
|
5959
|
-
return {
|
|
5960
|
-
contents: [
|
|
5961
|
-
{
|
|
5962
|
-
uri,
|
|
5963
|
-
mimeType: "application/json",
|
|
5964
|
-
text: payload,
|
|
5965
|
-
size: Buffer.byteLength(payload, "utf8"),
|
|
5966
|
-
annotations: {
|
|
5967
|
-
audience: ["assistant"],
|
|
5968
|
-
priority: 0.6,
|
|
5969
|
-
lastModified: deriveLastModifiedFromCollection(actions.map((a) => a.created_at))
|
|
5970
|
-
}
|
|
5971
|
-
}
|
|
5972
|
-
]
|
|
5973
|
-
};
|
|
5974
|
-
}
|
|
5975
|
-
}
|
|
5976
|
-
const actionIdMatch = uri.match(/^action:\/\/(\d+)$/);
|
|
5977
|
-
if (actionIdMatch) {
|
|
5978
|
-
const id = Number(actionIdMatch[1]);
|
|
5979
|
-
const action = db.actions.getActionById(id);
|
|
5980
|
-
if (!action) throw resourceNotFound(`Action with ID ${id} not found.`, uri);
|
|
5981
|
-
const payload = JSON.stringify(action, null, 2);
|
|
5982
|
-
return {
|
|
5983
|
-
contents: [
|
|
5984
|
-
{
|
|
5985
|
-
uri,
|
|
5986
|
-
mimeType: "application/json",
|
|
5987
|
-
text: payload,
|
|
5988
|
-
size: Buffer.byteLength(payload, "utf8"),
|
|
5989
|
-
annotations: {
|
|
5990
|
-
audience: ["assistant"],
|
|
5991
|
-
priority: 0.55,
|
|
5992
|
-
lastModified: action.created_at
|
|
5993
|
-
}
|
|
5994
|
-
}
|
|
5995
|
-
]
|
|
5996
|
-
};
|
|
5997
|
-
}
|
|
5998
|
-
throw resourceNotFound(`Unknown resource URI: ${uri}`, uri);
|
|
5999
|
-
}
|
|
6000
|
-
function parseRepoUri(uri) {
|
|
6001
|
-
const prefix = "repository://";
|
|
6002
|
-
if (!uri.startsWith(prefix)) return null;
|
|
6003
|
-
const rest = uri.slice(prefix.length);
|
|
6004
|
-
const queryStart = rest.indexOf("?");
|
|
6005
|
-
const withoutQuery = queryStart === -1 ? rest : rest.slice(0, queryStart);
|
|
6006
|
-
const queryString = queryStart === -1 ? "" : rest.slice(queryStart + 1);
|
|
6007
|
-
const slashIdx = withoutQuery.indexOf("/");
|
|
6008
|
-
if (slashIdx === -1) return null;
|
|
6009
|
-
const name = withoutQuery.slice(0, slashIdx);
|
|
6010
|
-
const path6 = withoutQuery.slice(slashIdx + 1);
|
|
6011
|
-
if (!name || !path6) return null;
|
|
6012
|
-
return { name, path: path6, query: new URLSearchParams(queryString) };
|
|
6013
|
-
}
|
|
6014
6179
|
function paginateEntries(key, entries, params) {
|
|
6015
6180
|
const limit = normalizeLimit(params?.limit);
|
|
6016
6181
|
const offset = decodeCursor(params?.cursor);
|
|
@@ -6027,16 +6192,6 @@ function normalizeLimit(limit) {
|
|
|
6027
6192
|
}
|
|
6028
6193
|
return Math.min(MAX_PAGE_SIZE, Math.max(1, Math.trunc(limit)));
|
|
6029
6194
|
}
|
|
6030
|
-
function deriveLastModifiedFromCollection(values) {
|
|
6031
|
-
const normalized = values.filter((value) => typeof value === "string" && value.length > 0);
|
|
6032
|
-
return normalized.sort().at(-1) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
6033
|
-
}
|
|
6034
|
-
function resourceNotFound(message, uri) {
|
|
6035
|
-
const error = new Error(message);
|
|
6036
|
-
error.code = -32002;
|
|
6037
|
-
error.data = { uri };
|
|
6038
|
-
return error;
|
|
6039
|
-
}
|
|
6040
6195
|
function invalidCompletionParams(message) {
|
|
6041
6196
|
const error = new Error(message);
|
|
6042
6197
|
error.code = -32602;
|
|
@@ -6070,51 +6225,6 @@ for (const name of promptFiles) {
|
|
|
6070
6225
|
logger.warn(`Failed to load prompt ${name}: ${e}`);
|
|
6071
6226
|
}
|
|
6072
6227
|
}
|
|
6073
|
-
async function listPrompts(db, session, params) {
|
|
6074
|
-
const allPrompts = Object.values(PROMPTS).map((p) => ({
|
|
6075
|
-
name: p.name,
|
|
6076
|
-
description: p.description,
|
|
6077
|
-
arguments: p.arguments,
|
|
6078
|
-
metadata: p.agent ? { agent: p.agent } : void 0
|
|
6079
|
-
}));
|
|
6080
|
-
const rawLimit = typeof params?.limit === "number" && Number.isInteger(params?.limit) ? params.limit : 50;
|
|
6081
|
-
const limit = Math.max(1, Math.min(100, Math.trunc(rawLimit)));
|
|
6082
|
-
const offset = decodeCursor(params?.cursor);
|
|
6083
|
-
const sliced = allPrompts.slice(offset, offset + limit);
|
|
6084
|
-
const nextOffset = offset + sliced.length;
|
|
6085
|
-
return {
|
|
6086
|
-
prompts: sliced,
|
|
6087
|
-
nextCursor: nextOffset < allPrompts.length ? encodeCursor(nextOffset) : void 0
|
|
6088
|
-
};
|
|
6089
|
-
}
|
|
6090
|
-
async function getPrompt(name, args = {}, db, session) {
|
|
6091
|
-
const prompt = PROMPTS[name];
|
|
6092
|
-
if (!prompt) {
|
|
6093
|
-
throw new Error(`Prompt not found: ${name}`);
|
|
6094
|
-
}
|
|
6095
|
-
const inferredRepo = inferRepoFromSession(session);
|
|
6096
|
-
const inferredOwner = inferOwnerFromSession(session);
|
|
6097
|
-
const messages = prompt.messages.map((m) => {
|
|
6098
|
-
let text = m.content.text;
|
|
6099
|
-
for (const [key, value] of Object.entries(args)) {
|
|
6100
|
-
text = text.replace(new RegExp(`\\{{${key}\\}}`, "g"), value);
|
|
6101
|
-
}
|
|
6102
|
-
text = text.replace(/{{current_repo}}/g, inferredRepo || "unknown-repo");
|
|
6103
|
-
text = text.replace(/{{current_owner}}/g, inferredOwner || "unknown-owner");
|
|
6104
|
-
return {
|
|
6105
|
-
...m,
|
|
6106
|
-
content: {
|
|
6107
|
-
...m.content,
|
|
6108
|
-
text
|
|
6109
|
-
}
|
|
6110
|
-
};
|
|
6111
|
-
});
|
|
6112
|
-
return {
|
|
6113
|
-
description: prompt.description,
|
|
6114
|
-
messages,
|
|
6115
|
-
metadata: prompt.agent ? { agent: prompt.agent } : void 0
|
|
6116
|
-
};
|
|
6117
|
-
}
|
|
6118
6228
|
async function completePromptArgument(name, argName, value, contextArguments, dataSources) {
|
|
6119
6229
|
void name;
|
|
6120
6230
|
void contextArguments;
|
|
@@ -6757,6 +6867,75 @@ var StandardDetailSchema = z6.object({
|
|
|
6757
6867
|
message: "Either id or code must be provided"
|
|
6758
6868
|
});
|
|
6759
6869
|
|
|
6870
|
+
// src/mcp/tools/schemas/agent.ts
|
|
6871
|
+
import { z as z7 } from "zod";
|
|
6872
|
+
var AgentContextSchema = z7.object({
|
|
6873
|
+
owner: z7.string().min(1),
|
|
6874
|
+
repo: z7.string().min(1),
|
|
6875
|
+
objective: z7.string().optional(),
|
|
6876
|
+
type_filter: z7.enum(["code_fact", "decision", "mistake", "pattern", "task_archive"]).optional(),
|
|
6877
|
+
limit: z7.number().min(1).max(100).default(5),
|
|
6878
|
+
structured: z7.boolean().default(false)
|
|
6879
|
+
});
|
|
6880
|
+
var DecisionLogSchema = z7.object({
|
|
6881
|
+
summary: z7.string().min(3).max(255, { message: "Summary must be at most 255 characters" }),
|
|
6882
|
+
context: z7.string().min(10, { message: "Context must be at least 10 characters" }),
|
|
6883
|
+
rationale: z7.string().min(10, { message: "Rationale must be at least 10 characters" }),
|
|
6884
|
+
alternatives: z7.array(z7.string()).optional(),
|
|
6885
|
+
tags: z7.array(z7.string()).optional(),
|
|
6886
|
+
owner: z7.string().optional(),
|
|
6887
|
+
repo: z7.string().optional(),
|
|
6888
|
+
structured: z7.boolean().default(false)
|
|
6889
|
+
});
|
|
6890
|
+
var SessionSummarizeSchema = z7.object({
|
|
6891
|
+
summary: z7.string().min(10, { message: "Summary must be at least 10 characters" }),
|
|
6892
|
+
key_decisions: z7.array(z7.string()).optional(),
|
|
6893
|
+
next_steps: z7.array(z7.string()).optional(),
|
|
6894
|
+
tags: z7.array(z7.string()).optional(),
|
|
6895
|
+
owner: z7.string().optional(),
|
|
6896
|
+
repo: z7.string().optional(),
|
|
6897
|
+
structured: z7.boolean().default(false)
|
|
6898
|
+
});
|
|
6899
|
+
|
|
6900
|
+
// src/mcp/tools/schemas/knowledge-graph.ts
|
|
6901
|
+
import { z as z8 } from "zod";
|
|
6902
|
+
var CreateEntitySchema = z8.object({
|
|
6903
|
+
name: z8.string().min(1).max(255),
|
|
6904
|
+
type: z8.string().default("unknown"),
|
|
6905
|
+
description: z8.string().optional(),
|
|
6906
|
+
owner: z8.string().optional(),
|
|
6907
|
+
repo: z8.string().optional(),
|
|
6908
|
+
structured: z8.boolean().default(false)
|
|
6909
|
+
});
|
|
6910
|
+
var DeleteEntitySchema = z8.object({
|
|
6911
|
+
name: z8.string().min(1),
|
|
6912
|
+
owner: z8.string().optional(),
|
|
6913
|
+
repo: z8.string().optional(),
|
|
6914
|
+
structured: z8.boolean().default(false)
|
|
6915
|
+
});
|
|
6916
|
+
var CreateRelationSchema = z8.object({
|
|
6917
|
+
from_entity: z8.string().min(1),
|
|
6918
|
+
to_entity: z8.string().min(1),
|
|
6919
|
+
relation_type: z8.string().min(1),
|
|
6920
|
+
owner: z8.string().optional(),
|
|
6921
|
+
repo: z8.string().optional(),
|
|
6922
|
+
structured: z8.boolean().default(false)
|
|
6923
|
+
});
|
|
6924
|
+
var DeleteRelationSchema = z8.object({
|
|
6925
|
+
from_entity: z8.string().min(1),
|
|
6926
|
+
to_entity: z8.string().min(1),
|
|
6927
|
+
relation_type: z8.string().min(1),
|
|
6928
|
+
owner: z8.string().optional(),
|
|
6929
|
+
repo: z8.string().optional(),
|
|
6930
|
+
structured: z8.boolean().default(false)
|
|
6931
|
+
});
|
|
6932
|
+
var DeleteObservationSchema = z8.object({
|
|
6933
|
+
id: z8.string().min(1),
|
|
6934
|
+
owner: z8.string().optional(),
|
|
6935
|
+
repo: z8.string().optional(),
|
|
6936
|
+
structured: z8.boolean().default(false)
|
|
6937
|
+
});
|
|
6938
|
+
|
|
6760
6939
|
// src/mcp/tools/handoff.manage.ts
|
|
6761
6940
|
function buildHandoffListSummary(repo, count, status, fromAgent, toAgent) {
|
|
6762
6941
|
const parts = [`Found ${count} handoff${count === 1 ? "" : "s"} in repo "${repo}".`];
|
|
@@ -7057,37 +7236,27 @@ function buildStandardVectorText(standard) {
|
|
|
7057
7236
|
}
|
|
7058
7237
|
|
|
7059
7238
|
export {
|
|
7060
|
-
MCP_PROTOCOL_VERSION,
|
|
7061
|
-
CAPABILITIES,
|
|
7062
7239
|
logger,
|
|
7063
|
-
setLogLevel,
|
|
7064
|
-
getLogLevel,
|
|
7065
7240
|
addLogSink,
|
|
7066
|
-
LOG_LEVEL_VALUES,
|
|
7067
7241
|
createFileSink,
|
|
7068
7242
|
parseRepoInput,
|
|
7069
7243
|
normalizeRepo,
|
|
7070
7244
|
SQLiteStore,
|
|
7071
7245
|
RealVectorStore,
|
|
7072
7246
|
TOOL_DEFINITIONS,
|
|
7073
|
-
|
|
7074
|
-
decodeCursor,
|
|
7247
|
+
rankCompletionValues,
|
|
7075
7248
|
listResources,
|
|
7076
|
-
listResourceTemplates,
|
|
7077
7249
|
completeResourceArgument,
|
|
7078
|
-
readResource,
|
|
7079
7250
|
createSessionContext,
|
|
7080
|
-
updateSessionFromInitialize,
|
|
7081
|
-
updateSessionRoots,
|
|
7082
|
-
extractRootsFromResult,
|
|
7083
7251
|
getFilesystemRoots,
|
|
7084
7252
|
isPathWithinRoots,
|
|
7085
7253
|
findContainingRoot,
|
|
7086
7254
|
inferRepoFromSession,
|
|
7087
7255
|
inferOwnerFromSession,
|
|
7256
|
+
listPromptFiles,
|
|
7257
|
+
loadPromptFromMarkdown,
|
|
7258
|
+
loadServerInstructions,
|
|
7088
7259
|
PROMPTS,
|
|
7089
|
-
listPrompts,
|
|
7090
|
-
getPrompt,
|
|
7091
7260
|
completePromptArgument,
|
|
7092
7261
|
createMcpResponse,
|
|
7093
7262
|
getPrimaryTextContent,
|
|
@@ -7113,6 +7282,14 @@ export {
|
|
|
7113
7282
|
StandardSearchSchema,
|
|
7114
7283
|
StandardDeleteSchema,
|
|
7115
7284
|
StandardDetailSchema,
|
|
7285
|
+
AgentContextSchema,
|
|
7286
|
+
DecisionLogSchema,
|
|
7287
|
+
SessionSummarizeSchema,
|
|
7288
|
+
CreateEntitySchema,
|
|
7289
|
+
DeleteEntitySchema,
|
|
7290
|
+
CreateRelationSchema,
|
|
7291
|
+
DeleteRelationSchema,
|
|
7292
|
+
DeleteObservationSchema,
|
|
7116
7293
|
handleHandoffCreate,
|
|
7117
7294
|
handleHandoffList,
|
|
7118
7295
|
handleHandoffUpdate,
|