context-vault 2.1.0 → 2.3.0

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.
Files changed (29) hide show
  1. package/bin/cli.js +408 -61
  2. package/node_modules/@context-vault/core/package.json +36 -0
  3. package/{src → node_modules/@context-vault/core/src}/capture/index.js +66 -1
  4. package/{src → node_modules/@context-vault/core/src}/core/categories.js +1 -0
  5. package/{src → node_modules/@context-vault/core/src}/core/files.js +1 -0
  6. package/{src → node_modules/@context-vault/core/src}/index/db.js +1 -0
  7. package/{src → node_modules/@context-vault/core/src}/index/embed.js +10 -1
  8. package/node_modules/@context-vault/core/src/index.js +29 -0
  9. package/node_modules/@context-vault/core/src/server/tools.js +433 -0
  10. package/package.json +8 -8
  11. package/src/server/index.js +21 -4
  12. package/ui/serve.js +7 -6
  13. package/LICENSE +0 -21
  14. package/README.md +0 -395
  15. package/smithery.yaml +0 -10
  16. package/src/capture/README.md +0 -23
  17. package/src/core/README.md +0 -20
  18. package/src/index/README.md +0 -28
  19. package/src/retrieve/README.md +0 -19
  20. package/src/server/README.md +0 -44
  21. package/src/server/tools.js +0 -211
  22. /package/{src → node_modules/@context-vault/core/src}/capture/file-ops.js +0 -0
  23. /package/{src → node_modules/@context-vault/core/src}/capture/formatters.js +0 -0
  24. /package/{src → node_modules/@context-vault/core/src}/core/config.js +0 -0
  25. /package/{src → node_modules/@context-vault/core/src}/core/frontmatter.js +0 -0
  26. /package/{src → node_modules/@context-vault/core/src}/core/status.js +0 -0
  27. /package/{src → node_modules/@context-vault/core/src}/index/index.js +0 -0
  28. /package/{src → node_modules/@context-vault/core/src}/retrieve/index.js +0 -0
  29. /package/{src → node_modules/@context-vault/core/src}/server/helpers.js +0 -0
