prism-mcp-server 5.2.0 → 5.5.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.
package/dist/server.js CHANGED
@@ -58,7 +58,9 @@ ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequ
58
58
  // Claude Desktop that the attached resource has changed.
59
59
  // Without this, the paperclipped context becomes stale.
60
60
  SubscribeRequestSchema, UnsubscribeRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
61
- import { SERVER_CONFIG, SESSION_MEMORY_ENABLED, PRISM_USER_ID, PRISM_ENABLE_HIVEMIND } from "./config.js";
61
+ import { SERVER_CONFIG, SESSION_MEMORY_ENABLED, PRISM_USER_ID, PRISM_ENABLE_HIVEMIND, WATCHDOG_INTERVAL_MS, WATCHDOG_STALE_MIN, WATCHDOG_FROZEN_MIN, WATCHDOG_OFFLINE_MIN, WATCHDOG_LOOP_THRESHOLD, PRISM_SCHEDULER_ENABLED, PRISM_SCHEDULER_INTERVAL_MS, PRISM_SCHOLAR_ENABLED, } from "./config.js";
62
+ import { startWatchdog, drainAlerts } from "./hivemindWatchdog.js";
63
+ import { startScheduler, startScholarScheduler } from "./backgroundScheduler.js";
62
64
  import { getSyncBus } from "./sync/factory.js";
63
65
  import { startDashboardServer } from "./dashboard/server.js";
64
66
  import { acquireLock, registerShutdownHandlers } from "./lifecycle.js";
@@ -201,6 +203,13 @@ const activeSubscriptions = new Set();
201
203
  // the promise, so they never block the MCP stdio pipe during startup.
202
204
  let storageReady = null;
203
205
  let storageIsReady = false;
206
+ // ─── v5.2.1: Deferred Auto-Push Tracking ─────────────────────
207
+ // Tracks whether any client has already called session_load_context.
208
+ // Used by the deferred auto-push to skip redundant context injection.
209
+ // This ensures Claude CLI (which calls the tool via its hook within
210
+ // seconds) is never affected, while Antigravity gets a fallback push
211
+ // when the model fails to comply with auto-load instructions.
212
+ let contextLoadedByClient = false;
204
213
  /**
205
214
  * Notifies subscribed clients that a resource has changed.
206
215
  *
@@ -630,6 +639,7 @@ export function createServer() {
630
639
  case "session_load_context":
631
640
  if (!SESSION_MEMORY_ENABLED)
632
641
  throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
642
+ contextLoadedByClient = true; // v5.2.1: suppress deferred auto-push
633
643
  result = await sessionLoadContextHandler(args);
634
644
  break;
635
645
  case "knowledge_search":
@@ -755,6 +765,34 @@ export function createServer() {
755
765
  };
756
766
  }
757
767
  rootSpan.setStatus({ code: SpanStatusCode.OK });
768
+ // ═══ v5.3: Hivemind Watchdog Alert Injection (Telepathy) ═══
769
+ // CRITICAL: Append alerts DIRECTLY to tool response content
770
+ // so the LLM actually reads them. sendLoggingMessage goes to
771
+ // debug logs which the LLM never sees.
772
+ if (PRISM_ENABLE_HIVEMIND && result && !result.isError) {
773
+ const project = args?.project;
774
+ if (typeof project === "string") {
775
+ const alerts = drainAlerts(project);
776
+ if (alerts.length > 0) {
777
+ const alertBlock = alerts.map(a => `[🐝 SYSTEM ALERT] ⚠️ Teammate "${a.role}"` +
778
+ (a.agentName ? ` (${a.agentName})` : "") +
779
+ ` is ${a.status.toUpperCase()}: ${a.message}`).join("\n");
780
+ // Inject into LLM context (primary mechanism)
781
+ result.content.push({
782
+ type: "text",
783
+ text: `\n\n${alertBlock}`,
784
+ });
785
+ // Also log to operator/debug channel (secondary)
786
+ try {
787
+ server.sendLoggingMessage({
788
+ level: "warning",
789
+ data: alertBlock,
790
+ });
791
+ }
792
+ catch { /* sendLoggingMessage is best-effort */ }
793
+ }
794
+ }
795
+ }
758
796
  return result;
759
797
  }
