prism-mcp-server 4.0.0 → 4.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.
@@ -15,10 +15,18 @@
15
15
  import { supabasePost, supabaseGet, supabaseRpc, supabasePatch, supabaseDelete, } from "../utils/supabaseApi.js";
16
16
  import { debugLog } from "../utils/logger.js";
17
17
  import { getSetting as cfgGet, setSetting as cfgSet, getAllSettings as cfgGetAll } from "./configStorage.js";
18
+ import { runAutoMigrations } from "./supabaseMigrations.js";
18
19
  export class SupabaseStorage {
19
20
  // ─── Lifecycle ─────────────────────────────────────────────
20
21
  async initialize() {
21
22
  debugLog("[SupabaseStorage] Initialized (REST API, stateless)");
23
+ // Auto-apply pending schema migrations (non-fatal)
24
+ try {
25
+ await runAutoMigrations();
26
+ }
27
+ catch (err) {
28
+ console.error("[SupabaseStorage] Auto-migration failed. Server will continue, but some tools may be unstable.", err instanceof Error ? err.message : err);
29
+ }
22
30
  }
23
31
  async close() {
24
32
  debugLog("[SupabaseStorage] Closed (no-op for REST)");
@@ -375,4 +383,29 @@ export class SupabaseStorage {
375
383
  debugLog("[SupabaseStorage] adjustImportance failed: " + (e instanceof Error ? e.message : String(e)));
376
384
  }
377
385
  }
386
+ // ─── v4.2: Graduated Insights Query ──────────────────────────
387
+ async getGraduatedInsights(project, userId, minImportance = 7) {
388
+ const data = await supabaseGet("session_ledger", {
389
+ project: `eq.${project}`,
390
+ user_id: `eq.${userId}`,
391
+ importance: `gte.${minImportance}`,
392
+ deleted_at: "is.null",
393
+ archived_at: "is.null",
394
+ select: "id,project,user_id,role,summary,importance,event_type,decisions,created_at",
395
+ order: "importance.desc,created_at.desc",
396
+ });
397
+ const rows = Array.isArray(data) ? data : [];
398
+ return rows.map((r) => ({
399
+ id: r.id,
400
+ project: r.project,
401
+ user_id: r.user_id,
402
+ role: r.role || "global",
403
+ summary: r.summary,
404
+ importance: r.importance || 0,
405
+ event_type: r.event_type || "session",
406
+ decisions: Array.isArray(r.decisions) ? r.decisions : [],
407
+ created_at: r.created_at,
408
+ conversation_id: "",
409
+ }));
410
+ }
378
411
  }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Supabase Auto-Migration Runner (v4.1)
