@elisym/mcp 0.1.6 → 0.1.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/README.md +3 -0
- package/dist/index.js +212 -44
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,6 +20,9 @@ npx @elisym/mcp install --agent <agent-name>
|
|
|
20
20
|
# List detected MCP clients
|
|
21
21
|
npx @elisym/mcp install --list
|
|
22
22
|
|
|
23
|
+
# Refresh the version pin in installed clients (preserves agent + env)
|
|
24
|
+
npx @elisym/mcp update
|
|
25
|
+
|
|
23
26
|
# Remove from MCP clients
|
|
24
27
|
npx @elisym/mcp uninstall
|
|
25
28
|
|
package/dist/index.js
CHANGED
|
@@ -319,6 +319,35 @@ function payment() {
|
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
// src/install.ts
|
|
322
|
+
function elisymPackageArgs() {
|
|
323
|
+
return ["-y", `@elisym/mcp@~${PACKAGE_VERSION}`];
|
|
324
|
+
}
|
|
325
|
+
function validateClientName(name) {
|
|
326
|
+
if (name === void 0) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (!CLIENTS.some((c) => c.name === name)) {
|
|
330
|
+
throw new Error(
|
|
331
|
+
`Unknown client "${name}". Known clients: ${CLIENTS.map((c) => c.name).join(", ")}`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
async function safeRewriteJson(path, expectedRaw, newConfig) {
|
|
336
|
+
let recheck;
|
|
337
|
+
try {
|
|
338
|
+
recheck = await readFile(path, "utf-8");
|
|
339
|
+
} catch (err) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
`${path} disappeared between read and write: ${err.message}. Re-run after the file is restored.`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
if (recheck !== expectedRaw) {
|
|
345
|
+
throw new Error(
|
|
346
|
+
`${path} was modified by another process during update. Close the MCP client and re-run.`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
await writeJsonAtomic(path, newConfig);
|
|
350
|
+
}
|
|
322
351
|
var CLIENTS = [
|
|
323
352
|
{
|
|
324
353
|
name: "claude-desktop",
|
|
@@ -361,7 +390,7 @@ var CLIENTS = [
|
|
|
361
390
|
function buildServerEntry(agentName, env) {
|
|
362
391
|
const entry = {
|
|
363
392
|
command: "npx",
|
|
364
|
-
args:
|
|
393
|
+
args: elisymPackageArgs()
|
|
365
394
|
};
|
|
366
395
|
const mergedEnv = { ...env };
|
|
367
396
|
if (agentName) {
|
|
@@ -373,6 +402,7 @@ function buildServerEntry(agentName, env) {
|
|
|
373
402
|
return entry;
|
|
374
403
|
}
|
|
375
404
|
async function runInstall(options) {
|
|
405
|
+
validateClientName(options.client);
|
|
376
406
|
if (options.agent) {
|
|
377
407
|
validateAgentName(options.agent);
|
|
378
408
|
}
|
|
@@ -402,7 +432,12 @@ async function runInstall(options) {
|
|
|
402
432
|
console.log("No MCP clients found to install into.");
|
|
403
433
|
}
|
|
404
434
|
}
|
|
405
|
-
async function
|
|
435
|
+
async function runUpdate(options) {
|
|
436
|
+
validateClientName(options.client);
|
|
437
|
+
if (options.agent) {
|
|
438
|
+
validateAgentName(options.agent);
|
|
439
|
+
}
|
|
440
|
+
let updated = 0;
|
|
406
441
|
for (const client of CLIENTS) {
|
|
407
442
|
if (options.client && client.name !== options.client) {
|
|
408
443
|
continue;
|
|
@@ -411,15 +446,105 @@ async function runUninstall(options) {
|
|
|
411
446
|
if (!path) {
|
|
412
447
|
continue;
|
|
413
448
|
}
|
|
449
|
+
let raw;
|
|
414
450
|
try {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
451
|
+
raw = await readFile(path, "utf-8");
|
|
452
|
+
} catch (err) {
|
|
453
|
+
if (err.code !== "ENOENT") {
|
|
454
|
+
console.log(`Skipped ${client.name}: ${err.message}`);
|
|
455
|
+
}
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
let config;
|
|
459
|
+
try {
|
|
460
|
+
config = JSON.parse(raw);
|
|
461
|
+
} catch {
|
|
462
|
+
console.log(`Warning: ${path} is not valid JSON. Skipping update for ${client.name}.`);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const existing = config.mcpServers?.elisym;
|
|
466
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
const rawEnv = existing.env;
|
|
470
|
+
const newEnv = rawEnv && typeof rawEnv === "object" && !Array.isArray(rawEnv) ? { ...rawEnv } : {};
|
|
471
|
+
const existingAgentRaw = typeof newEnv.ELISYM_AGENT === "string" ? newEnv.ELISYM_AGENT : void 0;
|
|
472
|
+
if (existingAgentRaw !== void 0 && options.agent === void 0) {
|
|
473
|
+
try {
|
|
474
|
+
validateAgentName(existingAgentRaw);
|
|
475
|
+
} catch (err) {
|
|
476
|
+
console.log(
|
|
477
|
+
`Skipped ${client.name}: existing ELISYM_AGENT in ${path} is invalid (${err.message}). Re-run with --agent <name> to overwrite.`
|
|
478
|
+
);
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (options.agent !== void 0) {
|
|
483
|
+
newEnv.ELISYM_AGENT = options.agent;
|
|
484
|
+
}
|
|
485
|
+
const entry = existing;
|
|
486
|
+
const newArgs = elisymPackageArgs();
|
|
487
|
+
if (Array.isArray(entry.args)) {
|
|
488
|
+
const idx = entry.args.findIndex(
|
|
489
|
+
(a) => typeof a === "string" && a.startsWith("@elisym/mcp@")
|
|
490
|
+
);
|
|
491
|
+
if (idx >= 0) {
|
|
492
|
+
entry.args[idx] = newArgs[1];
|
|
493
|
+
} else {
|
|
494
|
+
entry.args = newArgs;
|
|
421
495
|
}
|
|
496
|
+
} else {
|
|
497
|
+
entry.args = newArgs;
|
|
498
|
+
}
|
|
499
|
+
if (Object.keys(newEnv).length > 0) {
|
|
500
|
+
entry.env = newEnv;
|
|
501
|
+
} else {
|
|
502
|
+
delete entry.env;
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
await safeRewriteJson(path, raw, config);
|
|
506
|
+
} catch (err) {
|
|
507
|
+
console.log(`Skipped ${client.name}: ${err.message}`);
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
console.log(`Updated ${client.name}: ${path} -> @elisym/mcp@~${PACKAGE_VERSION}`);
|
|
511
|
+
updated++;
|
|
512
|
+
}
|
|
513
|
+
if (updated === 0) {
|
|
514
|
+
console.log("No existing elisym MCP installs found to update.");
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
async function runUninstall(options) {
|
|
518
|
+
validateClientName(options.client);
|
|
519
|
+
for (const client of CLIENTS) {
|
|
520
|
+
if (options.client && client.name !== options.client) {
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
const path = client.configPath();
|
|
524
|
+
if (!path) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
let raw;
|
|
528
|
+
try {
|
|
529
|
+
raw = await readFile(path, "utf-8");
|
|
530
|
+
} catch {
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
let config;
|
|
534
|
+
try {
|
|
535
|
+
config = JSON.parse(raw);
|
|
422
536
|
} catch {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
if (!config.mcpServers?.elisym) {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
delete config.mcpServers.elisym;
|
|
543
|
+
try {
|
|
544
|
+
await safeRewriteJson(path, raw, config);
|
|
545
|
+
console.log(`Removed from ${client.name}: ${path}`);
|
|
546
|
+
} catch (err) {
|
|
547
|
+
console.log(`Skipped ${client.name}: ${err.message}`);
|
|
423
548
|
}
|
|
424
549
|
}
|
|
425
550
|
}
|
|
@@ -444,18 +569,21 @@ async function writeJsonAtomic(path, data) {
|
|
|
444
569
|
await writeFileAtomic(path, JSON.stringify(data, null, 2), 384);
|
|
445
570
|
}
|
|
446
571
|
async function installToConfig(path, entry) {
|
|
447
|
-
let config;
|
|
448
572
|
let raw;
|
|
449
573
|
try {
|
|
450
574
|
raw = await readFile(path, "utf-8");
|
|
575
|
+
} catch (err) {
|
|
576
|
+
if (err.code !== "ENOENT") {
|
|
577
|
+
throw err;
|
|
578
|
+
}
|
|
579
|
+
await writeJsonAtomic(path, { mcpServers: { elisym: entry } });
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
let config;
|
|
583
|
+
try {
|
|
451
584
|
config = JSON.parse(raw);
|
|
452
585
|
} catch {
|
|
453
|
-
|
|
454
|
-
console.error(
|
|
455
|
-
`Warning: ${path} is not valid JSON. A backup was saved to ${path}.elisym-backup. Other MCP server entries may have been lost - restore from backup if needed.`
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
config = {};
|
|
586
|
+
throw new Error(`${path} is not valid JSON. Fix the file manually and re-run install.`);
|
|
459
587
|
}
|
|
460
588
|
if (!config.mcpServers) {
|
|
461
589
|
config.mcpServers = {};
|
|
@@ -463,14 +591,8 @@ async function installToConfig(path, entry) {
|
|
|
463
591
|
if (config.mcpServers.elisym) {
|
|
464
592
|
return false;
|
|
465
593
|
}
|
|
466
|
-
if (raw) {
|
|
467
|
-
try {
|
|
468
|
-
await writeFile(`${path}.elisym-backup`, raw, { mode: 384 });
|
|
469
|
-
} catch {
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
594
|
config.mcpServers.elisym = entry;
|
|
473
|
-
await
|
|
595
|
+
await safeRewriteJson(path, raw, config);
|
|
474
596
|
return true;
|
|
475
597
|
}
|
|
476
598
|
|
|
@@ -810,8 +932,13 @@ function normalizeConfusables(text) {
|
|
|
810
932
|
return text.normalize("NFKC").replace(CONFUSABLE_RE, (ch) => CONFUSABLE_MAP[ch] ?? ch);
|
|
811
933
|
}
|
|
812
934
|
var INJECTION_PATTERNS = [
|
|
813
|
-
// Role hijacking
|
|
814
|
-
|
|
935
|
+
// Role hijacking — agents legitimately describe themselves with "act as" /
|
|
936
|
+
// "you are" in their public cards, so this is noisy in structured contexts.
|
|
937
|
+
{
|
|
938
|
+
category: "role_hijack",
|
|
939
|
+
pattern: /\b(?:you are|act as|pretend to be|roleplay as)\b/i,
|
|
940
|
+
noisy: true
|
|
941
|
+
},
|
|
815
942
|
// Instruction override
|
|
816
943
|
{
|
|
817
944
|
category: "instruction_override",
|
|
@@ -829,16 +956,35 @@ var INJECTION_PATTERNS = [
|
|
|
829
956
|
},
|
|
830
957
|
// Delimiter injection
|
|
831
958
|
{ category: "delimiter_injection", pattern: /<\/system>|\[\/INST\]|```system|<\|im_end\|>/i },
|
|
832
|
-
// Data exfiltration
|
|
833
|
-
|
|
959
|
+
// Data exfiltration. Require a composite term ("secret key", "api key") or a strong
|
|
960
|
+
// single noun ("password", "credential", "seed phrase"). The previous version matched
|
|
961
|
+
// the bare word "key", which produces false positives on benign phrases like
|
|
962
|
+
// "send a link, get the key points" — common in legitimate agent descriptions.
|
|
963
|
+
// Cap the gap at 40 chars within the same clause (no sentence terminators) so the
|
|
964
|
+
// verb and noun must actually relate to each other. Composite-term separators are
|
|
965
|
+
// [\s_-]? so variants like `private-key`, `secret_key`, `seed-phrase` are caught.
|
|
966
|
+
// Marked `noisy` because even after tightening, NL verb+noun phrases remain a
|
|
967
|
+
// common source of FP on free-text agent descriptions.
|
|
968
|
+
{
|
|
969
|
+
category: "data_exfil",
|
|
970
|
+
pattern: /\b(?:send|post|exfiltrate|leak)\b[^.!?\n]{0,40}\b(?:secret[\s_-]?key|api[\s_-]?key|private[\s_-]?key|password|credential|auth[\s_-]?token|seed[\s_-]?phrase|mnemonic)\b/i,
|
|
971
|
+
noisy: true
|
|
972
|
+
},
|
|
834
973
|
// Payment manipulation
|
|
835
974
|
{ category: "payment_manipulation", pattern: /\b(?:change|modify).*?\b(?:recipient|address)\b/i },
|
|
836
975
|
{ category: "payment_manipulation", pattern: /\bsend all funds\b/i },
|
|
837
|
-
// Jailbreak
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
976
|
+
// Jailbreak — "from now on" is a common phrase in changelogs/release notes that
|
|
977
|
+
// an agent could legitimately put in its description, hence noisy.
|
|
978
|
+
{
|
|
979
|
+
category: "jailbreak",
|
|
980
|
+
pattern: /\b(?:DAN mode|developer mode enabled|from now on)\b/i,
|
|
981
|
+
noisy: true
|
|
982
|
+
},
|
|
983
|
+
// Urgency — agents may put "IMPORTANT: rate limited to N req/s" in their card,
|
|
984
|
+
// so the line-anchored prefix is noisy in structured contexts.
|
|
985
|
+
{ category: "urgency", pattern: /^(?:IMPORTANT|CRITICAL|URGENT|SYSTEM):/m, noisy: true }
|
|
841
986
|
];
|
|
987
|
+
var STRICT_INJECTION_PATTERNS = INJECTION_PATTERNS.filter((p) => !p.noisy);
|
|
842
988
|
function stripDangerousUnicode(text) {
|
|
843
989
|
return text.replace(
|
|
844
990
|
// eslint-disable-next-line no-control-regex
|
|
@@ -852,14 +998,18 @@ function truncateLongLines(text) {
|
|
|
852
998
|
).join("\n");
|
|
853
999
|
}
|
|
854
1000
|
var INJECTION_SCAN_BUDGET = 8e3;
|
|
855
|
-
function detectInjections(text) {
|
|
1001
|
+
function detectInjections(text, includeNoisy) {
|
|
856
1002
|
const normalized = normalizeConfusables(text);
|
|
1003
|
+
const patterns = includeNoisy ? INJECTION_PATTERNS : STRICT_INJECTION_PATTERNS;
|
|
857
1004
|
if (normalized.length <= INJECTION_SCAN_BUDGET) {
|
|
858
|
-
return
|
|
1005
|
+
return patterns.some((p) => p.pattern.test(normalized));
|
|
859
1006
|
}
|
|
860
1007
|
const head = normalized.slice(0, INJECTION_SCAN_BUDGET);
|
|
861
1008
|
const tail = normalized.slice(-INJECTION_SCAN_BUDGET);
|
|
862
|
-
return
|
|
1009
|
+
return patterns.some((p) => p.pattern.test(head) || p.pattern.test(tail));
|
|
1010
|
+
}
|
|
1011
|
+
function scanForInjections(text, mode = "full") {
|
|
1012
|
+
return detectInjections(text, mode === "full");
|
|
863
1013
|
}
|
|
864
1014
|
function isLikelyBase64(s) {
|
|
865
1015
|
if (s.length < 64) {
|
|
@@ -868,12 +1018,13 @@ function isLikelyBase64(s) {
|
|
|
868
1018
|
const base64Chars = s.replace(/[A-Za-z0-9+/=\s]/g, "");
|
|
869
1019
|
return base64Chars.length / s.length < 0.05;
|
|
870
1020
|
}
|
|
871
|
-
function sanitizeUntrusted(input, kind = "text") {
|
|
1021
|
+
function sanitizeUntrusted(input, kind = "text", options) {
|
|
872
1022
|
let text = stripDangerousUnicode(input);
|
|
873
1023
|
text = truncateLongLines(text);
|
|
874
1024
|
text = text.replaceAll(BOUNDARY_BEGIN, "--- [UNTRUSTED MARKER STRIPPED] ---");
|
|
875
1025
|
text = text.replaceAll(BOUNDARY_END, "--- [UNTRUSTED MARKER STRIPPED] ---");
|
|
876
|
-
const
|
|
1026
|
+
const scanned = kind !== "binary" && detectInjections(text, kind === "text");
|
|
1027
|
+
const injectionsDetected = scanned || options?.extraInjectionSignal === true;
|
|
877
1028
|
let wrapped = `${BOUNDARY_BEGIN}
|
|
878
1029
|
${text}
|
|
879
1030
|
${BOUNDARY_END}`;
|
|
@@ -884,14 +1035,14 @@ ${wrapped}`;
|
|
|
884
1035
|
}
|
|
885
1036
|
return { text: wrapped, injectionsDetected };
|
|
886
1037
|
}
|
|
1038
|
+
function sanitizeInner(input) {
|
|
1039
|
+
return truncateLongLines(stripDangerousUnicode(input));
|
|
1040
|
+
}
|
|
887
1041
|
function sanitizeField(input, maxLen) {
|
|
888
1042
|
let text = stripDangerousUnicode(input);
|
|
889
1043
|
if (text.length > maxLen) {
|
|
890
1044
|
text = text.slice(0, maxLen) + "...";
|
|
891
1045
|
}
|
|
892
|
-
if (detectInjections(text)) {
|
|
893
|
-
text = `[SUSPICIOUS] ${text}`;
|
|
894
|
-
}
|
|
895
1046
|
return text;
|
|
896
1047
|
}
|
|
897
1048
|
|
|
@@ -1212,13 +1363,26 @@ var customerTools = [
|
|
|
1212
1363
|
console.error("[mcp:list_my_jobs] queryJobResults failed:", e);
|
|
1213
1364
|
}
|
|
1214
1365
|
}
|
|
1366
|
+
let freetextSuspicious = false;
|
|
1215
1367
|
const results = mine.map((job) => {
|
|
1216
1368
|
const dec = decryptedByRequest.get(job.eventId);
|
|
1217
1369
|
let resultText;
|
|
1218
1370
|
if (dec) {
|
|
1219
|
-
|
|
1371
|
+
if (dec.decryptionFailed) {
|
|
1372
|
+
resultText = "[decryption failed - targeted result not for this agent]";
|
|
1373
|
+
} else {
|
|
1374
|
+
const cleaned = sanitizeInner(dec.content);
|
|
1375
|
+
if (scanForInjections(cleaned, "full")) {
|
|
1376
|
+
freetextSuspicious = true;
|
|
1377
|
+
}
|
|
1378
|
+
resultText = cleaned;
|
|
1379
|
+
}
|
|
1220
1380
|
} else if (job.result) {
|
|
1221
|
-
|
|
1381
|
+
const cleaned = sanitizeInner(job.result);
|
|
1382
|
+
if (scanForInjections(cleaned, "full")) {
|
|
1383
|
+
freetextSuspicious = true;
|
|
1384
|
+
}
|
|
1385
|
+
resultText = cleaned;
|
|
1222
1386
|
}
|
|
1223
1387
|
return {
|
|
1224
1388
|
event_id: job.eventId,
|
|
@@ -1229,10 +1393,11 @@ var customerTools = [
|
|
|
1229
1393
|
result: resultText
|
|
1230
1394
|
};
|
|
1231
1395
|
});
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1396
|
+
const { text: wrapped } = sanitizeUntrusted(JSON.stringify(results, null, 2), "structured", {
|
|
1397
|
+
extraInjectionSignal: freetextSuspicious
|
|
1398
|
+
});
|
|
1399
|
+
return textResult(`Found ${results.length} of your jobs:
|
|
1400
|
+
${wrapped}`);
|
|
1236
1401
|
}
|
|
1237
1402
|
}),
|
|
1238
1403
|
defineTool({
|
|
@@ -2378,6 +2543,9 @@ program.command("install").description("Install elisym MCP server into client co
|
|
|
2378
2543
|
await runInstall({ client: options.client, agent: options.agent });
|
|
2379
2544
|
}
|
|
2380
2545
|
});
|
|
2546
|
+
program.command("update").description("Refresh the elisym MCP entry in installed client configs").option("--client <name>", "Specific client (claude-desktop, claude-code, cursor, windsurf)").option("--agent <name>", "Override the agent binding").action(async (options) => {
|
|
2547
|
+
await runUpdate({ client: options.client, agent: options.agent });
|
|
2548
|
+
});
|
|
2381
2549
|
program.command("uninstall").description("Remove elisym from MCP client configs").option("--client <name>", "Specific client").action(async (options) => {
|
|
2382
2550
|
await runUninstall({ client: options.client });
|
|
2383
2551
|
});
|