760
798
  catch (error) {
@@ -866,6 +904,7 @@ export async function startServer() {
866
904
  const server = createServer();
867
905
  const transport = new StdioServerTransport();
868
906
  await server.connect(transport);
907
+ console.error(`[Prism] MCP Server successfully started and listening on stdio...`);
869
908
  // Register graceful shutdown handlers (SIGTERM, SIGINT, SIGHUP, stdin close).
870
909
  // The stdin close handler is critical — when MCP clients disconnect, they
871
910
  // often just close the pipe without sending a signal, leaving zombie processes.
@@ -887,18 +926,94 @@ export async function startServer() {
887
926
  ]).catch(err => {
888
927
  console.error(`[Prism] Storage pre-warm failed (non-fatal): ${err}`);
889
928
  });
890
- // ─── v4.1: Auto-Load is handled via dynamic tool descriptions ──
929
+ // ─── v4.1: Auto-Load via dynamic tool descriptions ──────────
891
930
  // The session_load_context tool description is dynamically modified
892
931
  // in createServer() → buildSessionMemoryTools() to include the
893
- // auto-load projects list. This is the ONLY universally reliable
894
- // mechanismtool descriptions are surfaced by ALL MCP clients.
932
+ // auto-load projects list. Tool descriptions are surfaced by ALL
933
+ // MCP clients this is the primary mechanism.
895
934
  //
896
- // Previous approaches that FAILED:
897
- // - sendLoggingMessage: goes to debug logs, not AI conversation
898
- // - instructions field: not supported by Claude Code or Claude CLI
935
+ // ─── v5.2.1: Deferred Auto-Push (fallback for non-compliant models) ──
936
+ // After storage warms up, wait AUTOLOAD_PUSH_DELAY_MS. If the client
937
+ // (model) hasn't called session_load_context by then, push context via
938
+ // sendLoggingMessage as a last resort. This is a FALLBACK — it's not
939
+ // guaranteed to be surfaced by all clients, but it's better than nothing.
899
940
  //
900
- // No runtime code needed here the instruction is baked into the
901
- // tool schema returned by ListTools.
941
+ // Why 10 seconds? Claude CLI always calls the tool within 2-3 seconds
942
+ // via its SessionStart hook. Antigravity models that comply also call it
943
+ // within 5 seconds. 10s gives ample time for well-behaved clients.
944
+ const AUTOLOAD_PUSH_DELAY_MS = 10_000;
945
+ // Read autoload projects from dashboard config (same source as createServer)
946
+ const pushAutoloadList = getSettingSync("autoload_projects", "")
947
+ .split(",").map(p => p.trim()).filter(Boolean);
948
+ if (pushAutoloadList.length > 0) {
949
+ // Wait for storage, then schedule the deferred push
950
+ storageReady?.then(async () => {
951
+ // Wait for the delay period to give the model a chance to call the tool
952
+ await new Promise(r => setTimeout(r, AUTOLOAD_PUSH_DELAY_MS));
953
+ // If the client already called session_load_context, skip the push
954
+ if (contextLoadedByClient) {
955
+ console.error(`[Prism] Auto-push skipped — client already loaded context`);
956
+ return;
957
+ }
958
+ console.error(`[Prism] Auto-push triggered — model did not call session_load_context within ${AUTOLOAD_PUSH_DELAY_MS / 1000}s`);
959
+ // Load and push context for each autoload project
960
+ try {
961
+ const storage = await getStorage();
962
+ const defaultLevel = getSettingSync("default_context_depth", "standard");
963
+ for (const project of pushAutoloadList) {
964
+ try {
965
+ const data = await storage.loadContext(project, defaultLevel, PRISM_USER_ID);
966
+ if (!data) {
967
+ server.sendLoggingMessage({
968
+ level: "info",
969
+ data: `[Prism Auto-Push] No context found for project "${project}". Starting fresh.`,
970
+ });
971
+ continue;
972
+ }
973
+ // Format context identically to sessionLoadContextHandler
974
+ const d = data;
975
+ let ctx = `📋 [AUTO-PUSH] Session context for "${project}" (${defaultLevel}):\n\n`;
976
+ if (d.last_summary)
977
+ ctx += `📝 Last Summary: ${d.last_summary}\n`;
978
+ if (d.active_branch)
979
+ ctx += `🌿 Active Branch: ${d.active_branch}\n`;
980
+ if (d.key_context)
981
+ ctx += `💡 Key Context: ${d.key_context}\n`;
982
+ if (d.pending_todo?.length) {
983
+ ctx += `\n✅ Open TODOs:\n` + d.pending_todo.map((t) => ` - ${t}`).join("\n") + `\n`;
984
+ }
985
+ if (d.keywords?.length) {
986
+ ctx += `\n🔑 Keywords: ${d.keywords.join(", ")}\n`;
987
+ }
988
+ if (d.recent_sessions?.length) {
989
+ ctx += `\n⏳ Recent Sessions:\n` + d.recent_sessions.map((s) => ` [${s.session_date?.split("T")[0]}] ${s.summary}`).join("\n") + `\n`;
990
+ }
991
+ // Agent identity
992
+ const agentName = getSettingSync("agent_name", "");
993
+ const defaultRole = getSettingSync("default_role", "");
994
+ if (agentName || defaultRole) {
995
+ ctx += `\n👤 Agent: ${defaultRole || "global"} — ${agentName || "Agent"}`;
996
+ }
997
+ const version = d.version;
998
+ if (version) {
999
+ ctx += `\n🔑 Session version: ${version}. Pass expected_version: ${version} when saving handoff.`;
1000
+ }
1001
+ server.sendLoggingMessage({
1002
+ level: "info",
1003
+ data: ctx,
1004
+ });
1005
+ console.error(`[Prism] Auto-pushed context for "${project}" (${defaultLevel})`);
1006
+ }
1007
+ catch (err) {
1008
+ console.error(`[Prism] Auto-push failed for "${project}" (non-fatal): ${err}`);
1009
+ }
1010
+ }
1011
+ }
1012
+ catch (err) {
1013
+ console.error(`[Prism] Auto-push storage error (non-fatal): ${err}`);
1014
+ }
1015
+ }).catch(() => { });
1016
+ }
902
1017
  }