@@ -1,211 +0,0 @@
1
- /**
2
- * tools.js — MCP tool registrations
3
- *
4
- * Three tools: save_context (write), get_context (read), context_status (diag).
5
- * Auto-reindex runs transparently on first tool call per session.
6
- */
7
-
8
- import { z } from "zod";
9
- import { existsSync } from "node:fs";
10
-
11
- import { captureAndIndex } from "../capture/index.js";
12
- import { hybridSearch } from "../retrieve/index.js";
13
- import { reindex, indexEntry } from "../index/index.js";
14
- import { gatherVaultStatus } from "../core/status.js";
15
- import { categoryFor } from "../core/categories.js";
16
- import { normalizeKind } from "../core/files.js";
17
- import { ok, err, ensureVaultExists, ensureValidKind } from "./helpers.js";
18
-
19
- /**
20
- * Register all MCP tools on the server.
21
- *
22
- * @param {import("@modelcontextprotocol/sdk/server/mcp.js").McpServer} server
23
- * @param {{ db, config, stmts, embed, insertVec, deleteVec }} ctx
24
- */
25
- export function registerTools(server, ctx) {
26
- const { config } = ctx;
27
-
28
- // ─── Auto-Reindex (runs once per session, on first tool call) ──────────────
29
-
30
- let reindexDone = false;
31
- let reindexPromise = null;
32
- let reindexAttempts = 0;
33
- let reindexFailed = false;
34
- const MAX_REINDEX_ATTEMPTS = 2;
35
-
36
- async function ensureIndexed() {
37
- if (reindexDone) return;
38
- if (reindexPromise) return reindexPromise;
39
- reindexPromise = reindex(ctx, { fullSync: true })
40
- .then((stats) => {
41
- reindexDone = true;
42
- const total = stats.added + stats.updated + stats.removed;
43
- if (total > 0) {
44
- console.error(`[context-mcp] Auto-reindex: +${stats.added} ~${stats.updated} -${stats.removed} (${stats.unchanged} unchanged)`);
45
- }
46
- })
47
- .catch((e) => {
48
- reindexAttempts++;
49
- console.error(`[context-mcp] Auto-reindex failed (attempt ${reindexAttempts}/${MAX_REINDEX_ATTEMPTS}): ${e.message}`);
50
- if (reindexAttempts >= MAX_REINDEX_ATTEMPTS) {
51
- console.error(`[context-mcp] Giving up on auto-reindex. Run \`context-mcp reindex\` manually to diagnose.`);
52
- reindexDone = true;
53
- reindexFailed = true;
54
- } else {
55
- reindexPromise = null; // Allow retry on next tool call
56
- }
57
- });
58
- return reindexPromise;
59
- }
60
-
61
- // ─── get_context (read) ────────────────────────────────────────────────────
62
-
63
- server.tool(
64
- "get_context",
65
- "Search your knowledge vault. Returns entries ranked by relevance using hybrid full-text + semantic search. Use this to find insights, decisions, patterns, or any saved context.",
66
- {
67
- query: z.string().describe("Search query (natural language or keywords)"),
68
- kind: z.string().optional().describe("Filter by kind (e.g. 'insight', 'decision', 'pattern')"),
69
- category: z.enum(["knowledge", "entity", "event"]).optional().describe("Filter by category"),
70
- tags: z.array(z.string()).optional().describe("Filter by tags (entries must match at least one)"),
71
- since: z.string().optional().describe("ISO date, return entries created after this"),
72
- until: z.string().optional().describe("ISO date, return entries created before this"),
73
- limit: z.number().optional().describe("Max results to return (default 10)"),
74
- },
75
- async ({ query, kind, category, tags, since, until, limit }) => {
76
- if (!query?.trim()) return err("Required: query (non-empty string)", "INVALID_INPUT");
77
- await ensureIndexed();
78
-
79
- const kindFilter = kind ? normalizeKind(kind) : null;
80
- const sorted = await hybridSearch(ctx, query, {
81
- kindFilter,
82
- categoryFilter: category || null,
83
- since: since || null,
84
- until: until || null,
85
- limit: limit || 10,
86
- });
87
-
88
- // Post-filter by tags if provided
89
- const filtered = tags?.length
90
- ? sorted.filter((r) => {
91
- const entryTags = r.tags ? JSON.parse(r.tags) : [];
92
- return tags.some((t) => entryTags.includes(t));
93
- })
94
- : sorted;
95
-
96
- if (!filtered.length) return ok("No results found for: " + query);
97
-
98
- const lines = [];
99
- if (reindexFailed) lines.push(`> **Warning:** Auto-reindex failed. Results may be stale. Run \`context-mcp reindex\` to fix.\n`);
100
- lines.push(`## Results for "${query}" (${filtered.length} matches)\n`);
101
- for (const r of filtered) {
102
- const meta = r.meta ? JSON.parse(r.meta) : {};
103
- lines.push(`### ${r.title || "(untitled)"} [${r.kind}/${r.category}]`);
104
- lines.push(`Score: ${r.score.toFixed(3)} | Tags: ${r.tags || "none"} | File: ${r.file_path || "n/a"}`);
105
- lines.push(r.body?.slice(0, 300) + (r.body?.length > 300 ? "..." : ""));
106
- lines.push("");
107
- }
108
- return ok(lines.join("\n"));
109
- }
110
- );
111
-
112
- // ─── save_context (write) ──────────────────────────────────────────────────
113
-
114
- server.tool(
115
- "save_context",
116
- "Save knowledge to your vault. Creates a .md file and indexes it for search. Use for any kind of context: insights, decisions, patterns, references, or any custom kind.",
117
- {
118
- kind: z.string().describe("Entry kind — determines folder (e.g. 'insight', 'decision', 'pattern', 'reference', or any custom kind)"),
119
- title: z.string().optional().describe("Entry title (optional for insights)"),
120
- body: z.string().describe("Main content"),
121
- tags: z.array(z.string()).optional().describe("Tags for categorization and search"),
122
- meta: z.any().optional().describe("Additional structured metadata (JSON object, e.g. { language: 'js', status: 'accepted' })"),
123
- folder: z.string().optional().describe("Subfolder within the kind directory (e.g. 'react/hooks')"),
124
- source: z.string().optional().describe("Where this knowledge came from"),
125
- identity_key: z.string().optional().describe("Required for entity kinds (contact, project, tool, source). The unique identifier for this entity."),
126
- expires_at: z.string().optional().describe("ISO date for TTL expiry"),
127
- },
128
- async ({ kind, title, body, tags, meta, folder, source, identity_key, expires_at }) => {
129
- const vaultErr = ensureVaultExists(config);
130
- if (vaultErr) return vaultErr;
131
- const kindErr = ensureValidKind(kind);
132
- if (kindErr) return kindErr;
133
- if (!body?.trim()) return err("Required: body (non-empty string)", "INVALID_INPUT");
134
-
135
- // Validate: entity kinds require identity_key
136
- if (categoryFor(kind) === "entity" && !identity_key) {
137
- return err(`Entity kind "${kind}" requires identity_key`, "MISSING_IDENTITY_KEY");
138
- }
139
-
140
- await ensureIndexed();
141
-
142
- const mergedMeta = { ...(meta || {}) };
143
- if (folder) mergedMeta.folder = folder;
144
- const finalMeta = Object.keys(mergedMeta).length ? mergedMeta : undefined;
145
-
146
- const entry = await captureAndIndex(ctx, { kind, title, body, meta: finalMeta, tags, source, folder, identity_key, expires_at }, indexEntry);
147
- return ok(`Saved ${kind} ${entry.id}\nFile: ${entry.filePath}${title ? "\nTitle: " + title : ""}`);
148
- }
149
- );
150
-
151
- // ─── context_status (diagnostics) ──────────────────────────────────────────
152
-
153
- server.tool(
154
- "context_status",
155
- "Show vault health: resolved config, file counts per kind, database size, and any issues. Use to verify setup or troubleshoot.",
156
- {},
157
- () => {
158
- const status = gatherVaultStatus(ctx);
159
-
160
- const lines = [
161
- `## Vault Status`,
162
- ``,
163
- `Vault: ${config.vaultDir} (exists: ${config.vaultDirExists}, ${status.fileCount} files)`,
164
- `Database: ${config.dbPath} (exists: ${existsSync(config.dbPath)}, ${status.dbSize})`,
165
- `Dev dir: ${config.devDir}`,
166
- `Data dir: ${config.dataDir}`,
167
- `Config: ${config.configPath}`,
168
- `Resolved via: ${status.resolvedFrom}`,
169
- `Schema: v5 (categories)`,
170
- ``,
171
- `### Indexed`,
172
- ];
173
-
174
- if (status.kindCounts.length) {
175
- for (const { kind, c } of status.kindCounts) lines.push(`- ${c} ${kind}s`);
176
- } else {
177
- lines.push(`- (empty)`);
178
- }
179
-
180
- if (status.categoryCounts.length) {
181
- lines.push(``);
182
- lines.push(`### Categories`);
183
- for (const { category, c } of status.categoryCounts) lines.push(`- ${category}: ${c}`);
184
- }
185
-
186
- if (status.subdirs.length) {
187
- lines.push(``);
188
- lines.push(`### Disk Directories`);
189
- for (const { name, count } of status.subdirs) lines.push(`- ${name}/: ${count} files`);
190
- }
191
-
192
- if (status.stalePaths) {
193
- lines.push(``);
194
- lines.push(`### Stale Paths Detected`);
195
- lines.push(`DB contains ${status.staleCount} paths not matching current vault dir.`);
196
- lines.push(`Auto-reindex will fix this on next search or save.`);
197
- }
198
-
199
- if (status.embeddingStatus) {
200
- const { indexed, total, missing } = status.embeddingStatus;
201
- if (missing > 0) {
202
- lines.push(``);
203
- lines.push(`### Embeddings`);
204
- lines.push(`${indexed}/${total} entries have embeddings (${missing} missing)`);
205
- }
206
- }
207
-
208
- return ok(lines.join("\n"));
209
- }
210
- );
211
- }