3
+ *
4
+ * On server startup, this module checks the `prism_schema_versions` table
5
+ * and applies any pending DDL migrations via the `prism_apply_ddl` RPC.
6
+ *
7
+ * ═══════════════════════════════════════════════════════════════════
8
+ * HOW IT WORKS:
9
+ * 1. For each migration in MIGRATIONS[], call prism_apply_ddl(version, name, sql)
10
+ * 2. The Postgres function checks if the version is already applied (idempotent)
11
+ * 3. If not applied, it EXECUTE's the SQL and records the version
12
+ *
13
+ * GRACEFUL DEGRADATION:
14
+ * If prism_apply_ddl doesn't exist (PGRST202), the runner logs a
15
+ * warning and skips — the server still starts, but v4+ tools may
16
+ * fail against an old schema.
17
+ *
18
+ * SECURITY NOTE:
19
+ * prism_apply_ddl is SECURITY DEFINER (runs as postgres owner).
20
+ * The prism_schema_versions table has RLS: only service_role can write.
21
+ * ═══════════════════════════════════════════════════════════════════
22
+ */
23
+ import { supabaseRpc } from "../utils/supabaseApi.js";
24
+ /**
25
+ * All Supabase DDL migrations.
26
+ *
27
+ * IMPORTANT: Only add migrations for schema changes that Supabase
28
+ * users need. SQLite handles its own schema in sqlite.ts.
29
+ *
30
+ * Each `sql` string is passed to Postgres EXECUTE — it runs as a
31
+ * single transaction. Use IF NOT EXISTS / IF EXISTS guards generously.
32
+ */
33
+ export const MIGRATIONS = [
34
+ {
35
+ version: 26,
36
+ name: "active_behavioral_memory",
37
+ sql: `
38
+ -- v4.0: Active Behavioral Memory columns
39
+ ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS event_type TEXT NOT NULL DEFAULT 'session';
40
+ ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS confidence_score INTEGER DEFAULT NULL;
41
+ ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS importance INTEGER NOT NULL DEFAULT 0;
42
+
43
+ -- Soft-delete / archival columns
44
+ ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ DEFAULT NULL;
45
+ ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS archived_at TIMESTAMPTZ DEFAULT NULL;
46
+ ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS deleted_reason TEXT DEFAULT NULL;
47
+
48
+ -- Indexes
49
+ CREATE INDEX IF NOT EXISTS idx_ledger_event_type ON session_ledger(event_type);
50
+ CREATE INDEX IF NOT EXISTS idx_ledger_importance ON session_ledger(importance DESC);
51
+
52
+ -- Partial index for high-priority warnings
53
+ CREATE INDEX IF NOT EXISTS idx_ledger_behavioral_warnings
54
+ ON session_ledger(project, user_id, role, importance DESC)
55
+ WHERE event_type = 'correction' AND importance >= 3
56
+ AND deleted_at IS NULL AND archived_at IS NULL;
57
+ `,
58
+ },
59
+ // Future migrations go here (version 28+)
60
+ ];
61
+ /**
62
+ * Current schema version — derived from the MIGRATIONS array.
63
+ * Automatically updates when new migrations are added.
64
+ * Used for logging and diagnostics.
65
+ */
66
+ export const CURRENT_SCHEMA_VERSION = MIGRATIONS.length > 0 ? MIGRATIONS[MIGRATIONS.length - 1].version : 27;
67
+ // ─── Runner ──────────────────────────────────────────────────────
68
+ /**
69
+ * Run all pending auto-migrations on Supabase startup.
70
+ *
71
+ * Called from SupabaseStorage.initialize(). Non-fatal: if the
72
+ * migration infrastructure (027) hasn't been applied, the runner
73
+ * logs a warning and returns silently.
74
+ */
75
+ export async function runAutoMigrations() {
76
+ if (MIGRATIONS.length === 0) {
77
+ return; // Nothing to apply
78
+ }
79
+ console.error(`[Prism Auto-Migration] Schema v${CURRENT_SCHEMA_VERSION} — checking ${MIGRATIONS.length} migration(s)…`);
80
+ for (const migration of MIGRATIONS) {
81
+ try {
82
+ const result = await supabaseRpc("prism_apply_ddl", {
83
+ p_version: migration.version,
84
+ p_name: migration.name,
85
+ p_sql: migration.sql,
86
+ });
87
+ // Parse the JSON result from the RPC
88
+ const data = (typeof result === "string" ? JSON.parse(result) : result);
89
+ if (data?.status === "applied") {
90
+ console.error(`[Prism Auto-Migration] ✅ Applied migration ${migration.version}: ${migration.name}`);
91
+ }
92
+ else if (data?.status === "already_applied") {
93
+ // Silent skip — expected for idempotent restarts
94
+ }
95
+ }
96
+ catch (err) {
97
+ const errMsg = err instanceof Error ? err.message : String(err);
98
+ // PGRST202 = function not found → migration infra (027) not applied yet
99
+ if (errMsg.includes("PGRST202") || errMsg.includes("Could not find the function")) {
100
+ console.error("[Prism Auto-Migration] ⚠️ prism_apply_ddl() not found. " +
101
+ "Apply migration 027_auto_migration_infra.sql to enable auto-migrations.\n" +
102
+ " Run: supabase db push (or apply the SQL in the Supabase Dashboard SQL Editor)");
103
+ return; // Stop — no point trying further migrations
104
+ }
105
+ // Any other error: log and throw to surface the problem
106
+ console.error(`[Prism Auto-Migration] ❌ Migration ${migration.version} (${migration.name}) failed: ${errMsg}`);
107
+ throw err;
108
+ }
109
+ }
110
+ }
@@ -26,8 +26,8 @@ export { webSearchHandler, braveWebSearchCodeModeHandler, localSearchHandler, br
26
26
  // This file always exports them — server.ts decides whether to include them in the tool list.
27
27
  //
28
28
  // v0.4.0: Added SESSION_COMPACT_LEDGER_TOOL and SESSION_SEARCH_MEMORY_TOOL
29
- export { SESSION_SAVE_LEDGER_TOOL, SESSION_SAVE_HANDOFF_TOOL, SESSION_LOAD_CONTEXT_TOOL, KNOWLEDGE_SEARCH_TOOL, KNOWLEDGE_FORGET_TOOL, SESSION_COMPACT_LEDGER_TOOL, SESSION_SEARCH_MEMORY_TOOL, MEMORY_HISTORY_TOOL, MEMORY_CHECKOUT_TOOL, SESSION_SAVE_IMAGE_TOOL, SESSION_VIEW_IMAGE_TOOL, SESSION_HEALTH_CHECK_TOOL, SESSION_FORGET_MEMORY_TOOL, KNOWLEDGE_SET_RETENTION_TOOL, SESSION_SAVE_EXPERIENCE_TOOL, KNOWLEDGE_UPVOTE_TOOL, KNOWLEDGE_DOWNVOTE_TOOL } from "./sessionMemoryDefinitions.js";
30
- export { sessionSaveLedgerHandler, sessionSaveHandoffHandler, sessionLoadContextHandler, knowledgeSearchHandler, knowledgeForgetHandler, sessionSearchMemoryHandler, backfillEmbeddingsHandler, memoryHistoryHandler, memoryCheckoutHandler, sessionSaveImageHandler, sessionViewImageHandler, sessionHealthCheckHandler, sessionForgetMemoryHandler, knowledgeSetRetentionHandler, sessionSaveExperienceHandler, knowledgeUpvoteHandler, knowledgeDownvoteHandler } from "./sessionMemoryHandlers.js";
29
+ export { SESSION_SAVE_LEDGER_TOOL, SESSION_SAVE_HANDOFF_TOOL, SESSION_LOAD_CONTEXT_TOOL, KNOWLEDGE_SEARCH_TOOL, KNOWLEDGE_FORGET_TOOL, SESSION_COMPACT_LEDGER_TOOL, SESSION_SEARCH_MEMORY_TOOL, MEMORY_HISTORY_TOOL, MEMORY_CHECKOUT_TOOL, SESSION_SAVE_IMAGE_TOOL, SESSION_VIEW_IMAGE_TOOL, SESSION_HEALTH_CHECK_TOOL, SESSION_FORGET_MEMORY_TOOL, KNOWLEDGE_SET_RETENTION_TOOL, SESSION_SAVE_EXPERIENCE_TOOL, KNOWLEDGE_UPVOTE_TOOL, KNOWLEDGE_DOWNVOTE_TOOL, KNOWLEDGE_SYNC_RULES_TOOL } from "./sessionMemoryDefinitions.js";
30
+ export { sessionSaveLedgerHandler, sessionSaveHandoffHandler, sessionLoadContextHandler, knowledgeSearchHandler, knowledgeForgetHandler, sessionSearchMemoryHandler, backfillEmbeddingsHandler, memoryHistoryHandler, memoryCheckoutHandler, sessionSaveImageHandler, sessionViewImageHandler, sessionHealthCheckHandler, sessionForgetMemoryHandler, knowledgeSetRetentionHandler, sessionSaveExperienceHandler, knowledgeUpvoteHandler, knowledgeDownvoteHandler, knowledgeSyncRulesHandler } from "./sessionMemoryHandlers.js";
31
31
  // ── Compaction Handler (v0.4.0 — Enhancement #2) ──
32
32
  // The compaction handler is in a separate file because it's significantly
33
33
  // more complex than the other session memory handlers (chunked Gemini
@@ -743,3 +743,43 @@ export function isKnowledgeVoteArgs(args) {
743
743
  "id" in args &&
744
744
  typeof args.id === "string");
745
745
  }