903
1018
  // ─── v2.0 Step 6: Initialize SyncBus (Telepathy) ───
904
1019
  // Fire-and-forget — SyncBus is non-critical for startup.
@@ -938,6 +1053,46 @@ export async function startServer() {
938
1053
  console.error(`[Dashboard] Mind Palace startup failed (non-fatal): ${err}`);
939
1054
  });
940
1055
  }, 0);
1056
+ // ─── v5.3: Hivemind Watchdog ──────────────────────────────
1057
+ // Start the server-side health monitor after storage is warm.
1058
+ // Runs every WATCHDOG_INTERVAL_MS (default 60s) to detect
1059
+ // frozen agents, infinite loops, and task overruns.
1060
+ if (PRISM_ENABLE_HIVEMIND && SESSION_MEMORY_ENABLED) {
1061
+ storageReady?.then(() => {
1062
+ startWatchdog({
1063
+ intervalMs: WATCHDOG_INTERVAL_MS,
1064
+ staleThresholdMin: WATCHDOG_STALE_MIN,
1065
+ frozenThresholdMin: WATCHDOG_FROZEN_MIN,
1066
+ offlineThresholdMin: WATCHDOG_OFFLINE_MIN,
1067
+ loopThreshold: WATCHDOG_LOOP_THRESHOLD,
1068
+ });
1069
+ }).catch(err => {
1070
+ console.error(`[Watchdog] Startup failed (non-fatal): ${err}`);
1071
+ });
1072
+ }
1073
+ // ─── v5.4: Background Purge Scheduler ────────────────────
1074
+ // Automated storage maintenance: TTL sweep, importance decay,
1075
+ // compaction, and deep purge. Runs every PRISM_SCHEDULER_INTERVAL_MS
1076
+ // (default: 12 hours). Independent from the Watchdog (60s cadence).
1077
+ if (PRISM_SCHEDULER_ENABLED && SESSION_MEMORY_ENABLED) {
1078
+ storageReady?.then(() => {
1079
+ startScheduler({
1080
+ intervalMs: PRISM_SCHEDULER_INTERVAL_MS,
1081
+ });
1082
+ }).catch(err => {
1083
+ console.error(`[Scheduler] Startup failed (non-fatal): ${err}`);
1084
+ });
1085
+ }
1086
+ // ─── v5.4: Autonomous Web Scholar Scheduler ──────────────
1087
+ // Background LLM research pipeline. Independent from the
1088
+ // maintenance scheduler — has its own interval and enable flag.
1089
+ if (PRISM_SCHOLAR_ENABLED && SESSION_MEMORY_ENABLED) {
1090
+ storageReady?.then(() => {
1091
+ startScholarScheduler();
1092
+ }).catch(err => {
1093
+ console.error(`[WebScholar] Startup failed (non-fatal): ${err}`);
1094
+ });
1095
+ }
941
1096
  // Keep the process alive — without this, Node.js would exit
942
1097
  // because there are no active event loop handles after the
943
1098
  // synchronous setup completes.
@@ -945,8 +1100,15 @@ export async function startServer() {
945
1100
  // Heartbeat to keep the process running
946
1101
  }, 10000);
947
1102
  }
948
- // Only auto-start when this module is executed directly (not imported by Smithery scanner)
949
- const isDirectExecution = process.argv[1]?.endsWith('server.js') || process.argv[1]?.endsWith('server.ts');
1103
+ // Only auto-start when this module is executed directly (not imported by Smithery scanner).
1104
+ // IMPORTANT: npm install -g creates a symlink like /usr/local/bin/prism-mcp-server
1105
+ // whose path does NOT end with 'server.js'. Node.js sets process.argv[1] to the
1106
+ // symlink path, not the resolved target. Without the bin-name check, startServer()
1107
+ // never fires and the process silently exits with zero stdout (see issue #21).
1108
+ const entryScript = process.argv[1] ?? '';
1109
+ const isDirectExecution = entryScript.endsWith('server.js') ||
1110
+ entryScript.endsWith('server.ts') ||
1111
+ entryScript.endsWith('prism-mcp-server');
950
1112
  if (isDirectExecution) {
951
1113
  startServer().catch((error) => {
952
1114
  console.error('Fatal error running server:', error);