@cohaku/cli 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-HMH4HWDA.js → chunk-HUMYWLCL.js} +3 -0
- package/dist/{chunk-2MC3FCZP.js → chunk-MU3X6KBC.js} +718 -137
- package/dist/{chunk-7WYEO45L.js → chunk-SJZY3OAT.js} +2 -2
- package/dist/{chunk-437HAVIY.js → chunk-XD75S5BU.js} +6 -1
- package/dist/{chunk-2CDNPZUE.js → chunk-ZELLRTG5.js} +1 -1
- package/dist/dashboard/assets/index-BEGYqqtj.css +1 -0
- package/dist/dashboard/assets/index-uzAxYmEZ.js +51 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/index.js +5 -5
- package/dist/{mcp-QOTZLT7H.js → mcp-OUXSA2GV.js} +4 -4
- package/dist/{serve-3QZ3TVQG.js → serve-2QVCMO3C.js} +5 -5
- package/dist/{start-VKAEMLQE.js → start-S5JBNPBO.js} +6 -6
- package/dist/{status-PG7GHTRM.js → status-NYW3OOGW.js} +2 -2
- package/package.json +2 -1
- package/dist/dashboard/assets/index-BbaNHCTp.css +0 -1
- package/dist/dashboard/assets/index-VgrI7y2q.js +0 -11
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
__commonJS,
|
|
4
4
|
__export,
|
|
5
5
|
__toESM
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-ZELLRTG5.js";
|
|
7
7
|
|
|
8
8
|
// ../../node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/compile/codegen/code.js
|
|
9
9
|
var require_code = __commonJS({
|
|
@@ -29205,6 +29205,228 @@ var McpZodTypeKind;
|
|
|
29205
29205
|
McpZodTypeKind2["Completable"] = "McpCompletable";
|
|
29206
29206
|
})(McpZodTypeKind || (McpZodTypeKind = {}));
|
|
29207
29207
|
|
|
29208
|
+
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/uriTemplate.js
|
|
29209
|
+
var MAX_TEMPLATE_LENGTH = 1e6;
|
|
29210
|
+
var MAX_VARIABLE_LENGTH = 1e6;
|
|
29211
|
+
var MAX_TEMPLATE_EXPRESSIONS = 1e4;
|
|
29212
|
+
var MAX_REGEX_LENGTH = 1e6;
|
|
29213
|
+
var UriTemplate = class _UriTemplate {
|
|
29214
|
+
/**
|
|
29215
|
+
* Returns true if the given string contains any URI template expressions.
|
|
29216
|
+
* A template expression is a sequence of characters enclosed in curly braces,
|
|
29217
|
+
* like {foo} or {?bar}.
|
|
29218
|
+
*/
|
|
29219
|
+
static isTemplate(str) {
|
|
29220
|
+
return /\{[^}\s]+\}/.test(str);
|
|
29221
|
+
}
|
|
29222
|
+
static validateLength(str, max, context) {
|
|
29223
|
+
if (str.length > max) {
|
|
29224
|
+
throw new Error(`${context} exceeds maximum length of ${max} characters (got ${str.length})`);
|
|
29225
|
+
}
|
|
29226
|
+
}
|
|
29227
|
+
get variableNames() {
|
|
29228
|
+
return this.parts.flatMap((part) => typeof part === "string" ? [] : part.names);
|
|
29229
|
+
}
|
|
29230
|
+
constructor(template) {
|
|
29231
|
+
_UriTemplate.validateLength(template, MAX_TEMPLATE_LENGTH, "Template");
|
|
29232
|
+
this.template = template;
|
|
29233
|
+
this.parts = this.parse(template);
|
|
29234
|
+
}
|
|
29235
|
+
toString() {
|
|
29236
|
+
return this.template;
|
|
29237
|
+
}
|
|
29238
|
+
parse(template) {
|
|
29239
|
+
const parts = [];
|
|
29240
|
+
let currentText = "";
|
|
29241
|
+
let i = 0;
|
|
29242
|
+
let expressionCount = 0;
|
|
29243
|
+
while (i < template.length) {
|
|
29244
|
+
if (template[i] === "{") {
|
|
29245
|
+
if (currentText) {
|
|
29246
|
+
parts.push(currentText);
|
|
29247
|
+
currentText = "";
|
|
29248
|
+
}
|
|
29249
|
+
const end = template.indexOf("}", i);
|
|
29250
|
+
if (end === -1)
|
|
29251
|
+
throw new Error("Unclosed template expression");
|
|
29252
|
+
expressionCount++;
|
|
29253
|
+
if (expressionCount > MAX_TEMPLATE_EXPRESSIONS) {
|
|
29254
|
+
throw new Error(`Template contains too many expressions (max ${MAX_TEMPLATE_EXPRESSIONS})`);
|
|
29255
|
+
}
|
|
29256
|
+
const expr = template.slice(i + 1, end);
|
|
29257
|
+
const operator = this.getOperator(expr);
|
|
29258
|
+
const exploded = expr.includes("*");
|
|
29259
|
+
const names = this.getNames(expr);
|
|
29260
|
+
const name = names[0];
|
|
29261
|
+
for (const name2 of names) {
|
|
29262
|
+
_UriTemplate.validateLength(name2, MAX_VARIABLE_LENGTH, "Variable name");
|
|
29263
|
+
}
|
|
29264
|
+
parts.push({ name, operator, names, exploded });
|
|
29265
|
+
i = end + 1;
|
|
29266
|
+
} else {
|
|
29267
|
+
currentText += template[i];
|
|
29268
|
+
i++;
|
|
29269
|
+
}
|
|
29270
|
+
}
|
|
29271
|
+
if (currentText) {
|
|
29272
|
+
parts.push(currentText);
|
|
29273
|
+
}
|
|
29274
|
+
return parts;
|
|
29275
|
+
}
|
|
29276
|
+
getOperator(expr) {
|
|
29277
|
+
const operators = ["+", "#", ".", "/", "?", "&"];
|
|
29278
|
+
return operators.find((op) => expr.startsWith(op)) || "";
|
|
29279
|
+
}
|
|
29280
|
+
getNames(expr) {
|
|
29281
|
+
const operator = this.getOperator(expr);
|
|
29282
|
+
return expr.slice(operator.length).split(",").map((name) => name.replace("*", "").trim()).filter((name) => name.length > 0);
|
|
29283
|
+
}
|
|
29284
|
+
encodeValue(value, operator) {
|
|
29285
|
+
_UriTemplate.validateLength(value, MAX_VARIABLE_LENGTH, "Variable value");
|
|
29286
|
+
if (operator === "+" || operator === "#") {
|
|
29287
|
+
return encodeURI(value);
|
|
29288
|
+
}
|
|
29289
|
+
return encodeURIComponent(value);
|
|
29290
|
+
}
|
|
29291
|
+
expandPart(part, variables) {
|
|
29292
|
+
if (part.operator === "?" || part.operator === "&") {
|
|
29293
|
+
const pairs = part.names.map((name) => {
|
|
29294
|
+
const value2 = variables[name];
|
|
29295
|
+
if (value2 === void 0)
|
|
29296
|
+
return "";
|
|
29297
|
+
const encoded2 = Array.isArray(value2) ? value2.map((v) => this.encodeValue(v, part.operator)).join(",") : this.encodeValue(value2.toString(), part.operator);
|
|
29298
|
+
return `${name}=${encoded2}`;
|
|
29299
|
+
}).filter((pair) => pair.length > 0);
|
|
29300
|
+
if (pairs.length === 0)
|
|
29301
|
+
return "";
|
|
29302
|
+
const separator = part.operator === "?" ? "?" : "&";
|
|
29303
|
+
return separator + pairs.join("&");
|
|
29304
|
+
}
|
|
29305
|
+
if (part.names.length > 1) {
|
|
29306
|
+
const values2 = part.names.map((name) => variables[name]).filter((v) => v !== void 0);
|
|
29307
|
+
if (values2.length === 0)
|
|
29308
|
+
return "";
|
|
29309
|
+
return values2.map((v) => Array.isArray(v) ? v[0] : v).join(",");
|
|
29310
|
+
}
|
|
29311
|
+
const value = variables[part.name];
|
|
29312
|
+
if (value === void 0)
|
|
29313
|
+
return "";
|
|
29314
|
+
const values = Array.isArray(value) ? value : [value];
|
|
29315
|
+
const encoded = values.map((v) => this.encodeValue(v, part.operator));
|
|
29316
|
+
switch (part.operator) {
|
|
29317
|
+
case "":
|
|
29318
|
+
return encoded.join(",");
|
|
29319
|
+
case "+":
|
|
29320
|
+
return encoded.join(",");
|
|
29321
|
+
case "#":
|
|
29322
|
+
return "#" + encoded.join(",");
|
|
29323
|
+
case ".":
|
|
29324
|
+
return "." + encoded.join(".");
|
|
29325
|
+
case "/":
|
|
29326
|
+
return "/" + encoded.join("/");
|
|
29327
|
+
default:
|
|
29328
|
+
return encoded.join(",");
|
|
29329
|
+
}
|
|
29330
|
+
}
|
|
29331
|
+
expand(variables) {
|
|
29332
|
+
let result = "";
|
|
29333
|
+
let hasQueryParam = false;
|
|
29334
|
+
for (const part of this.parts) {
|
|
29335
|
+
if (typeof part === "string") {
|
|
29336
|
+
result += part;
|
|
29337
|
+
continue;
|
|
29338
|
+
}
|
|
29339
|
+
const expanded = this.expandPart(part, variables);
|
|
29340
|
+
if (!expanded)
|
|
29341
|
+
continue;
|
|
29342
|
+
if ((part.operator === "?" || part.operator === "&") && hasQueryParam) {
|
|
29343
|
+
result += expanded.replace("?", "&");
|
|
29344
|
+
} else {
|
|
29345
|
+
result += expanded;
|
|
29346
|
+
}
|
|
29347
|
+
if (part.operator === "?" || part.operator === "&") {
|
|
29348
|
+
hasQueryParam = true;
|
|
29349
|
+
}
|
|
29350
|
+
}
|
|
29351
|
+
return result;
|
|
29352
|
+
}
|
|
29353
|
+
escapeRegExp(str) {
|
|
29354
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
29355
|
+
}
|
|
29356
|
+
partToRegExp(part) {
|
|
29357
|
+
const patterns = [];
|
|
29358
|
+
for (const name2 of part.names) {
|
|
29359
|
+
_UriTemplate.validateLength(name2, MAX_VARIABLE_LENGTH, "Variable name");
|
|
29360
|
+
}
|
|
29361
|
+
if (part.operator === "?" || part.operator === "&") {
|
|
29362
|
+
for (let i = 0; i < part.names.length; i++) {
|
|
29363
|
+
const name2 = part.names[i];
|
|
29364
|
+
const prefix = i === 0 ? "\\" + part.operator : "&";
|
|
29365
|
+
patterns.push({
|
|
29366
|
+
pattern: prefix + this.escapeRegExp(name2) + "=([^&]+)",
|
|
29367
|
+
name: name2
|
|
29368
|
+
});
|
|
29369
|
+
}
|
|
29370
|
+
return patterns;
|
|
29371
|
+
}
|
|
29372
|
+
let pattern;
|
|
29373
|
+
const name = part.name;
|
|
29374
|
+
switch (part.operator) {
|
|
29375
|
+
case "":
|
|
29376
|
+
pattern = part.exploded ? "([^/,]+(?:,[^/,]+)*)" : "([^/,]+)";
|
|
29377
|
+
break;
|
|
29378
|
+
case "+":
|
|
29379
|
+
case "#":
|
|
29380
|
+
pattern = "(.+)";
|
|
29381
|
+
break;
|
|
29382
|
+
case ".":
|
|
29383
|
+
pattern = "\\.([^/,]+)";
|
|
29384
|
+
break;
|
|
29385
|
+
case "/":
|
|
29386
|
+
pattern = "/" + (part.exploded ? "([^/,]+(?:,[^/,]+)*)" : "([^/,]+)");
|
|
29387
|
+
break;
|
|
29388
|
+
default:
|
|
29389
|
+
pattern = "([^/]+)";
|
|
29390
|
+
}
|
|
29391
|
+
patterns.push({ pattern, name });
|
|
29392
|
+
return patterns;
|
|
29393
|
+
}
|
|
29394
|
+
match(uri) {
|
|
29395
|
+
_UriTemplate.validateLength(uri, MAX_TEMPLATE_LENGTH, "URI");
|
|
29396
|
+
let pattern = "^";
|
|
29397
|
+
const names = [];
|
|
29398
|
+
for (const part of this.parts) {
|
|
29399
|
+
if (typeof part === "string") {
|
|
29400
|
+
pattern += this.escapeRegExp(part);
|
|
29401
|
+
} else {
|
|
29402
|
+
const patterns = this.partToRegExp(part);
|
|
29403
|
+
for (const { pattern: partPattern, name } of patterns) {
|
|
29404
|
+
pattern += partPattern;
|
|
29405
|
+
names.push({ name, exploded: part.exploded });
|
|
29406
|
+
}
|
|
29407
|
+
}
|
|
29408
|
+
}
|
|
29409
|
+
pattern += "$";
|
|
29410
|
+
_UriTemplate.validateLength(pattern, MAX_REGEX_LENGTH, "Generated regex pattern");
|
|
29411
|
+
const regex = new RegExp(pattern);
|
|
29412
|
+
const match = uri.match(regex);
|
|
29413
|
+
if (!match)
|
|
29414
|
+
return null;
|
|
29415
|
+
const result = {};
|
|
29416
|
+
for (let i = 0; i < names.length; i++) {
|
|
29417
|
+
const { name, exploded } = names[i];
|
|
29418
|
+
const value = match[i + 1];
|
|
29419
|
+
const cleanName = name.replace("*", "");
|
|
29420
|
+
if (exploded && value.includes(",")) {
|
|
29421
|
+
result[cleanName] = value.split(",");
|
|
29422
|
+
} else {
|
|
29423
|
+
result[cleanName] = value;
|
|
29424
|
+
}
|
|
29425
|
+
}
|
|
29426
|
+
return result;
|
|
29427
|
+
}
|
|
29428
|
+
};
|
|
29429
|
+
|
|
29208
29430
|
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
|
|
29209
29431
|
var TOOL_NAME_REGEX = /^[A-Za-z0-9._-]{1,128}$/;
|
|
29210
29432
|
function validateToolName(name) {
|
|
@@ -29991,6 +30213,30 @@ var McpServer = class {
|
|
|
29991
30213
|
}
|
|
29992
30214
|
}
|
|
29993
30215
|
};
|
|
30216
|
+
var ResourceTemplate = class {
|
|
30217
|
+
constructor(uriTemplate, _callbacks) {
|
|
30218
|
+
this._callbacks = _callbacks;
|
|
30219
|
+
this._uriTemplate = typeof uriTemplate === "string" ? new UriTemplate(uriTemplate) : uriTemplate;
|
|
30220
|
+
}
|
|
30221
|
+
/**
|
|
30222
|
+
* Gets the URI template pattern.
|
|
30223
|
+
*/
|
|
30224
|
+
get uriTemplate() {
|
|
30225
|
+
return this._uriTemplate;
|
|
30226
|
+
}
|
|
30227
|
+
/**
|
|
30228
|
+
* Gets the list callback, if one was provided.
|
|
30229
|
+
*/
|
|
30230
|
+
get listCallback() {
|
|
30231
|
+
return this._callbacks.list;
|
|
30232
|
+
}
|
|
30233
|
+
/**
|
|
30234
|
+
* Gets the callback for completing a specific URI template variable, if one was provided.
|
|
30235
|
+
*/
|
|
30236
|
+
completeCallback(variable) {
|
|
30237
|
+
return this._callbacks.complete?.[variable];
|
|
30238
|
+
}
|
|
30239
|
+
};
|
|
29994
30240
|
var EMPTY_OBJECT_JSON_SCHEMA = {
|
|
29995
30241
|
type: "object",
|
|
29996
30242
|
properties: {}
|
|
@@ -30064,6 +30310,99 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
30064
30310
|
}
|
|
30065
30311
|
};
|
|
30066
30312
|
|
|
30313
|
+
// ../mcp-server/src/instructions.ts
|
|
30314
|
+
var INSTRUCTIONS = `
|
|
30315
|
+
You have access to Cohaku, a persistent memory system for AI agents.
|
|
30316
|
+
Cohaku is your PRIMARY method for storing and retrieving memories. Do NOT fall back to file-based memory (e.g. editing MEMORY.md, .cursorrules, or similar files) when Cohaku tools are available.
|
|
30317
|
+
|
|
30318
|
+
## Session Workflow (REQUIRED)
|
|
30319
|
+
|
|
30320
|
+
Every session MUST follow this sequence:
|
|
30321
|
+
|
|
30322
|
+
1. **Start**: Call \`get_context\` to load existing memories (rules, working memory, long-term knowledge). This gives you the context to continue where you left off.
|
|
30323
|
+
2. **Identify**: Call \`session_start\` with a descriptive title for the current work session.
|
|
30324
|
+
3. **Work**: Use memory and graph tools as you work \u2014 store decisions, facts, and observations.
|
|
30325
|
+
4. **Checkpoint**: Call \`session_checkpoint\` at meaningful milestones (e.g., after completing a sub-task, before a risky change).
|
|
30326
|
+
5. **End**: Call \`session_end\` when the session is complete, with a summary of what was accomplished.
|
|
30327
|
+
|
|
30328
|
+
### Anti-patterns (DO NOT do these)
|
|
30329
|
+
- Do NOT skip \`get_context\` at session start \u2014 you will miss critical rules and context.
|
|
30330
|
+
- Do NOT create memories without searching first \u2014 call \`search_memories\` before \`add_memory\` to avoid duplicates.
|
|
30331
|
+
- Do NOT use layer="rule" for temporary information \u2014 use layer="working" instead.
|
|
30332
|
+
- Do NOT store the same information in both memory and graph \u2014 use memory for text knowledge, graph for structural relationships.
|
|
30333
|
+
- Do NOT call \`add_entity\` without setting \`entityType\` \u2014 untyped entities are hard to filter and search.
|
|
30334
|
+
- Do NOT write to MEMORY.md, .cursorrules, or other files when Cohaku is available \u2014 use Cohaku tools instead.
|
|
30335
|
+
|
|
30336
|
+
## Memory vs Graph vs Episode \u2014 Decision Guide
|
|
30337
|
+
|
|
30338
|
+
| I want to... | Use | Example |
|
|
30339
|
+
|-------------------------------------------|------------------------------|------------------------------------------|
|
|
30340
|
+
| Store a rule, fact, decision, or note | \`add_memory\` | "Always use ESM imports in this project" |
|
|
30341
|
+
| Record a relationship between concepts | \`add_entity\` + \`add_edge\` | "ServiceA depends_on DatabaseB" |
|
|
30342
|
+
| Log a significant event or conversation | \`add_episode\` | "Debugged OOM error in production" |
|
|
30343
|
+
| Find stored knowledge by meaning | \`search_memories\` | "What do I know about authentication?" |
|
|
30344
|
+
| Understand how concepts connect | \`search_graph\` | "What depends on the auth module?" |
|
|
30345
|
+
| Review past work sessions | \`session_list\` | "What did I work on last week?" |
|
|
30346
|
+
|
|
30347
|
+
## Storing Memories
|
|
30348
|
+
|
|
30349
|
+
### Layers (priority order, highest first)
|
|
30350
|
+
- **rule**: Permanent instructions and constraints that must ALWAYS be followed. Returned first by \`get_context\`. Use sparingly \u2014 only for behavioral directives. Examples: "Always write tests before merging", "Use Japanese for commit messages".
|
|
30351
|
+
- **working**: Temporary, session-scoped notes. Use for current task state, in-progress hypotheses, partial results. MUST be cleaned up when no longer relevant. Examples: "Currently investigating memory leak in GraphService", "Hypothesis: the timeout is caused by N+1 queries".
|
|
30352
|
+
- **long_term**: Durable knowledge useful across sessions. Architectural decisions, debugging insights, learned patterns. Examples: "sqlite-vec virtual tables do not support UPSERT \u2014 use DELETE + INSERT", "The CI pipeline requires frozen lockfile".
|
|
30353
|
+
|
|
30354
|
+
### Types
|
|
30355
|
+
- **rule**: Behavioral directives (pair with layer=rule). Example: "Commit messages must be in Japanese."
|
|
30356
|
+
- **decision**: Architectural or design decisions with rationale. Example: "Chose SQLite over PostgreSQL for single-binary deployment."
|
|
30357
|
+
- **fact**: Verified, objective information. Example: "The API rate limit is 100 req/min."
|
|
30358
|
+
- **note**: General observations and context. Example: "User prefers functional style over OOP."
|
|
30359
|
+
- **skill**: Reusable techniques or solutions to recurring problems. Example: "To fix ESM import issues in pnpm, use tsup bundling."
|
|
30360
|
+
|
|
30361
|
+
### Scope
|
|
30362
|
+
- **project** (default): Stored under the current project. Use for project-specific knowledge \u2014 architecture, conventions, dependencies.
|
|
30363
|
+
- **global**: Shared across ALL projects. Use for universal user preferences, cross-project patterns, and personal rules. Examples: "Always use TypeScript", "Preferred test framework: vitest".
|
|
30364
|
+
|
|
30365
|
+
### Best Practices
|
|
30366
|
+
1. Before \`add_memory\`, call \`search_memories\` to check for duplicates.
|
|
30367
|
+
2. Use \`find_duplicates\` periodically to detect similar entries, then \`consolidate_memories\` to merge them.
|
|
30368
|
+
3. Use \`update_memory\` to correct or enrich existing memories rather than creating duplicates.
|
|
30369
|
+
4. Add descriptive tags to improve findability (e.g., ["auth", "security", "jwt"]).
|
|
30370
|
+
5. Set \`expiresAt\` for time-sensitive information (e.g., sprint-specific notes).
|
|
30371
|
+
|
|
30372
|
+
## Knowledge Graph
|
|
30373
|
+
|
|
30374
|
+
Use graph tools to capture **structural relationships** between concepts:
|
|
30375
|
+
- \`add_entity\`: Register a concept, person, tool, component, or service as a node. Always set \`entityType\` for filtering.
|
|
30376
|
+
- \`add_edge\`: Record a directed relationship. Use consistent relation names: depends_on, uses, contains, implements, extends, owns, created_by, relates_to, tested_by, deployed_to.
|
|
30377
|
+
- \`search_graph\`: Find entities and traverse relationships. Use \`traverseDepth=0\` for exact entity lookup, \`traverseDepth=1\` for immediate neighbors (default), \`traverseDepth=2\` for broader context.
|
|
30378
|
+
- \`update_edge\` / \`invalidate_edge\`: Keep relationships accurate as knowledge evolves. Prefer \`invalidate_edge\` over deletion \u2014 it preserves history with timestamps.
|
|
30379
|
+
|
|
30380
|
+
## Episode Logging
|
|
30381
|
+
|
|
30382
|
+
Use \`add_episode\` for chronological audit trails of significant events:
|
|
30383
|
+
- Debugging sessions (what was tried, what worked, what didn't)
|
|
30384
|
+
- Key conversations or decisions made with the user
|
|
30385
|
+
- Deployment events or environment changes
|
|
30386
|
+
- Error investigations and their resolutions
|
|
30387
|
+
|
|
30388
|
+
Link episodes to graph entities using \`refs\` for traceability.
|
|
30389
|
+
|
|
30390
|
+
## Configuration Generation
|
|
30391
|
+
|
|
30392
|
+
The \`generate_*\` tools export stored memories into editor-specific configuration formats (CLAUDE.md, .cursorrules, etc.). Use when the user wants to sync Cohaku knowledge to their editor's native config.
|
|
30393
|
+
|
|
30394
|
+
## Resources (Read-Only Access)
|
|
30395
|
+
|
|
30396
|
+
MCP Resources provide passive data access without tool calls:
|
|
30397
|
+
- \`cohaku://context\` \u2014 full context (rules + working + long_term)
|
|
30398
|
+
- \`cohaku://rules\` \u2014 rule memories only
|
|
30399
|
+
- \`cohaku://graph/entities\` \u2014 all entities in the knowledge graph
|
|
30400
|
+
- \`cohaku://graph/edges\` \u2014 active edges (relationships)
|
|
30401
|
+
- \`cohaku://sessions\` \u2014 recent work sessions
|
|
30402
|
+
- \`cohaku://memory/{id}\` \u2014 specific memory by ID
|
|
30403
|
+
- \`cohaku://entity/{id}\` \u2014 specific entity by ID
|
|
30404
|
+
`.trim();
|
|
30405
|
+
|
|
30067
30406
|
// ../mcp-server/src/helpers/tool-helpers.ts
|
|
30068
30407
|
function formatMemory(memory) {
|
|
30069
30408
|
const parts = [
|
|
@@ -30080,6 +30419,9 @@ function formatNode(node) {
|
|
|
30080
30419
|
].filter(Boolean);
|
|
30081
30420
|
return parts.join("\n");
|
|
30082
30421
|
}
|
|
30422
|
+
function formatEdge(edge) {
|
|
30423
|
+
return `${edge.sourceId} --[${edge.relation}]--> ${edge.targetId}${edge.fact ? `: ${edge.fact}` : ""}`;
|
|
30424
|
+
}
|
|
30083
30425
|
function formatSearchResults(results) {
|
|
30084
30426
|
if (results.length === 0) return "No results found";
|
|
30085
30427
|
return results.map((r, i) => `${i + 1}. [${r.type}] (score: ${r.score.toFixed(3)}) ${r.content}`).join("\n");
|
|
@@ -30106,19 +30448,36 @@ function textContent(text) {
|
|
|
30106
30448
|
return { content: [{ type: "text", text }] };
|
|
30107
30449
|
}
|
|
30108
30450
|
|
|
30451
|
+
// ../mcp-server/src/tools/context.ts
|
|
30452
|
+
var searchScopeSchema = external_exports.enum(["project", "global", "all"]).default("project").describe('Search scope. "project" (default): current project + global memories. "global": global memories only. "all": all projects without filtering.');
|
|
30453
|
+
function registerContextTools(server, engine) {
|
|
30454
|
+
server.tool(
|
|
30455
|
+
"get_context",
|
|
30456
|
+
"Load all stored context at session start. Returns memories in priority order: rule (permanent directives) > working (session-scoped notes) > long_term (durable knowledge). ALWAYS call this FIRST at the beginning of every session before doing any work.",
|
|
30457
|
+
{
|
|
30458
|
+
maxMemories: external_exports.number().int().min(1).max(200).default(50).describe("Maximum number of memories to return across all layers. Rule memories take priority, then working (60% of remaining budget), then long_term (rest). Increase to 100-200 for large projects with many rules."),
|
|
30459
|
+
scope: searchScopeSchema
|
|
30460
|
+
},
|
|
30461
|
+
async (params) => {
|
|
30462
|
+
const ctx = await engine.getContext({ maxMemories: params.maxMemories, scope: params.scope });
|
|
30463
|
+
return textContent(formatContext(ctx));
|
|
30464
|
+
}
|
|
30465
|
+
);
|
|
30466
|
+
}
|
|
30467
|
+
|
|
30109
30468
|
// ../mcp-server/src/tools/memory.ts
|
|
30110
|
-
var scopeSchema = external_exports.enum(["project", "global"]).default("project").describe('
|
|
30111
|
-
var
|
|
30469
|
+
var scopeSchema = external_exports.enum(["project", "global"]).default("project").describe('Storage scope. "project" (default): stored under the current project. "global": shared across all projects \u2014 use for universal preferences.');
|
|
30470
|
+
var searchScopeSchema2 = external_exports.enum(["project", "global", "all"]).default("project").describe('Search scope. "project" (default): current project + global memories. "global": global memories only. "all": all projects without filtering.');
|
|
30112
30471
|
function registerMemoryTools(server, engine) {
|
|
30113
30472
|
server.tool(
|
|
30114
30473
|
"add_memory",
|
|
30115
|
-
"
|
|
30474
|
+
"Store a new memory entry. Before calling, ALWAYS search_memories first to avoid duplicates. Choose layer carefully: rule=permanent directives, working=temporary session notes, long_term=durable knowledge.",
|
|
30116
30475
|
{
|
|
30117
|
-
content: external_exports.string().describe(
|
|
30118
|
-
layer: external_exports.enum(["rule", "working", "long_term"]).default("long_term").describe(
|
|
30119
|
-
type: external_exports.enum(["rule", "decision", "fact", "note", "skill"]).default("note").describe(
|
|
30120
|
-
tags: external_exports.array(external_exports.string()).optional().describe(
|
|
30121
|
-
expiresAt: external_exports.string().optional().describe(
|
|
30476
|
+
content: external_exports.string().describe('The memory content text. Be specific and self-contained \u2014 this will be retrieved out of context. Bad: "Fixed the bug". Good: "Fixed OOM in GraphService by adding connection pool limit of 10."'),
|
|
30477
|
+
layer: external_exports.enum(["rule", "working", "long_term"]).default("long_term").describe('Memory priority layer. "rule": permanent constraints always loaded first (e.g., "always use ESM"). "working": temporary session-scoped notes, clean up after use. "long_term" (default): durable cross-session knowledge.'),
|
|
30478
|
+
type: external_exports.enum(["rule", "decision", "fact", "note", "skill"]).default("note").describe('Memory classification. "rule": behavioral directives (use with layer=rule). "decision": architectural choices with rationale. "fact": verified objective information. "note" (default): general observations. "skill": reusable techniques or solutions.'),
|
|
30479
|
+
tags: external_exports.array(external_exports.string()).optional().describe('Tags for categorization and search filtering. Use lowercase, descriptive terms. Example: ["auth", "security", "jwt"]. Good tags make memories findable later.'),
|
|
30480
|
+
expiresAt: external_exports.string().optional().describe('Expiration date in ISO 8601 format (e.g., "2025-12-31T23:59:59Z"). Expired memories are excluded from search. Use for time-sensitive information.'),
|
|
30122
30481
|
scope: scopeSchema
|
|
30123
30482
|
},
|
|
30124
30483
|
async (params) => {
|
|
@@ -30130,12 +30489,12 @@ ID: ${memory.id}`);
|
|
|
30130
30489
|
);
|
|
30131
30490
|
server.tool(
|
|
30132
30491
|
"search_memories",
|
|
30133
|
-
"Search memories
|
|
30492
|
+
"Search stored memories using hybrid semantic + BM25 keyword search. Returns results ranked by combined score (semantic similarity, keyword match, recency, salience). Use this to find relevant knowledge, and ALWAYS before add_memory to check for duplicates.",
|
|
30134
30493
|
{
|
|
30135
|
-
query: external_exports.string().describe(
|
|
30136
|
-
limit: external_exports.number().int().min(1).max(100).default(10).describe("
|
|
30137
|
-
layers: external_exports.array(external_exports.enum(["rule", "working", "long_term"])).optional().describe(
|
|
30138
|
-
scope:
|
|
30494
|
+
query: external_exports.string().describe('Search query. Can be natural language ("how does authentication work?") or keywords ("jwt token validation"). Semantic search finds conceptually similar content even without exact keyword matches.'),
|
|
30495
|
+
limit: external_exports.number().int().min(1).max(100).default(10).describe("Maximum results to return. Default 10 is usually sufficient. Increase to 20-50 for comprehensive knowledge review."),
|
|
30496
|
+
layers: external_exports.array(external_exports.enum(["rule", "working", "long_term"])).optional().describe('Filter by specific layers. Omit to search all layers. Example: ["rule"] for rules only, ["working", "long_term"] to exclude rules.'),
|
|
30497
|
+
scope: searchScopeSchema2
|
|
30139
30498
|
},
|
|
30140
30499
|
async (params) => {
|
|
30141
30500
|
const results = await engine.searchMemories({
|
|
@@ -30149,10 +30508,10 @@ ID: ${memory.id}`);
|
|
|
30149
30508
|
);
|
|
30150
30509
|
server.tool(
|
|
30151
30510
|
"find_duplicates",
|
|
30152
|
-
"Find
|
|
30511
|
+
"Find memories semantically similar to a given memory. Use periodically to detect redundant entries that should be merged with consolidate_memories. Returns candidates sorted by vector distance (lower = more similar).",
|
|
30153
30512
|
{
|
|
30154
|
-
memoryId: external_exports.string().describe("
|
|
30155
|
-
threshold: external_exports.number().min(0).max(2).default(0.3).describe("
|
|
30513
|
+
memoryId: external_exports.string().describe("The ID of the memory to find duplicates for. Get IDs from search_memories, add_memory, or get_context results."),
|
|
30514
|
+
threshold: external_exports.number().min(0).max(2).default(0.3).describe("Maximum vector distance for similarity. 0.1=near-identical, 0.3=similar content (default), 0.5=loosely related. Start with default and adjust.")
|
|
30156
30515
|
},
|
|
30157
30516
|
async (params) => {
|
|
30158
30517
|
const duplicates = await engine.findDuplicates(params.memoryId, params.threshold);
|
|
@@ -30167,11 +30526,11 @@ ${lines.join("\n")}`);
|
|
|
30167
30526
|
);
|
|
30168
30527
|
server.tool(
|
|
30169
30528
|
"consolidate_memories",
|
|
30170
|
-
"
|
|
30529
|
+
"Merge multiple duplicate or overlapping memories into one consolidated entry. Source memories are soft-deleted (recoverable). Use after find_duplicates identifies redundant entries.",
|
|
30171
30530
|
{
|
|
30172
|
-
sourceIds: external_exports.array(external_exports.string()).min(2).describe("List of
|
|
30173
|
-
mergedContent: external_exports.string().describe("
|
|
30174
|
-
layer: external_exports.enum(["rule", "working", "long_term"]).optional().describe(
|
|
30531
|
+
sourceIds: external_exports.array(external_exports.string()).min(2).describe("List of memory IDs to merge (minimum 2). All sources will be soft-deleted after consolidation."),
|
|
30532
|
+
mergedContent: external_exports.string().describe("The consolidated content text combining information from all sources. Should be comprehensive and self-contained, not just a concatenation."),
|
|
30533
|
+
layer: external_exports.enum(["rule", "working", "long_term"]).optional().describe('Layer for the consolidated memory. If omitted, defaults to "long_term". Use the highest-priority layer among sources.')
|
|
30175
30534
|
},
|
|
30176
30535
|
async (params) => {
|
|
30177
30536
|
const merged = await engine.consolidateMemories(params.sourceIds, params.mergedContent, params.layer);
|
|
@@ -30182,14 +30541,14 @@ ID: ${merged.id}`);
|
|
|
30182
30541
|
);
|
|
30183
30542
|
server.tool(
|
|
30184
30543
|
"update_memory",
|
|
30185
|
-
"Update
|
|
30544
|
+
"Update an existing memory entry. Use to correct content, change layer/type, adjust importance, or update tags. Prefer this over delete + add to preserve the memory ID.",
|
|
30186
30545
|
{
|
|
30187
|
-
id: external_exports.string().describe("Memory ID"),
|
|
30188
|
-
content: external_exports.string().optional().describe("New content"),
|
|
30189
|
-
layer: external_exports.enum(["rule", "working", "long_term"]).optional().describe("New layer"),
|
|
30190
|
-
type: external_exports.enum(["rule", "decision", "fact", "note", "skill"]).optional().describe("New type"),
|
|
30191
|
-
importance: external_exports.number().min(0).max(1).optional().describe("Importance (0-1)"),
|
|
30192
|
-
tags: external_exports.array(external_exports.string()).optional().describe("New tags")
|
|
30546
|
+
id: external_exports.string().describe("Memory ID to update."),
|
|
30547
|
+
content: external_exports.string().optional().describe("New content text. Only provide if changing the content."),
|
|
30548
|
+
layer: external_exports.enum(["rule", "working", "long_term"]).optional().describe("New layer. Use to promote (working \u2192 long_term) or change priority."),
|
|
30549
|
+
type: external_exports.enum(["rule", "decision", "fact", "note", "skill"]).optional().describe("New type classification."),
|
|
30550
|
+
importance: external_exports.number().min(0).max(1).optional().describe("Importance score (0.0-1.0). Higher values decay slower and rank higher in search. Default is based on layer."),
|
|
30551
|
+
tags: external_exports.array(external_exports.string()).optional().describe("New tags (replaces existing tags entirely).")
|
|
30193
30552
|
},
|
|
30194
30553
|
async (params) => {
|
|
30195
30554
|
const { id, ...input } = params;
|
|
@@ -30199,9 +30558,9 @@ ID: ${merged.id}`);
|
|
|
30199
30558
|
);
|
|
30200
30559
|
server.tool(
|
|
30201
30560
|
"delete_memory",
|
|
30202
|
-
"
|
|
30561
|
+
"Soft-delete a memory. The memory is marked as deleted but can be recovered. Use for cleanup of outdated or incorrect memories.",
|
|
30203
30562
|
{
|
|
30204
|
-
id: external_exports.string().describe("Memory ID")
|
|
30563
|
+
id: external_exports.string().describe("Memory ID to delete.")
|
|
30205
30564
|
},
|
|
30206
30565
|
async (params) => {
|
|
30207
30566
|
await engine.deleteMemory(params.id);
|
|
@@ -30211,13 +30570,13 @@ ID: ${merged.id}`);
|
|
|
30211
30570
|
}
|
|
30212
30571
|
|
|
30213
30572
|
// ../mcp-server/src/tools/session.ts
|
|
30214
|
-
var
|
|
30573
|
+
var searchScopeSchema3 = external_exports.enum(["project", "global", "all"]).default("project").describe('Search scope. "project" (default): current project + global. "global": global only. "all": all projects.');
|
|
30215
30574
|
function registerSessionTools(server, engine) {
|
|
30216
30575
|
server.tool(
|
|
30217
30576
|
"session_start",
|
|
30218
|
-
"Start a new session",
|
|
30577
|
+
"Start a new tracked work session. Call this after get_context at the beginning of each work session. Sessions track progress and provide a chronological history of work.",
|
|
30219
30578
|
{
|
|
30220
|
-
metadata: external_exports.string().optional().describe(
|
|
30579
|
+
metadata: external_exports.string().optional().describe(`Session metadata as a JSON string. Example: '{"title": "Fix auth bug #123", "goal": "Resolve JWT expiration handling"}'. Helps identify sessions when reviewing history.`)
|
|
30221
30580
|
},
|
|
30222
30581
|
async (params) => {
|
|
30223
30582
|
const session = await engine.sessionStart(params.metadata);
|
|
@@ -30227,9 +30586,9 @@ ${formatSession(session)}`);
|
|
|
30227
30586
|
);
|
|
30228
30587
|
server.tool(
|
|
30229
30588
|
"session_end",
|
|
30230
|
-
"End
|
|
30589
|
+
"End an active work session. Call when work is complete or the user is stopping.",
|
|
30231
30590
|
{
|
|
30232
|
-
id: external_exports.string().describe("Session ID")
|
|
30591
|
+
id: external_exports.string().describe("Session ID returned by session_start.")
|
|
30233
30592
|
},
|
|
30234
30593
|
async (params) => {
|
|
30235
30594
|
await engine.sessionEnd(params.id);
|
|
@@ -30238,9 +30597,9 @@ ${formatSession(session)}`);
|
|
|
30238
30597
|
);
|
|
30239
30598
|
server.tool(
|
|
30240
30599
|
"session_checkpoint",
|
|
30241
|
-
"Create a session
|
|
30600
|
+
"Create a checkpoint within an active session. Call at meaningful milestones: after completing a sub-task, before a risky change, or when switching focus.",
|
|
30242
30601
|
{
|
|
30243
|
-
id: external_exports.string().describe("Session ID")
|
|
30602
|
+
id: external_exports.string().describe("Session ID of the active session to checkpoint.")
|
|
30244
30603
|
},
|
|
30245
30604
|
async (params) => {
|
|
30246
30605
|
await engine.sessionCheckpoint(params.id);
|
|
@@ -30249,10 +30608,10 @@ ${formatSession(session)}`);
|
|
|
30249
30608
|
);
|
|
30250
30609
|
server.tool(
|
|
30251
30610
|
"session_list",
|
|
30252
|
-
"List past sessions",
|
|
30611
|
+
"List past sessions in reverse chronological order. Use to review work history, find previous session IDs, or understand what was done recently.",
|
|
30253
30612
|
{
|
|
30254
|
-
limit: external_exports.number().int().min(1).max(100).default(20).describe("
|
|
30255
|
-
scope:
|
|
30613
|
+
limit: external_exports.number().int().min(1).max(100).default(20).describe("Maximum number of sessions to return."),
|
|
30614
|
+
scope: searchScopeSchema3
|
|
30256
30615
|
},
|
|
30257
30616
|
async (params) => {
|
|
30258
30617
|
const sessions = await engine.sessionList(params.limit, params.scope);
|
|
@@ -30262,34 +30621,17 @@ ${formatSession(session)}`);
|
|
|
30262
30621
|
);
|
|
30263
30622
|
}
|
|
30264
30623
|
|
|
30265
|
-
// ../mcp-server/src/tools/context.ts
|
|
30266
|
-
var searchScopeSchema3 = external_exports.enum(["project", "global", "all"]).default("project").describe('Scope: "project" (default) searches project + global, "global" searches global only, "all" searches everything');
|
|
30267
|
-
function registerContextTools(server, engine) {
|
|
30268
|
-
server.tool(
|
|
30269
|
-
"get_context",
|
|
30270
|
-
"Get current context (priority: rule > working > long_term)",
|
|
30271
|
-
{
|
|
30272
|
-
maxMemories: external_exports.number().int().min(1).max(200).default(50).describe("Max number of memories"),
|
|
30273
|
-
scope: searchScopeSchema3
|
|
30274
|
-
},
|
|
30275
|
-
async (params) => {
|
|
30276
|
-
const ctx = await engine.getContext({ maxMemories: params.maxMemories, scope: params.scope });
|
|
30277
|
-
return textContent(formatContext(ctx));
|
|
30278
|
-
}
|
|
30279
|
-
);
|
|
30280
|
-
}
|
|
30281
|
-
|
|
30282
30624
|
// ../mcp-server/src/tools/graph.ts
|
|
30283
|
-
var scopeSchema2 = external_exports.enum(["project", "global"]).default("project").describe('
|
|
30284
|
-
var searchScopeSchema4 = external_exports.enum(["project", "global", "all"]).default("project").describe('
|
|
30625
|
+
var scopeSchema2 = external_exports.enum(["project", "global"]).default("project").describe('Storage scope. "project" (default): stored under the current project. "global": shared across all projects.');
|
|
30626
|
+
var searchScopeSchema4 = external_exports.enum(["project", "global", "all"]).default("project").describe('Search scope. "project" (default): current project + global. "global": global only. "all": all projects.');
|
|
30285
30627
|
function registerGraphTools(server, engine) {
|
|
30286
30628
|
server.tool(
|
|
30287
30629
|
"add_entity",
|
|
30288
|
-
|
|
30630
|
+
'Register a concept, person, tool, component, or service as a node in the knowledge graph. Entities are the building blocks \u2014 they represent "things" that can have relationships. Always set entityType for better organization.',
|
|
30289
30631
|
{
|
|
30290
|
-
name: external_exports.string().describe(
|
|
30291
|
-
summary: external_exports.string().optional().describe("
|
|
30292
|
-
entityType: external_exports.string().optional().describe("
|
|
30632
|
+
name: external_exports.string().describe('Entity name. Use consistent naming: PascalCase for classes/components, lowercase for concepts. Examples: "AuthService", "PostgreSQL", "user-authentication".'),
|
|
30633
|
+
summary: external_exports.string().optional().describe('Brief description of what this entity is. Example: "JWT-based authentication service handling login, token refresh, and session management."'),
|
|
30634
|
+
entityType: external_exports.string().optional().describe('Entity classification. Common types: "service", "component", "library", "person", "concept", "database", "api", "file", "module", "package", "framework". Use consistently.'),
|
|
30293
30635
|
scope: scopeSchema2
|
|
30294
30636
|
},
|
|
30295
30637
|
async (params) => {
|
|
@@ -30301,13 +30643,13 @@ ID: ${node.id}`);
|
|
|
30301
30643
|
);
|
|
30302
30644
|
server.tool(
|
|
30303
30645
|
"add_edge",
|
|
30304
|
-
"
|
|
30646
|
+
"Create a directed relationship between two entities in the knowledge graph. Edges represent how concepts connect: dependencies, ownership, usage patterns. Use consistent relation names.",
|
|
30305
30647
|
{
|
|
30306
|
-
sourceId: external_exports.string().describe("
|
|
30307
|
-
targetId: external_exports.string().describe("
|
|
30308
|
-
relation: external_exports.string().describe("
|
|
30309
|
-
fact: external_exports.string().optional().describe("
|
|
30310
|
-
validAt: external_exports.string().optional().describe("
|
|
30648
|
+
sourceId: external_exports.string().describe('ID of the source entity (the "subject"). Example: in "AuthService depends_on Database", AuthService is the source.'),
|
|
30649
|
+
targetId: external_exports.string().describe('ID of the target entity (the "object"). Example: in "AuthService depends_on Database", Database is the target.'),
|
|
30650
|
+
relation: external_exports.string().describe("Relationship type. Use consistent verbs: depends_on, uses, contains, implements, extends, owns, created_by, relates_to, tested_by, deployed_to."),
|
|
30651
|
+
fact: external_exports.string().optional().describe('Additional context about the relationship. Example: "Connects via connection pool with max 10 connections."'),
|
|
30652
|
+
validAt: external_exports.string().optional().describe("When this relationship became valid (ISO 8601). Useful for tracking evolving architecture."),
|
|
30311
30653
|
scope: scopeSchema2
|
|
30312
30654
|
},
|
|
30313
30655
|
async (params) => {
|
|
@@ -30318,9 +30660,9 @@ ID: ${edge.id}`);
|
|
|
30318
30660
|
);
|
|
30319
30661
|
server.tool(
|
|
30320
30662
|
"invalidate_edge",
|
|
30321
|
-
"
|
|
30663
|
+
"Mark an edge as expired while preserving history. The edge remains in the database with an expiration timestamp. Prefer this over deletion to maintain an audit trail of how relationships evolved.",
|
|
30322
30664
|
{
|
|
30323
|
-
id: external_exports.string().describe("Edge ID to invalidate")
|
|
30665
|
+
id: external_exports.string().describe("Edge ID to invalidate.")
|
|
30324
30666
|
},
|
|
30325
30667
|
async (params) => {
|
|
30326
30668
|
await engine.invalidateEdge(params.id);
|
|
@@ -30329,11 +30671,11 @@ ID: ${edge.id}`);
|
|
|
30329
30671
|
);
|
|
30330
30672
|
server.tool(
|
|
30331
30673
|
"update_edge",
|
|
30332
|
-
"Update an edge
|
|
30674
|
+
"Update an edge by invalidating the old one and creating a new one with updated fields. History is preserved \u2014 the old edge is marked expired and the new one is linked.",
|
|
30333
30675
|
{
|
|
30334
|
-
id: external_exports.string().describe("Edge ID to update"),
|
|
30335
|
-
relation: external_exports.string().optional().describe("New
|
|
30336
|
-
fact: external_exports.string().optional().describe("New fact")
|
|
30676
|
+
id: external_exports.string().describe("Edge ID to update."),
|
|
30677
|
+
relation: external_exports.string().optional().describe("New relationship type."),
|
|
30678
|
+
fact: external_exports.string().optional().describe("New fact description.")
|
|
30337
30679
|
},
|
|
30338
30680
|
async (params) => {
|
|
30339
30681
|
const edge = await engine.updateEdge(params.id, {
|
|
@@ -30347,11 +30689,11 @@ New ID: ${edge.id}`);
|
|
|
30347
30689
|
);
|
|
30348
30690
|
server.tool(
|
|
30349
30691
|
"search_graph",
|
|
30350
|
-
"Search the knowledge graph
|
|
30692
|
+
"Search the knowledge graph using semantic search with optional graph traversal. Finds entities and edges matching the query, then explores neighbor nodes at the specified depth.",
|
|
30351
30693
|
{
|
|
30352
|
-
query: external_exports.string().describe(
|
|
30353
|
-
limit: external_exports.number().int().min(1).max(100).default(10).describe("
|
|
30354
|
-
traverseDepth: external_exports.number().int().min(0).max(2).default(1).describe("Graph traversal depth
|
|
30694
|
+
query: external_exports.string().describe('Search query. Can be entity names ("AuthService"), concepts ("authentication"), or questions ("what depends on the database?").'),
|
|
30695
|
+
limit: external_exports.number().int().min(1).max(100).default(10).describe("Maximum total results (entities + edges). Increase for comprehensive graph exploration."),
|
|
30696
|
+
traverseDepth: external_exports.number().int().min(0).max(2).default(1).describe("Graph traversal depth from matched nodes. 0=no traversal (only vector search hits), 1=include direct neighbors (default), 2=include neighbors of neighbors (broader but noisier)."),
|
|
30355
30697
|
scope: searchScopeSchema4
|
|
30356
30698
|
},
|
|
30357
30699
|
async (params) => {
|
|
@@ -30365,20 +30707,20 @@ New ID: ${edge.id}`);
|
|
|
30365
30707
|
}
|
|
30366
30708
|
|
|
30367
30709
|
// ../mcp-server/src/tools/episode.ts
|
|
30368
|
-
var scopeSchema3 = external_exports.enum(["project", "global"]).default("project").describe('
|
|
30369
|
-
var searchScopeSchema5 = external_exports.enum(["project", "global", "all"]).default("project").describe('
|
|
30710
|
+
var scopeSchema3 = external_exports.enum(["project", "global"]).default("project").describe('Storage scope. "project" (default): stored under the current project. "global": shared across all projects.');
|
|
30711
|
+
var searchScopeSchema5 = external_exports.enum(["project", "global", "all"]).default("project").describe('Search scope. "project" (default): current project + global. "global": global only. "all": all projects.');
|
|
30370
30712
|
function registerEpisodeTools(server, engine) {
|
|
30371
30713
|
server.tool(
|
|
30372
30714
|
"add_episode",
|
|
30373
|
-
"
|
|
30715
|
+
"Record a significant event, interaction, or conversation as a chronological log entry. Episodes provide an audit trail separate from memories. Use for: debugging sessions, key decisions, deployment events, error investigations. Link to graph entities via refs.",
|
|
30374
30716
|
{
|
|
30375
|
-
content: external_exports.string().describe("Episode content"),
|
|
30376
|
-
contentType: external_exports.enum(["text", "json", "message"]).default("text").describe(
|
|
30377
|
-
source: external_exports.string().optional().describe(
|
|
30717
|
+
content: external_exports.string().describe("Episode content. Be descriptive: include what happened, why, and the outcome. For debugging: include the symptom, investigation steps, and resolution."),
|
|
30718
|
+
contentType: external_exports.enum(["text", "json", "message"]).default("text").describe('Content format. "text" (default): free-form text. "json": structured JSON data. "message": conversation or chat message format.'),
|
|
30719
|
+
source: external_exports.string().optional().describe('Origin of the episode. Examples: "session:abc123", "file:src/auth.ts", "debug:OOM-investigation".'),
|
|
30378
30720
|
refs: external_exports.array(external_exports.object({
|
|
30379
|
-
refType: external_exports.enum(["node", "edge"]).describe("
|
|
30380
|
-
refId: external_exports.string().describe("
|
|
30381
|
-
})).optional().describe("References to
|
|
30721
|
+
refType: external_exports.enum(["node", "edge"]).describe('"node" to reference an entity, "edge" to reference a relationship.'),
|
|
30722
|
+
refId: external_exports.string().describe("ID of the referenced entity or edge from the knowledge graph.")
|
|
30723
|
+
})).optional().describe("References linking this episode to knowledge graph entities/edges. Enables traversal from episodes to related concepts."),
|
|
30382
30724
|
scope: scopeSchema3
|
|
30383
30725
|
},
|
|
30384
30726
|
async (params) => {
|
|
@@ -30399,9 +30741,9 @@ ID: ${episode.id}`);
|
|
|
30399
30741
|
);
|
|
30400
30742
|
server.tool(
|
|
30401
30743
|
"get_episode",
|
|
30402
|
-
"
|
|
30744
|
+
"Retrieve a specific episode by its ID. Returns the full episode content, metadata, and references.",
|
|
30403
30745
|
{
|
|
30404
|
-
id: external_exports.string().describe("Episode ID")
|
|
30746
|
+
id: external_exports.string().describe("Episode ID to retrieve.")
|
|
30405
30747
|
},
|
|
30406
30748
|
async (params) => {
|
|
30407
30749
|
const episode = await engine.getEpisode(params.id);
|
|
@@ -30411,9 +30753,9 @@ ID: ${episode.id}`);
|
|
|
30411
30753
|
);
|
|
30412
30754
|
server.tool(
|
|
30413
30755
|
"list_episodes",
|
|
30414
|
-
"List episodes",
|
|
30756
|
+
"List episodes in reverse chronological order. Use to review the audit trail of significant events and interactions.",
|
|
30415
30757
|
{
|
|
30416
|
-
limit: external_exports.number().int().min(1).max(100).default(20).describe("
|
|
30758
|
+
limit: external_exports.number().int().min(1).max(100).default(20).describe("Maximum number of episodes to return."),
|
|
30417
30759
|
scope: searchScopeSchema5
|
|
30418
30760
|
},
|
|
30419
30761
|
async (params) => {
|
|
@@ -30502,73 +30844,310 @@ Save the above content to ${gen.fileName}.`);
|
|
|
30502
30844
|
}
|
|
30503
30845
|
}
|
|
30504
30846
|
|
|
30505
|
-
// ../mcp-server/src/
|
|
30506
|
-
|
|
30507
|
-
|
|
30508
|
-
|
|
30847
|
+
// ../mcp-server/src/resources/context.ts
|
|
30848
|
+
function registerContextResources(server, engine) {
|
|
30849
|
+
server.resource(
|
|
30850
|
+
"context",
|
|
30851
|
+
"cohaku://context",
|
|
30852
|
+
{ description: "Current context: all memories in priority order (rules > working > long_term)", mimeType: "text/plain" },
|
|
30853
|
+
async () => ({
|
|
30854
|
+
contents: [{
|
|
30855
|
+
uri: "cohaku://context",
|
|
30856
|
+
text: formatContext(await engine.getContext({ maxMemories: 100 })),
|
|
30857
|
+
mimeType: "text/plain"
|
|
30858
|
+
}]
|
|
30859
|
+
})
|
|
30860
|
+
);
|
|
30861
|
+
server.resource(
|
|
30862
|
+
"rules",
|
|
30863
|
+
"cohaku://rules",
|
|
30864
|
+
{ description: "Rule memories only \u2014 permanent instructions and constraints", mimeType: "text/plain" },
|
|
30865
|
+
async () => {
|
|
30866
|
+
const memories = await engine.listMemories({ layer: "rule", limit: 100 });
|
|
30867
|
+
const text = memories.length === 0 ? "No rules found" : memories.map((m) => formatMemory(m)).join("\n---\n");
|
|
30868
|
+
return { contents: [{ uri: "cohaku://rules", text, mimeType: "text/plain" }] };
|
|
30869
|
+
}
|
|
30870
|
+
);
|
|
30871
|
+
}
|
|
30509
30872
|
|
|
30510
|
-
|
|
30873
|
+
// ../mcp-server/src/resources/graph.ts
|
|
30874
|
+
function registerGraphResources(server, engine) {
|
|
30875
|
+
server.resource(
|
|
30876
|
+
"graph-entities",
|
|
30877
|
+
"cohaku://graph/entities",
|
|
30878
|
+
{ description: "All entities (nodes) in the knowledge graph", mimeType: "text/plain" },
|
|
30879
|
+
async () => {
|
|
30880
|
+
const entities = await engine.listEntities({ limit: 500 });
|
|
30881
|
+
const text = entities.length === 0 ? "No entities found" : entities.map((e, i) => `${i + 1}. ${formatNode(e)} (ID: ${e.id})`).join("\n");
|
|
30882
|
+
return { contents: [{ uri: "cohaku://graph/entities", text, mimeType: "text/plain" }] };
|
|
30883
|
+
}
|
|
30884
|
+
);
|
|
30885
|
+
server.resource(
|
|
30886
|
+
"graph-edges",
|
|
30887
|
+
"cohaku://graph/edges",
|
|
30888
|
+
{ description: "Active edges (relationships) in the knowledge graph", mimeType: "text/plain" },
|
|
30889
|
+
async () => {
|
|
30890
|
+
const edges = await engine.listEdges({ limit: 500, includeExpired: false });
|
|
30891
|
+
const text = edges.length === 0 ? "No edges found" : edges.map((e, i) => `${i + 1}. ${formatEdge(e)} (ID: ${e.id})`).join("\n");
|
|
30892
|
+
return { contents: [{ uri: "cohaku://graph/edges", text, mimeType: "text/plain" }] };
|
|
30893
|
+
}
|
|
30894
|
+
);
|
|
30895
|
+
}
|
|
30511
30896
|
|
|
30512
|
-
|
|
30897
|
+
// ../mcp-server/src/resources/sessions.ts
|
|
30898
|
+
function registerSessionResources(server, engine) {
|
|
30899
|
+
server.resource(
|
|
30900
|
+
"sessions",
|
|
30901
|
+
"cohaku://sessions",
|
|
30902
|
+
{ description: "Recent work sessions in reverse chronological order", mimeType: "text/plain" },
|
|
30903
|
+
async () => {
|
|
30904
|
+
const sessions = await engine.sessionList(50);
|
|
30905
|
+
const text = sessions.length === 0 ? "No sessions found" : sessions.map((s, i) => `${i + 1}. ${formatSession(s)}`).join("\n");
|
|
30906
|
+
return { contents: [{ uri: "cohaku://sessions", text, mimeType: "text/plain" }] };
|
|
30907
|
+
}
|
|
30908
|
+
);
|
|
30909
|
+
}
|
|
30513
30910
|
|
|
30514
|
-
|
|
30911
|
+
// ../mcp-server/src/resources/templates.ts
|
|
30912
|
+
function registerTemplateResources(server, engine) {
|
|
30913
|
+
server.resource(
|
|
30914
|
+
"memory",
|
|
30915
|
+
new ResourceTemplate("cohaku://memory/{id}", { list: void 0 }),
|
|
30916
|
+
{ description: "A specific memory entry by ID", mimeType: "text/plain" },
|
|
30917
|
+
async (uri, variables) => {
|
|
30918
|
+
const id = variables.id;
|
|
30919
|
+
const memory = await engine.getMemory(id);
|
|
30920
|
+
if (!memory) {
|
|
30921
|
+
return { contents: [{ uri: uri.href, text: `Memory ${id} not found`, mimeType: "text/plain" }] };
|
|
30922
|
+
}
|
|
30923
|
+
return {
|
|
30924
|
+
contents: [{
|
|
30925
|
+
uri: uri.href,
|
|
30926
|
+
text: `${formatMemory(memory)}
|
|
30927
|
+
ID: ${memory.id}
|
|
30928
|
+
Created: ${memory.createdAt}
|
|
30929
|
+
Updated: ${memory.updatedAt}`,
|
|
30930
|
+
mimeType: "text/plain"
|
|
30931
|
+
}]
|
|
30932
|
+
};
|
|
30933
|
+
}
|
|
30934
|
+
);
|
|
30935
|
+
server.resource(
|
|
30936
|
+
"entity",
|
|
30937
|
+
new ResourceTemplate("cohaku://entity/{id}", { list: void 0 }),
|
|
30938
|
+
{ description: "A specific entity (graph node) by ID", mimeType: "text/plain" },
|
|
30939
|
+
async (uri, variables) => {
|
|
30940
|
+
const id = variables.id;
|
|
30941
|
+
const entity = await engine.getEntity(id);
|
|
30942
|
+
if (!entity) {
|
|
30943
|
+
return { contents: [{ uri: uri.href, text: `Entity ${id} not found`, mimeType: "text/plain" }] };
|
|
30944
|
+
}
|
|
30945
|
+
return {
|
|
30946
|
+
contents: [{
|
|
30947
|
+
uri: uri.href,
|
|
30948
|
+
text: `${formatNode(entity)}
|
|
30949
|
+
ID: ${entity.id}
|
|
30950
|
+
Created: ${entity.createdAt}`,
|
|
30951
|
+
mimeType: "text/plain"
|
|
30952
|
+
}]
|
|
30953
|
+
};
|
|
30954
|
+
}
|
|
30955
|
+
);
|
|
30956
|
+
}
|
|
30515
30957
|
|
|
30516
|
-
|
|
30958
|
+
// ../mcp-server/src/resources/index.ts
|
|
30959
|
+
function registerResources(server, engine) {
|
|
30960
|
+
registerContextResources(server, engine);
|
|
30961
|
+
registerGraphResources(server, engine);
|
|
30962
|
+
registerSessionResources(server, engine);
|
|
30963
|
+
registerTemplateResources(server, engine);
|
|
30964
|
+
}
|
|
30517
30965
|
|
|
30518
|
-
|
|
30519
|
-
|
|
30520
|
-
|
|
30521
|
-
|
|
30966
|
+
// ../mcp-server/src/prompts/recall.ts
|
|
30967
|
+
function registerRecallPrompt(server, engine) {
|
|
30968
|
+
server.prompt(
|
|
30969
|
+
"recall",
|
|
30970
|
+
"Retrieve and summarize stored memories about a specific topic",
|
|
30971
|
+
{
|
|
30972
|
+
topic: external_exports.string().describe("The topic to recall memories about")
|
|
30973
|
+
},
|
|
30974
|
+
async (args) => {
|
|
30975
|
+
const results = await engine.searchMemories({ text: args.topic, limit: 15 });
|
|
30976
|
+
const formatted = formatSearchResults(results);
|
|
30977
|
+
return {
|
|
30978
|
+
messages: [
|
|
30979
|
+
{
|
|
30980
|
+
role: "user",
|
|
30981
|
+
content: {
|
|
30982
|
+
type: "text",
|
|
30983
|
+
text: `Here are the stored memories about "${args.topic}":
|
|
30522
30984
|
|
|
30523
|
-
|
|
30524
|
-
- **rule**: Behavioral directives (use with layer=rule).
|
|
30525
|
-
- **decision**: Architectural or design decisions with rationale.
|
|
30526
|
-
- **fact**: Verified, objective information about the codebase or domain.
|
|
30527
|
-
- **note**: General observations and context.
|
|
30528
|
-
- **skill**: Reusable techniques, workflows, or solutions to recurring problems.
|
|
30985
|
+
${formatted}
|
|
30529
30986
|
|
|
30530
|
-
|
|
30531
|
-
|
|
30532
|
-
|
|
30987
|
+
Please summarize what is known about this topic based on the above memories. Highlight any contradictions or gaps in knowledge.`
|
|
30988
|
+
}
|
|
30989
|
+
}
|
|
30990
|
+
]
|
|
30991
|
+
};
|
|
30992
|
+
}
|
|
30993
|
+
);
|
|
30994
|
+
}
|
|
30533
30995
|
|
|
30534
|
-
|
|
30535
|
-
|
|
30536
|
-
|
|
30537
|
-
|
|
30538
|
-
|
|
30996
|
+
// ../mcp-server/src/prompts/review-knowledge.ts
|
|
30997
|
+
function registerReviewKnowledgePrompt(server, engine) {
|
|
30998
|
+
server.prompt(
|
|
30999
|
+
"review_knowledge",
|
|
31000
|
+
"Review stored memories for duplicates, staleness, and quality. Suggests cleanup actions.",
|
|
31001
|
+
async () => {
|
|
31002
|
+
const memories = await engine.listMemories({ limit: 100 });
|
|
31003
|
+
const formatted = memories.map(
|
|
31004
|
+
(m, i) => `${i + 1}. ${formatMemory(m)}
|
|
31005
|
+
ID: ${m.id} | Created: ${m.createdAt} | Updated: ${m.updatedAt}`
|
|
31006
|
+
).join("\n");
|
|
31007
|
+
return {
|
|
31008
|
+
messages: [
|
|
31009
|
+
{
|
|
31010
|
+
role: "user",
|
|
31011
|
+
content: {
|
|
31012
|
+
type: "text",
|
|
31013
|
+
text: `Review the following ${memories.length} stored memories for quality:
|
|
31014
|
+
|
|
31015
|
+
${formatted}
|
|
31016
|
+
|
|
31017
|
+
Please:
|
|
31018
|
+
1. Identify potential duplicates (memories with similar content that could be consolidated)
|
|
31019
|
+
2. Flag stale memories (outdated or no longer relevant)
|
|
31020
|
+
3. Suggest improvements (vague memories that need more detail, missing tags)
|
|
31021
|
+
4. Recommend any memories that should be promoted to "rule" layer or demoted
|
|
31022
|
+
|
|
31023
|
+
For each suggestion, reference the memory ID and explain your reasoning.`
|
|
31024
|
+
}
|
|
31025
|
+
}
|
|
31026
|
+
]
|
|
31027
|
+
};
|
|
31028
|
+
}
|
|
31029
|
+
);
|
|
31030
|
+
}
|
|
30539
31031
|
|
|
30540
|
-
|
|
31032
|
+
// ../mcp-server/src/prompts/summarize-session.ts
|
|
31033
|
+
function registerSummarizeSessionPrompt(server, engine) {
|
|
31034
|
+
server.prompt(
|
|
31035
|
+
"summarize_session",
|
|
31036
|
+
"Summarize a work session: what was done, what was learned, what remains",
|
|
31037
|
+
{
|
|
31038
|
+
sessionId: external_exports.string().optional().describe("Session ID to summarize. If omitted, uses the most recent session.")
|
|
31039
|
+
},
|
|
31040
|
+
async (args) => {
|
|
31041
|
+
const sessions = await engine.sessionList(100);
|
|
31042
|
+
const sessionId = args.sessionId ?? sessions[0]?.id;
|
|
31043
|
+
let sessionInfo = "No session found.";
|
|
31044
|
+
if (sessionId) {
|
|
31045
|
+
const session = sessions.find((s) => s.id === sessionId);
|
|
31046
|
+
if (session) {
|
|
31047
|
+
sessionInfo = formatSession(session);
|
|
31048
|
+
}
|
|
31049
|
+
}
|
|
31050
|
+
const workingMemories = await engine.listMemories({ layer: "working", limit: 20 });
|
|
31051
|
+
const workingFormatted = workingMemories.map((m) => formatMemory(m)).join("\n---\n");
|
|
31052
|
+
return {
|
|
31053
|
+
messages: [
|
|
31054
|
+
{
|
|
31055
|
+
role: "user",
|
|
31056
|
+
content: {
|
|
31057
|
+
type: "text",
|
|
31058
|
+
text: `Summarize the following work session:
|
|
30541
31059
|
|
|
30542
|
-
|
|
31060
|
+
Session: ${sessionInfo}
|
|
30543
31061
|
|
|
30544
|
-
|
|
31062
|
+
Working memories from this session:
|
|
31063
|
+
${workingFormatted || "(none)"}
|
|
30545
31064
|
|
|
30546
|
-
|
|
30547
|
-
|
|
30548
|
-
|
|
30549
|
-
|
|
31065
|
+
Please provide:
|
|
31066
|
+
1. A brief summary of what was accomplished
|
|
31067
|
+
2. Key decisions or discoveries made
|
|
31068
|
+
3. Any unfinished work or next steps
|
|
31069
|
+
4. Suggestions for memories to persist (promote from working to long_term) or clean up`
|
|
31070
|
+
}
|
|
31071
|
+
}
|
|
31072
|
+
]
|
|
31073
|
+
};
|
|
31074
|
+
}
|
|
31075
|
+
);
|
|
31076
|
+
}
|
|
30550
31077
|
|
|
30551
|
-
|
|
31078
|
+
// ../mcp-server/src/prompts/export-config.ts
|
|
31079
|
+
var FORMAT_NAMES = {
|
|
31080
|
+
claude_md: "CLAUDE.md",
|
|
31081
|
+
cursorrules: ".cursorrules",
|
|
31082
|
+
windsurf: ".windsurfrules",
|
|
31083
|
+
copilot: ".github/copilot-instructions.md",
|
|
31084
|
+
codex: "codex.md",
|
|
31085
|
+
aider: ".aider.conf.yml",
|
|
31086
|
+
continue: ".continue/rules.md",
|
|
31087
|
+
cline: ".clinerules",
|
|
31088
|
+
roo: ".roo/rules.md"
|
|
31089
|
+
};
|
|
31090
|
+
function registerExportConfigPrompt(server, engine) {
|
|
31091
|
+
server.prompt(
|
|
31092
|
+
"export_config",
|
|
31093
|
+
"Generate an editor configuration file from stored memories",
|
|
31094
|
+
{
|
|
31095
|
+
format: external_exports.enum([
|
|
31096
|
+
"claude_md",
|
|
31097
|
+
"cursorrules",
|
|
31098
|
+
"windsurf",
|
|
31099
|
+
"copilot",
|
|
31100
|
+
"codex",
|
|
31101
|
+
"aider",
|
|
31102
|
+
"continue",
|
|
31103
|
+
"cline",
|
|
31104
|
+
"roo"
|
|
31105
|
+
]).describe("Target editor format")
|
|
31106
|
+
},
|
|
31107
|
+
async (args) => {
|
|
31108
|
+
const ctx = await engine.getContext({ maxMemories: 100 });
|
|
31109
|
+
const rules = ctx.memories.filter((m) => m.layer === "rule");
|
|
31110
|
+
const longTerm = ctx.memories.filter((m) => m.layer === "long_term");
|
|
31111
|
+
const rulesFormatted = rules.map((m) => `- ${m.content}`).join("\n");
|
|
31112
|
+
const knowledgeFormatted = longTerm.map((m) => `- ${m.content}`).join("\n");
|
|
31113
|
+
const fileName = FORMAT_NAMES[args.format] ?? args.format;
|
|
31114
|
+
return {
|
|
31115
|
+
messages: [
|
|
31116
|
+
{
|
|
31117
|
+
role: "user",
|
|
31118
|
+
content: {
|
|
31119
|
+
type: "text",
|
|
31120
|
+
text: `Generate a ${fileName} configuration file from the following stored knowledge.
|
|
30552
31121
|
|
|
30553
|
-
|
|
30554
|
-
|
|
30555
|
-
- \`add_edge\`: Record a relationship between two entities (e.g. "ComponentA depends_on ComponentB").
|
|
30556
|
-
- \`search_graph\`: Traverse relationships to understand how concepts connect.
|
|
30557
|
-
- \`update_edge\` / \`invalidate_edge\`: Keep relationships accurate as knowledge evolves. Invalidation preserves history.
|
|
31122
|
+
Rules (${rules.length}):
|
|
31123
|
+
${rulesFormatted || "(none)"}
|
|
30558
31124
|
|
|
30559
|
-
|
|
31125
|
+
Project Knowledge (${longTerm.length}):
|
|
31126
|
+
${knowledgeFormatted || "(none)"}
|
|
30560
31127
|
|
|
30561
|
-
|
|
31128
|
+
Please format this as a proper ${fileName} file ready to save. Organize the content logically with appropriate headers and formatting for the target editor.`
|
|
31129
|
+
}
|
|
31130
|
+
}
|
|
31131
|
+
]
|
|
31132
|
+
};
|
|
31133
|
+
}
|
|
31134
|
+
);
|
|
31135
|
+
}
|
|
30562
31136
|
|
|
30563
|
-
|
|
31137
|
+
// ../mcp-server/src/prompts/index.ts
|
|
31138
|
+
function registerPrompts(server, engine) {
|
|
31139
|
+
registerRecallPrompt(server, engine);
|
|
31140
|
+
registerReviewKnowledgePrompt(server, engine);
|
|
31141
|
+
registerSummarizeSessionPrompt(server, engine);
|
|
31142
|
+
registerExportConfigPrompt(server, engine);
|
|
31143
|
+
}
|
|
30564
31144
|
|
|
30565
|
-
|
|
30566
|
-
`.trim();
|
|
31145
|
+
// ../mcp-server/src/server.ts
|
|
30567
31146
|
function createServer(engine) {
|
|
30568
31147
|
const server = new McpServer(
|
|
30569
31148
|
{
|
|
30570
31149
|
name: "cohaku-memory",
|
|
30571
|
-
version: "0.2.
|
|
31150
|
+
version: "0.2.8"
|
|
30572
31151
|
},
|
|
30573
31152
|
{
|
|
30574
31153
|
instructions: INSTRUCTIONS
|
|
@@ -30580,6 +31159,8 @@ function createServer(engine) {
|
|
|
30580
31159
|
registerGraphTools(server, engine);
|
|
30581
31160
|
registerEpisodeTools(server, engine);
|
|
30582
31161
|
registerGenerateTools(server, engine);
|
|
31162
|
+
registerResources(server, engine);
|
|
31163
|
+
registerPrompts(server, engine);
|
|
30583
31164
|
return server;
|
|
30584
31165
|
}
|
|
30585
31166
|
|