746
+ // ─── v4.2: Knowledge Sync Rules Tool ─────────────────────────
747
+ export const KNOWLEDGE_SYNC_RULES_TOOL = {
748
+ name: "knowledge_sync_rules",
749
+ description: "Auto-sync graduated insights (importance >= 7) into your project's IDE rules file " +
750
+ "(.cursorrules or .clauderules). This bridges behavioral memory with static IDE context — " +
751
+ "turning dynamic agent learnings into always-on rules.\n\n" +
752
+ "**How it works:**\n" +
753
+ "1. Fetches graduated insights from the ledger\n" +
754
+ "2. Formats them as markdown rules inside sentinel markers\n" +
755
+ "3. Idempotently writes them into the target file at the project's configured repo_path\n\n" +
756
+ "**Requirements:** The project must have a repo_path configured in the dashboard.\n\n" +
757
+ "**Idempotency:** Uses `<!-- PRISM:AUTO-RULES:START -->` / `<!-- PRISM:AUTO-RULES:END -->` " +
758
+ "sentinel markers. Running this tool multiple times produces the same file. " +
759
+ "User-maintained content outside the sentinels is never touched.",
760
+ inputSchema: {
761
+ type: "object",
762
+ properties: {
763
+ project: {
764
+ type: "string",
765
+ description: "Project identifier. Must have a repo_path configured in the dashboard.",
766
+ },
767
+ target_file: {
768
+ type: "string",
769
+ description: "Target rules filename (default: '.cursorrules'). " +
770
+ "Common values: '.cursorrules', '.clauderules'.",
771
+ },
772
+ dry_run: {
773
+ type: "boolean",
774
+ description: "If true, returns a preview of the rules block without writing to disk. Default: false.",
775
+ },
776
+ },
777
+ required: ["project"],
778
+ },
779
+ };
780
+ export function isKnowledgeSyncRulesArgs(args) {
781
+ return (typeof args === "object" &&
782
+ args !== null &&
783
+ "project" in args &&
784
+ typeof args.project === "string");
785
+ }
@@ -34,7 +34,13 @@ import { isSessionSaveLedgerArgs, isSessionSaveHandoffArgs, isSessionLoadContext
34
34
  isSessionForgetMemoryArgs, // Phase 2: GDPR-compliant memory deletion type guard
35
35
  isKnowledgeSetRetentionArgs, // v3.1: TTL retention policy type guard
36
36
  // v4.0: Active Behavioral Memory type guards
37
- isSessionSaveExperienceArgs, isKnowledgeVoteArgs, } from "./sessionMemoryDefinitions.js";
37
+ isSessionSaveExperienceArgs, isKnowledgeVoteArgs,
38
+ // v4.2: Sync Rules type guard
39
+ isKnowledgeSyncRulesArgs, } from "./sessionMemoryDefinitions.js";
40
+ // v4.2: File system access for knowledge_sync_rules
41
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
42
+ import { existsSync } from "node:fs";
43
+ import { join, dirname } from "node:path";
38
44
  // v3.1: In-memory debounce lock for auto-compaction.
