prism-mcp-server 20.21.6 → 20.21.7
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/connect.js +52 -3
- package/dist/dashboard/dashboardToken.js +13 -7
- package/dist/dashboard/graphRouter.js +12 -2
- package/dist/dashboard/ledgerReader.js +9 -0
- package/dist/dashboard/server.js +7 -14
- package/dist/dashboard/ui.js +7 -1
- package/dist/storage/synalux.js +66 -0
- package/package.json +1 -1
package/dist/connect.js
CHANGED
|
@@ -1250,6 +1250,7 @@ function getHostDefinitions(homeDir, platform, env, includeSystemPaths) {
|
|
|
1250
1250
|
label: "Gemini CLI",
|
|
1251
1251
|
format: "json",
|
|
1252
1252
|
configPath: join(homeDir, ".gemini", "settings.json"),
|
|
1253
|
+
additionalConfigPaths: antigravityMcpConfigPaths(homeDir),
|
|
1253
1254
|
detectionPaths: [join(homeDir, ".gemini")],
|
|
1254
1255
|
executables: ["gemini"],
|
|
1255
1256
|
}, {
|
|
@@ -1304,6 +1305,22 @@ function executableExists(name, platform, pathEnv) {
|
|
|
1304
1305
|
}
|
|
1305
1306
|
return false;
|
|
1306
1307
|
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Antigravity has used three user-level MCP locations across releases. Keep
|
|
1310
|
+
* the Gemini CLI settings registration as the primary result, and converge
|
|
1311
|
+
* only Antigravity paths whose parent directory already exists. That avoids
|
|
1312
|
+
* creating an unrelated host config on a Gemini-CLI-only installation while
|
|
1313
|
+
* repairing an existing Antigravity installation, including the legacy IDE
|
|
1314
|
+
* path that can otherwise continue launching an old server indefinitely.
|
|
1315
|
+
*/
|
|
1316
|
+
function antigravityMcpConfigPaths(homeDir) {
|
|
1317
|
+
const geminiDir = join(homeDir, ".gemini");
|
|
1318
|
+
return [
|
|
1319
|
+
join(geminiDir, "config", "mcp_config.json"),
|
|
1320
|
+
join(geminiDir, "antigravity", "mcp_config.json"),
|
|
1321
|
+
join(geminiDir, "antigravity-ide", "mcp_config.json"),
|
|
1322
|
+
].filter((configPath) => existsSync(configPath) || existsSync(dirname(configPath)));
|
|
1323
|
+
}
|
|
1307
1324
|
function buildMcpEntry(nodePath, serverPath, env) {
|
|
1308
1325
|
const storage = env.PRISM_STORAGE ?? "auto";
|
|
1309
1326
|
if (!CONNECT_STORAGE_BACKENDS.includes(storage)) {
|
|
@@ -1331,10 +1348,42 @@ function registerHost(definition, entry, dryRun, refresh, beforeCommit) {
|
|
|
1331
1348
|
if (definition.configurationError) {
|
|
1332
1349
|
return result(definition, "error", definition.configurationError);
|
|
1333
1350
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1351
|
+
const configDefinitions = [
|
|
1352
|
+
definition,
|
|
1353
|
+
...(definition.additionalConfigPaths ?? []).map((configPath) => ({ ...definition, configPath })),
|
|
1354
|
+
];
|
|
1355
|
+
const registerOne = (configDefinition) => {
|
|
1356
|
+
if (configDefinition.format === "codex-toml") {
|
|
1357
|
+
return registerCodexTomlHost(configDefinition, entry, dryRun, refresh, beforeCommit);
|
|
1358
|
+
}
|
|
1359
|
+
return registerJsonHost(configDefinition, entry, dryRun, refresh, beforeCommit);
|
|
1360
|
+
};
|
|
1361
|
+
const primary = registerOne(configDefinitions[0]);
|
|
1362
|
+
if (primary.status === "error")
|
|
1363
|
+
return primary;
|
|
1364
|
+
const registrations = [primary, ...configDefinitions.slice(1).map(registerOne)];
|
|
1365
|
+
const failures = registrations.slice(1).filter((registration) => registration.status === "error");
|
|
1366
|
+
if (failures.length > 0) {
|
|
1367
|
+
return {
|
|
1368
|
+
...primary,
|
|
1369
|
+
status: "error",
|
|
1370
|
+
startupCompatible: false,
|
|
1371
|
+
message: `additional config registration failed: ${failures.map((failure) => `${failure.path}: ${failure.message || "unknown error"}`).join("; ")}`,
|
|
1372
|
+
};
|
|
1336
1373
|
}
|
|
1337
|
-
|
|
1374
|
+
const additionalChanges = registrations.slice(1).filter((registration) => registration.status !== "existing");
|
|
1375
|
+
const incompatible = registrations.filter((registration) => !registration.startupCompatible);
|
|
1376
|
+
const additionalMessage = additionalChanges.length > 0
|
|
1377
|
+
? `also ${additionalChanges.map((registration) => `${registration.status} ${registration.path}`).join(", ")}`
|
|
1378
|
+
: undefined;
|
|
1379
|
+
const incompatibleMessage = incompatible.length > 0
|
|
1380
|
+
? `retained incompatible or unmanaged registration(s): ${incompatible.map((registration) => registration.path).join(", ")}`
|
|
1381
|
+
: undefined;
|
|
1382
|
+
return {
|
|
1383
|
+
...primary,
|
|
1384
|
+
startupCompatible: registrations.every((registration) => registration.startupCompatible),
|
|
1385
|
+
message: [primary.message, additionalMessage, incompatibleMessage].filter(Boolean).join("; ") || undefined,
|
|
1386
|
+
};
|
|
1338
1387
|
}
|
|
1339
1388
|
function registerJsonHost(definition, entry, dryRun, refresh, beforeCommit) {
|
|
1340
1389
|
const { configPath } = definition;
|
|
@@ -20,24 +20,30 @@ export function resolveDashboardToken(cfg) {
|
|
|
20
20
|
return randomBytes(32).toString("hex");
|
|
21
21
|
}
|
|
22
22
|
/** Extract the prism_dashboard_token cookie value, if present. */
|
|
23
|
-
export function
|
|
23
|
+
export function dashboardTokenCookieName(port) {
|
|
24
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
25
|
+
throw new Error('Invalid dashboard port');
|
|
26
|
+
return `prism_dashboard_token_${port}`;
|
|
27
|
+
}
|
|
28
|
+
export function tokenFromCookie(cookieHeader, cookieName = 'prism_dashboard_token') {
|
|
24
29
|
// Capture the whole value (any run of non-";", non-space) so pinned tokens
|
|
25
30
|
// with hyphens/underscores match; the name is anchored to start-or-"; " so a
|
|
26
31
|
// look-alike cookie (evil_prism_dashboard_token=…) cannot match.
|
|
27
|
-
const
|
|
28
|
-
|
|
32
|
+
const entry = (cookieHeader || '').split(';').map(c => c.trim()).find(c => c.startsWith(`${cookieName}=`));
|
|
33
|
+
const value = entry?.slice(cookieName.length + 1);
|
|
34
|
+
return value && !/\s/.test(value) ? value : null;
|
|
29
35
|
}
|
|
30
36
|
/**
|
|
31
37
|
* True when the request presents the active token via the cookie, the
|
|
32
38
|
* X-Prism-Dashboard-Token header, or a ?token= query param. Every comparison is
|
|
33
39
|
* timing-safe and only runs against the single active token.
|
|
34
40
|
*/
|
|
35
|
-
export function requestHasToken(headers, queryToken, activeToken) {
|
|
36
|
-
const candidates = [tokenFromCookie(headers.cookie), headers.headerToken ?? null, queryToken];
|
|
41
|
+
export function requestHasToken(headers, queryToken, activeToken, cookieName = 'prism_dashboard_token') {
|
|
42
|
+
const candidates = [tokenFromCookie(headers.cookie, cookieName), tokenFromCookie(headers.cookie), headers.headerToken ?? null, queryToken];
|
|
37
43
|
return candidates.some((c) => c !== null && safeCompare(c, activeToken));
|
|
38
44
|
}
|
|
39
45
|
/** Build the Set-Cookie value that stores the token for a browser session. */
|
|
40
|
-
export function buildTokenCookie(token, maxAgeMs, secure) {
|
|
41
|
-
return (
|
|
46
|
+
export function buildTokenCookie(token, maxAgeMs, secure, cookieName = 'prism_dashboard_token') {
|
|
47
|
+
return (`${cookieName}=${token}; Path=/; HttpOnly; SameSite=Strict; ` +
|
|
42
48
|
`Max-Age=${Math.floor(maxAgeMs / 1000)}${secure ? "; Secure" : ""}`);
|
|
43
49
|
}
|
|
@@ -65,16 +65,26 @@ export async function handleGraphRoutes(url, req, res, getStorageSafe) {
|
|
|
65
65
|
else {
|
|
66
66
|
params.limit = "200"; // Bump limit when exploring specific filters
|
|
67
67
|
}
|
|
68
|
+
let createdAfter;
|
|
68
69
|
if (project)
|
|
69
70
|
params.project = `eq.${project}`;
|
|
70
71
|
if (days) {
|
|
71
72
|
const past = new Date();
|
|
72
73
|
past.setDate(past.getDate() - parseInt(days, 10));
|
|
73
|
-
|
|
74
|
+
createdAfter = past.toISOString();
|
|
75
|
+
params.created_at = `gte.${createdAfter}`;
|
|
74
76
|
}
|
|
75
77
|
if (min_importance)
|
|
76
78
|
params.importance = `gte.${parseInt(min_importance, 10)}`;
|
|
77
|
-
const
|
|
79
|
+
const graphLimit = !project && !days && !min_importance ? 30 : 200;
|
|
80
|
+
const entries = s.getDashboardGraphEntries
|
|
81
|
+
? await s.getDashboardGraphEntries({
|
|
82
|
+
...(project ? { project } : {}),
|
|
83
|
+
...(createdAfter ? { createdAfter } : {}),
|
|
84
|
+
...(min_importance ? { minImportance: parseInt(min_importance, 10) } : {}),
|
|
85
|
+
limit: graphLimit,
|
|
86
|
+
})
|
|
87
|
+
: await s.getLedgerEntries(params);
|
|
78
88
|
const now = Date.now();
|
|
79
89
|
// ── Decay tracking: aggregate per-keyword and per-project ──
|
|
80
90
|
// For each keyword/project, track the *freshest* entry's decay info
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Paid clients have Portal credentials, not direct Supabase credentials. */
|
|
2
|
+
export async function readDashboardLedger(storage, backend, project, order, limit) {
|
|
3
|
+
if (backend !== "synalux") {
|
|
4
|
+
return storage.getLedgerEntries({ project: `eq.${project}`, order, limit: String(limit) });
|
|
5
|
+
}
|
|
6
|
+
if (!storage.getDashboardLedger)
|
|
7
|
+
throw new Error("Cloud dashboard ledger reader unavailable");
|
|
8
|
+
return storage.getDashboardLedger(project, order, limit);
|
|
9
|
+
}
|
package/dist/dashboard/server.js
CHANGED
|
@@ -22,7 +22,8 @@ import * as os from "os";
|
|
|
22
22
|
import * as fs from "fs";
|
|
23
23
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
24
24
|
import { createServer, getAllPossibleTools } from "../server.js";
|
|
25
|
-
import { getStorage } from "../storage/index.js";
|
|
25
|
+
import { getStorage, activeStorageBackend } from "../storage/index.js";
|
|
26
|
+
import { readDashboardLedger } from "./ledgerReader.js";
|
|
26
27
|
import { PRISM_USER_ID, SERVER_CONFIG } from "../config.js";
|
|
27
28
|
import { renderDashboardHTML } from "./ui.js";
|
|
28
29
|
import { computeIntentHealth } from "./intentHealth.js";
|
|
@@ -34,7 +35,7 @@ import { redactSettings } from "../tools/commonHelpers.js";
|
|
|
34
35
|
import { handleGraphRoutes } from "./graphRouter.js";
|
|
35
36
|
import { isDashboardSettingKeyAllowed, isDashboardSettingValueAllowed } from "./settingsPolicy.js";
|
|
36
37
|
import { isTrustedRequest, isRebindGuardedPath } from "./hostGuard.js";
|
|
37
|
-
import { resolveDashboardToken, requestHasToken, buildTokenCookie, } from "./dashboardToken.js";
|
|
38
|
+
import { resolveDashboardToken, requestHasToken, buildTokenCookie, dashboardTokenCookieName, } from "./dashboardToken.js";
|
|
38
39
|
import { safeCompare, generateToken, isAuthenticated, createRateLimiter, initJWKS, } from "./authUtils.js";
|
|
39
40
|
const PORT = parseInt(process.env.PRISM_DASHBOARD_PORT || "3000", 10);
|
|
40
41
|
/** Read HTTP request body as string (Buffer-based to avoid GC thrash on large imports) */
|
|
@@ -238,7 +239,7 @@ return false;}
|
|
|
238
239
|
reqUrl.searchParams.delete("token");
|
|
239
240
|
const cleanTarget = reqUrl.pathname + (reqUrl.search ? reqUrl.search : "");
|
|
240
241
|
res.writeHead(302, {
|
|
241
|
-
"Set-Cookie": buildTokenCookie(DASHBOARD_TOKEN, SESSION_TTL_MS, COOKIE_SECURE),
|
|
242
|
+
"Set-Cookie": buildTokenCookie(DASHBOARD_TOKEN, SESSION_TTL_MS, COOKIE_SECURE, dashboardTokenCookieName(req.socket.localPort)),
|
|
242
243
|
Location: cleanTarget,
|
|
243
244
|
});
|
|
244
245
|
return res.end();
|
|
@@ -247,7 +248,7 @@ return false;}
|
|
|
247
248
|
!requestHasToken({
|
|
248
249
|
cookie: req.headers.cookie,
|
|
249
250
|
headerToken: req.headers["x-prism-dashboard-token"] || null,
|
|
250
|
-
}, qToken, DASHBOARD_TOKEN)) {
|
|
251
|
+
}, qToken, DASHBOARD_TOKEN, dashboardTokenCookieName(req.socket.localPort))) {
|
|
251
252
|
res.writeHead(401, { "Content-Type": "application/json" });
|
|
252
253
|
return res.end(JSON.stringify({
|
|
253
254
|
error: "Dashboard token required — open the tokenized URL printed in the Prism startup log.",
|
|
@@ -482,11 +483,7 @@ return false;}
|
|
|
482
483
|
return res.end(JSON.stringify({ error: "Storage initializing..." }));
|
|
483
484
|
}
|
|
484
485
|
const context = await s.loadContext(projectName, "deep", PRISM_USER_ID);
|
|
485
|
-
const ledger = await s.
|
|
486
|
-
project: `eq.${projectName}`,
|
|
487
|
-
order: "created_at.desc",
|
|
488
|
-
limit: "20",
|
|
489
|
-
});
|
|
486
|
+
const ledger = await readDashboardLedger(s, activeStorageBackend, projectName, "created_at.desc", 20);
|
|
490
487
|
let history = [];
|
|
491
488
|
try {
|
|
492
489
|
history = await s.getHistory(projectName, PRISM_USER_ID, 10);
|
|
@@ -824,11 +821,7 @@ return false;}
|
|
|
824
821
|
}
|
|
825
822
|
// Gather data (mirrors sessionExportMemoryHandler)
|
|
826
823
|
const ctx = await s.loadContext(projectName, "deep", PRISM_USER_ID);
|
|
827
|
-
const rawLedger = await s.
|
|
828
|
-
project: `eq.${projectName}`,
|
|
829
|
-
order: "created_at.asc",
|
|
830
|
-
limit: "1000",
|
|
831
|
-
});
|
|
824
|
+
const rawLedger = await readDashboardLedger(s, activeStorageBackend, projectName, "created_at.asc", 1000);
|
|
832
825
|
// Strip binary embedding fields
|
|
833
826
|
const cleanLedger = rawLedger.map(({ embedding: _e, embedding_compressed: _ec, ...rest }) => rest);
|
|
834
827
|
const rawSettings = await getAllSettings();
|
package/dist/dashboard/ui.js
CHANGED
|
@@ -712,6 +712,7 @@ export function renderDashboardHTML(version) {
|
|
|
712
712
|
<button class="settings-btn" onclick="openSettings()" title="Settings">⚙️</button>
|
|
713
713
|
</div>
|
|
714
714
|
</header>
|
|
715
|
+
<p id="projectLoadError" role="alert" style="display:none; color:var(--accent-rose);"></p>
|
|
715
716
|
|
|
716
717
|
<div class="main-tabs" style="display:flex; gap: 1rem; border-bottom: 1px solid var(--border-glass); margin-bottom: 1.5rem; padding-bottom: 0;">
|
|
717
718
|
<button class="s-tab active" id="mtab-project" onclick="switchMainTab('project')" style="font-size: 1rem;">📁 Project View</button>
|
|
@@ -2049,6 +2050,7 @@ function loadIdentityChip() {
|
|
|
2049
2050
|
return [4 /*yield*/, res.json()];
|
|
2050
2051
|
case 2:
|
|
2051
2052
|
data = _a.sent();
|
|
2053
|
+
if (!res.ok) throw new Error(data.error || ('Request failed (' + res.status + ')'));
|
|
2052
2054
|
select = document.getElementById('projectSelect');
|
|
2053
2055
|
if (data.projects && data.projects.length > 0) {
|
|
2054
2056
|
select.innerHTML = '<option value="">— Select a project —</option>' +
|
|
@@ -2097,6 +2099,8 @@ function loadIdentityChip() {
|
|
|
2097
2099
|
case 3:
|
|
2098
2100
|
e_2 = _a.sent();
|
|
2099
2101
|
document.getElementById('projectSelect').innerHTML = '<option value="">Error loading projects</option>';
|
|
2102
|
+
document.getElementById('projectLoadError').textContent = e_2.message || 'Error loading projects';
|
|
2103
|
+
document.getElementById('projectLoadError').style.display = 'block';
|
|
2100
2104
|
return [3 /*break*/, 4];
|
|
2101
2105
|
case 4:
|
|
2102
2106
|
// Load identity chip once settings are available
|
|
@@ -2918,7 +2922,9 @@ function loadGraph() {
|
|
|
2918
2922
|
}
|
|
2919
2923
|
MAX_NODES = 200;
|
|
2920
2924
|
if (data.nodes.length > MAX_NODES) {
|
|
2921
|
-
|
|
2925
|
+
// Keep the project/category/keyword ordering truthy because the
|
|
2926
|
+
// comparator falls back with a high priority for unknown groups.
|
|
2927
|
+
priority = { project: 1, category: 2, keyword: 3 };
|
|
2922
2928
|
data.nodes.sort(function (a, b) { return (priority[a.group] || 9) - (priority[b.group] || 9); });
|
|
2923
2929
|
kept = new Set(data.nodes.slice(0, MAX_NODES).map(function (n) { return n.id; }));
|
|
2924
2930
|
data.nodes = data.nodes.slice(0, MAX_NODES);
|
package/dist/storage/synalux.js
CHANGED
|
@@ -451,6 +451,46 @@ export class SynaluxStorage extends SupabaseStorage {
|
|
|
451
451
|
const entries = Array.isArray(result.entries) ? result.entries : [];
|
|
452
452
|
return entries;
|
|
453
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* Read the bounded, authenticated ledger projection used by the dashboard
|
|
456
|
+
* graph. This is deliberately separate from getLedgerEntries: the latter
|
|
457
|
+
* has many PostgREST callers that are not representable by the portal API.
|
|
458
|
+
*/
|
|
459
|
+
async getDashboardGraphEntries(params) {
|
|
460
|
+
if (!Number.isInteger(params.limit) || params.limit < 1 || params.limit > 200) {
|
|
461
|
+
throw new Error("Invalid dashboard graph limit");
|
|
462
|
+
}
|
|
463
|
+
if (params.project !== undefined && (!params.project.trim() || params.project.length > 100)) {
|
|
464
|
+
throw new Error("Invalid dashboard graph project");
|
|
465
|
+
}
|
|
466
|
+
if (params.createdAfter !== undefined
|
|
467
|
+
&& (Number.isNaN(new Date(params.createdAfter).getTime())
|
|
468
|
+
|| new Date(params.createdAfter).toISOString() !== params.createdAfter)) {
|
|
469
|
+
throw new Error("Invalid dashboard graph timestamp");
|
|
470
|
+
}
|
|
471
|
+
if (params.minImportance !== undefined
|
|
472
|
+
&& (!Number.isInteger(params.minImportance)
|
|
473
|
+
|| params.minImportance < -2147483648 || params.minImportance > 2147483647)) {
|
|
474
|
+
throw new Error("Invalid dashboard graph importance");
|
|
475
|
+
}
|
|
476
|
+
if (params.keywords !== undefined
|
|
477
|
+
&& (params.keywords.length === 0 || params.keywords.length > 50
|
|
478
|
+
|| params.keywords.some((keyword) => !keyword || keyword.length > 200))) {
|
|
479
|
+
throw new Error("Invalid dashboard graph keywords");
|
|
480
|
+
}
|
|
481
|
+
const result = await this.portalPost("/api/v1/prism/memory", {
|
|
482
|
+
action: "dashboard_ledger",
|
|
483
|
+
...(params.project !== undefined ? { project: params.project.trim() } : {}),
|
|
484
|
+
...(params.createdAfter !== undefined ? { created_after: params.createdAfter } : {}),
|
|
485
|
+
...(params.minImportance !== undefined ? { min_importance: params.minImportance } : {}),
|
|
486
|
+
...(params.keywords !== undefined ? { keywords: params.keywords } : {}),
|
|
487
|
+
limit: params.limit,
|
|
488
|
+
});
|
|
489
|
+
if (!Array.isArray(result.ledger)) {
|
|
490
|
+
throw new Error("Dashboard graph contract drift: ledger[] is required");
|
|
491
|
+
}
|
|
492
|
+
return result.ledger;
|
|
493
|
+
}
|
|
454
494
|
// ─── Project inventory + export ──────────────────────────────
|
|
455
495
|
// Both portal actions shipped in Phase 3 but the client was never
|
|
456
496
|
// wired: listProjects and the export path fell through to
|
|
@@ -503,6 +543,32 @@ export class SynaluxStorage extends SupabaseStorage {
|
|
|
503
543
|
}
|
|
504
544
|
return rows;
|
|
505
545
|
}
|
|
546
|
+
/** Read the requested dashboard window without the backup's 10k-row cap. */
|
|
547
|
+
async getDashboardLedger(project, order, limit) {
|
|
548
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 1000)
|
|
549
|
+
throw new Error("Invalid dashboard ledger limit");
|
|
550
|
+
let offset = 0;
|
|
551
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
552
|
+
const result = await this.portalPost("/api/v1/prism/memory", {
|
|
553
|
+
action: "export_memory", project, offset, limit,
|
|
554
|
+
});
|
|
555
|
+
const page = result.page;
|
|
556
|
+
const total = page?.total;
|
|
557
|
+
if (!Array.isArray(result.ledger) || typeof total !== "number" || !Number.isSafeInteger(total) || total < 0) {
|
|
558
|
+
throw new Error("Dashboard export contract drift: ledger and total are required");
|
|
559
|
+
}
|
|
560
|
+
const target = order === "created_at.desc" ? Math.max(0, total - limit) : 0;
|
|
561
|
+
if (offset !== target) {
|
|
562
|
+
offset = target;
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const rows = result.ledger;
|
|
566
|
+
const direction = order === "created_at.asc" ? 1 : -1;
|
|
567
|
+
return [...rows].sort((a, b) => direction * (String(a.created_at).localeCompare(String(b.created_at))
|
|
568
|
+
|| String(a.id).localeCompare(String(b.id))));
|
|
569
|
+
}
|
|
570
|
+
throw new Error("Dashboard ledger changed during read; retry");
|
|
571
|
+
}
|
|
506
572
|
// ─── Time Travel ─────────────────────────────────────────────
|
|
507
573
|
// Phase 3 Tier B: route memory_history through portal instead of
|
|
508
574
|
// falling through to SupabaseStorage (which requires a direct
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prism-mcp-server",
|
|
3
|
-
"version": "20.21.
|
|
3
|
+
"version": "20.21.7",
|
|
4
4
|
"mcpName": "io.github.dcostenco/prism-coder",
|
|
5
5
|
"description": "Persistent session memory for AI coding agents that never leaves your machine \u2014 including the on-device model that reasons over it. Restores your prior decisions, open TODOs, and changed files across sessions; adds associative recall of related past work, semantic drift detection, and local inference. Local-first by default. Works with Claude Code, Cursor, and Codex.",
|
|
6
6
|
"module": "index.ts",
|