@memly/mcp-server 0.1.1 → 0.2.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/README.md +13 -1
- package/dist/index.js +261 -25
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -56,7 +56,19 @@ Go to [memly.site/dashboard/api-keys](https://memly.site/dashboard/api-keys)
|
|
|
56
56
|
}
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
### Automatic Context (Zero Friction) ⚡
|
|
60
|
+
|
|
61
|
+
To make your AI agent proactively load context at the start of every session, add this to your `.cursorrules`, `.windsurfrules`, or `GEMINI.md`:
|
|
62
|
+
|
|
63
|
+
```markdown
|
|
64
|
+
# Memly Integration
|
|
65
|
+
At the start of every session, you MUST:
|
|
66
|
+
1. Call `memly.search_memories({ query: "current active task", limit: 5 })`
|
|
67
|
+
2. Use this context to understand the project state.
|
|
68
|
+
3. When you make major decisions, call `memly.save_memory`.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Tools
|
|
60
72
|
|
|
61
73
|
| Tool | Description |
|
|
62
74
|
|---|---|
|
package/dist/index.js
CHANGED
|
@@ -19968,7 +19968,48 @@ class MemlyClient {
|
|
|
19968
19968
|
|
|
19969
19969
|
// src/tools.ts
|
|
19970
19970
|
function registerTools(server, client) {
|
|
19971
|
-
server.tool("
|
|
19971
|
+
server.tool("load_context", [
|
|
19972
|
+
"ALWAYS call this tool FIRST, before responding to any message in a new conversation.",
|
|
19973
|
+
"It loads your persistent memory from previous sessions so you can provide accurate, context-aware assistance.",
|
|
19974
|
+
"Do not skip this — without it you are missing critical context about the user's project, decisions, and preferences."
|
|
19975
|
+
].join(" "), {
|
|
19976
|
+
topic: exports_external.string().optional().describe('Optional: topic or question to focus the memory search (e.g. "authentication", "database schema"). Leave empty to load general recent context.')
|
|
19977
|
+
}, async ({ topic }) => {
|
|
19978
|
+
try {
|
|
19979
|
+
const query = topic ?? "recent technical decisions architecture patterns current work";
|
|
19980
|
+
const result = await client.searchMemories(query, undefined, 10);
|
|
19981
|
+
if (result.is_cold_start || result.memories.length === 0) {
|
|
19982
|
+
return {
|
|
19983
|
+
content: [{
|
|
19984
|
+
type: "text",
|
|
19985
|
+
text: "\uD83E\uDDE0 Memly: No memories yet for this project. I'll start learning from this session — important decisions and context will be saved automatically."
|
|
19986
|
+
}]
|
|
19987
|
+
};
|
|
19988
|
+
}
|
|
19989
|
+
const formatted = result.memories.map((m, i) => `[${i + 1}] ${m.chunk_type ?? "general"} | confidence: ${m.confidence ? (m.confidence * 100).toFixed(0) + "%" : "n/a"}
|
|
19990
|
+
${m.content}`).join(`
|
|
19991
|
+
|
|
19992
|
+
`);
|
|
19993
|
+
return {
|
|
19994
|
+
content: [{
|
|
19995
|
+
type: "text",
|
|
19996
|
+
text: `\uD83E\uDDE0 Memly: Loaded ${result.memories.length} memories from previous sessions.
|
|
19997
|
+
|
|
19998
|
+
${formatted}
|
|
19999
|
+
|
|
20000
|
+
Use this context to inform your responses.`
|
|
20001
|
+
}]
|
|
20002
|
+
};
|
|
20003
|
+
} catch (err) {
|
|
20004
|
+
return {
|
|
20005
|
+
content: [{
|
|
20006
|
+
type: "text",
|
|
20007
|
+
text: `\uD83E\uDDE0 Memly: Could not load context (${err instanceof Error ? err.message : String(err)}). Continuing without memory.`
|
|
20008
|
+
}]
|
|
20009
|
+
};
|
|
20010
|
+
}
|
|
20011
|
+
});
|
|
20012
|
+
server.tool("search_memories", "Search your stored memories semantically. Call this when you need to recall a specific previous decision, code pattern, or technical context that may not have been loaded by load_context.", {
|
|
19972
20013
|
query: exports_external.string().min(1).describe("What to search for (natural language)"),
|
|
19973
20014
|
project_id: exports_external.string().uuid().optional().describe("Limit search to a specific project"),
|
|
19974
20015
|
limit: exports_external.number().int().min(1).max(20).optional().describe("Max results (default: 5)")
|
|
@@ -20003,8 +20044,8 @@ ${formatted}`
|
|
|
20003
20044
|
};
|
|
20004
20045
|
}
|
|
20005
20046
|
});
|
|
20006
|
-
server.tool("remember", "
|
|
20007
|
-
content: exports_external.string().min(1).max(1e4).describe("The content to remember"),
|
|
20047
|
+
server.tool("remember", "Save an important fact, decision, or code pattern to persistent memory. Call this proactively whenever: the user makes an architectural decision, reveals important project context, you solve a non-trivial problem, or the user explicitly asks you to remember something.", {
|
|
20048
|
+
content: exports_external.string().min(1).max(1e4).describe("The content to remember — be specific and self-contained so it makes sense out of context"),
|
|
20008
20049
|
project_id: exports_external.string().uuid().optional().describe("Associate with a specific project")
|
|
20009
20050
|
}, async ({ content, project_id }) => {
|
|
20010
20051
|
try {
|
|
@@ -20012,7 +20053,7 @@ ${formatted}`
|
|
|
20012
20053
|
return {
|
|
20013
20054
|
content: [{
|
|
20014
20055
|
type: "text",
|
|
20015
|
-
text: `✅
|
|
20056
|
+
text: `✅ Saved to memory (${content.length} chars). This will be available in future sessions.`
|
|
20016
20057
|
}]
|
|
20017
20058
|
};
|
|
20018
20059
|
} catch (err) {
|
|
@@ -20025,27 +20066,6 @@ ${formatted}`
|
|
|
20025
20066
|
};
|
|
20026
20067
|
}
|
|
20027
20068
|
});
|
|
20028
|
-
server.tool("forget", "Delete a specific memory by its ID.", {
|
|
20029
|
-
memory_id: exports_external.string().uuid().describe("The ID of the memory to delete")
|
|
20030
|
-
}, async ({ memory_id }) => {
|
|
20031
|
-
try {
|
|
20032
|
-
await client.deleteMemory(memory_id);
|
|
20033
|
-
return {
|
|
20034
|
-
content: [{
|
|
20035
|
-
type: "text",
|
|
20036
|
-
text: `\uD83D\uDDD1️ Memory ${memory_id} deleted.`
|
|
20037
|
-
}]
|
|
20038
|
-
};
|
|
20039
|
-
} catch (err) {
|
|
20040
|
-
return {
|
|
20041
|
-
content: [{
|
|
20042
|
-
type: "text",
|
|
20043
|
-
text: `Failed to delete: ${err instanceof Error ? err.message : String(err)}`
|
|
20044
|
-
}],
|
|
20045
|
-
isError: true
|
|
20046
|
-
};
|
|
20047
|
-
}
|
|
20048
|
-
});
|
|
20049
20069
|
server.tool("list_projects", "List all your projects with memory statistics.", {}, async () => {
|
|
20050
20070
|
try {
|
|
20051
20071
|
const stats = await client.getStats();
|
|
@@ -20139,8 +20159,222 @@ function registerResources(server, client) {
|
|
|
20139
20159
|
});
|
|
20140
20160
|
}
|
|
20141
20161
|
|
|
20162
|
+
// src/prompts.ts
|
|
20163
|
+
function registerPrompts(server, client) {
|
|
20164
|
+
server.prompt("restore_context", {
|
|
20165
|
+
project_id: exports_external.string().uuid().optional().describe("Project ID to load context from")
|
|
20166
|
+
}, async (args) => {
|
|
20167
|
+
const project_id = args?.project_id;
|
|
20168
|
+
const stats = await client.getStats();
|
|
20169
|
+
const project = stats.projects.find((p) => p.project_id === project_id) ?? stats.projects[0];
|
|
20170
|
+
if (!project) {
|
|
20171
|
+
return {
|
|
20172
|
+
messages: [
|
|
20173
|
+
{
|
|
20174
|
+
role: "assistant",
|
|
20175
|
+
content: {
|
|
20176
|
+
type: "text",
|
|
20177
|
+
text: "No memories found. I'm ready to start learning from this session."
|
|
20178
|
+
}
|
|
20179
|
+
}
|
|
20180
|
+
]
|
|
20181
|
+
};
|
|
20182
|
+
}
|
|
20183
|
+
const result = await client.searchMemories("context restore", project.project_id, 10);
|
|
20184
|
+
const memoryText = result.memories.map((m) => `[MEMORY:${m.id} (${m.created_at})]
|
|
20185
|
+
${m.content}`).join(`
|
|
20186
|
+
|
|
20187
|
+
`);
|
|
20188
|
+
return {
|
|
20189
|
+
messages: [
|
|
20190
|
+
{
|
|
20191
|
+
role: "user",
|
|
20192
|
+
content: {
|
|
20193
|
+
type: "text",
|
|
20194
|
+
text: `Restore context for project ${project.project_id}`
|
|
20195
|
+
}
|
|
20196
|
+
},
|
|
20197
|
+
{
|
|
20198
|
+
role: "assistant",
|
|
20199
|
+
content: {
|
|
20200
|
+
type: "text",
|
|
20201
|
+
text: `I've retrieved the following context from Memly:
|
|
20202
|
+
|
|
20203
|
+
${memoryText}
|
|
20204
|
+
|
|
20205
|
+
I am ready to continue our work.`
|
|
20206
|
+
}
|
|
20207
|
+
}
|
|
20208
|
+
]
|
|
20209
|
+
};
|
|
20210
|
+
});
|
|
20211
|
+
}
|
|
20212
|
+
|
|
20213
|
+
// src/init.ts
|
|
20214
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
20215
|
+
import { join, resolve } from "node:path";
|
|
20216
|
+
import { homedir, platform } from "node:os";
|
|
20217
|
+
var MEMLY_INSTRUCTION_BLOCK = `
|
|
20218
|
+
## Memly — Persistent Memory (Auto-Enabled)
|
|
20219
|
+
|
|
20220
|
+
You have access to the Memly MCP server (\`memly\`).
|
|
20221
|
+
|
|
20222
|
+
**RULES (always follow, no exceptions):**
|
|
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.
|
|
20228
|
+
|
|
20229
|
+
2. **During the conversation**, call \`remember\` proactively whenever:
|
|
20230
|
+
- The user makes an architectural or technical decision
|
|
20231
|
+
- 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"
|
|
20234
|
+
|
|
20235
|
+
3. **Never** ask the user if you should remember something — just do it silently.
|
|
20236
|
+
`.trim();
|
|
20237
|
+
function writeCopilotInstructions(projectRoot) {
|
|
20238
|
+
const githubDir = join(projectRoot, ".github");
|
|
20239
|
+
const filePath = join(githubDir, "copilot-instructions.md");
|
|
20240
|
+
if (!existsSync(githubDir))
|
|
20241
|
+
mkdirSync(githubDir, { recursive: true });
|
|
20242
|
+
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" };
|
|
20247
|
+
}
|
|
20248
|
+
writeFileSync(filePath, `${existing.trimEnd()}
|
|
20249
|
+
|
|
20250
|
+
${MARKER}
|
|
20251
|
+
${MEMLY_INSTRUCTION_BLOCK}
|
|
20252
|
+
${MARKER}
|
|
20253
|
+
`);
|
|
20254
|
+
return { ide: "VS Code / GitHub Copilot", file: filePath, status: "updated" };
|
|
20255
|
+
}
|
|
20256
|
+
writeFileSync(filePath, `${MARKER}
|
|
20257
|
+
${MEMLY_INSTRUCTION_BLOCK}
|
|
20258
|
+
${MARKER}
|
|
20259
|
+
`);
|
|
20260
|
+
return { ide: "VS Code / GitHub Copilot", file: filePath, status: "created" };
|
|
20261
|
+
}
|
|
20262
|
+
function writeCursorRules(projectRoot) {
|
|
20263
|
+
const filePath = join(projectRoot, ".cursorrules");
|
|
20264
|
+
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()}
|
|
20271
|
+
|
|
20272
|
+
${MARKER}
|
|
20273
|
+
${MEMLY_INSTRUCTION_BLOCK}
|
|
20274
|
+
`);
|
|
20275
|
+
return { ide: "Cursor", file: filePath, status: "updated" };
|
|
20276
|
+
}
|
|
20277
|
+
writeFileSync(filePath, `${MARKER}
|
|
20278
|
+
${MEMLY_INSTRUCTION_BLOCK}
|
|
20279
|
+
`);
|
|
20280
|
+
return { ide: "Cursor", file: filePath, status: "created" };
|
|
20281
|
+
}
|
|
20282
|
+
function writeWindsurfRules(projectRoot) {
|
|
20283
|
+
const filePath = join(projectRoot, ".windsurfrules");
|
|
20284
|
+
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()}
|
|
20291
|
+
|
|
20292
|
+
${MARKER}
|
|
20293
|
+
${MEMLY_INSTRUCTION_BLOCK}
|
|
20294
|
+
`);
|
|
20295
|
+
return { ide: "Windsurf", file: filePath, status: "updated" };
|
|
20296
|
+
}
|
|
20297
|
+
writeFileSync(filePath, `${MARKER}
|
|
20298
|
+
${MEMLY_INSTRUCTION_BLOCK}
|
|
20299
|
+
`);
|
|
20300
|
+
return { ide: "Windsurf", file: filePath, status: "created" };
|
|
20301
|
+
}
|
|
20302
|
+
function writeClaudeDesktop() {
|
|
20303
|
+
const os = platform();
|
|
20304
|
+
let configPath;
|
|
20305
|
+
if (os === "darwin") {
|
|
20306
|
+
configPath = join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
20307
|
+
} else if (os === "win32") {
|
|
20308
|
+
configPath = join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
20309
|
+
} else {
|
|
20310
|
+
configPath = join(homedir(), ".config", "Claude", "claude_desktop_config.json");
|
|
20311
|
+
}
|
|
20312
|
+
if (!existsSync(configPath))
|
|
20313
|
+
return null;
|
|
20314
|
+
try {
|
|
20315
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
20316
|
+
const config2 = JSON.parse(raw);
|
|
20317
|
+
const servers = config2["mcpServers"];
|
|
20318
|
+
if (!servers?.["memly"])
|
|
20319
|
+
return null;
|
|
20320
|
+
if (servers["memly"]["systemPrompt"]) {
|
|
20321
|
+
return { ide: "Claude Desktop", file: configPath, status: "skipped" };
|
|
20322
|
+
}
|
|
20323
|
+
servers["memly"]["systemPrompt"] = MEMLY_INSTRUCTION_BLOCK;
|
|
20324
|
+
writeFileSync(configPath, JSON.stringify(config2, null, 2));
|
|
20325
|
+
return { ide: "Claude Desktop", file: configPath, status: "updated" };
|
|
20326
|
+
} catch {
|
|
20327
|
+
return null;
|
|
20328
|
+
}
|
|
20329
|
+
}
|
|
20330
|
+
function runInit() {
|
|
20331
|
+
const projectRoot = resolve(process.cwd());
|
|
20332
|
+
const results = [];
|
|
20333
|
+
console.log(`
|
|
20334
|
+
\uD83E\uDDE0 Memly Init — setting up auto-context for your IDEs
|
|
20335
|
+
`);
|
|
20336
|
+
console.log(` Project root: ${projectRoot}
|
|
20337
|
+
`);
|
|
20338
|
+
results.push(writeCopilotInstructions(projectRoot));
|
|
20339
|
+
const hasCursor = existsSync(join(projectRoot, ".cursor")) || existsSync(join(projectRoot, ".cursorrules")) || existsSync(join(homedir(), ".cursor"));
|
|
20340
|
+
if (hasCursor) {
|
|
20341
|
+
results.push(writeCursorRules(projectRoot));
|
|
20342
|
+
}
|
|
20343
|
+
const hasWindsurf = existsSync(join(projectRoot, ".windsurfrules")) || existsSync(join(homedir(), ".codeium"));
|
|
20344
|
+
if (hasWindsurf) {
|
|
20345
|
+
results.push(writeWindsurfRules(projectRoot));
|
|
20346
|
+
}
|
|
20347
|
+
const claudeResult = writeClaudeDesktop();
|
|
20348
|
+
if (claudeResult)
|
|
20349
|
+
results.push(claudeResult);
|
|
20350
|
+
const icons = {
|
|
20351
|
+
created: "✅",
|
|
20352
|
+
updated: "✅",
|
|
20353
|
+
skipped: "⏭️ "
|
|
20354
|
+
};
|
|
20355
|
+
for (const r of results) {
|
|
20356
|
+
const rel = r.file.replace(projectRoot, ".").replace(homedir(), "~");
|
|
20357
|
+
console.log(` ${icons[r.status]} ${r.ide} — ${r.status}: ${rel}`);
|
|
20358
|
+
}
|
|
20359
|
+
if (results.length === 0) {
|
|
20360
|
+
console.log(" ℹ️ No supported IDEs detected automatically.");
|
|
20361
|
+
console.log(` Add the following to your IDE's instruction file manually:
|
|
20362
|
+
`);
|
|
20363
|
+
console.log(MEMLY_INSTRUCTION_BLOCK);
|
|
20364
|
+
}
|
|
20365
|
+
console.log(`
|
|
20366
|
+
✅ Done! Memly will now load context automatically at the start of every conversation.
|
|
20367
|
+
`);
|
|
20368
|
+
console.log(` Tip: run this again after adding a new IDE to your workflow.
|
|
20369
|
+
`);
|
|
20370
|
+
}
|
|
20371
|
+
|
|
20142
20372
|
// src/index.ts
|
|
20143
20373
|
var apiKey = process.env.MEMLY_API_KEY;
|
|
20374
|
+
if (process.argv.includes("--init")) {
|
|
20375
|
+
runInit();
|
|
20376
|
+
process.exit(0);
|
|
20377
|
+
}
|
|
20144
20378
|
if (!apiKey) {
|
|
20145
20379
|
console.error("❌ MEMLY_API_KEY environment variable is required.");
|
|
20146
20380
|
console.error(" Get your key at https://memly.site/dashboard/api-keys");
|
|
@@ -20162,6 +20396,7 @@ if (isHttp) {
|
|
|
20162
20396
|
const server = new McpServer({ name: "memly", version: "0.1.0" });
|
|
20163
20397
|
registerTools(server, client);
|
|
20164
20398
|
registerResources(server, client);
|
|
20399
|
+
registerPrompts(server, client);
|
|
20165
20400
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
20166
20401
|
sessionIdGenerator: undefined
|
|
20167
20402
|
});
|
|
@@ -20178,6 +20413,7 @@ if (isHttp) {
|
|
|
20178
20413
|
const server = new McpServer({ name: "memly", version: "0.1.0" });
|
|
20179
20414
|
registerTools(server, client);
|
|
20180
20415
|
registerResources(server, client);
|
|
20416
|
+
registerPrompts(server, client);
|
|
20181
20417
|
const transport = new StdioServerTransport;
|
|
20182
20418
|
await server.connect(transport);
|
|
20183
20419
|
console.error("\uD83E\uDDE0 Memly MCP Server (stdio) connected");
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memly/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Memly MCP Server — persistent memory for any IDE",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"memly-mcp": "dist/index.js"
|
|
8
|
+
"memly-mcp": "dist/index.js",
|
|
9
|
+
"memly": "dist/index.js"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|
|
11
12
|
"dist",
|