39
45
  // Prevents multiple concurrent Gemini compaction tasks for the same project
40
46
  // when many agents call session_save_ledger at the same time.
@@ -55,6 +61,23 @@ export async function sessionSaveLedgerHandler(args) {
55
61
  }
56
62
  const { project, conversation_id, summary, todos, files_changed, decisions, role } = args;
57
63
  const storage = await getStorage();
64
+ // ─── Repo path mismatch validation (v4.2) ───
65
+ let repoPathWarning = "";
66
+ if (files_changed && files_changed.length > 0) {
67
+ try {
68
+ const configuredPath = await getSetting(`repo_path:${project}`, "");
69
+ if (configuredPath && configuredPath.trim()) {
70
+ const normalizedPath = configuredPath.trim().replace(/\\/g, "/").replace(/\/+$/, ""); // normalize + strip trailing slash
71
+ const mismatched = files_changed.filter((f) => !f.replace(/\\/g, "/").startsWith(normalizedPath));
72
+ if (mismatched.length === files_changed.length) {
73
+ repoPathWarning = `\n\n⚠️ Project mismatch: none of the files_changed paths match repo_path "${normalizedPath}" ` +
74
+ `configured for project "${project}". Consider saving under the correct project.`;
75
+ debugLog(`[session_save_ledger] Repo path mismatch for "${project}": expected prefix "${normalizedPath}"`);
76
+ }
77
+ }
78
+ }
79
+ catch { /* getSetting non-fatal */ }
80
+ }
58
81
  debugLog(`[session_save_ledger] Saving ledger entry for project="${project}"`);
59
82
  // Auto-extract keywords from summary + decisions for knowledge accumulation
60
83
  const combinedText = [summary, ...(decisions || [])].join(" ");
