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 +21 -0
- package/README.md +193 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +201 -0
- package/dist/store.d.ts +19 -0
- package/dist/store.js +151 -0
- package/package.json +48 -0
- package/templates/memory.md +14 -0
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
|
package/dist/index.d.ts
ADDED
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);
|
package/dist/store.d.ts
ADDED
|
@@ -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
|
+
}
|