prism-mcp-server 20.2.4 → 20.2.6
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 +124 -111
- package/package.json +9 -4
package/dist/connect.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { accessSync, constants, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
1
|
+
import { accessSync, closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep, win32 as win32Path } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -69,6 +69,72 @@ const CODEX_LOCAL_FIRST_POLICY = {
|
|
|
69
69
|
};
|
|
70
70
|
const CODEX_POLICY_MARKER_PREFIX = "# >>> prism connect managed: local-first";
|
|
71
71
|
const CODEX_POLICY_MARKER_SUFFIX = "# <<< prism connect managed: local-first";
|
|
72
|
+
/**
|
|
73
|
+
* Read a host-owned text file through one descriptor, then verify that the
|
|
74
|
+
* pathname still identifies that same regular file. This prevents a path from
|
|
75
|
+
* being swapped between a metadata check and the read while retaining Prism's
|
|
76
|
+
* documented support for symlinked dotfiles.
|
|
77
|
+
*/
|
|
78
|
+
function readRegularTextSnapshot(filePath, allowSymlink = true) {
|
|
79
|
+
const nonBlocking = typeof constants.O_NONBLOCK === "number" ? constants.O_NONBLOCK : 0;
|
|
80
|
+
const noFollow = !allowSymlink && typeof constants.O_NOFOLLOW === "number"
|
|
81
|
+
? constants.O_NOFOLLOW
|
|
82
|
+
: 0;
|
|
83
|
+
let descriptor;
|
|
84
|
+
try {
|
|
85
|
+
descriptor = openSync(filePath, constants.O_RDONLY | nonBlocking | noFollow);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
// A dangling symlink is an existing, unsafe entry rather than an absent
|
|
89
|
+
// config. Detect it only after the descriptor open failed, so this probe
|
|
90
|
+
// cannot become a check-then-read race.
|
|
91
|
+
if (isErrno(error, "ENOENT")) {
|
|
92
|
+
let pathInfo;
|
|
93
|
+
try {
|
|
94
|
+
pathInfo = lstatSync(filePath);
|
|
95
|
+
}
|
|
96
|
+
catch (probeError) {
|
|
97
|
+
if (!isErrno(probeError, "ENOENT"))
|
|
98
|
+
throw probeError;
|
|
99
|
+
}
|
|
100
|
+
if (pathInfo?.isSymbolicLink()) {
|
|
101
|
+
throw new Error(`symlink target is unavailable: ${filePath}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const openedInfo = fstatSync(descriptor);
|
|
108
|
+
if (!openedInfo.isFile())
|
|
109
|
+
throw new Error("target is not a regular file");
|
|
110
|
+
const pathInfo = lstatSync(filePath);
|
|
111
|
+
let writePath = filePath;
|
|
112
|
+
let symlinkPath;
|
|
113
|
+
if (pathInfo.isSymbolicLink()) {
|
|
114
|
+
if (!allowSymlink)
|
|
115
|
+
throw new Error("symlink is not allowed");
|
|
116
|
+
writePath = realpathSync(filePath);
|
|
117
|
+
symlinkPath = filePath;
|
|
118
|
+
}
|
|
119
|
+
else if (!pathInfo.isFile()) {
|
|
120
|
+
throw new Error("target is not a regular file");
|
|
121
|
+
}
|
|
122
|
+
const currentInfo = statSync(writePath);
|
|
123
|
+
if (!currentInfo.isFile()
|
|
124
|
+
|| currentInfo.dev !== openedInfo.dev
|
|
125
|
+
|| currentInfo.ino !== openedInfo.ino) {
|
|
126
|
+
throw new Error("file changed while Prism was opening it; retry");
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
text: readFileSync(descriptor, "utf8"),
|
|
130
|
+
writePath,
|
|
131
|
+
symlinkPath,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
closeSync(descriptor);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
72
138
|
export function normalizeHostName(value) {
|
|
73
139
|
const normalized = value.trim().toLowerCase();
|
|
74
140
|
const host = HOST_ALIASES[normalized];
|
|
@@ -188,11 +254,7 @@ export function writeInstallationReceipt(homeDir, receipt, beforeCommit) {
|
|
|
188
254
|
}
|
|
189
255
|
let originalText;
|
|
190
256
|
try {
|
|
191
|
-
|
|
192
|
-
if (receiptInfo.isSymbolicLink() || !receiptInfo.isFile()) {
|
|
193
|
-
throw new Error("receipt must be a regular Prism-owned file");
|
|
194
|
-
}
|
|
195
|
-
originalText = readFileSync(receiptPath, "utf8");
|
|
257
|
+
originalText = readRegularTextSnapshot(receiptPath, false).text;
|
|
196
258
|
const current = JSON.parse(originalText);
|
|
197
259
|
if (!isJsonObject(current)
|
|
198
260
|
|| current.schema_version !== INSTALLATION_RECEIPT_SCHEMA_VERSION
|
|
@@ -251,14 +313,10 @@ export function migrateLegacyClaudeProjectMcp(homeDir = homedir(), cwd = process
|
|
|
251
313
|
let symlinkPath;
|
|
252
314
|
let originalText;
|
|
253
315
|
try {
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
if (!statSync(writePath).isFile())
|
|
260
|
-
throw new Error("config is not a regular file");
|
|
261
|
-
originalText = readFileSync(writePath, "utf8");
|
|
316
|
+
const snapshot = readRegularTextSnapshot(configPath);
|
|
317
|
+
writePath = snapshot.writePath;
|
|
318
|
+
symlinkPath = snapshot.symlinkPath;
|
|
319
|
+
originalText = snapshot.text;
|
|
262
320
|
}
|
|
263
321
|
catch (error) {
|
|
264
322
|
throw new Error(`Could not inspect Claude project MCP config: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -321,14 +379,10 @@ export function migrateLegacyClaudeHooks(homeDir = homedir(), dryRun = false, be
|
|
|
321
379
|
let symlinkPath;
|
|
322
380
|
let originalText;
|
|
323
381
|
try {
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (!statSync(writePath).isFile())
|
|
329
|
-
throw new Error("target is not a regular file");
|
|
330
|
-
}
|
|
331
|
-
originalText = readFileSync(writePath, "utf8");
|
|
382
|
+
const snapshot = readRegularTextSnapshot(configPath);
|
|
383
|
+
writePath = snapshot.writePath;
|
|
384
|
+
symlinkPath = snapshot.symlinkPath;
|
|
385
|
+
originalText = snapshot.text;
|
|
332
386
|
}
|
|
333
387
|
catch (error) {
|
|
334
388
|
if (isErrno(error, "ENOENT"))
|
|
@@ -403,15 +457,11 @@ export function migrateLegacyClaudeInstructions(homeDir = homedir(), dryRun = fa
|
|
|
403
457
|
let originalText;
|
|
404
458
|
let instructionEntryExists = false;
|
|
405
459
|
try {
|
|
406
|
-
const
|
|
460
|
+
const snapshot = readRegularTextSnapshot(instructionPath);
|
|
407
461
|
instructionEntryExists = true;
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
if (!statSync(writePath).isFile())
|
|
412
|
-
throw new Error("target is not a regular file");
|
|
413
|
-
}
|
|
414
|
-
originalText = readFileSync(writePath, "utf8");
|
|
462
|
+
writePath = snapshot.writePath;
|
|
463
|
+
symlinkPath = snapshot.symlinkPath;
|
|
464
|
+
originalText = snapshot.text;
|
|
415
465
|
}
|
|
416
466
|
catch (error) {
|
|
417
467
|
if (!instructionEntryExists && isErrno(error, "ENOENT")) {
|
|
@@ -447,15 +497,11 @@ export function migrateLegacyClaudeManagedStartup(homeDir = homedir(), dryRun =
|
|
|
447
497
|
let originalText;
|
|
448
498
|
let instructionEntryExists = false;
|
|
449
499
|
try {
|
|
450
|
-
const
|
|
500
|
+
const snapshot = readRegularTextSnapshot(instructionPath);
|
|
451
501
|
instructionEntryExists = true;
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (!statSync(writePath).isFile())
|
|
456
|
-
throw new Error("target is not a regular file");
|
|
457
|
-
}
|
|
458
|
-
originalText = readFileSync(writePath, "utf8");
|
|
502
|
+
writePath = snapshot.writePath;
|
|
503
|
+
symlinkPath = snapshot.symlinkPath;
|
|
504
|
+
originalText = snapshot.text;
|
|
459
505
|
}
|
|
460
506
|
catch (error) {
|
|
461
507
|
if (!instructionEntryExists && isErrno(error, "ENOENT")) {
|
|
@@ -508,15 +554,11 @@ export function configureClaudeNativeStartup(homeDir = homedir(), dryRun = false
|
|
|
508
554
|
let originalText;
|
|
509
555
|
let instructionEntryExists = false;
|
|
510
556
|
try {
|
|
511
|
-
const
|
|
557
|
+
const snapshot = readRegularTextSnapshot(instructionPath);
|
|
512
558
|
instructionEntryExists = true;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
if (!statSync(writePath).isFile())
|
|
517
|
-
throw new Error("target is not a regular file");
|
|
518
|
-
}
|
|
519
|
-
originalText = readFileSync(writePath, "utf8");
|
|
559
|
+
writePath = snapshot.writePath;
|
|
560
|
+
symlinkPath = snapshot.symlinkPath;
|
|
561
|
+
originalText = snapshot.text;
|
|
520
562
|
}
|
|
521
563
|
catch (error) {
|
|
522
564
|
if (instructionEntryExists || !isErrno(error, "ENOENT")) {
|
|
@@ -604,15 +646,11 @@ export function configureGeminiNativeStartup(homeDir = homedir(), dryRun = false
|
|
|
604
646
|
let originalText;
|
|
605
647
|
let instructionEntryExists = false;
|
|
606
648
|
try {
|
|
607
|
-
const
|
|
649
|
+
const snapshot = readRegularTextSnapshot(instructionPath);
|
|
608
650
|
instructionEntryExists = true;
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
if (!statSync(writePath).isFile())
|
|
613
|
-
throw new Error("target is not a regular file");
|
|
614
|
-
}
|
|
615
|
-
originalText = readFileSync(writePath, "utf8");
|
|
651
|
+
writePath = snapshot.writePath;
|
|
652
|
+
symlinkPath = snapshot.symlinkPath;
|
|
653
|
+
originalText = snapshot.text;
|
|
616
654
|
}
|
|
617
655
|
catch (error) {
|
|
618
656
|
if (instructionEntryExists || !isErrno(error, "ENOENT")) {
|
|
@@ -680,15 +718,11 @@ export function configureCodexNativeStartup(homeDir, dryRun = false, beforeCommi
|
|
|
680
718
|
let originalText;
|
|
681
719
|
let instructionEntryExists = false;
|
|
682
720
|
try {
|
|
683
|
-
const
|
|
721
|
+
const snapshot = readRegularTextSnapshot(instructionPath);
|
|
684
722
|
instructionEntryExists = true;
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
if (!statSync(writePath).isFile())
|
|
689
|
-
throw new Error("target is not a regular file");
|
|
690
|
-
}
|
|
691
|
-
originalText = readFileSync(writePath, "utf8");
|
|
723
|
+
writePath = snapshot.writePath;
|
|
724
|
+
symlinkPath = snapshot.symlinkPath;
|
|
725
|
+
originalText = snapshot.text;
|
|
692
726
|
}
|
|
693
727
|
catch (error) {
|
|
694
728
|
if (instructionEntryExists || !isErrno(error, "ENOENT")) {
|
|
@@ -737,14 +771,10 @@ function configureJsonAgentPolicy(configPath, label, mutate, dryRun, beforeCommi
|
|
|
737
771
|
let originalText;
|
|
738
772
|
let config = {};
|
|
739
773
|
try {
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
if (!statSync(writePath).isFile())
|
|
745
|
-
throw new Error("target is not a regular file");
|
|
746
|
-
}
|
|
747
|
-
originalText = readFileSync(writePath, "utf8");
|
|
774
|
+
const snapshot = readRegularTextSnapshot(configPath);
|
|
775
|
+
writePath = snapshot.writePath;
|
|
776
|
+
symlinkPath = snapshot.symlinkPath;
|
|
777
|
+
originalText = snapshot.text;
|
|
748
778
|
const parsed = JSON.parse(originalText);
|
|
749
779
|
if (!isJsonObject(parsed))
|
|
750
780
|
throw new Error("top-level JSON value must be an object");
|
|
@@ -858,14 +888,10 @@ export function configureCodexAgentPolicy(homeDir, dryRun = false, beforeCommit,
|
|
|
858
888
|
let symlinkPath;
|
|
859
889
|
let originalText;
|
|
860
890
|
try {
|
|
861
|
-
const
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if (!statSync(writePath).isFile())
|
|
866
|
-
throw new Error("target is not a regular file");
|
|
867
|
-
}
|
|
868
|
-
originalText = readFileSync(writePath, "utf8");
|
|
891
|
+
const snapshot = readRegularTextSnapshot(configPath);
|
|
892
|
+
writePath = snapshot.writePath;
|
|
893
|
+
symlinkPath = snapshot.symlinkPath;
|
|
894
|
+
originalText = snapshot.text;
|
|
869
895
|
parseToml(originalText);
|
|
870
896
|
}
|
|
871
897
|
catch (error) {
|
|
@@ -1063,18 +1089,18 @@ function registerJsonHost(definition, entry, dryRun, refresh, beforeCommit) {
|
|
|
1063
1089
|
let writePath = configPath;
|
|
1064
1090
|
let symlinkPath;
|
|
1065
1091
|
try {
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
}
|
|
1092
|
+
const snapshot = readRegularTextSnapshot(configPath);
|
|
1093
|
+
writePath = snapshot.writePath;
|
|
1094
|
+
symlinkPath = snapshot.symlinkPath;
|
|
1095
|
+
originalText = snapshot.text;
|
|
1096
|
+
}
|
|
1097
|
+
catch (error) {
|
|
1098
|
+
if (!isErrno(error, "ENOENT")) {
|
|
1099
|
+
return result(definition, "error", `could not inspect config: ${error instanceof Error ? error.message : String(error)}`);
|
|
1075
1100
|
}
|
|
1101
|
+
}
|
|
1102
|
+
if (originalText !== undefined) {
|
|
1076
1103
|
try {
|
|
1077
|
-
originalText = readFileSync(writePath, "utf8");
|
|
1078
1104
|
const parsed = JSON.parse(originalText);
|
|
1079
1105
|
if (!isJsonObject(parsed)) {
|
|
1080
1106
|
throw new Error("top-level JSON value must be an object");
|
|
@@ -1085,11 +1111,6 @@ function registerJsonHost(definition, entry, dryRun, refresh, beforeCommit) {
|
|
|
1085
1111
|
return result(definition, "error", `could not parse config: ${error instanceof Error ? error.message : String(error)}`);
|
|
1086
1112
|
}
|
|
1087
1113
|
}
|
|
1088
|
-
catch (error) {
|
|
1089
|
-
if (!isErrno(error, "ENOENT")) {
|
|
1090
|
-
return result(definition, "error", `could not inspect config: ${error instanceof Error ? error.message : String(error)}`);
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
1114
|
const currentServers = config.mcpServers;
|
|
1094
1115
|
if (currentServers !== undefined && !isJsonObject(currentServers)) {
|
|
1095
1116
|
return result(definition, "error", '"mcpServers" must be a JSON object');
|
|
@@ -1145,21 +1166,18 @@ function registerCodexTomlHost(definition, entry, dryRun, refresh, beforeCommit)
|
|
|
1145
1166
|
let writePath = configPath;
|
|
1146
1167
|
let symlinkPath;
|
|
1147
1168
|
try {
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
}
|
|
1157
|
-
catch (error) {
|
|
1158
|
-
return result(definition, "error", `config symlink target is unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
1159
|
-
}
|
|
1169
|
+
const snapshot = readRegularTextSnapshot(configPath);
|
|
1170
|
+
writePath = snapshot.writePath;
|
|
1171
|
+
symlinkPath = snapshot.symlinkPath;
|
|
1172
|
+
originalText = snapshot.text;
|
|
1173
|
+
}
|
|
1174
|
+
catch (error) {
|
|
1175
|
+
if (!isErrno(error, "ENOENT")) {
|
|
1176
|
+
return result(definition, "error", `could not inspect config: ${error instanceof Error ? error.message : String(error)}`);
|
|
1160
1177
|
}
|
|
1178
|
+
}
|
|
1179
|
+
if (originalText !== undefined) {
|
|
1161
1180
|
try {
|
|
1162
|
-
originalText = readFileSync(writePath, "utf8");
|
|
1163
1181
|
const parsed = parseToml(originalText);
|
|
1164
1182
|
if (!isJsonObject(parsed)) {
|
|
1165
1183
|
throw new Error("top-level TOML value must be a table");
|
|
@@ -1170,11 +1188,6 @@ function registerCodexTomlHost(definition, entry, dryRun, refresh, beforeCommit)
|
|
|
1170
1188
|
return result(definition, "error", `could not parse config: ${error instanceof Error ? error.message : String(error)}`);
|
|
1171
1189
|
}
|
|
1172
1190
|
}
|
|
1173
|
-
catch (error) {
|
|
1174
|
-
if (!isErrno(error, "ENOENT")) {
|
|
1175
|
-
return result(definition, "error", `could not inspect config: ${error instanceof Error ? error.message : String(error)}`);
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
1191
|
let managedBlock;
|
|
1179
1192
|
try {
|
|
1180
1193
|
managedBlock = locateCodexManagedBlock(originalText ?? "");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prism-mcp-server",
|
|
3
|
-
"version": "20.2.
|
|
3
|
+
"version": "20.2.6",
|
|
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 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",
|
|
@@ -125,9 +125,14 @@
|
|
|
125
125
|
}
|
|
126
126
|
},
|
|
127
127
|
"overrides": {
|
|
128
|
-
"hono": "^
|
|
129
|
-
"
|
|
130
|
-
"
|
|
128
|
+
"@hono/node-server": "^2.0.5",
|
|
129
|
+
"body-parser": "^2.3.0",
|
|
130
|
+
"fast-uri": "^3.1.4",
|
|
131
|
+
"hono": "^4.12.27",
|
|
132
|
+
"ip-address": "^10.2.0",
|
|
133
|
+
"protobufjs": "^7.6.5",
|
|
134
|
+
"sharp": "^0.35.0",
|
|
135
|
+
"tar": "^7.5.19"
|
|
131
136
|
},
|
|
132
137
|
"dependencies": {
|
|
133
138
|
"@anthropic-ai/sdk": "^0.92.0",
|