@@ -130,6 +153,7 @@ export async function sessionSaveLedgerHandler(args) {
130
153
  (files_changed?.length ? `Files changed: ${files_changed.length}\n` : "") +
131
154
  (decisions?.length ? `Decisions: ${decisions.length}\n` : "") +
132
155
  (GOOGLE_API_KEY ? `📊 Embedding generation queued for semantic search.\n` : "") +
156
+ repoPathWarning +
133
157
  `\nRaw response: ${JSON.stringify(result)}`,
134
158
  }],
135
159
  isError: false,
@@ -360,7 +384,8 @@ export async function sessionLoadContextHandler(args) {
360
384
  throw new Error("Invalid arguments for session_load_context");
361
385
  }
362
386
  const { project, level = "standard", role } = args;
363
- const maxTokens = args.max_tokens; // v4.0
387
+ const maxTokens = args.max_tokens
388
+ || parseInt(await getSetting("max_tokens", "0"), 10) || undefined; // v4.0: arg > dashboard setting > none
364
389
  const agentName = await getSetting("agent_name", "");
365
390
  const validLevels = ["quick", "standard", "deep"];
366
391
  if (!validLevels.includes(level)) {
@@ -1684,3 +1709,127 @@ export async function knowledgeDownvoteHandler(args) {
1684
1709
  isError: false,
1685
1710
  };
1686
1711
  }
