@more-md/mcp-memory 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ugur Cekmez and more.md contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # @more-md/mcp-memory
2
+
3
+ Portable long-term memory for AI assistants — as an **MCP server**.
4
+
5
+ Connect it once and your assistant can remember things across chats, and the
6
+ same memory follows you from Claude to Cursor to any other MCP-capable
7
+ assistant. You hold the key (a short PIN); the memory is encrypted at rest and
8
+ lives at a URL you control ([mem.more.md](https://mem.more.md)).
9
+
10
+ ## Why an MCP server (and not "just give the AI a URL")?
11
+
12
+ Telling a chat model to *"fetch this URL and use it as your memory"* is the
13
+ exact shape of a prompt‑injection attack — a web page telling the assistant to
14
+ persist your data somewhere. Well‑aligned models refuse it, and they're right
15
+ to.
16
+
17
+ An MCP server flips that around:
18
+
19
+ - **You** add this server to your assistant's config (with your memory URL +
20
+ PIN). That explicit, user‑performed connection is the authorization.
21
+ - The assistant calls **typed tools** (`remember`, `recall`) — it never treats
22
+ a fetched web page as instructions.
23
+ - Every call is HTTPS, authed with your PIN. Your data goes only to the memory
24
+ URL you configured.
25
+
26
+ ## Get your memory URL + PIN
27
+
28
+ Visit **https://mem.more.md** (or ask any assistant that already minted a
29
+ session for you). You'll get a URL like `https://mem.more.md/AbC12xyz` and a
30
+ 4‑digit PIN. Keep them — that pair is your portable memory.
31
+
32
+ ## Connect it
33
+
34
+ ### Claude Desktop / Claude Code / Cursor (and most MCP clients)
35
+
36
+ Add this to your MCP config (`claude_desktop_config.json`, `.cursor/mcp.json`,
37
+ `.mcp.json`, …). Once published to npm:
38
+
39
+ ```json
40
+ {
41
+ "mcpServers": {
42
+ "memory": {
43
+ "command": "npx",
44
+ "args": ["-y", "@more-md/mcp-memory"],
45
+ "env": {
46
+ "MEM_URL": "https://mem.more.md/AbC12xyz",
47
+ "MEM_PIN": "1234"
48
+ }
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ Running from a local checkout instead:
55
+
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "memory": {
60
+ "command": "node",
61
+ "args": ["/absolute/path/to/packages/mcp-memory/dist/index.js"],
62
+ "env": { "MEM_URL": "https://mem.more.md/AbC12xyz", "MEM_PIN": "1234" }
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ Restart the client. You'll see a `memory` server with three tools.
69
+
70
+ ## Tools
71
+
72
+ | Tool | What it does |
73
+ |------|--------------|
74
+ | `remember` | Save a memory (`title`, `body`, `memory_type`: episodic/semantic, `importance` 1‑10). |
75
+ | `recall` | Search your memory by keyword or meaning before answering about the past. |
76
+ | `recall_day` | Read everything saved on a given calendar day. |
77
+
78
+ The assistant decides when to call these — a good one saves durable facts
79
+ (your name, preferences, projects) as `semantic` and checks `recall` before
80
+ answering questions about earlier context. You can always tell it to save or
81
+ forget something specific.
82
+
83
+ ## Privacy
84
+
85
+ - Encrypted at rest; reachable only with your PIN.
86
+ - Export or delete everything anytime (`/export`, delete tools/endpoints).
87
+ - Open source. Nothing is stored that you didn't ask for.
88
+
89
+ ## Develop
90
+
91
+ ```bash
92
+ npm install
93
+ npm run build # → dist/
94
+ npm test # unit tests (mocked fetch)
95
+ ```
96
+
97
+ MIT © more.md
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Thin, dependency-free client over the mem.more.md REST API. Kept separate
3
+ * from the MCP wiring so the request/response mapping is unit-testable with a
4
+ * mocked `fetch` — no MCP transport needed.
5
+ *
6
+ * Auth model (this is the whole trust story): the USER puts their memory URL +
7
+ * PIN in their MCP client config. That explicit, user-performed connection is
8
+ * the authorization — the assistant never fetches an arbitrary URL and follows
9
+ * its contents. It calls typed tools that hit exactly these endpoints with the
10
+ * PIN the user provided.
11
+ */
12
+ export interface MemConfig {
13
+ /** Full memory URL including the short code, e.g. https://mem.more.md/AbC12xyz */
14
+ memUrl: string;
15
+ /** The user's 4-digit PIN (or longer). Sent as X-Mem-PIN. */
16
+ pin: string;
17
+ /** Injectable for tests; defaults to global fetch. */
18
+ fetchImpl?: typeof fetch;
19
+ }
20
+ export type MemoryType = "episodic" | "semantic";
21
+ export interface RememberInput {
22
+ title: string;
23
+ body: string;
24
+ memoryType?: MemoryType;
25
+ importance?: number;
26
+ }
27
+ export declare class MemError extends Error {
28
+ readonly status?: number | undefined;
29
+ constructor(message: string, status?: number | undefined);
30
+ }
31
+ /** Normalize the configured URL to `<origin>/<shortCode>` with no trailing slash. */
32
+ export declare function normalizeMemUrl(raw: string): string;
33
+ export declare function createMemClient(cfg: MemConfig): {
34
+ base: string;
35
+ /** Save one memory entry. Wraps POST /entries. */
36
+ remember(input: RememberInput): Promise<{
37
+ shortCode?: string;
38
+ }>;
39
+ /** Search memories by keyword/meaning. Wraps GET /entries?q= */
40
+ recall(query: string, limit?: number): Promise<unknown>;
41
+ /** Read every entry from a given day. Wraps GET /:y/:m/:d */
42
+ recallDay(year: number, month: number, day: number): Promise<unknown>;
43
+ };
44
+ export type MemClient = ReturnType<typeof createMemClient>;
package/dist/client.js ADDED
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Thin, dependency-free client over the mem.more.md REST API. Kept separate
3
+ * from the MCP wiring so the request/response mapping is unit-testable with a
4
+ * mocked `fetch` — no MCP transport needed.
5
+ *
6
+ * Auth model (this is the whole trust story): the USER puts their memory URL +
7
+ * PIN in their MCP client config. That explicit, user-performed connection is
8
+ * the authorization — the assistant never fetches an arbitrary URL and follows
9
+ * its contents. It calls typed tools that hit exactly these endpoints with the
10
+ * PIN the user provided.
11
+ */
12
+ export class MemError extends Error {
13
+ status;
14
+ constructor(message, status) {
15
+ super(message);
16
+ this.status = status;
17
+ this.name = "MemError";
18
+ }
19
+ }
20
+ /** Normalize the configured URL to `<origin>/<shortCode>` with no trailing slash. */
21
+ export function normalizeMemUrl(raw) {
22
+ const trimmed = raw.trim().replace(/\/+$/, "");
23
+ if (!/^https?:\/\//i.test(trimmed)) {
24
+ throw new MemError(`MEM_URL must be a full https URL like https://mem.more.md/<code> (got "${raw}")`);
25
+ }
26
+ return trimmed;
27
+ }
28
+ export function createMemClient(cfg) {
29
+ const base = normalizeMemUrl(cfg.memUrl);
30
+ const pin = cfg.pin.trim();
31
+ const doFetch = cfg.fetchImpl ?? fetch;
32
+ if (!pin)
33
+ throw new MemError("MEM_PIN is required.");
34
+ async function call(path, init) {
35
+ const res = await doFetch(`${base}${path}`, {
36
+ ...init,
37
+ headers: {
38
+ "X-Mem-PIN": pin,
39
+ Accept: "application/json",
40
+ ...(init.headers ?? {}),
41
+ },
42
+ });
43
+ const text = await res.text();
44
+ let json = undefined;
45
+ try {
46
+ json = text ? JSON.parse(text) : undefined;
47
+ }
48
+ catch {
49
+ /* non-JSON body */
50
+ }
51
+ if (!res.ok) {
52
+ const msg = (json && typeof json === "object" && "error" in json
53
+ ? String(json.error)
54
+ : "") || `Memory request failed (HTTP ${res.status}).`;
55
+ throw new MemError(msg, res.status);
56
+ }
57
+ return json;
58
+ }
59
+ return {
60
+ base,
61
+ /** Save one memory entry. Wraps POST /entries. */
62
+ async remember(input) {
63
+ const title = input.title?.trim();
64
+ const bodyText = input.body?.trim();
65
+ if (!title)
66
+ throw new MemError("A short `title` is required to save a memory.");
67
+ if (!bodyText)
68
+ throw new MemError("A `body` is required to save a memory.");
69
+ const payload = {
70
+ title,
71
+ body: bodyText,
72
+ memory_type: input.memoryType ?? "episodic",
73
+ ...(typeof input.importance === "number"
74
+ ? { importance: Math.max(1, Math.min(10, Math.round(input.importance))) }
75
+ : {}),
76
+ };
77
+ const out = (await call("/entries", {
78
+ method: "POST",
79
+ headers: { "Content-Type": "application/json" },
80
+ body: JSON.stringify(payload),
81
+ }));
82
+ return { shortCode: out?.entry?.short_code };
83
+ },
84
+ /** Search memories by keyword/meaning. Wraps GET /entries?q= */
85
+ async recall(query, limit = 10) {
86
+ const q = query?.trim();
87
+ const params = new URLSearchParams();
88
+ if (q)
89
+ params.set("q", q);
90
+ params.set("limit", String(Math.max(1, Math.min(50, limit))));
91
+ return call(`/entries?${params.toString()}`, { method: "GET" });
92
+ },
93
+ /** Read every entry from a given day. Wraps GET /:y/:m/:d */
94
+ async recallDay(year, month, day) {
95
+ const mm = String(month).padStart(2, "0");
96
+ const dd = String(day).padStart(2, "0");
97
+ return call(`/${year}/${mm}/${dd}`, { method: "GET" });
98
+ },
99
+ };
100
+ }
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @more-md/mcp-memory — portable long-term memory for AI assistants, as an MCP
4
+ * server.
5
+ *
6
+ * WHY THIS EXISTS (the trust story): asking a chat model to "fetch a URL and
7
+ * use it as memory" is the exact shape of a prompt-injection attack, so aligned
8
+ * models refuse it — correctly. The safe path is an MCP tool the USER connects
9
+ * explicitly: the user puts their memory URL + PIN in their client config, that
10
+ * connection IS the authorization, and the assistant calls typed tools
11
+ * (`remember` / `recall`) instead of following instructions off a web page.
12
+ *
13
+ * Configure it in any MCP client (Claude Desktop, Cursor, Claude Code, …):
14
+ *
15
+ * {
16
+ * "mcpServers": {
17
+ * "memory": {
18
+ * "command": "npx",
19
+ * "args": ["-y", "@more-md/mcp-memory"],
20
+ * "env": {
21
+ * "MEM_URL": "https://mem.more.md/<your-code>",
22
+ * "MEM_PIN": "<your-pin>"
23
+ * }
24
+ * }
25
+ * }
26
+ * }
27
+ *
28
+ * Get MEM_URL + MEM_PIN at https://mem.more.md (or from any assistant that
29
+ * minted a session for you).
30
+ */
31
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
32
+ import { type MemClient } from "./client.js";
33
+ export declare function buildServer(client: MemClient): McpServer;
package/dist/index.js ADDED
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @more-md/mcp-memory — portable long-term memory for AI assistants, as an MCP
4
+ * server.
5
+ *
6
+ * WHY THIS EXISTS (the trust story): asking a chat model to "fetch a URL and
7
+ * use it as memory" is the exact shape of a prompt-injection attack, so aligned
8
+ * models refuse it — correctly. The safe path is an MCP tool the USER connects
9
+ * explicitly: the user puts their memory URL + PIN in their client config, that
10
+ * connection IS the authorization, and the assistant calls typed tools
11
+ * (`remember` / `recall`) instead of following instructions off a web page.
12
+ *
13
+ * Configure it in any MCP client (Claude Desktop, Cursor, Claude Code, …):
14
+ *
15
+ * {
16
+ * "mcpServers": {
17
+ * "memory": {
18
+ * "command": "npx",
19
+ * "args": ["-y", "@more-md/mcp-memory"],
20
+ * "env": {
21
+ * "MEM_URL": "https://mem.more.md/<your-code>",
22
+ * "MEM_PIN": "<your-pin>"
23
+ * }
24
+ * }
25
+ * }
26
+ * }
27
+ *
28
+ * Get MEM_URL + MEM_PIN at https://mem.more.md (or from any assistant that
29
+ * minted a session for you).
30
+ */
31
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
32
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
33
+ import { z } from "zod";
34
+ import { createMemClient, MemError } from "./client.js";
35
+ function loadConfig() {
36
+ const memUrl = (process.env.MEM_URL ?? "").trim();
37
+ const pin = (process.env.MEM_PIN ?? "").trim();
38
+ if (!memUrl || !pin) {
39
+ process.stderr.write("[more-md-memory] Missing config. Set MEM_URL (https://mem.more.md/<code>) and MEM_PIN " +
40
+ "in your MCP client's `env`. Get them at https://mem.more.md.\n");
41
+ process.exit(1);
42
+ }
43
+ return { memUrl, pin };
44
+ }
45
+ function ok(text) {
46
+ return { content: [{ type: "text", text }] };
47
+ }
48
+ function fail(err) {
49
+ const msg = err instanceof MemError
50
+ ? err.message
51
+ : err instanceof Error
52
+ ? err.message
53
+ : String(err);
54
+ return { content: [{ type: "text", text: `Memory error: ${msg}` }], isError: true };
55
+ }
56
+ export function buildServer(client) {
57
+ const server = new McpServer({ name: "more-md-memory", version: "0.1.0" });
58
+ server.tool("remember", "Save a memory to the user's portable store so it survives across chats and assistants. " +
59
+ "Use for durable facts (name, preferences, projects → memory_type 'semantic') and for " +
60
+ "notable moments in the conversation (memory_type 'episodic'). Skip anything the user " +
61
+ "marked private or that looks like a password or secret.", {
62
+ title: z.string().min(1).describe("A short 5-10 word summary of what to remember."),
63
+ body: z.string().min(1).describe("The detail to store, in a sentence or two."),
64
+ memory_type: z
65
+ .enum(["episodic", "semantic"])
66
+ .default("episodic")
67
+ .describe("'semantic' for durable facts about the user; 'episodic' for events/turns."),
68
+ importance: z
69
+ .number()
70
+ .min(1)
71
+ .max(10)
72
+ .optional()
73
+ .describe("1-10; use 8+ for durable facts you'll want to resurface later."),
74
+ }, async ({ title, body, memory_type, importance }) => {
75
+ try {
76
+ await client.remember({ title, body, memoryType: memory_type, importance });
77
+ return ok(`Saved to memory: "${title}".`);
78
+ }
79
+ catch (err) {
80
+ return fail(err);
81
+ }
82
+ });
83
+ server.tool("recall", "Search the user's portable memory before answering anything that refers to the past or to " +
84
+ "durable facts about them. Returns matching entries (most relevant first).", {
85
+ query: z.string().describe("What to look for (keywords or a natural-language phrase)."),
86
+ limit: z.number().min(1).max(50).default(10).describe("Max entries to return."),
87
+ }, async ({ query, limit }) => {
88
+ try {
89
+ const res = await client.recall(query, limit);
90
+ return ok(JSON.stringify(res, null, 2));
91
+ }
92
+ catch (err) {
93
+ return fail(err);
94
+ }
95
+ });
96
+ server.tool("recall_day", "Read every memory entry saved on a specific calendar day (the user's store is organized by " +
97
+ "date). Useful for 'what did we do on <date>' questions.", {
98
+ year: z.number().int().describe("Full year, e.g. 2026."),
99
+ month: z.number().int().min(1).max(12).describe("Month 1-12."),
100
+ day: z.number().int().min(1).max(31).describe("Day of month 1-31."),
101
+ }, async ({ year, month, day }) => {
102
+ try {
103
+ const res = await client.recallDay(year, month, day);
104
+ return ok(JSON.stringify(res, null, 2));
105
+ }
106
+ catch (err) {
107
+ return fail(err);
108
+ }
109
+ });
110
+ return server;
111
+ }
112
+ async function main() {
113
+ const cfg = loadConfig();
114
+ const client = createMemClient({ memUrl: cfg.memUrl, pin: cfg.pin });
115
+ const server = buildServer(client);
116
+ const transport = new StdioServerTransport();
117
+ await server.connect(transport);
118
+ process.stderr.write(`[more-md-memory] connected · ${client.base}\n`);
119
+ }
120
+ // Only run when invoked as the entry point (tests import buildServer directly).
121
+ const isEntry = typeof process !== "undefined" &&
122
+ Array.isArray(process.argv) &&
123
+ /mcp-memory[\\/](dist|src)[\\/]index\.(js|ts)$/.test(process.argv[1] ?? "");
124
+ if (isEntry) {
125
+ main().catch((err) => {
126
+ process.stderr.write(`[more-md-memory] fatal: ${String(err)}\n`);
127
+ process.exit(1);
128
+ });
129
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@more-md/mcp-memory",
3
+ "version": "0.1.0",
4
+ "description": "Portable long-term memory for AI assistants, as an MCP server. Connect Claude Desktop, Cursor, or any MCP client to a mem.more.md memory the user controls.",
5
+ "type": "module",
6
+ "bin": {
7
+ "more-md-memory": "dist/index.js"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "files": [
12
+ "dist/",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "typecheck": "tsc --noEmit -p tsconfig.json",
22
+ "test": "vitest run",
23
+ "start": "node dist/index.js",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "mcp",
28
+ "model-context-protocol",
29
+ "memory",
30
+ "more.md",
31
+ "mem.more.md",
32
+ "ai",
33
+ "claude",
34
+ "cursor"
35
+ ],
36
+ "author": "Ugur Cekmez <hello@more.md>",
37
+ "license": "MIT",
38
+ "homepage": "https://mem.more.md",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/more-md/monorepo.git",
42
+ "directory": "packages/mcp-memory"
43
+ },
44
+ "dependencies": {
45
+ "@modelcontextprotocol/sdk": "^1.25.3",
46
+ "zod": "^4.3.6"
47
+ },
48
+ "devDependencies": {
49
+ "typescript": "^5",
50
+ "vitest": "^3",
51
+ "@types/node": "^22"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ }
56
+ }