prism-mcp-server 20.2.0 → 20.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,6 +18,18 @@ A paid subscription adds cloud sync, higher model tiers, and team features throu
18
18
 
19
19
  ---
20
20
 
21
+ ## What's New in v20.2.1
22
+
23
+ ### Subscription-Aware Memory Storage
24
+ `prism connect` now carries an explicit `PRISM_STORAGE=auto|local|synalux|supabase`
25
+ into every managed host registration and rejects invalid values before changing a
26
+ config file. In `auto`, a portal-confirmed free tier uses local SQLite, while
27
+ Standard, Advanced, and Enterprise use Synalux cloud memory. If entitlement
28
+ resolution is unavailable, Prism fails closed instead of splitting history across
29
+ backends. Storage remains independent of local-first model routing.
30
+
31
+ ---
32
+
21
33
  ## What's New in v20.2.0
22
34
 
23
35
  ### One Command Connects Every Supported Host
@@ -124,6 +136,11 @@ an entry previously created by Prism; custom entries remain untouched.
124
136
  Close the target MCP hosts before a non-dry-run registration so they cannot
125
137
  edit their configuration at the same time.
126
138
 
139
+ Set `PRISM_STORAGE` before running `prism connect` to preserve an explicit
140
+ storage choice in the generated host entries. This does not change local-model
141
+ routing; Synalux cloud storage separately requires an active cloud-memory
142
+ entitlement.
143
+
127
144
  Codex registration preserves `~/.codex/config.toml` and appends only a marked
128
145
  Prism-managed block. `CODEX_HOME` is respected when set and must already exist,
129
146
  matching Codex's own contract. Restart Codex CLI, the