1712
+ // ─── v4.2: Knowledge Sync Rules Handler ─────────────────────
1713
+ //
1714
+ // "The Bridge" — bridges v4.0 Behavioral Memory with v4.2 Repo
1715
+ // Registry. Extracts graduated insights (importance >= 7) from
1716
+ // the ledger and idempotently syncs them into the project's
1717
+ // .cursorrules or .clauderules file, turning dynamic learnings
1718
+ // into static, always-on IDE context.
1719
+ //
1720
+ // Sentinel markers ensure the auto-generated block is isolated
1721
+ // from user-maintained rules. Re-running always produces the
1722
+ // same output, preventing drift.
1723
+ const SENTINEL_START = "<!-- PRISM:AUTO-RULES:START -->";
1724
+ const SENTINEL_END = "<!-- PRISM:AUTO-RULES:END -->";
1725
+ /**
1726
+ * Formats graduated insights into a markdown rules block.
1727
+ * Each insight is rendered as a bullet with its importance score,
1728
+ * event type, and the summary/correction text.
1729
+ */
1730
+ function formatRulesBlock(insights, project) {
1731
+ const header = `## Prism Graduated Insights (auto-synced)\n\n` +
1732
+ `> These rules were automatically generated by [Prism MCP](https://github.com/fdarcy/prism) ` +
1733
+ `from behavioral memory for project \"${project}\".\n` +
1734
+ `> Last synced: ${new Date().toISOString().split("T")[0]}\n\n`;
1735
+ const rules = insights.map(i => {
1736
+ const tag = i.event_type && i.event_type !== "session" ? ` (${i.event_type})` : "";
1737
+ return `- **[importance: ${i.importance}]**${tag} ${i.summary}`;
1738
+ }).join("\n");
1739
+ return `${SENTINEL_START}\n${header}${rules}\n${SENTINEL_END}`;
1740
+ }
1741
+ /**
1742
+ * Idempotently replaces or appends the sentinel block in a rules file.
1743
+ * Content outside the sentinels is never modified.
1744
+ */
1745
+ function applySentinelBlock(existingContent, rulesBlock) {
1746
+ const startIdx = existingContent.indexOf(SENTINEL_START);
1747
+ const endIdx = existingContent.indexOf(SENTINEL_END);
1748
+ if (startIdx !== -1 && endIdx !== -1) {
1749
+ // Replace existing block
1750
+ const before = existingContent.substring(0, startIdx);
1751
+ const after = existingContent.substring(endIdx + SENTINEL_END.length);
1752
+ return `${before}${rulesBlock}${after}`;
1753
+ }
1754
+ // Append with separator
1755
+ const separator = existingContent.length > 0 && !existingContent.endsWith("\n\n")
1756
+ ? (existingContent.endsWith("\n") ? "\n" : "\n\n")
1757
+ : "";
1758
+ return `${existingContent}${separator}${rulesBlock}\n`;
1759
+ }
1760
+ export async function knowledgeSyncRulesHandler(args) {
1761
+ if (!isKnowledgeSyncRulesArgs(args)) {
1762
+ throw new Error("Invalid arguments for knowledge_sync_rules");
1763
+ }
1764
+ const { project, target_file = ".cursorrules", dry_run = false } = args;
1765
+ const storage = await getStorage();
1766
+ // 1. Resolve repo path
1767
+ const repoPath = await getSetting(`repo_path:${project}`, "");
1768
+ if (!repoPath || !repoPath.trim()) {
1769
+ return {
1770
+ content: [{
1771
+ type: "text",
1772
+ text: `❌ No repo_path configured for project "${project}".\n` +
1773
+ `Set it in the Mind Palace dashboard (Settings → Project Repo Paths) before syncing rules.`,
1774
+ }],
1775
+ isError: true,
1776
+ };
1777
+ }
1778
+ const normalizedRepoPath = repoPath.trim().replace(/\/+$/, "");
1779
+ // 2. Fetch graduated insights
1780
+ const insights = await storage.getGraduatedInsights(project, PRISM_USER_ID, 7);
1781
+ if (insights.length === 0) {
1782
+ return {
1783
+ content: [{
1784
+ type: "text",
1785
+ text: `ℹ️ No graduated insights found for project "${project}".\n` +
1786
+ `Insights graduate when their importance score reaches 7 or higher.\n` +
1787
+ `Use \`knowledge_upvote\` to increase importance of valuable entries.`,
1788
+ }],
1789
+ isError: false,
1790
+ };
1791
+ }
1792
+ // 3. Format rules block
1793
+ const rulesBlock = formatRulesBlock(insights.map(i => ({ ...i, importance: i.importance ?? 0 })), project);
1794
+ // 4. Dry-run: return preview without writing
1795
+ if (dry_run) {
1796
+ return {
1797
+ content: [{
1798
+ type: "text",
1799
+ text: `🔍 **Dry Run Preview** — ${insights.length} graduated insight(s) for "${project}":\n\n` +
1800
+ `Target: ${normalizedRepoPath}/${target_file}\n\n` +
1801
+ `\`\`\`markdown\n${rulesBlock}\n\`\`\`\n\n` +
1802
+ `Run again without \`dry_run\` to write this to disk.`,
1803
+ }],
1804
+ isError: false,
1805
+ };
1806
+ }
1807
+ // 5. Idempotent file write
1808
+ const targetPath = join(normalizedRepoPath, target_file);
1809
+ // Ensure directory exists (handles nested target_file like ".config/rules.md")
1810
+ const targetDir = dirname(targetPath);
1811
+ if (!existsSync(targetDir)) {
1812
+ await mkdir(targetDir, { recursive: true });
1813
+ }
1814
+ let existingContent = "";
1815
+ try {
1816
+ existingContent = await readFile(targetPath, "utf-8");
1817
+ }
1818
+ catch {
1819
+ // File doesn't exist yet — will be created
1820
+ debugLog(`[knowledge_sync_rules] File ${targetPath} doesn't exist, creating new`);
1821
+ }
1822
+ const newContent = applySentinelBlock(existingContent, rulesBlock);
1823
+ await writeFile(targetPath, newContent, "utf-8");
1824
+ debugLog(`[knowledge_sync_rules] Synced ${insights.length} insights to ${targetPath}`);
1825
+ return {
1826
+ content: [{
1827
+ type: "text",
1828
+ text: `✅ Synced ${insights.length} graduated insight(s) to \`${targetPath}\`\n\n` +
1829
+ `Top insights synced:\n` +
1830
+ insights.slice(0, 5).map(i => ` • [${i.importance}] ${i.summary.substring(0, 80)}${i.summary.length > 80 ? "..." : ""}`).join("\n") +
1831
+ (insights.length > 5 ? `\n ... and ${insights.length - 5} more` : ""),
1832
+ }],
1833
+ isError: false,
1834
+ };
1835
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "4.0.0",
3
+ "version": "4.3.0",
4
4
  "mcpName": "io.github.dcostenco/prism-mcp",
5
5
  "description": "The Mind Palace for AI Agents — local-first MCP server with persistent memory (SQLite/Supabase), visual dashboard, time travel, multi-agent sync, Morning Briefings, reality drift detection, code mode templates, semantic vector search, and Brave Search + Gemini analysis. Zero-config local mode.",
6
6
  "module": "index.ts",