mcp-prose-memory 1.0.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 Soroush
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,193 @@
1
+ # mcp-prose-memory
2
+
3
+ An MCP (Model Context Protocol) server for prose-based persistent memory with markdown storage. Enables LLMs to maintain context across sessions using a structured markdown file.
4
+
5
+ ## Features
6
+
7
+ - Prose-based memory storage in markdown format
8
+ - Structured sections for organized context
9
+ - YAML frontmatter for metadata
10
+ - Simple tools for reading and updating memory
11
+ - Configurable storage location via environment variable
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install -g mcp-prose-memory
17
+ ```
18
+
19
+ Or use directly with npx:
20
+
21
+ ```bash
22
+ npx mcp-prose-memory
23
+ ```
24
+
25
+ ## Configuration
26
+
27
+ ### Claude Desktop
28
+
29
+ Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "memory": {
35
+ "command": "npx",
36
+ "args": ["mcp-prose-memory"]
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ### Claude CLI
43
+
44
+ Add to your Claude CLI configuration (`~/.claude/mcp-servers.json`):
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "memory": {
50
+ "command": "npx",
51
+ "args": ["mcp-prose-memory"]
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### Custom Memory Location
58
+
59
+ By default, memory is stored at `~/.mcp/memory.md`. Override with the `MEMORY_PATH` environment variable:
60
+
61
+ ```json
62
+ {
63
+ "mcpServers": {
64
+ "memory": {
65
+ "command": "npx",
66
+ "args": ["mcp-prose-memory"],
67
+ "env": {
68
+ "MEMORY_PATH": "/path/to/your/memory.md"
69
+ }
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## Document Structure
76
+
77
+ The memory document uses YAML frontmatter and markdown sections:
78
+
79
+ ```markdown
80
+ ---
81
+ version: 2
82
+ updated: 2025-01-15T10:30:00.000Z
83
+ ---
84
+
85
+ ## Work Context
86
+
87
+ Professional context, projects, colleagues, tools.
88
+
89
+ ## Personal Context
90
+
91
+ Location, preferences, interests, personal facts.
92
+
93
+ ## Top of Mind
94
+
95
+ Current focuses, active tasks.
96
+
97
+ ## Brief History
98
+
99
+ Past events, completed work.
100
+
101
+ ## Other Instructions
102
+
103
+ Standing rules, behavioral preferences.
104
+ ```
105
+
106
+ ## Tools
107
+
108
+ ### memory_get
109
+
110
+ Get the full memory document or a specific section.
111
+
112
+ **Parameters:**
113
+ - `section` (optional): One of `work`, `personal`, `top_of_mind`, `history`, `instructions`
114
+
115
+ **Example:**
116
+ ```json
117
+ { "section": "work" }
118
+ ```
119
+
120
+ ### memory_update_section
121
+
122
+ Replace the content of a specific section.
123
+
124
+ **Parameters:**
125
+ - `section` (required): Section to update
126
+ - `content` (required): New content (prose/markdown)
127
+
128
+ **Example:**
129
+ ```json
130
+ {
131
+ "section": "personal",
132
+ "content": "Based in Berlin, Germany. Prefers concise communication."
133
+ }
134
+ ```
135
+
136
+ ### memory_remember
137
+
138
+ Get the current document with guidance for integrating new information. Use this when asked to "remember" something.
139
+
140
+ **Parameters:**
141
+ - `info` (required): The information to remember
142
+
143
+ **Example:**
144
+ ```json
145
+ { "info": "User prefers dark mode" }
146
+ ```
147
+
148
+ ### memory_context
149
+
150
+ Get the full memory document for session initialization. Equivalent to `memory_get` without parameters.
151
+
152
+ ### memory_quick_add
153
+
154
+ Quickly append a fact to a section as a bullet point. More efficient than `memory_remember` + `memory_update_section` for simple facts.
155
+
156
+ **Parameters:**
157
+ - `section` (required): Section to append to
158
+ - `fact` (required): The fact to add
159
+
160
+ **Example:**
161
+ ```json
162
+ {
163
+ "section": "personal",
164
+ "fact": "Prefers tea over coffee"
165
+ }
166
+ ```
167
+
168
+ ## Sections
169
+
170
+ | Section | Purpose |
171
+ |---------|---------|
172
+ | `work` | Professional context, projects, colleagues, tools |
173
+ | `personal` | Location, preferences, interests, personal facts |
174
+ | `top_of_mind` | Current focuses, active tasks |
175
+ | `history` | Past events, completed work |
176
+ | `instructions` | Standing rules, behavioral preferences |
177
+
178
+ ## Development
179
+
180
+ ```bash
181
+ git clone https://github.com/gabrimatic/mcp-prose-memory.git
182
+ cd mcp-prose-memory
183
+ npm install
184
+ npm run build
185
+ ```
186
+
187
+ ## Developer
188
+
189
+ By [Soroush](https://gabrimatic.info)
190
+
191
+ ## License
192
+
193
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { getSection, updateSection, getFullContext, appendToSection, } from "./store.js";
6
+ const server = new Server({ name: "mcp-prose-memory", version: "1.0.0" }, { capabilities: { tools: {} } });
7
+ const SECTIONS = ["work", "personal", "top_of_mind", "history", "instructions"];
8
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
9
+ tools: [
10
+ {
11
+ name: "memory_get",
12
+ description: "Get full memory document or a specific section. Use without params for full doc.",
13
+ inputSchema: {
14
+ type: "object",
15
+ properties: {
16
+ section: {
17
+ type: "string",
18
+ enum: SECTIONS,
19
+ description: "Optional: specific section to retrieve (work|personal|top_of_mind|history|instructions)",
20
+ },
21
+ },
22
+ },
23
+ },
24
+ {
25
+ name: "memory_update_section",
26
+ description: "Replace the content of a specific section. Content should be prose/markdown.",
27
+ inputSchema: {
28
+ type: "object",
29
+ properties: {
30
+ section: {
31
+ type: "string",
32
+ enum: SECTIONS,
33
+ description: "Section to update: work|personal|top_of_mind|history|instructions",
34
+ },
35
+ content: {
36
+ type: "string",
37
+ description: "New content for the section (prose/markdown)",
38
+ },
39
+ },
40
+ required: ["section", "content"],
41
+ },
42
+ },
43
+ {
44
+ name: "memory_remember",
45
+ description: "Get current document with hint to integrate new info. Use when asked to 'remember' something.",
46
+ inputSchema: {
47
+ type: "object",
48
+ properties: {
49
+ info: {
50
+ type: "string",
51
+ description: "The information to remember (for context)",
52
+ },
53
+ },
54
+ required: ["info"],
55
+ },
56
+ },
57
+ {
58
+ name: "memory_context",
59
+ description: "Get full memory document for session start. Returns the complete markdown file.",
60
+ inputSchema: {
61
+ type: "object",
62
+ properties: {},
63
+ },
64
+ },
65
+ {
66
+ name: "memory_quick_add",
67
+ description: "Quickly append a fact to a section. Use for simple 'remember X' requests. More efficient than memory_remember + memory_update_section.",
68
+ inputSchema: {
69
+ type: "object",
70
+ properties: {
71
+ section: {
72
+ type: "string",
73
+ enum: SECTIONS,
74
+ description: "Section: work|personal|top_of_mind|history|instructions"
75
+ },
76
+ fact: {
77
+ type: "string",
78
+ description: "The fact to add (will be appended as a bullet point)"
79
+ }
80
+ },
81
+ required: ["section", "fact"]
82
+ }
83
+ },
84
+ ],
85
+ }));
86
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
87
+ const { name, arguments: args } = request.params;
88
+ try {
89
+ switch (name) {
90
+ case "memory_get": {
91
+ if (args?.section) {
92
+ const sectionType = args.section;
93
+ if (!SECTIONS.includes(sectionType)) {
94
+ throw new Error(`Invalid section: ${sectionType}`);
95
+ }
96
+ const content = await getSection(sectionType);
97
+ return {
98
+ content: [
99
+ {
100
+ type: "text",
101
+ text: content || `(${sectionType} section is empty)`,
102
+ },
103
+ ],
104
+ };
105
+ }
106
+ const fullDoc = await getFullContext();
107
+ return {
108
+ content: [
109
+ {
110
+ type: "text",
111
+ text: fullDoc || "(No memory document exists yet)",
112
+ },
113
+ ],
114
+ };
115
+ }
116
+ case "memory_update_section": {
117
+ const sectionType = args?.section;
118
+ const content = args?.content;
119
+ if (!sectionType || !SECTIONS.includes(sectionType)) {
120
+ throw new Error(`Invalid section: ${sectionType}`);
121
+ }
122
+ if (!content) {
123
+ throw new Error("Content is required");
124
+ }
125
+ const doc = await updateSection(sectionType, content);
126
+ return {
127
+ content: [
128
+ {
129
+ type: "text",
130
+ text: `Updated ${sectionType} section. Document last updated: ${doc.metadata.updated}`,
131
+ },
132
+ ],
133
+ };
134
+ }
135
+ case "memory_remember": {
136
+ const info = args?.info;
137
+ if (!info) {
138
+ throw new Error("Info is required");
139
+ }
140
+ const fullDoc = await getFullContext();
141
+ const hint = `
142
+ To integrate this information into memory:
143
+
144
+ 1. Choose the appropriate section:
145
+ - work: professional context, projects, colleagues, tools
146
+ - personal: location, preferences, interests, personal facts
147
+ - top_of_mind: current focuses, active tasks
148
+ - history: past events, completed work
149
+ - instructions: standing rules, behavioral preferences
150
+
151
+ 2. Keep content concise and well-organized
152
+ 3. Use memory_update_section to save changes
153
+
154
+ INFO TO REMEMBER: ${info}
155
+
156
+ CURRENT DOCUMENT:
157
+ ${fullDoc || "(empty)"}`;
158
+ return { content: [{ type: "text", text: hint }] };
159
+ }
160
+ case "memory_context": {
161
+ const fullDoc = await getFullContext();
162
+ return {
163
+ content: [
164
+ {
165
+ type: "text",
166
+ text: fullDoc || "(No memory document exists yet)",
167
+ },
168
+ ],
169
+ };
170
+ }
171
+ case "memory_quick_add": {
172
+ const sectionType = args?.section;
173
+ const fact = args?.fact;
174
+ if (!sectionType || !SECTIONS.includes(sectionType)) {
175
+ throw new Error(`Invalid section: ${sectionType}`);
176
+ }
177
+ if (!fact) {
178
+ throw new Error("Fact is required");
179
+ }
180
+ await appendToSection(sectionType, fact);
181
+ return {
182
+ content: [{ type: "text", text: `Added to ${sectionType}.` }]
183
+ };
184
+ }
185
+ default:
186
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
187
+ }
188
+ }
189
+ catch (e) {
190
+ return {
191
+ content: [
192
+ {
193
+ type: "text",
194
+ text: `Error: ${e instanceof Error ? e.message : e}`,
195
+ },
196
+ ],
197
+ };
198
+ }
199
+ });
200
+ const transport = new StdioServerTransport();
201
+ server.connect(transport).catch(console.error);
@@ -0,0 +1,19 @@
1
+ export type SectionType = "work" | "personal" | "top_of_mind" | "history" | "instructions";
2
+ export interface DocumentMetadata {
3
+ version: number;
4
+ updated: string;
5
+ }
6
+ export interface MemoryDocument {
7
+ metadata: DocumentMetadata;
8
+ sections: Record<SectionType, string>;
9
+ }
10
+ declare const MEMORY_PATH: string;
11
+ declare const SECTION_HEADERS: Record<SectionType, string>;
12
+ declare const SECTION_ORDER: SectionType[];
13
+ export declare function loadDocument(): Promise<MemoryDocument>;
14
+ export declare function saveDocument(doc: MemoryDocument): Promise<void>;
15
+ export declare function getSection(sectionType: SectionType): Promise<string>;
16
+ export declare function updateSection(sectionType: SectionType, content: string): Promise<MemoryDocument>;
17
+ export declare function getFullContext(): Promise<string>;
18
+ export declare function appendToSection(sectionType: SectionType, fact: string): Promise<void>;
19
+ export { MEMORY_PATH, SECTION_ORDER, SECTION_HEADERS };
package/dist/store.js ADDED
@@ -0,0 +1,151 @@
1
+ import { readFile, writeFile, mkdir } from "fs/promises";
2
+ import { existsSync } from "fs";
3
+ import { dirname } from "path";
4
+ import { homedir } from "os";
5
+ const MEMORY_PATH = process.env.MEMORY_PATH || `${homedir()}/.mcp/memory.md`;
6
+ const SECTION_HEADERS = {
7
+ work: "## Work Context",
8
+ personal: "## Personal Context",
9
+ top_of_mind: "## Top of Mind",
10
+ history: "## Brief History",
11
+ instructions: "## Other Instructions",
12
+ };
13
+ const SECTION_ORDER = ["work", "personal", "top_of_mind", "history", "instructions"];
14
+ async function ensureDir(path) {
15
+ const dir = dirname(path);
16
+ if (!existsSync(dir))
17
+ await mkdir(dir, { recursive: true });
18
+ }
19
+ function parseYamlFrontmatter(content) {
20
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
21
+ if (!frontmatterMatch) {
22
+ return {
23
+ metadata: { version: 2, updated: new Date().toISOString() },
24
+ body: content,
25
+ };
26
+ }
27
+ const [, yaml, body] = frontmatterMatch;
28
+ const metadata = { version: 2, updated: new Date().toISOString() };
29
+ for (const line of yaml.split("\n")) {
30
+ const match = line.match(/^(\w+):\s*(.+)$/);
31
+ if (match) {
32
+ const [, key, value] = match;
33
+ if (key === "version")
34
+ metadata.version = parseInt(value, 10);
35
+ if (key === "updated")
36
+ metadata.updated = value;
37
+ }
38
+ }
39
+ return { metadata, body };
40
+ }
41
+ function parseSections(body) {
42
+ const sections = {
43
+ work: "",
44
+ personal: "",
45
+ top_of_mind: "",
46
+ history: "",
47
+ instructions: "",
48
+ };
49
+ const sectionPattern = /^## (Work Context|Personal Context|Top of Mind|Brief History|Other Instructions)\n/gm;
50
+ const headerToType = {
51
+ "Work Context": "work",
52
+ "Personal Context": "personal",
53
+ "Top of Mind": "top_of_mind",
54
+ "Brief History": "history",
55
+ "Other Instructions": "instructions",
56
+ };
57
+ const matches = [];
58
+ let match;
59
+ while ((match = sectionPattern.exec(body)) !== null) {
60
+ const type = headerToType[match[1]];
61
+ if (type) {
62
+ matches.push({
63
+ type,
64
+ start: match.index,
65
+ headerEnd: match.index + match[0].length,
66
+ });
67
+ }
68
+ }
69
+ for (let i = 0; i < matches.length; i++) {
70
+ const current = matches[i];
71
+ const nextStart = matches[i + 1]?.start ?? body.length;
72
+ const content = body.slice(current.headerEnd, nextStart).trim();
73
+ sections[current.type] = content;
74
+ }
75
+ return sections;
76
+ }
77
+ function serializeDocument(doc) {
78
+ const lines = [
79
+ "---",
80
+ `version: ${doc.metadata.version}`,
81
+ `updated: ${doc.metadata.updated}`,
82
+ "---",
83
+ "",
84
+ ];
85
+ for (const sectionType of SECTION_ORDER) {
86
+ lines.push(SECTION_HEADERS[sectionType]);
87
+ lines.push("");
88
+ const content = doc.sections[sectionType];
89
+ if (content) {
90
+ lines.push(content);
91
+ lines.push("");
92
+ }
93
+ }
94
+ return lines.join("\n");
95
+ }
96
+ export async function loadDocument() {
97
+ try {
98
+ const content = await readFile(MEMORY_PATH, "utf-8");
99
+ const { metadata, body } = parseYamlFrontmatter(content);
100
+ const sections = parseSections(body);
101
+ return { metadata, sections };
102
+ }
103
+ catch (e) {
104
+ if (e.code === "ENOENT") {
105
+ return {
106
+ metadata: { version: 2, updated: new Date().toISOString() },
107
+ sections: {
108
+ work: "",
109
+ personal: "",
110
+ top_of_mind: "",
111
+ history: "",
112
+ instructions: "",
113
+ },
114
+ };
115
+ }
116
+ throw e;
117
+ }
118
+ }
119
+ export async function saveDocument(doc) {
120
+ doc.metadata.updated = new Date().toISOString();
121
+ await ensureDir(MEMORY_PATH);
122
+ await writeFile(MEMORY_PATH, serializeDocument(doc));
123
+ }
124
+ export async function getSection(sectionType) {
125
+ const doc = await loadDocument();
126
+ return doc.sections[sectionType];
127
+ }
128
+ export async function updateSection(sectionType, content) {
129
+ const doc = await loadDocument();
130
+ doc.sections[sectionType] = content.trim();
131
+ await saveDocument(doc);
132
+ return doc;
133
+ }
134
+ export async function getFullContext() {
135
+ try {
136
+ return await readFile(MEMORY_PATH, "utf-8");
137
+ }
138
+ catch (e) {
139
+ if (e.code === "ENOENT") {
140
+ return "";
141
+ }
142
+ throw e;
143
+ }
144
+ }
145
+ export async function appendToSection(sectionType, fact) {
146
+ const doc = await loadDocument();
147
+ const existing = doc.sections[sectionType].trim();
148
+ doc.sections[sectionType] = existing ? `${existing}\n- ${fact.trim()}` : `- ${fact.trim()}`;
149
+ await saveDocument(doc);
150
+ }
151
+ export { MEMORY_PATH, SECTION_ORDER, SECTION_HEADERS };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "mcp-prose-memory",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for prose-based persistent memory with markdown storage",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "bin": {
9
+ "mcp-prose-memory": "dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "templates",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "prepare": "npm run build"
20
+ },
21
+ "dependencies": {
22
+ "@modelcontextprotocol/sdk": "^1.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.0.0",
26
+ "typescript": "^5.6.0"
27
+ },
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ },
31
+ "keywords": [
32
+ "mcp",
33
+ "model-context-protocol",
34
+ "memory",
35
+ "claude",
36
+ "ai",
37
+ "markdown"
38
+ ],
39
+ "author": "Soroush (https://gabrimatic.info)",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/gabrimatic/mcp-prose-memory.git"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/gabrimatic/mcp-prose-memory/issues"
46
+ },
47
+ "homepage": "https://github.com/gabrimatic/mcp-prose-memory#readme"
48
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ version: 2
3
+ updated:
4
+ ---
5
+
6
+ ## Work Context
7
+
8
+ ## Personal Context
9
+
10
+ ## Top of Mind
11
+
12
+ ## Brief History
13
+
14
+ ## Other Instructions