@@ -692,7 +709,7 @@ Routing is automatic: `9b → 4b → cloud fallback` on desktop/server, `2b →
692
709
  | `PRISM_FORCE_LOCAL` | Force local SQLite regardless of credentials | `false` |
693
710
  | `TELEMETRY_WRITE_TOKEN` | Portal analytics token (optional — metrics display works without it) | -- |
694
711
 
695
- With no variables set, Prism runs fully local. Set `PRISM_SYNALUX_API_KEY` (and leave `PRISM_STORAGE=auto`) to use the cloud backend.
712
+ With no variables set, Prism runs fully local. With an active cloud-memory subscription, set `PRISM_SYNALUX_API_KEY` (and leave `PRISM_STORAGE=auto`) to use the Synalux backend; a portal-confirmed free tier remains on local SQLite.
696
713
 
697
714
  ---
698
715
 
package/dist/cli.js CHANGED
@@ -67,7 +67,7 @@ program
67
67
  console.log('\nDry run complete — no files changed.');
68
68
  }
69
69
  if (!summary.usedApiKey) {
70
- console.log('PRISM_SYNALUX_API_KEY is not set; new registrations use local/free mode.');
70
+ console.log('PRISM_SYNALUX_API_KEY is not set; no Synalux subscription key was copied into new registrations.');
71
71
  }
72
72
  }
73
73
  catch (err) {
package/dist/config.js CHANGED
@@ -81,7 +81,8 @@ export const GOOGLE_SEARCH_CX = process.env.GOOGLE_SEARCH_CX;
81
81
  //
82
82
  // Auto-resolution (PRISM_STORAGE=auto, the default) picks in this order:
83
83
  // 1. PRISM_FORCE_LOCAL=true → "local" (override everything)
84
- // 2. SYNALUX_API_KEY + PRISM_SYNALUX_BASE_URL set → "synalux"
84
+ // 2. Synalux credentials + portal-confirmed cloud-memory entitlement → "synalux"
85
+ // (portal-confirmed free → "local"; unverifiable entitlement → fail closed)
85
86
  // 3. SUPABASE_URL + SUPABASE_KEY set → "supabase" (legacy)
86
87
  // 4. else → "local"
87
88
  export const PRISM_STORAGE = process.env.PRISM_STORAGE || "auto";
package/dist/connect.js CHANGED
@@ -25,6 +25,7 @@ const HOST_ALIASES = {
25
25
  };
26
26
  const CODEX_MANAGED_START = "# >>> prism connect managed: prism-mcp";
27
27
  const CODEX_MANAGED_END = "# <<< prism connect managed: prism-mcp";
28
+ const CONNECT_STORAGE_BACKENDS = ["auto", "local", "synalux", "supabase"];
28
29
  export function normalizeHostName(value) {
29
30
  const normalized = value.trim().toLowerCase();
30
31
  const host = HOST_ALIASES[normalized];
@@ -208,10 +209,14 @@ function executableExists(name, platform, pathEnv) {
208
209
  return false;
209
210
  }
210
211
  function buildMcpEntry(nodePath, serverPath, env) {
212
+ const storage = env.PRISM_STORAGE ?? "auto";
213
+ if (!CONNECT_STORAGE_BACKENDS.includes(storage)) {
214
+ throw new Error(`Invalid PRISM_STORAGE "${storage}". Choose one of: ${CONNECT_STORAGE_BACKENDS.join(", ")}`);
215
+ }
211
216
  const serverEnv = {
212
217
  PRISM_INSTANCE: "prism-mcp",
213
218
  PRISM_SYNALUX_BASE_URL: env.PRISM_SYNALUX_BASE_URL || "https://synalux.ai",
214
- PRISM_STORAGE: "auto",
219
+ PRISM_STORAGE: storage,
215
220
  };
216
221
  if (env.PRISM_SYNALUX_API_KEY) {
217
222
  serverEnv.PRISM_SYNALUX_API_KEY = env.PRISM_SYNALUX_API_KEY;
@@ -68,9 +68,19 @@ export async function getStorage() {
68
68
  debugLog("[Prism Storage] PRISM_FORCE_LOCAL=true — forcing local SQLite");
69
69
  }
70
70
  // ─── Resolve "auto" → synalux > supabase > local ─────────────
71
+ // Synalux is eligible only when the portal is the entitlement source;
72
+ // direct Supabase remains an independent legacy backend.
71
73
  if (requested === "auto") {
72
74
  if (await ensureSynaluxCredentials()) {
73
- requested = "synalux";
75
+ const { getEntitlements } = await import("../utils/entitlements.js");
76
+ const entitlements = await getEntitlements();
77
+ const memoryEntitled = entitlements.features?.session_memory_unlimited;
78
+ if (entitlements.source !== "portal" || typeof memoryEntitled !== "boolean") {
79
+ throw new Error("[Prism Storage] Could not verify the Synalux cloud-memory entitlement. " +
80
+ "Refusing to fall back to another backend because that could split session history. " +
81
+ "Retry when Synalux is reachable or set PRISM_STORAGE=local explicitly.");
82
+ }
83
+ requested = memoryEntitled ? "synalux" : "local";
74
84
  }
75
85
  else if (await ensureSupabaseCredentials()) {
76
86
  requested = "supabase";
@@ -38,6 +38,18 @@ let ensured = null;
38
38
  let disabled = false;
39
39
  let initFailures = 0;
40
40
  const MAX_INIT_FAILURES = 3;
41
+ function closeClient(context) {
42
+ const activeClient = client;
43
+ client = null;
44
+ if (!activeClient)
45
+ return;
46
+ try {
47
+ activeClient.close();
48
+ }
49
+ catch (e) {
50
+ debugLog(`[infer-ledger] ${context} close failed: ${e instanceof Error ? e.message : e}`);
51
+ }
52
+ }
41
53
  function ensureTable() {
42
54
  if (!ensured) {
43
55
  ensured = (async () => {
@@ -72,7 +84,7 @@ function ensureTable() {
72
84
  // the next append; only repeated failure disables for the process.
73
85
  initFailures++;
74
86
  ensured = null;
75
- client = null;
87
+ closeClient("init failure");
76
88
  if (initFailures >= MAX_INIT_FAILURES)
77
89
  disabled = true;
78
90
  debugLog(`[infer-ledger] init failed (${initFailures}/${MAX_INIT_FAILURES}${disabled ? ", ledger disabled" : ", will retry"}): ${e instanceof Error ? e.message : e}`);
@@ -152,7 +164,10 @@ export async function queryInferMetrics(sinceTs) {
152
164
  }
153
165
  /** Test hook — reset module state so a fresh DB path/env can be exercised. */
154
166
  export function _resetInferLedgerForTest() {
155
- client = null;
167
+ // Close the logical client instead of only dropping its reference.
168
+ // libsql 0.5.29 can still retain native prepared-statement handles until
169
+ // V8 GC (libsql-js#228), so this is not a synchronous file-unlock barrier.
170
+ closeClient("test reset");
156
171
  ensured = null;
157
172
  disabled = false;
158
173
  initFailures = 0;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * SQLite Local Storage Backend (v2.0 — Step 2)
3
3
  *
4
- * Zero-cloud, local-first storage using @libsql/client (libSQL/SQLite).
4
+ * Zero-cloud local SQLite storage using @libsql/client (libSQL/SQLite).
5
5
  * Data lives at ~/.prism-mcp/data.db — no account, no API keys, no network.
6
6
  *
7
7
  * ═══════════════════════════════════════════════════════════════════
@@ -68,7 +68,11 @@ export function clampCeiling(requested, planCeiling) {
68
68
  }
69
69
  // ── Fetch ─────────────────────────────────────────────────────────
70
70
  async function fetchEntitlements() {
71
- if (!SYNALUX_CONFIGURED || !PRISM_SYNALUX_BASE_URL) {
71
+ // Re-read process.env because dashboard/bootstrap configuration can inject
72
+ // credentials after config.ts captured its module-load constants.
73
+ const baseUrl = process.env.PRISM_SYNALUX_BASE_URL?.trim() || PRISM_SYNALUX_BASE_URL;
74
+ const apiKey = process.env.PRISM_SYNALUX_API_KEY?.trim();
75
+ if ((!SYNALUX_CONFIGURED && !apiKey) || !baseUrl) {
72
76
  debugLog("[entitlements] no Synalux auth configured — free tier");
73
77
  return { ...FREE_ENTITLEMENTS, source: "unconfigured" };
74
78
  }
@@ -78,7 +82,7 @@ async function fetchEntitlements() {
78
82
  return { ...FREE_ENTITLEMENTS, source: "fallback_free" };
79
83
  }
80
84
  try {
81
- const url = `${PRISM_SYNALUX_BASE_URL}/api/v1/prism/entitlements`;
85
+ const url = `${baseUrl}/api/v1/prism/entitlements`;
82
86
  const res = await fetch(url, {
83
87
  method: "GET",
84
88
  headers: { Authorization: `Bearer ${jwt}` },
@@ -32,7 +32,11 @@ let inFlight = null;
32
32
  * Concurrent callers share a single in-flight exchange (no thundering herd).
33
33
  */
34
34
  export async function getSynaluxJwt() {
35
- if (!PRISM_SYNALUX_BASE_URL || !PRISM_SYNALUX_API_KEY) {
35
+ // Re-read process.env because storage/dashboard configuration can inject
36
+ // credentials after config.ts captured its module-load constants.
37
+ const baseUrl = process.env.PRISM_SYNALUX_BASE_URL?.trim() || PRISM_SYNALUX_BASE_URL;
38
+ const apiKey = process.env.PRISM_SYNALUX_API_KEY?.trim() || PRISM_SYNALUX_API_KEY;
39
+ if (!baseUrl || !apiKey) {
36
40
  return null;
37
41
  }
38
42
  const now = Date.now();
@@ -43,11 +47,11 @@ export async function getSynaluxJwt() {
43
47
  return inFlight;
44
48
  inFlight = (async () => {
45
49
  try {
46
- const url = `${PRISM_SYNALUX_BASE_URL}/api/v1/auth/jwt`;
50
+ const url = `${baseUrl}/api/v1/auth/jwt`;
47
51
  const res = await fetch(url, {
48
52
  method: "POST",
49
53
  headers: {
50
- "Authorization": `Bearer ${PRISM_SYNALUX_API_KEY}`,
54
+ "Authorization": `Bearer ${apiKey}`,
51
55
  "Content-Type": "application/json",
52
56
  },
53
57
  signal: AbortSignal.timeout(10_000),
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "20.2.0",
3
+ "version": "20.2.1",
4
4
  "mcpName": "io.github.dcostenco/prism-coder",
5
- "description": "Prism Coder — Cognitive memory + tool-calling intelligence for AI agents. Mind Palace persistent memory (BFCL Gold Certified, 100% Tool-Call Accuracy, 114 Agent Skills, PHI Guard, Tier Enforcement, Prompt-Based Skill Routing, Zero-Search HDC/HRR retrieval, HRR Semantic Drift Detection across BCBA/Coding/AAC domains, HIPAA-hardened local-first storage, SLERP-optimized GRPO alignment) plus the prism-coder 1.7B–32B open-weights LLM fleet.",
5
+ "description": "Prism Coder — Cognitive memory + tool-calling intelligence for AI agents. Mind Palace persistent memory (BFCL Gold Certified, 100% Tool-Call Accuracy, 114 Agent Skills, PHI Guard, Tier Enforcement, Prompt-Based Skill Routing, Zero-Search HDC/HRR retrieval, HRR Semantic Drift Detection across BCBA/Coding/AAC domains, HIPAA-hardened local or subscription-gated Synalux storage, SLERP-optimized GRPO alignment) plus the prism-coder 1.7B–32B open-weights LLM fleet.",
6
6
  "module": "index.ts",
7
7
  "type": "module",
8
8
  "main": "dist/server.js",
@@ -16,6 +16,8 @@
16
16
  "dist"
17
17
  ],
18
18
  "scripts": {
19
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
20
+ "prebuild": "npm run clean",
19
21
  "build": "tsc && npm run chmod-bins",
20
22
  "chmod-bins": "node -e \"['dist/cli.js','dist/server.js','dist/utils/universalImporter.js'].forEach(f => { try { require('fs').chmodSync(f, 0o755); } catch (e) { console.warn('chmod skipped', f, e.message); } })\"",
21
23
  "prepublishOnly": "npm run build",