@memly/mcp-server 0.2.2 → 0.2.4

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.
Files changed (2) hide show
  1. package/dist/index.js +321 -84
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -20211,130 +20211,286 @@ I am ready to continue our work.`
20211
20211
  }
20212
20212
 
20213
20213
  // src/init.ts
20214
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
20214
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from "node:fs";
20215
20215
  import { join, resolve } from "node:path";
20216
20216
  import { homedir, platform } from "node:os";
20217
20217
  var MEMLY_INSTRUCTION_BLOCK = `
20218
- ## Memly Persistent Memory (Auto-Enabled)
20218
+ ## Memly — Persistent Memory (Auto-Enabled)
20219
20219
 
20220
20220
  You have access to the Memly MCP server (\`memly\`).
20221
20221
 
20222
- **RULES (always follow, no exceptions):**
20222
+ **RULES (always follow):**
20223
20223
 
20224
- 1. **At the start of EVERY conversation**, call \`load_context\` before answering anything.
20225
- - This loads memories from previous sessions.
20226
- - If the user's message gives a clear topic, pass it as \`topic\` (e.g., \`topic: "authentication"\`).
20227
- - Otherwise call with no arguments.
20224
+ 1. **At the start of EVERY conversation**, call \`load_context\` before answering.
20225
+ - Pass \`topic\` if the user's message has a clear subject (e.g., \`topic: "authentication"\`)
20226
+ - Otherwise call with no arguments
20228
20227
 
20229
- 2. **During the conversation**, call \`remember\` proactively whenever:
20230
- - The user makes an architectural or technical decision
20228
+ 2. **During the conversation**, call \`remember\` proactively when:
20229
+ - User makes an architectural or technical decision
20231
20230
  - You solve a non-trivial bug or problem
20232
- - The user shares important project context, preferences, or constraints
20233
- - The user explicitly says "remember this"
20231
+ - User shares important project context, preferences, or constraints
20232
+ - User explicitly says "remember this"
20234
20233
 
20235
- 3. **Never** ask the user if you should remember something just do it silently.
20234
+ 3. **Never** ask if you should remember something — just do it silently.
20236
20235
  `.trim();
20236
+ async function validateApiKey(apiKey) {
20237
+ try {
20238
+ const controller = new AbortController;
20239
+ const timeout = setTimeout(() => controller.abort(), 5000);
20240
+ const response = await fetch("https://api.memly.site/health", {
20241
+ headers: { Authorization: `Bearer ${apiKey}` },
20242
+ signal: controller.signal
20243
+ });
20244
+ clearTimeout(timeout);
20245
+ return response.ok;
20246
+ } catch {
20247
+ return false;
20248
+ }
20249
+ }
20237
20250
  function writeCopilotInstructions(projectRoot) {
20238
20251
  const githubDir = join(projectRoot, ".github");
20239
20252
  const filePath = join(githubDir, "copilot-instructions.md");
20240
- if (!existsSync(githubDir))
20241
- mkdirSync(githubDir, { recursive: true });
20242
20253
  const MARKER = "<!-- memly-instructions -->";
20243
- if (existsSync(filePath)) {
20244
- const existing = readFileSync(filePath, "utf-8");
20245
- if (existing.includes(MARKER)) {
20246
- return { ide: "VS Code / GitHub Copilot", file: filePath, status: "skipped" };
20254
+ try {
20255
+ if (!existsSync(githubDir)) {
20256
+ mkdirSync(githubDir, { recursive: true });
20247
20257
  }
20248
- writeFileSync(filePath, `${existing.trimEnd()}
20258
+ if (existsSync(filePath)) {
20259
+ const existing = readFileSync(filePath, "utf-8");
20260
+ if (existing.includes(MARKER)) {
20261
+ return { ide: "VS Code / GitHub Copilot", file: filePath, status: "skipped" };
20262
+ }
20263
+ writeFileSync(filePath, `${existing.trimEnd()}
20249
20264
 
20250
20265
  ${MARKER}
20251
20266
  ${MEMLY_INSTRUCTION_BLOCK}
20252
20267
  ${MARKER}
20253
20268
  `);
20254
- return { ide: "VS Code / GitHub Copilot", file: filePath, status: "updated" };
20255
- }
20256
- writeFileSync(filePath, `${MARKER}
20269
+ return { ide: "VS Code / GitHub Copilot", file: filePath, status: "updated" };
20270
+ }
20271
+ writeFileSync(filePath, `${MARKER}
20257
20272
  ${MEMLY_INSTRUCTION_BLOCK}
20258
20273
  ${MARKER}
20259
20274
  `);
20260
- return { ide: "VS Code / GitHub Copilot", file: filePath, status: "created" };
20275
+ return { ide: "VS Code / GitHub Copilot", file: filePath, status: "created" };
20276
+ } catch (err) {
20277
+ return {
20278
+ ide: "VS Code / GitHub Copilot",
20279
+ file: filePath,
20280
+ status: "failed",
20281
+ error: err instanceof Error ? err.message : String(err)
20282
+ };
20283
+ }
20261
20284
  }
20262
20285
  function writeCursorRules(projectRoot) {
20263
20286
  const filePath = join(projectRoot, ".cursorrules");
20264
20287
  const MARKER = "# memly-instructions";
20265
- if (existsSync(filePath)) {
20266
- const existing = readFileSync(filePath, "utf-8");
20267
- if (existing.includes(MARKER)) {
20268
- return { ide: "Cursor", file: filePath, status: "skipped" };
20269
- }
20270
- writeFileSync(filePath, `${existing.trimEnd()}
20288
+ try {
20289
+ if (existsSync(filePath)) {
20290
+ const existing = readFileSync(filePath, "utf-8");
20291
+ if (existing.includes(MARKER)) {
20292
+ return { ide: "Cursor", file: filePath, status: "skipped" };
20293
+ }
20294
+ writeFileSync(filePath, `${existing.trimEnd()}
20271
20295
 
20272
20296
  ${MARKER}
20273
20297
  ${MEMLY_INSTRUCTION_BLOCK}
20274
20298
  `);
20275
- return { ide: "Cursor", file: filePath, status: "updated" };
20276
- }
20277
- writeFileSync(filePath, `${MARKER}
20299
+ return { ide: "Cursor", file: filePath, status: "updated" };
20300
+ }
20301
+ writeFileSync(filePath, `${MARKER}
20278
20302
  ${MEMLY_INSTRUCTION_BLOCK}
20279
20303
  `);
20280
- return { ide: "Cursor", file: filePath, status: "created" };
20304
+ return { ide: "Cursor", file: filePath, status: "created" };
20305
+ } catch (err) {
20306
+ return {
20307
+ ide: "Cursor",
20308
+ file: filePath,
20309
+ status: "failed",
20310
+ error: err instanceof Error ? err.message : String(err)
20311
+ };
20312
+ }
20281
20313
  }
20282
20314
  function writeWindsurfRules(projectRoot) {
20283
20315
  const filePath = join(projectRoot, ".windsurfrules");
20284
20316
  const MARKER = "# memly-instructions";
20285
- if (existsSync(filePath)) {
20286
- const existing = readFileSync(filePath, "utf-8");
20287
- if (existing.includes(MARKER)) {
20288
- return { ide: "Windsurf", file: filePath, status: "skipped" };
20289
- }
20290
- writeFileSync(filePath, `${existing.trimEnd()}
20317
+ try {
20318
+ if (existsSync(filePath)) {
20319
+ const existing = readFileSync(filePath, "utf-8");
20320
+ if (existing.includes(MARKER)) {
20321
+ return { ide: "Windsurf", file: filePath, status: "skipped" };
20322
+ }
20323
+ writeFileSync(filePath, `${existing.trimEnd()}
20291
20324
 
20292
20325
  ${MARKER}
20293
20326
  ${MEMLY_INSTRUCTION_BLOCK}
20294
20327
  `);
20295
- return { ide: "Windsurf", file: filePath, status: "updated" };
20328
+ return { ide: "Windsurf", file: filePath, status: "updated" };
20329
+ }
20330
+ writeFileSync(filePath, `${MARKER}
20331
+ ${MEMLY_INSTRUCTION_BLOCK}
20332
+ `);
20333
+ return { ide: "Windsurf", file: filePath, status: "created" };
20334
+ } catch (err) {
20335
+ return {
20336
+ ide: "Windsurf",
20337
+ file: filePath,
20338
+ status: "failed",
20339
+ error: err instanceof Error ? err.message : String(err)
20340
+ };
20296
20341
  }
20297
- writeFileSync(filePath, `${MARKER}
20342
+ }
20343
+ function writeClineConfig(projectRoot) {
20344
+ const filePath = join(projectRoot, ".clinerules");
20345
+ const MARKER = "# memly-instructions";
20346
+ try {
20347
+ if (existsSync(filePath)) {
20348
+ const existing = readFileSync(filePath, "utf-8");
20349
+ if (existing.includes(MARKER)) {
20350
+ return { ide: "Cline (VS Code)", file: filePath, status: "skipped" };
20351
+ }
20352
+ writeFileSync(filePath, `${existing.trimEnd()}
20353
+
20354
+ ${MARKER}
20355
+ ${MEMLY_INSTRUCTION_BLOCK}
20356
+ `);
20357
+ return { ide: "Cline (VS Code)", file: filePath, status: "updated" };
20358
+ }
20359
+ writeFileSync(filePath, `${MARKER}
20298
20360
  ${MEMLY_INSTRUCTION_BLOCK}
20299
20361
  `);
20300
- return { ide: "Windsurf", file: filePath, status: "created" };
20362
+ return { ide: "Cline (VS Code)", file: filePath, status: "created" };
20363
+ } catch (err) {
20364
+ return {
20365
+ ide: "Cline (VS Code)",
20366
+ file: filePath,
20367
+ status: "failed",
20368
+ error: err instanceof Error ? err.message : String(err)
20369
+ };
20370
+ }
20371
+ }
20372
+ function writeContinueConfig() {
20373
+ const configPath = join(homedir(), ".continue", "config.json");
20374
+ if (!existsSync(configPath))
20375
+ return null;
20376
+ try {
20377
+ const raw = readFileSync(configPath, "utf-8");
20378
+ const config2 = JSON.parse(raw);
20379
+ const mcpServers = config2["mcpServers"] ?? {};
20380
+ if (mcpServers["memly"]) {
20381
+ return { ide: "Continue.dev", file: configPath, status: "skipped" };
20382
+ }
20383
+ mcpServers["memly"] = {
20384
+ command: "npx",
20385
+ args: ["-y", "@memly/mcp-server"],
20386
+ env: { MEMLY_API_KEY: process.env.MEMLY_API_KEY ?? "<your-memly-api-key>" }
20387
+ };
20388
+ config2["mcpServers"] = mcpServers;
20389
+ writeFileSync(configPath, JSON.stringify(config2, null, 2));
20390
+ return { ide: "Continue.dev", file: configPath, status: "updated" };
20391
+ } catch (err) {
20392
+ return {
20393
+ ide: "Continue.dev",
20394
+ file: configPath,
20395
+ status: "failed",
20396
+ error: err instanceof Error ? err.message : String(err)
20397
+ };
20398
+ }
20399
+ }
20400
+ function writeZedConfig() {
20401
+ const os = platform();
20402
+ const configPath = os === "darwin" ? join(homedir(), "Library", "Application Support", "Zed", "settings.json") : join(homedir(), ".config", "zed", "settings.json");
20403
+ if (!existsSync(configPath))
20404
+ return null;
20405
+ try {
20406
+ const raw = readFileSync(configPath, "utf-8");
20407
+ const config2 = JSON.parse(raw);
20408
+ const assistant = config2["assistant"] ?? {};
20409
+ const instructions = assistant["instructions"] ?? "";
20410
+ if (instructions.includes("Memly")) {
20411
+ return { ide: "Zed", file: configPath, status: "skipped" };
20412
+ }
20413
+ assistant["instructions"] = instructions ? `${instructions}
20414
+
20415
+ ${MEMLY_INSTRUCTION_BLOCK}` : MEMLY_INSTRUCTION_BLOCK;
20416
+ config2["assistant"] = assistant;
20417
+ writeFileSync(configPath, JSON.stringify(config2, null, 2));
20418
+ return { ide: "Zed", file: configPath, status: "updated" };
20419
+ } catch (err) {
20420
+ return {
20421
+ ide: "Zed",
20422
+ file: configPath,
20423
+ status: "failed",
20424
+ error: err instanceof Error ? err.message : String(err)
20425
+ };
20426
+ }
20427
+ }
20428
+ function writeAiderConfig() {
20429
+ const configPath = join(homedir(), ".aider.conf.yml");
20430
+ if (!existsSync(configPath))
20431
+ return null;
20432
+ try {
20433
+ const existing = readFileSync(configPath, "utf-8");
20434
+ if (existing.includes("memly-instructions")) {
20435
+ return { ide: "Aider (CLI)", file: configPath, status: "skipped" };
20436
+ }
20437
+ const instruction = `
20438
+ # memly-instructions
20439
+ edit-format: |
20440
+ ${MEMLY_INSTRUCTION_BLOCK.replace(/\n/g, `
20441
+ `)}
20442
+ `;
20443
+ writeFileSync(configPath, existing + instruction);
20444
+ return { ide: "Aider (CLI)", file: configPath, status: "updated" };
20445
+ } catch (err) {
20446
+ return {
20447
+ ide: "Aider (CLI)",
20448
+ file: configPath,
20449
+ status: "failed",
20450
+ error: err instanceof Error ? err.message : String(err)
20451
+ };
20452
+ }
20301
20453
  }
20302
20454
  function writeJetBrains(projectRoot) {
20303
20455
  const ideaDir = join(projectRoot, ".idea");
20304
20456
  if (!existsSync(ideaDir))
20305
20457
  return null;
20306
20458
  const filePath = join(ideaDir, "mcp.json");
20307
- const MARKER = '"memly"';
20308
- if (existsSync(filePath)) {
20309
- const existing = readFileSync(filePath, "utf-8");
20310
- if (existing.includes(MARKER)) {
20311
- return { ide: "JetBrains", file: filePath, status: "skipped" };
20312
- }
20313
- try {
20459
+ try {
20460
+ if (existsSync(filePath)) {
20461
+ const existing = readFileSync(filePath, "utf-8");
20314
20462
  const config2 = JSON.parse(existing);
20463
+ if (config2.servers?.["memly"]) {
20464
+ return { ide: "JetBrains", file: filePath, status: "skipped" };
20465
+ }
20315
20466
  config2.servers = config2.servers ?? {};
20316
20467
  config2.servers["memly"] = {
20317
20468
  command: "npx",
20318
20469
  args: ["-y", "@memly/mcp-server"],
20319
- env: { MEMLY_API_KEY: "<your-memly-api-key>" }
20470
+ env: { MEMLY_API_KEY: process.env.MEMLY_API_KEY ?? "<your-memly-api-key>" }
20320
20471
  };
20321
20472
  writeFileSync(filePath, JSON.stringify(config2, null, 2));
20322
20473
  return { ide: "JetBrains", file: filePath, status: "updated" };
20323
- } catch {
20324
- return null;
20325
20474
  }
20326
- }
20327
- mkdirSync(ideaDir, { recursive: true });
20328
- writeFileSync(filePath, JSON.stringify({
20329
- servers: {
20330
- memly: {
20331
- command: "npx",
20332
- args: ["-y", "@memly/mcp-server"],
20333
- env: { MEMLY_API_KEY: "<your-memly-api-key>" }
20475
+ mkdirSync(ideaDir, { recursive: true });
20476
+ writeFileSync(filePath, JSON.stringify({
20477
+ servers: {
20478
+ memly: {
20479
+ command: "npx",
20480
+ args: ["-y", "@memly/mcp-server"],
20481
+ env: { MEMLY_API_KEY: process.env.MEMLY_API_KEY ?? "<your-memly-api-key>" }
20482
+ }
20334
20483
  }
20335
- }
20336
- }, null, 2));
20337
- return { ide: "JetBrains", file: filePath, status: "created" };
20484
+ }, null, 2));
20485
+ return { ide: "JetBrains", file: filePath, status: "created" };
20486
+ } catch (err) {
20487
+ return {
20488
+ ide: "JetBrains",
20489
+ file: filePath,
20490
+ status: "failed",
20491
+ error: err instanceof Error ? err.message : String(err)
20492
+ };
20493
+ }
20338
20494
  }
20339
20495
  function writeClaudeDesktop() {
20340
20496
  const os = platform();
@@ -20360,59 +20516,140 @@ function writeClaudeDesktop() {
20360
20516
  servers["memly"]["systemPrompt"] = MEMLY_INSTRUCTION_BLOCK;
20361
20517
  writeFileSync(configPath, JSON.stringify(config2, null, 2));
20362
20518
  return { ide: "Claude Desktop", file: configPath, status: "updated" };
20363
- } catch {
20364
- return null;
20519
+ } catch (err) {
20520
+ return {
20521
+ ide: "Claude Desktop",
20522
+ file: configPath,
20523
+ status: "failed",
20524
+ error: err instanceof Error ? err.message : String(err)
20525
+ };
20365
20526
  }
20366
20527
  }
20367
- function runInit() {
20528
+ async function reportTelemetry(results) {
20529
+ try {
20530
+ const controller = new AbortController;
20531
+ const timeout = setTimeout(() => controller.abort(), 3000);
20532
+ await fetch("https://api.memly.site/api/telemetry/init", {
20533
+ method: "POST",
20534
+ headers: { "Content-Type": "application/json" },
20535
+ body: JSON.stringify({
20536
+ ides: results.map((r) => r.ide),
20537
+ os: platform(),
20538
+ timestamp: new Date().toISOString()
20539
+ }),
20540
+ signal: controller.signal
20541
+ });
20542
+ clearTimeout(timeout);
20543
+ } catch {}
20544
+ }
20545
+ async function runInit() {
20368
20546
  const projectRoot = resolve(process.cwd());
20369
- const results = [];
20547
+ if (projectRoot === homedir()) {
20548
+ console.error("Error: Running in home directory.");
20549
+ console.error(`Run this command inside your project folder.
20550
+ `);
20551
+ process.exit(1);
20552
+ }
20370
20553
  console.log(`
20371
- \uD83E\uDDE0 Memly Init — setting up auto-context for your IDEs
20554
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
20555
+ console.log(" Memly Init — Auto-Memory Setup");
20556
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
20557
+ `);
20558
+ const apiKey = process.env.MEMLY_API_KEY;
20559
+ if (!apiKey) {
20560
+ console.error(`Error: MEMLY_API_KEY not set.
20561
+ `);
20562
+ console.error("Get your API key at: https://memly.site/dashboard");
20563
+ console.error(`Then run: export MEMLY_API_KEY=memly_...
20564
+ `);
20565
+ process.exit(1);
20566
+ }
20567
+ process.stdout.write("Validating API key... ");
20568
+ const valid = await validateApiKey(apiKey);
20569
+ if (!valid) {
20570
+ console.error(`✗ Invalid
20372
20571
  `);
20373
- console.log(` Project root: ${projectRoot}
20572
+ console.error(`Check your API key at: https://memly.site/dashboard
20374
20573
  `);
20574
+ process.exit(1);
20575
+ }
20576
+ console.log(`✓ Valid
20577
+ `);
20578
+ console.log(`Project: ${projectRoot}
20579
+ `);
20580
+ console.log(`Detecting IDEs...
20581
+ `);
20582
+ const results = [];
20375
20583
  results.push(writeCopilotInstructions(projectRoot));
20376
20584
  const hasCursor = existsSync(join(projectRoot, ".cursor")) || existsSync(join(projectRoot, ".cursorrules")) || existsSync(join(homedir(), ".cursor"));
20377
- if (hasCursor) {
20585
+ if (hasCursor)
20378
20586
  results.push(writeCursorRules(projectRoot));
20379
- }
20380
20587
  const hasWindsurf = existsSync(join(projectRoot, ".windsurfrules")) || existsSync(join(homedir(), ".codeium"));
20381
- if (hasWindsurf) {
20588
+ if (hasWindsurf)
20382
20589
  results.push(writeWindsurfRules(projectRoot));
20383
- }
20590
+ const clineExtDir = join(homedir(), ".vscode", "extensions");
20591
+ const hasCline = existsSync(join(projectRoot, ".clinerules")) || existsSync(clineExtDir) && readdirSync(clineExtDir).some((d) => d.startsWith("saoudrizwan.claude-dev"));
20592
+ if (hasCline)
20593
+ results.push(writeClineConfig(projectRoot));
20594
+ const continueResult = writeContinueConfig();
20595
+ if (continueResult)
20596
+ results.push(continueResult);
20597
+ const zedResult = writeZedConfig();
20598
+ if (zedResult)
20599
+ results.push(zedResult);
20600
+ const aiderResult = writeAiderConfig();
20601
+ if (aiderResult)
20602
+ results.push(aiderResult);
20384
20603
  const claudeResult = writeClaudeDesktop();
20385
20604
  if (claudeResult)
20386
20605
  results.push(claudeResult);
20387
20606
  const jetbrainsResult = writeJetBrains(projectRoot);
20388
20607
  if (jetbrainsResult)
20389
20608
  results.push(jetbrainsResult);
20390
- const icons = {
20391
- created: "✅",
20392
- updated: "✅",
20393
- skipped: "⏭️ "
20394
- };
20609
+ const icons = { created: "✓", updated: "✓", skipped: "⊘", failed: "✗" };
20610
+ console.log(`Results:
20611
+ `);
20612
+ console.log(" IDE Status File");
20613
+ console.log(" ──────────────────────────────────────────────────────────────────");
20395
20614
  for (const r of results) {
20396
20615
  const rel = r.file.replace(projectRoot, ".").replace(homedir(), "~");
20397
- console.log(` ${icons[r.status]} ${r.ide} ${r.status}: ${rel}`);
20616
+ const statusLabel = r.status === "failed" ? `failed (${r.error})` : r.status;
20617
+ console.log(` ${icons[r.status]} ${r.ide.padEnd(28)} ${r.status.padEnd(8)} ${rel}`);
20618
+ if (r.status === "failed")
20619
+ console.log(` └─ ${statusLabel}`);
20398
20620
  }
20399
20621
  if (results.length === 0) {
20400
- console.log(" ℹ️ No supported IDEs detected automatically.");
20401
- console.log(` Add the following to your IDE's instruction file manually:
20622
+ console.log(`
20623
+ No supported IDEs detected.`);
20624
+ console.log(` Add this block manually to your IDE config:
20402
20625
  `);
20403
20626
  console.log(MEMLY_INSTRUCTION_BLOCK);
20627
+ console.log(`
20628
+ `);
20629
+ return;
20404
20630
  }
20631
+ try {
20632
+ writeFileSync(join(projectRoot, ".memly-initialized"), new Date().toISOString());
20633
+ } catch {}
20634
+ await reportTelemetry(results);
20405
20635
  console.log(`
20406
- ✅ Done! Memly will now load context automatically at the start of every conversation.
20636
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
20637
+ `);
20638
+ console.log(`Setup complete. Next steps:
20639
+ `);
20640
+ console.log(" 1. Restart your IDE");
20641
+ console.log(" 2. Start a new conversation");
20642
+ console.log(` 3. Memly loads context automatically
20407
20643
  `);
20408
- console.log(` Tip: run this again after adding a new IDE to your workflow.
20644
+ console.log("Dashboard: https://memly.site/dashboard");
20645
+ console.log(`Docs: https://memly.site/docs
20409
20646
  `);
20410
20647
  }
20411
20648
 
20412
20649
  // src/index.ts
20413
20650
  var apiKey = process.env.MEMLY_API_KEY;
20414
20651
  if (process.argv.includes("--init") || process.argv.includes("init")) {
20415
- runInit();
20652
+ await runInit();
20416
20653
  process.exit(0);
20417
20654
  }
20418
20655
  if (!apiKey) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memly/mcp-server",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Memly MCP Server — persistent memory for any IDE",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",