@sksoftofficial/mindroot 1.0.5 → 1.0.6
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/package.json +1 -1
- package/src/mcp.js +24 -24
- package/test/mcp.test.js +21 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sksoftofficial/mindroot",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Long-term memory for AI agents: hybrid-searched memories over human-editable markdown notes. 100% local — offline ONNX embeddings, SQLite FTS5, MCP server, REST API, CLI, dashboard. No cloud, no API keys.",
|
|
5
5
|
"homepage": "https://skbilisim.com/en/projects/mindroot",
|
|
6
6
|
"type": "module",
|
package/src/mcp.js
CHANGED
|
@@ -4,12 +4,12 @@ const PROTOCOL_VERSION = "2025-06-18";
|
|
|
4
4
|
export function tools() {
|
|
5
5
|
return [
|
|
6
6
|
{
|
|
7
|
-
name: "
|
|
7
|
+
name: "list_projects",
|
|
8
8
|
description: "List all projects known to mindroot with their note and memory counts. Use to discover valid project slugs before calling project-scoped tools.",
|
|
9
9
|
inputSchema: { type: "object", properties: {} },
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
|
-
name: "
|
|
12
|
+
name: "search_projects",
|
|
13
13
|
description:
|
|
14
14
|
"Find projects by fuzzy name. Matches project slugs semantically and by token overlap (e.g. 'mem-agent-mcp' finds 'proper-agent-memory'). Use to discover the right project slug before calling project-scoped tools.",
|
|
15
15
|
inputSchema: {
|
|
@@ -19,7 +19,7 @@ export function tools() {
|
|
|
19
19
|
},
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
|
-
name: "
|
|
22
|
+
name: "rename_project",
|
|
23
23
|
description:
|
|
24
24
|
"Rename a project by slug. Moves its notes directory and updates the slug; notes, memories, and search indexes are preserved under the internal project id. The fuzzy-search vector for the new slug is rebuilt automatically on the next project search.",
|
|
25
25
|
inputSchema: {
|
|
@@ -32,7 +32,7 @@ export function tools() {
|
|
|
32
32
|
},
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
|
-
name: "
|
|
35
|
+
name: "search_memories",
|
|
36
36
|
description:
|
|
37
37
|
"Search a project's memories — short, retrieval-optimized notes about durable facts, conventions, decisions, and gotchas. This is mindroot's primary semantic search layer (hybrid vector + keyword). Use before exploring a codebase or asking the user things that may already be remembered; mindroot_search_notes is only a literal string fallback. Hits include ids usable with mindroot_delete_memory.",
|
|
38
38
|
inputSchema: {
|
|
@@ -46,7 +46,7 @@ export function tools() {
|
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
|
-
name: "
|
|
49
|
+
name: "search_notes",
|
|
50
50
|
description:
|
|
51
51
|
"Literal case-insensitive string search over a project's note sections — no semantics. Memories are the semantic index: prefer mindroot_search_memories first and use this only as a fallback or to locate exact keywords/identifiers inside notes. Returns the note path and heading section per hit; fetch just that section with mindroot_read_note's section argument.",
|
|
52
52
|
inputSchema: {
|
|
@@ -60,7 +60,7 @@ export function tools() {
|
|
|
60
60
|
},
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
|
-
name: "
|
|
63
|
+
name: "save_memory",
|
|
64
64
|
description:
|
|
65
65
|
"Save a memory — a short, retrieval-optimized text about a durable fact, convention, decision, or gotcha for a project. Optionally link it to a note path ('notes/foo.md') or section ('notes/foo.md::Heading::Subheading'). Write memories so they answer 'when would someone need this'.",
|
|
66
66
|
inputSchema: {
|
|
@@ -74,7 +74,7 @@ export function tools() {
|
|
|
74
74
|
},
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
|
-
name: "
|
|
77
|
+
name: "delete_memory",
|
|
78
78
|
description: "Delete a memory by id. Use to dedupe or remove stale entries. Ids come from mindroot_search_memories hits.",
|
|
79
79
|
inputSchema: {
|
|
80
80
|
type: "object",
|
|
@@ -83,7 +83,7 @@ export function tools() {
|
|
|
83
83
|
},
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
|
-
name: "
|
|
86
|
+
name: "list_notes",
|
|
87
87
|
description:
|
|
88
88
|
"List all memory notes for a project with their titles and section heading paths. Use this to see a note's section layout, then read only the section you need via mindroot_read_note's section argument instead of fetching the whole note.",
|
|
89
89
|
inputSchema: {
|
|
@@ -93,7 +93,7 @@ export function tools() {
|
|
|
93
93
|
},
|
|
94
94
|
},
|
|
95
95
|
{
|
|
96
|
-
name: "
|
|
96
|
+
name: "read_note",
|
|
97
97
|
description:
|
|
98
98
|
"Read a memory note. Without `section`, returns full content plus the section list. With `section` (a heading path like 'Architecture::Storage' from mindroot_list_notes or mindroot_search_notes targets), returns only that section's body — prefer this when you don't need the whole note. Verifies file integrity first and re-indexes automatically if the file was edited externally.",
|
|
99
99
|
inputSchema: {
|
|
@@ -107,7 +107,7 @@ export function tools() {
|
|
|
107
107
|
},
|
|
108
108
|
},
|
|
109
109
|
{
|
|
110
|
-
name: "
|
|
110
|
+
name: "save_note",
|
|
111
111
|
description:
|
|
112
112
|
"Create or fully overwrite a markdown memory note. Sections are parsed from headings (# .. ######) and are individually readable (mindroot_read_note `section`) and string-searchable (mindroot_search_notes); embeddings are generated for memories only. After saving, consider saving 1-3 linked memories (mindroot_save_memory with target_path) so key facts surface in memory searches.",
|
|
113
113
|
inputSchema: {
|
|
@@ -121,7 +121,7 @@ export function tools() {
|
|
|
121
121
|
},
|
|
122
122
|
},
|
|
123
123
|
{
|
|
124
|
-
name: "
|
|
124
|
+
name: "update_section",
|
|
125
125
|
description:
|
|
126
126
|
"Replace the body of an existing heading-section, or append it to the note when missing.",
|
|
127
127
|
inputSchema: {
|
|
@@ -136,7 +136,7 @@ export function tools() {
|
|
|
136
136
|
},
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
|
-
name: "
|
|
139
|
+
name: "delete_note",
|
|
140
140
|
description: "Delete a memory note and its sections index entries.",
|
|
141
141
|
inputSchema: {
|
|
142
142
|
type: "object",
|
|
@@ -150,29 +150,29 @@ export function tools() {
|
|
|
150
150
|
async function callTool(db, name, args) {
|
|
151
151
|
const core = await import("./core.js");
|
|
152
152
|
switch (name) {
|
|
153
|
-
case "
|
|
153
|
+
case "list_projects":
|
|
154
154
|
return core.listProjects(db);
|
|
155
|
-
case "
|
|
155
|
+
case "search_projects":
|
|
156
156
|
return core.searchProjects(db, args.query);
|
|
157
|
-
case "
|
|
157
|
+
case "rename_project":
|
|
158
158
|
return core.renameProject(db, args.project, args.new_slug);
|
|
159
|
-
case "
|
|
159
|
+
case "search_memories":
|
|
160
160
|
return core.searchMemories(db, args.project, args.query, args.limit ?? 8);
|
|
161
|
-
case "
|
|
161
|
+
case "search_notes":
|
|
162
162
|
return core.searchNotes(db, args.project, args.query, args.limit ?? 8);
|
|
163
|
-
case "
|
|
163
|
+
case "save_memory":
|
|
164
164
|
return core.addMemory(db, args.project, args.text, args.target_path);
|
|
165
|
-
case "
|
|
165
|
+
case "delete_memory":
|
|
166
166
|
return core.deleteMemory(db, args.project, Number(args.id));
|
|
167
|
-
case "
|
|
167
|
+
case "list_notes":
|
|
168
168
|
return core.listDocs(db, args.project);
|
|
169
|
-
case "
|
|
169
|
+
case "read_note":
|
|
170
170
|
return core.readNote(db, args.project, args.path, args.section);
|
|
171
|
-
case "
|
|
171
|
+
case "save_note":
|
|
172
172
|
return core.saveNote(db, args.project, args.path, args.content);
|
|
173
|
-
case "
|
|
173
|
+
case "update_section":
|
|
174
174
|
return core.updateSection(db, args.project, args.path, args.heading_path, args.content);
|
|
175
|
-
case "
|
|
175
|
+
case "delete_note":
|
|
176
176
|
return core.deleteNote(db, args.project, args.path);
|
|
177
177
|
default:
|
|
178
178
|
throw new Error(`unknown tool: ${name}`);
|
package/test/mcp.test.js
CHANGED
|
@@ -10,18 +10,18 @@ let db;
|
|
|
10
10
|
let open;
|
|
11
11
|
|
|
12
12
|
const EXPECTED_TOOLS = [
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
13
|
+
"list_projects",
|
|
14
|
+
"search_projects",
|
|
15
|
+
"rename_project",
|
|
16
|
+
"search_memories",
|
|
17
|
+
"search_notes",
|
|
18
|
+
"save_memory",
|
|
19
|
+
"delete_memory",
|
|
20
|
+
"list_notes",
|
|
21
|
+
"read_note",
|
|
22
|
+
"save_note",
|
|
23
|
+
"update_section",
|
|
24
|
+
"delete_note",
|
|
25
25
|
];
|
|
26
26
|
|
|
27
27
|
before(async () => {
|
|
@@ -58,19 +58,19 @@ test("unknown tool returns clean error", async () => {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
test("search without project is a clean isError", async () => {
|
|
61
|
-
const res = await call("
|
|
61
|
+
const res = await call("search_memories", { query: "x" });
|
|
62
62
|
assert.equal(res.body.result.isError, true);
|
|
63
63
|
assert.match(res.body.result.content[0].text, /project is required/);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
test("invalid slug is rejected before any lookup", async () => {
|
|
67
|
-
const res = await call("
|
|
67
|
+
const res = await call("search_memories", { project: "../evil", query: "x" });
|
|
68
68
|
assert.equal(res.body.result.isError, true);
|
|
69
69
|
assert.match(res.body.result.content[0].text, /invalid project slug/);
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
test("unknown project is a clean error", async () => {
|
|
73
|
-
const res = await call("
|
|
73
|
+
const res = await call("search_memories", { project: "ghost", query: "x" });
|
|
74
74
|
assert.match(res.body.result.content[0].text, /unknown project: ghost/);
|
|
75
75
|
});
|
|
76
76
|
|
|
@@ -89,35 +89,35 @@ test("rename_project moves slug, dir, and embeddings", async () => {
|
|
|
89
89
|
fs.mkdirSync(path.join(NOTES_DIR, "old-name"), { recursive: true });
|
|
90
90
|
fs.writeFileSync(path.join(NOTES_DIR, "old-name", "overview.md"), "# Old\n");
|
|
91
91
|
|
|
92
|
-
const res = await call("
|
|
92
|
+
const res = await call("rename_project", { project: "old-name", new_slug: "new-name" });
|
|
93
93
|
const data = JSON.parse(res.body.result.content[0].text);
|
|
94
94
|
assert.deepEqual(data.renamed, { from: "old-name", to: "new-name" });
|
|
95
95
|
assert.equal(db.prepare("SELECT slug FROM projects WHERE id = ?").get(pid).slug, "new-name");
|
|
96
96
|
assert.ok(fs.existsSync(path.join(NOTES_DIR, "new-name", "overview.md")));
|
|
97
97
|
assert.ok(!fs.existsSync(path.join(NOTES_DIR, "old-name")));
|
|
98
98
|
assert.equal(db.prepare("SELECT COUNT(*) n FROM project_embeddings WHERE project_id = ?").get(pid).n, 0);
|
|
99
|
-
const listed = JSON.parse((await call("
|
|
99
|
+
const listed = JSON.parse((await call("list_projects", {})).body.result.content[0].text);
|
|
100
100
|
const row = listed.find((p) => p.slug === "new-name");
|
|
101
101
|
assert.equal(row.memories, 1);
|
|
102
102
|
assert.ok(!listed.some((p) => p.slug === "old-name"));
|
|
103
103
|
|
|
104
|
-
const dup = await call("
|
|
104
|
+
const dup = await call("rename_project", { project: "new-name", new_slug: "new-name" });
|
|
105
105
|
assert.equal(dup.body.result.isError, true);
|
|
106
106
|
assert.match(dup.body.result.content[0].text, /already named/);
|
|
107
107
|
|
|
108
|
-
const bad = await call("
|
|
108
|
+
const bad = await call("rename_project", { project: "new-name", new_slug: "../evil" });
|
|
109
109
|
assert.equal(bad.body.result.isError, true);
|
|
110
110
|
assert.match(bad.body.result.content[0].text, /invalid project slug/);
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
test("update_section appends a missing section", async () => {
|
|
114
|
-
await call("
|
|
114
|
+
await call("save_note", {
|
|
115
115
|
project: "section-upsert",
|
|
116
116
|
path: "note.md",
|
|
117
117
|
content: "# Note\n\n## Existing\n\nkeep me\n",
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
-
const res = await call("
|
|
120
|
+
const res = await call("update_section", {
|
|
121
121
|
project: "section-upsert",
|
|
122
122
|
path: "note.md",
|
|
123
123
|
heading_path: "Added",
|