prism-mcp-server 19.2.6 → 19.2.8
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/dashboard/server.js +1 -1
- package/dist/scholar/webScholar.js +44 -11
- package/dist/server.js +2 -1
- package/dist/storage/configStorage.js +14 -8
- package/dist/tools/commonHelpers.js +1 -1
- package/dist/tools/ingestHandler.js +2 -1
- package/dist/tools/ledgerHandlers.js +3 -2
- package/dist/utils/imageCaptioner.js +7 -7
- package/dist/utils/llm/factory.js +3 -2
- package/dist/utils/logger.js +3 -3
- package/dist/utils/migration/utils.js +4 -4
- package/dist/utils/telemetry.js +4 -3
- package/package.json +1 -1
package/dist/dashboard/server.js
CHANGED
|
@@ -558,7 +558,7 @@ return false;}
|
|
|
558
558
|
// GET /api/skills → { skills: { dev: "...", qa: "..." } }
|
|
559
559
|
if (url.pathname === "/api/skills" && req.method === "GET") {
|
|
560
560
|
const all = await getAllSettings();
|
|
561
|
-
const skills =
|
|
561
|
+
const skills = Object.create(null);
|
|
562
562
|
for (const [k, v] of Object.entries(all)) {
|
|
563
563
|
if (k.startsWith("skill:") && v) {
|
|
564
564
|
skills[k.replace("skill:", "")] = v;
|
|
@@ -3,7 +3,7 @@ import { getStorage } from "../storage/index.js";
|
|
|
3
3
|
import { debugLog } from "../utils/logger.js";
|
|
4
4
|
import { getLLMProvider } from "../utils/llm/factory.js";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { performWebSearchRaw } from "../utils/braveApi.js";
|
|
@@ -115,20 +115,53 @@ function acquireFileLock() {
|
|
|
115
115
|
const lockDir = dirname(LOCK_PATH);
|
|
116
116
|
if (!existsSync(lockDir))
|
|
117
117
|
mkdirSync(lockDir, { recursive: true });
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
const lockContent = `${process.pid}\n${new Date().toISOString()}`;
|
|
119
|
+
// Fast path: exclusive create — no lock exists.
|
|
120
|
+
try {
|
|
121
|
+
writeFileSync(LOCK_PATH, lockContent, { flag: "wx" });
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
catch (wxErr) {
|
|
125
|
+
if (wxErr?.code !== "EEXIST")
|
|
122
126
|
return false;
|
|
123
|
-
unlinkSync(LOCK_PATH);
|
|
124
127
|
}
|
|
125
|
-
//
|
|
126
|
-
|
|
128
|
+
// Lock exists — check if stale.
|
|
129
|
+
let stat;
|
|
130
|
+
try {
|
|
131
|
+
stat = statSync(LOCK_PATH);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
if (Date.now() - stat.mtimeMs < LOCK_STALE_MS)
|
|
137
|
+
return false;
|
|
138
|
+
// Stale lock — atomic takeover via rename.
|
|
139
|
+
// Write our identity to a temp file, then rename over the lock.
|
|
140
|
+
// rename() is atomic on POSIX; no window where the lock is absent.
|
|
141
|
+
const tmpLock = `${LOCK_PATH}.${process.pid}`;
|
|
142
|
+
try {
|
|
143
|
+
writeFileSync(tmpLock, lockContent, { flag: "wx" });
|
|
144
|
+
renameSync(tmpLock, LOCK_PATH);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
try {
|
|
148
|
+
unlinkSync(tmpLock);
|
|
149
|
+
}
|
|
150
|
+
catch { /* cleanup */ }
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
// Verify we own the lock (another process may have renamed over us).
|
|
154
|
+
try {
|
|
155
|
+
const owner = readFileSync(LOCK_PATH, "utf-8").split("\n")[0];
|
|
156
|
+
if (owner !== String(process.pid))
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
127
162
|
return true;
|
|
128
163
|
}
|
|
129
|
-
catch
|
|
130
|
-
if (err?.code === "EEXIST")
|
|
131
|
-
return false;
|
|
164
|
+
catch {
|
|
132
165
|
return false;
|
|
133
166
|
}
|
|
134
167
|
}
|
package/dist/server.js
CHANGED
|
@@ -76,6 +76,7 @@ import { verifyBehaviorHandler } from "./tools/behavioralVerifierHandler.js";
|
|
|
76
76
|
import { getStorage } from "./storage/index.js";
|
|
77
77
|
import { getSettingSync, initConfigStorage } from "./storage/configStorage.js";
|
|
78
78
|
import { sanitizeMcpOutput } from "./utils/sanitizer.js";
|
|
79
|
+
import { sanitizeForLog } from "./utils/logger.js";
|
|
79
80
|
import { getTracer, initTelemetry } from "./utils/telemetry.js";
|
|
80
81
|
import { context as otelContext, trace, SpanStatusCode } from "@opentelemetry/api";
|
|
81
82
|
import { ddInfo, ddError as ddLogError } from "./utils/ddLogger.js";
|
|
@@ -1020,7 +1021,7 @@ export function createServer() {
|
|
|
1020
1021
|
catch (error) {
|
|
1021
1022
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
1022
1023
|
const _ddErrDuration = Date.now() - _ddStart;
|
|
1023
|
-
console.error(`Error in tool handler: ${errMsg}`);
|
|
1024
|
+
console.error(`Error in tool handler: ${sanitizeForLog(errMsg)}`);
|
|
1024
1025
|
ddLogError("mcp.tool.error", error instanceof Error ? error : undefined, { tool: name, project: args?.project, durationMs: _ddErrDuration });
|
|
1025
1026
|
rootSpan.recordException(error instanceof Error ? error : new Error(errMsg));
|
|
1026
1027
|
rootSpan.setStatus({
|
|
@@ -2,6 +2,7 @@ import { createClient } from "@libsql/client";
|
|
|
2
2
|
import { resolve, dirname } from "path";
|
|
3
3
|
import { homedir } from "os";
|
|
4
4
|
import { existsSync, mkdirSync } from "fs";
|
|
5
|
+
const PROTO_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
5
6
|
// We use a small, dedicated DB just for configuration settings.
|
|
6
7
|
// This solves the chicken-and-egg problem: we need to know WHICH
|
|
7
8
|
// storage backend to boot *before* we can use that backend.
|
|
@@ -22,7 +23,7 @@ let initialized = false;
|
|
|
22
23
|
// (e.g. ReadResourceRequestSchema) can read settings synchronously
|
|
23
24
|
// without opening an additional SQLite round-trip and stalling the
|
|
24
25
|
// MCP stdio handshake (which causes a black-screen on startup).
|
|
25
|
-
let settingsCache = null;
|
|
26
|
+
let settingsCache = null; // assigned as Object.create(null) below
|
|
26
27
|
function getClient() {
|
|
27
28
|
if (!configClient) {
|
|
28
29
|
// Ensure the directory exists before opening the DB.
|
|
@@ -52,9 +53,12 @@ export async function initConfigStorage() {
|
|
|
52
53
|
`);
|
|
53
54
|
// Preload all rows into the cache so subsequent reads are zero-cost.
|
|
54
55
|
const rs = await client.execute("SELECT key, value FROM system_settings");
|
|
55
|
-
settingsCache =
|
|
56
|
+
settingsCache = Object.create(null);
|
|
57
|
+
const cache = settingsCache;
|
|
56
58
|
for (const row of rs.rows) {
|
|
57
|
-
|
|
59
|
+
const k = row.key;
|
|
60
|
+
if (!PROTO_KEYS.has(k))
|
|
61
|
+
cache[k] = row.value;
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
64
|
catch (err) {
|
|
@@ -63,7 +67,7 @@ export async function initConfigStorage() {
|
|
|
63
67
|
// getSettingSync() will return defaults; getSetting()/setSetting()
|
|
64
68
|
// will attempt to re-open the DB on first call.
|
|
65
69
|
console.error(`[configStorage] Failed to initialize (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
66
|
-
settingsCache =
|
|
70
|
+
settingsCache = Object.create(null);
|
|
67
71
|
}
|
|
68
72
|
initialized = true;
|
|
69
73
|
}
|
|
@@ -93,7 +97,7 @@ export async function getSetting(key, defaultValue = "") {
|
|
|
93
97
|
if (rs.rows.length > 0) {
|
|
94
98
|
const value = rs.rows[0].value;
|
|
95
99
|
// Populate cache entry for future reads.
|
|
96
|
-
if (settingsCache)
|
|
100
|
+
if (settingsCache && !PROTO_KEYS.has(key))
|
|
97
101
|
settingsCache[key] = value;
|
|
98
102
|
return value;
|
|
99
103
|
}
|
|
@@ -117,7 +121,7 @@ export async function setSetting(key, value) {
|
|
|
117
121
|
args: [key, value],
|
|
118
122
|
});
|
|
119
123
|
// Keep the cache in sync so getSettingSync() reflects the new value immediately.
|
|
120
|
-
if (settingsCache && typeof key === "string" && !
|
|
124
|
+
if (settingsCache && typeof key === "string" && !PROTO_KEYS.has(key)) {
|
|
121
125
|
settingsCache[key] = value;
|
|
122
126
|
}
|
|
123
127
|
return; // Success — exit
|
|
@@ -142,9 +146,11 @@ export async function getAllSettings() {
|
|
|
142
146
|
}
|
|
143
147
|
const client = getClient();
|
|
144
148
|
const rs = await client.execute("SELECT key, value FROM system_settings");
|
|
145
|
-
const settings =
|
|
149
|
+
const settings = Object.create(null);
|
|
146
150
|
for (const row of rs.rows) {
|
|
147
|
-
|
|
151
|
+
const k = row.key;
|
|
152
|
+
if (!PROTO_KEYS.has(k))
|
|
153
|
+
settings[k] = row.value;
|
|
148
154
|
}
|
|
149
155
|
return settings;
|
|
150
156
|
}
|
|
@@ -46,7 +46,7 @@ export function applySentinelBlock(existingContent, rulesBlock) {
|
|
|
46
46
|
return `${existingContent}${separator}${rulesBlock}\n`;
|
|
47
47
|
}
|
|
48
48
|
export function redactSettings(settings) {
|
|
49
|
-
const redacted =
|
|
49
|
+
const redacted = Object.create(null);
|
|
50
50
|
for (const [k, v] of Object.entries(settings || {})) {
|
|
51
51
|
if (typeof k !== "string" || k === "__proto__" || k === "constructor" || k === "prototype")
|
|
52
52
|
continue;
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* The handler is storage-agnostic — works with SQLite (local) or Supabase (remote).
|
|
13
13
|
*/
|
|
14
14
|
import { readFileSync, existsSync } from "fs";
|
|
15
|
+
import { resolve as resolvePath } from "path";
|
|
15
16
|
import { basename } from "path";
|
|
16
17
|
import { PRISM_USER_ID } from "../config.js";
|
|
17
18
|
import { getStorage } from "../storage/index.js";
|
|
@@ -103,7 +104,7 @@ export async function ingestKnowledge(args) {
|
|
|
103
104
|
const { project, source_label, chunk_size = 4000, } = args;
|
|
104
105
|
let content = args.content || "";
|
|
105
106
|
if (args.file_path) {
|
|
106
|
-
const resolved =
|
|
107
|
+
const resolved = resolvePath(args.file_path);
|
|
107
108
|
const blocked = ["/etc", "/var", "/usr", "/sys", "/proc", "/dev", "/root",
|
|
108
109
|
"/.ssh", "/.env", "/.git/config", "/private/etc"].some(p => resolved.startsWith(p) || resolved.includes("/."));
|
|
109
110
|
if (blocked) {
|
|
@@ -1581,7 +1581,8 @@ export async function sessionExportMemoryHandler(args) {
|
|
|
1581
1581
|
// Serialize
|
|
1582
1582
|
const ext = format === "markdown" ? "md" : format === "vault" ? "zip" : "json";
|
|
1583
1583
|
const safeProject = project.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1584
|
-
const
|
|
1584
|
+
const token = randomUUID().slice(0, 8);
|
|
1585
|
+
const filename = `prism-export-${safeProject}-${dateSuffix}-${token}.${ext}`;
|
|
1585
1586
|
const outputPath = join(output_dir, filename);
|
|
1586
1587
|
let content;
|
|
1587
1588
|
if (format === "vault") {
|
|
@@ -1611,7 +1612,7 @@ export async function sessionExportMemoryHandler(args) {
|
|
|
1611
1612
|
const dot = filename.lastIndexOf(".");
|
|
1612
1613
|
const stem = dot > 0 ? filename.slice(0, dot) : filename;
|
|
1613
1614
|
const extPart = dot > 0 ? filename.slice(dot) : "";
|
|
1614
|
-
finalPath = join(output_dir, `${stem}-${
|
|
1615
|
+
finalPath = join(output_dir, `${stem}-${randomUUID()}${extPart}`);
|
|
1615
1616
|
await writeFile(finalPath, content, { flag: "wx" });
|
|
1616
1617
|
}
|
|
1617
1618
|
else {
|
|
@@ -30,7 +30,7 @@ import * as fs from "fs";
|
|
|
30
30
|
import * as nodePath from "path";
|
|
31
31
|
import { getLLMProvider } from "./llm/factory.js";
|
|
32
32
|
import { getStorage } from "../storage/index.js";
|
|
33
|
-
import { debugLog } from "./logger.js";
|
|
33
|
+
import { debugLog, sanitizeForLog } from "./logger.js";
|
|
34
34
|
import { PRISM_USER_ID } from "../config.js";
|
|
35
35
|
import { getTracer } from "./telemetry.js";
|
|
36
36
|
import { SpanStatusCode, context as otelContext, trace } from "@opentelemetry/api";
|
|
@@ -93,7 +93,7 @@ export function fireCaptionAsync(project, imageId, vaultPath, userContext) {
|
|
|
93
93
|
code: SpanStatusCode.ERROR,
|
|
94
94
|
message: err instanceof Error ? err.message : String(err),
|
|
95
95
|
});
|
|
96
|
-
console.error(`[ImageCaptioner] Failed for [${imageId}]: ${err instanceof Error ? err.message : String(err)}`);
|
|
96
|
+
console.error(`[ImageCaptioner] Failed for [${sanitizeForLog(imageId)}]: ${sanitizeForLog(err instanceof Error ? err.message : String(err))}`);
|
|
97
97
|
})
|
|
98
98
|
.finally(() => {
|
|
99
99
|
// Always end the span — even on VLM failure — to flush the BatchSpanProcessor.
|
|
@@ -127,8 +127,8 @@ async function captionImageAsync(project, imageId, vaultPath, userContext) {
|
|
|
127
127
|
if (fileSizeBytes > maxBytes) {
|
|
128
128
|
const limitMB = (maxBytes / 1024 / 1024).toFixed(0);
|
|
129
129
|
const actualMB = (fileSizeBytes / 1024 / 1024).toFixed(1);
|
|
130
|
-
console.warn(`[ImageCaptioner] Image [${imageId}] is ${actualMB}MB, exceeding ` +
|
|
131
|
-
`the ${textProvider} VLM limit (${limitMB}MB). Captioning skipped. ` +
|
|
130
|
+
console.warn(`[ImageCaptioner] Image [${sanitizeForLog(imageId)}] is ${sanitizeForLog(actualMB)}MB, exceeding ` +
|
|
131
|
+
`the ${sanitizeForLog(textProvider)} VLM limit (${limitMB}MB). Captioning skipped. ` +
|
|
132
132
|
(textProvider === "anthropic"
|
|
133
133
|
? "Switch Embedding Provider to Gemini/OpenAI to caption larger images."
|
|
134
134
|
: "Consider resizing the image."));
|
|
@@ -165,8 +165,8 @@ async function captionImageAsync(project, imageId, vaultPath, userContext) {
|
|
|
165
165
|
// Don't poison the caption pipeline. The caption already succeeded;
|
|
166
166
|
// OCR is additive value. Surface as warn (not error) so it's grep-able
|
|
167
167
|
// but doesn't trip alerting on transient VLM blips.
|
|
168
|
-
console.warn(`[ImageCaptioner] OCR failed for [${imageId}] (non-fatal): ` +
|
|
169
|
-
`${ocrErr instanceof Error ? ocrErr.message : String(ocrErr)}`);
|
|
168
|
+
console.warn(`[ImageCaptioner] OCR failed for [${sanitizeForLog(imageId)}] (non-fatal): ` +
|
|
169
|
+
`${sanitizeForLog(ocrErr instanceof Error ? ocrErr.message : String(ocrErr))}`);
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
// ── Step 4: Patch handoff visual_memory entry ─────────────────────────
|
|
@@ -282,6 +282,6 @@ async function updateHandoffCaption(project, imageId, caption, vaultPath, ocrTex
|
|
|
282
282
|
// OCC conflict — retry once with fresh version
|
|
283
283
|
debugLog(`[ImageCaptioner] OCC conflict patching [${imageId}], attempt ${attempt}. Retrying…`);
|
|
284
284
|
}
|
|
285
|
-
console.warn(`[ImageCaptioner] Could not patch handoff for [${imageId}] after 2 attempts. ` +
|
|
285
|
+
console.warn(`[ImageCaptioner] Could not patch handoff for [${sanitizeForLog(imageId)}] after 2 attempts. ` +
|
|
286
286
|
`Caption is still saved in the ledger and will surface via semantic search.`);
|
|
287
287
|
}
|
|
@@ -47,6 +47,7 @@ import { VoyageAdapter } from "./adapters/voyage.js";
|
|
|
47
47
|
import { LocalEmbeddingAdapter } from "./adapters/local.js";
|
|
48
48
|
import { DisabledTextAdapter } from "./adapters/disabledText.js";
|
|
49
49
|
import { TracingLLMProvider } from "./adapters/traced.js";
|
|
50
|
+
import { sanitizeForLog } from "../logger.js";
|
|
50
51
|
// Module-level singleton — one composed provider per MCP server process.
|
|
51
52
|
let providerInstance = null;
|
|
52
53
|
// ─── Adapter Builders ─────────────────────────────────────────────────────────
|
|
@@ -149,13 +150,13 @@ export function getLLMProvider() {
|
|
|
149
150
|
// The text provider name is used as the primary span attribute label.
|
|
150
151
|
providerInstance = new TracingLLMProvider(composed, textType);
|
|
151
152
|
if (textType !== embedType) {
|
|
152
|
-
console.info(`[LLMFactory] Split provider: text=${textType}, embedding=${embedType}`);
|
|
153
|
+
console.info(`[LLMFactory] Split provider: text=${sanitizeForLog(textType)}, embedding=${sanitizeForLog(embedType)}`);
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
catch (err) {
|
|
156
157
|
// Init failure (e.g. missing API key) → fall back to full Gemini provider.
|
|
157
158
|
// A crash here would silently kill the MCP server.
|
|
158
|
-
console.error(`[LLMFactory] Failed to initialise providers (text=${textType}, embed=${embedType}): ${err instanceof Error ? err.message : String(err)}. ` +
|
|
159
|
+
console.error(`[LLMFactory] Failed to initialise providers (text=${sanitizeForLog(textType)}, embed=${sanitizeForLog(embedType)}): ${sanitizeForLog(err instanceof Error ? err.message : String(err))}. ` +
|
|
159
160
|
`Falling back to GeminiAdapter for both.`);
|
|
160
161
|
const fallback = new GeminiAdapter();
|
|
161
162
|
const fallbackComposed = {
|
package/dist/utils/logger.js
CHANGED
|
@@ -4,11 +4,11 @@ import { PRISM_DEBUG_LOGGING } from "../config.js";
|
|
|
4
4
|
* newlines, and ANSI escape sequences that could be used for log
|
|
5
5
|
* injection or terminal escape attacks.
|
|
6
6
|
*/
|
|
7
|
-
function sanitizeForLog(msg) {
|
|
7
|
+
export function sanitizeForLog(msg) {
|
|
8
8
|
return msg
|
|
9
|
-
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "") // control chars (keep \n \r \t)
|
|
9
|
+
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/g, "") // C0 + C1 control chars (keep \n \r \t)
|
|
10
10
|
.replace(/\r?\n/g, " ⏎ ") // newlines → visible marker
|
|
11
|
-
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, ""); //
|
|
11
|
+
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, ""); // CSI residue (defense-in-depth if first regex relaxed)
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* Logs a message to stderr only if PRISM_DEBUG_LOGGING is true.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { openSync, readSync, closeSync } from "node:fs";
|
|
1
2
|
/**
|
|
2
3
|
* ═══════════════════════════════════════════════════════════════════
|
|
3
4
|
* Migration Utilities — Shared Normalization Helpers
|
|
@@ -43,11 +44,10 @@
|
|
|
43
44
|
* OpenAI → `"tool_calls":` or `"created_at":` (Unix epoch) or `"role":"system"`
|
|
44
45
|
*/
|
|
45
46
|
export function sniffFormat(filePath) {
|
|
46
|
-
const
|
|
47
|
-
const fd = fs.openSync(filePath, 'r');
|
|
47
|
+
const fd = openSync(filePath, 'r');
|
|
48
48
|
const buf = Buffer.alloc(4096);
|
|
49
|
-
const bytesRead =
|
|
50
|
-
|
|
49
|
+
const bytesRead = readSync(fd, buf, 0, 4096, 0);
|
|
50
|
+
closeSync(fd);
|
|
51
51
|
if (bytesRead === 0)
|
|
52
52
|
return null;
|
|
53
53
|
const head = buf.toString('utf8', 0, bytesRead);
|
package/dist/utils/telemetry.js
CHANGED
|
@@ -61,6 +61,7 @@ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
|
61
61
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
62
62
|
import { SEMRESATTRS_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
63
63
|
import { getSettingSync } from "../storage/configStorage.js";
|
|
64
|
+
import { sanitizeForLog } from "./logger.js";
|
|
64
65
|
// ─── Module-level singleton ───────────────────────────────────────────────────
|
|
65
66
|
// Null when OTel is disabled or before initTelemetry() is called.
|
|
66
67
|
let _provider = null;
|
|
@@ -175,11 +176,11 @@ export function initTelemetry() {
|
|
|
175
176
|
});
|
|
176
177
|
_provider.register();
|
|
177
178
|
console.error(`[Telemetry] OpenTelemetry initialized. ` +
|
|
178
|
-
`Service: "${serviceName}", Endpoint: "${endpoint}"`);
|
|
179
|
+
`Service: "${sanitizeForLog(serviceName)}", Endpoint: "${sanitizeForLog(endpoint)}"`);
|
|
179
180
|
}
|
|
180
181
|
catch (err) {
|
|
181
182
|
// OTel init errors must NEVER crash the server. Log and continue.
|
|
182
|
-
console.error(`[Telemetry] Failed to initialize OTel (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
183
|
+
console.error(`[Telemetry] Failed to initialize OTel (non-fatal): ${sanitizeForLog(err instanceof Error ? err.message : String(err))}`);
|
|
183
184
|
_provider = null; // Clean state so getTracer() returns no-op
|
|
184
185
|
}
|
|
185
186
|
}
|
|
@@ -226,6 +227,6 @@ export async function shutdownTelemetry() {
|
|
|
226
227
|
}
|
|
227
228
|
catch (err) {
|
|
228
229
|
// Log but don't rethrow — shutdown errors must not prevent DB cleanup.
|
|
229
|
-
console.error(`[Telemetry] Error during shutdown (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
230
|
+
console.error(`[Telemetry] Error during shutdown (non-fatal): ${sanitizeForLog(err instanceof Error ? err.message : String(err))}`);
|
|
230
231
|
}
|
|
231
232
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prism-mcp-server",
|
|
3
|
-
"version": "19.2.
|
|
3
|
+
"version": "19.2.8",
|
|
4
4
|
"mcpName": "io.github.dcostenco/prism-coder",
|
|
5
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.",
|
|
6
6
|
"module": "index.ts",
|