project-mem-mcp 0.1.0__tar.gz
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.
- project_mem_mcp-0.1.0/LICENSE +21 -0
- project_mem_mcp-0.1.0/PKG-INFO +183 -0
- project_mem_mcp-0.1.0/README.md +171 -0
- project_mem_mcp-0.1.0/pyproject.toml +20 -0
- project_mem_mcp-0.1.0/setup.cfg +4 -0
- project_mem_mcp-0.1.0/src/project_mem_mcp/__init__.py +1 -0
- project_mem_mcp-0.1.0/src/project_mem_mcp/server.py +375 -0
- project_mem_mcp-0.1.0/src/project_mem_mcp.egg-info/PKG-INFO +183 -0
- project_mem_mcp-0.1.0/src/project_mem_mcp.egg-info/SOURCES.txt +11 -0
- project_mem_mcp-0.1.0/src/project_mem_mcp.egg-info/dependency_links.txt +1 -0
- project_mem_mcp-0.1.0/src/project_mem_mcp.egg-info/entry_points.txt +2 -0
- project_mem_mcp-0.1.0/src/project_mem_mcp.egg-info/requires.txt +1 -0
- project_mem_mcp-0.1.0/src/project_mem_mcp.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 PYNESYS LLC.
|
|
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.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: project-mem-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An MCP Server to store and retreive project information from memory file
|
|
5
|
+
Author: PYNESYS LLC
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: fastmcp<3.0.0,>=2.2.0
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# Project Memory MCP
|
|
14
|
+
|
|
15
|
+
An MCP Server to store and retrieve project information from memory file. This allows AI agents (like Claude) to maintain persistent memory about projects between conversations.
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
Project Memory MCP provides a simple way to:
|
|
20
|
+
- Store project information in Markdown format
|
|
21
|
+
- Retrieve project information at the beginning of conversations
|
|
22
|
+
- Update project information using patches
|
|
23
|
+
|
|
24
|
+
The memory is stored in a `MEMORY.md` file in each project directory.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
### Using uvx
|
|
29
|
+
|
|
30
|
+
This method uses `uvx` (from the `uv` Python package manager) to run the server without permanent installation:
|
|
31
|
+
|
|
32
|
+
#### Prerequisites
|
|
33
|
+
|
|
34
|
+
Install `uvx` from [uv](https://docs.astral.sh/uv/installation/) if you don't have it already.
|
|
35
|
+
|
|
36
|
+
#### Set up MCP Client (Claude Desktop, Cursor, etc.)
|
|
37
|
+
|
|
38
|
+
Merge the following config with your existing config file (e.g. `claude_desktop_config.json`):
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"project-memory": {
|
|
44
|
+
"command": "uvx",
|
|
45
|
+
"args": [
|
|
46
|
+
"project-mem-mcp",
|
|
47
|
+
"--allowed-dir", "/Users/your-username/projects",
|
|
48
|
+
"--allowed-dir", "/Users/your-username/Documents/code"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> **Note:** Replace `/Users/your-username` with the actual path to your own projects and code directories.
|
|
56
|
+
|
|
57
|
+
### Install from Source
|
|
58
|
+
|
|
59
|
+
#### Prerequisites
|
|
60
|
+
|
|
61
|
+
- Python 3.11 or higher
|
|
62
|
+
- Pip package manager
|
|
63
|
+
|
|
64
|
+
#### Clone the repository
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
git clone https://github.com/your-username/project-mem-mcp.git
|
|
68
|
+
python -m venv venv
|
|
69
|
+
source venv/bin/activate
|
|
70
|
+
pip install -e .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### Set up MCP Client (Claude Desktop, Cursor, etc.)
|
|
74
|
+
|
|
75
|
+
Merge the following config with your existing config file (e.g. `claude_desktop_config.json`):
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"mcpServers": {
|
|
80
|
+
"project-memory": {
|
|
81
|
+
"command": "path/to/your/venv/bin/project-mem-mcp",
|
|
82
|
+
"args": [
|
|
83
|
+
"--allowed-dir", "/Users/your-username/projects",
|
|
84
|
+
"--allowed-dir", "/Users/your-username/Documents/code"
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
> **Note:** Replace `/Users/your-username` with the actual path to your own projects and code directories.
|
|
92
|
+
|
|
93
|
+
## Arguments
|
|
94
|
+
|
|
95
|
+
The `--allowed-dir` argument is used to specify the directories that the server has access to. You can use it multiple times to allow access to multiple directories. All directories inside the allowed directories are also allowed.
|
|
96
|
+
It is optional. If not provided, the server will only have access to the home directory of the user running the server.
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
## Usage
|
|
100
|
+
|
|
101
|
+
The MCP server is started by the client (e.g., Claude Desktop) based on the configuration you provide. You don't need to start the server manually.
|
|
102
|
+
|
|
103
|
+
### Tools
|
|
104
|
+
|
|
105
|
+
Project Memory MCP provides three tools:
|
|
106
|
+
|
|
107
|
+
#### get_project_memory
|
|
108
|
+
|
|
109
|
+
Retrieves the entire project memory in Markdown format. Should be used at the beginning of every conversation about a project.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
get_project_memory(project_path: str) -> str
|
|
113
|
+
```
|
|
114
|
+
- **project_path**: Full path to the project directory.
|
|
115
|
+
- Returns the content of the MEMORY.md file as a string.
|
|
116
|
+
- Raises `FileNotFoundError` if the project or memory file does not exist.
|
|
117
|
+
- Raises `PermissionError` if the project path is not in allowed directories.
|
|
118
|
+
|
|
119
|
+
#### set_project_memory
|
|
120
|
+
|
|
121
|
+
Sets (overwrites) the entire project memory. Use this when creating a new memory file, replacing the whole memory, or if `update_project_memory` fails.
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
set_project_memory(project_path: str, project_info: str)
|
|
125
|
+
```
|
|
126
|
+
- **project_path**: Full path to the project directory.
|
|
127
|
+
- **project_info**: Complete project information in Markdown format.
|
|
128
|
+
- Overwrites the MEMORY.md file with the provided content.
|
|
129
|
+
- Raises `FileNotFoundError` if the project path does not exist.
|
|
130
|
+
- Raises `PermissionError` if the project path is not in allowed directories.
|
|
131
|
+
|
|
132
|
+
#### update_project_memory
|
|
133
|
+
|
|
134
|
+
Updates the project memory by applying one or more block-based patches to the memory file. This is more efficient for small changes.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
update_project_memory(project_path: str, patch_content: str)
|
|
138
|
+
```
|
|
139
|
+
- **project_path**: Full path to the project directory.
|
|
140
|
+
- **patch_content**: Block-based patch content using SEARCH/REPLACE markers (see below).
|
|
141
|
+
- Each patch block must have the following format:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
<<<<<<< SEARCH
|
|
145
|
+
Text to find in the memory file
|
|
146
|
+
=======
|
|
147
|
+
Text to replace it with
|
|
148
|
+
>>>>>>> REPLACE
|
|
149
|
+
```
|
|
150
|
+
Multiple blocks can be included in a single request.
|
|
151
|
+
- Each search text must appear **exactly once** in the file, otherwise an error is raised.
|
|
152
|
+
- Raises `FileNotFoundError` if the project or memory file does not exist.
|
|
153
|
+
- Raises `ValueError` if the patch format is invalid or the search text is not unique.
|
|
154
|
+
- Raises `RuntimeError` if patch application fails for any reason.
|
|
155
|
+
|
|
156
|
+
### Example Workflow
|
|
157
|
+
|
|
158
|
+
1. Begin a conversation with LLM about a project
|
|
159
|
+
2. LLM uses `get_project_memory` to retrieve project information
|
|
160
|
+
3. Throughout the conversation, LLM uses `update_project_memory` to persist new information
|
|
161
|
+
4. If the update fails, LLM can use `set_project_memory` instead
|
|
162
|
+
|
|
163
|
+
#### Claude Desktop
|
|
164
|
+
|
|
165
|
+
if you use Claude Desktop, it is best to use the project feature.
|
|
166
|
+
|
|
167
|
+
Edit the project instructions:
|
|
168
|
+
- Add a line like this: "The path of the project is <project_path>"
|
|
169
|
+
- If it does not always use the memory, you can add a line like this: "Always use the project memory, it is not optional"
|
|
170
|
+
|
|
171
|
+
## Security Considerations
|
|
172
|
+
|
|
173
|
+
- Memory files should never contain sensitive information
|
|
174
|
+
- Project paths are validated against allowed directories
|
|
175
|
+
- All file operations are restricted to allowed directories
|
|
176
|
+
|
|
177
|
+
## Dependencies
|
|
178
|
+
|
|
179
|
+
- fastmcp (>=2.2.0, <3.0.0)
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Project Memory MCP
|
|
2
|
+
|
|
3
|
+
An MCP Server to store and retrieve project information from memory file. This allows AI agents (like Claude) to maintain persistent memory about projects between conversations.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Project Memory MCP provides a simple way to:
|
|
8
|
+
- Store project information in Markdown format
|
|
9
|
+
- Retrieve project information at the beginning of conversations
|
|
10
|
+
- Update project information using patches
|
|
11
|
+
|
|
12
|
+
The memory is stored in a `MEMORY.md` file in each project directory.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
### Using uvx
|
|
17
|
+
|
|
18
|
+
This method uses `uvx` (from the `uv` Python package manager) to run the server without permanent installation:
|
|
19
|
+
|
|
20
|
+
#### Prerequisites
|
|
21
|
+
|
|
22
|
+
Install `uvx` from [uv](https://docs.astral.sh/uv/installation/) if you don't have it already.
|
|
23
|
+
|
|
24
|
+
#### Set up MCP Client (Claude Desktop, Cursor, etc.)
|
|
25
|
+
|
|
26
|
+
Merge the following config with your existing config file (e.g. `claude_desktop_config.json`):
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"project-memory": {
|
|
32
|
+
"command": "uvx",
|
|
33
|
+
"args": [
|
|
34
|
+
"project-mem-mcp",
|
|
35
|
+
"--allowed-dir", "/Users/your-username/projects",
|
|
36
|
+
"--allowed-dir", "/Users/your-username/Documents/code"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
> **Note:** Replace `/Users/your-username` with the actual path to your own projects and code directories.
|
|
44
|
+
|
|
45
|
+
### Install from Source
|
|
46
|
+
|
|
47
|
+
#### Prerequisites
|
|
48
|
+
|
|
49
|
+
- Python 3.11 or higher
|
|
50
|
+
- Pip package manager
|
|
51
|
+
|
|
52
|
+
#### Clone the repository
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
git clone https://github.com/your-username/project-mem-mcp.git
|
|
56
|
+
python -m venv venv
|
|
57
|
+
source venv/bin/activate
|
|
58
|
+
pip install -e .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### Set up MCP Client (Claude Desktop, Cursor, etc.)
|
|
62
|
+
|
|
63
|
+
Merge the following config with your existing config file (e.g. `claude_desktop_config.json`):
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"project-memory": {
|
|
69
|
+
"command": "path/to/your/venv/bin/project-mem-mcp",
|
|
70
|
+
"args": [
|
|
71
|
+
"--allowed-dir", "/Users/your-username/projects",
|
|
72
|
+
"--allowed-dir", "/Users/your-username/Documents/code"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> **Note:** Replace `/Users/your-username` with the actual path to your own projects and code directories.
|
|
80
|
+
|
|
81
|
+
## Arguments
|
|
82
|
+
|
|
83
|
+
The `--allowed-dir` argument is used to specify the directories that the server has access to. You can use it multiple times to allow access to multiple directories. All directories inside the allowed directories are also allowed.
|
|
84
|
+
It is optional. If not provided, the server will only have access to the home directory of the user running the server.
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
## Usage
|
|
88
|
+
|
|
89
|
+
The MCP server is started by the client (e.g., Claude Desktop) based on the configuration you provide. You don't need to start the server manually.
|
|
90
|
+
|
|
91
|
+
### Tools
|
|
92
|
+
|
|
93
|
+
Project Memory MCP provides three tools:
|
|
94
|
+
|
|
95
|
+
#### get_project_memory
|
|
96
|
+
|
|
97
|
+
Retrieves the entire project memory in Markdown format. Should be used at the beginning of every conversation about a project.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
get_project_memory(project_path: str) -> str
|
|
101
|
+
```
|
|
102
|
+
- **project_path**: Full path to the project directory.
|
|
103
|
+
- Returns the content of the MEMORY.md file as a string.
|
|
104
|
+
- Raises `FileNotFoundError` if the project or memory file does not exist.
|
|
105
|
+
- Raises `PermissionError` if the project path is not in allowed directories.
|
|
106
|
+
|
|
107
|
+
#### set_project_memory
|
|
108
|
+
|
|
109
|
+
Sets (overwrites) the entire project memory. Use this when creating a new memory file, replacing the whole memory, or if `update_project_memory` fails.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
set_project_memory(project_path: str, project_info: str)
|
|
113
|
+
```
|
|
114
|
+
- **project_path**: Full path to the project directory.
|
|
115
|
+
- **project_info**: Complete project information in Markdown format.
|
|
116
|
+
- Overwrites the MEMORY.md file with the provided content.
|
|
117
|
+
- Raises `FileNotFoundError` if the project path does not exist.
|
|
118
|
+
- Raises `PermissionError` if the project path is not in allowed directories.
|
|
119
|
+
|
|
120
|
+
#### update_project_memory
|
|
121
|
+
|
|
122
|
+
Updates the project memory by applying one or more block-based patches to the memory file. This is more efficient for small changes.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
update_project_memory(project_path: str, patch_content: str)
|
|
126
|
+
```
|
|
127
|
+
- **project_path**: Full path to the project directory.
|
|
128
|
+
- **patch_content**: Block-based patch content using SEARCH/REPLACE markers (see below).
|
|
129
|
+
- Each patch block must have the following format:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
<<<<<<< SEARCH
|
|
133
|
+
Text to find in the memory file
|
|
134
|
+
=======
|
|
135
|
+
Text to replace it with
|
|
136
|
+
>>>>>>> REPLACE
|
|
137
|
+
```
|
|
138
|
+
Multiple blocks can be included in a single request.
|
|
139
|
+
- Each search text must appear **exactly once** in the file, otherwise an error is raised.
|
|
140
|
+
- Raises `FileNotFoundError` if the project or memory file does not exist.
|
|
141
|
+
- Raises `ValueError` if the patch format is invalid or the search text is not unique.
|
|
142
|
+
- Raises `RuntimeError` if patch application fails for any reason.
|
|
143
|
+
|
|
144
|
+
### Example Workflow
|
|
145
|
+
|
|
146
|
+
1. Begin a conversation with LLM about a project
|
|
147
|
+
2. LLM uses `get_project_memory` to retrieve project information
|
|
148
|
+
3. Throughout the conversation, LLM uses `update_project_memory` to persist new information
|
|
149
|
+
4. If the update fails, LLM can use `set_project_memory` instead
|
|
150
|
+
|
|
151
|
+
#### Claude Desktop
|
|
152
|
+
|
|
153
|
+
if you use Claude Desktop, it is best to use the project feature.
|
|
154
|
+
|
|
155
|
+
Edit the project instructions:
|
|
156
|
+
- Add a line like this: "The path of the project is <project_path>"
|
|
157
|
+
- If it does not always use the memory, you can add a line like this: "Always use the project memory, it is not optional"
|
|
158
|
+
|
|
159
|
+
## Security Considerations
|
|
160
|
+
|
|
161
|
+
- Memory files should never contain sensitive information
|
|
162
|
+
- Project paths are validated against allowed directories
|
|
163
|
+
- All file operations are restricted to allowed directories
|
|
164
|
+
|
|
165
|
+
## Dependencies
|
|
166
|
+
|
|
167
|
+
- fastmcp (>=2.2.0, <3.0.0)
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "project-mem-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "An MCP Server to store and retreive project information from memory file"
|
|
5
|
+
authors = [{ name = "PYNESYS LLC" }]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.11"
|
|
8
|
+
license = { text = "MIT" }
|
|
9
|
+
|
|
10
|
+
dependencies = ["fastmcp>=2.2.0, <3.0.0"]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
project-mem-mcp = "project_mem_mcp.server:main"
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
17
|
+
build-backend = "setuptools.build_meta"
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
where = ["src"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import sys
|
|
3
|
+
import argparse
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from fastmcp import FastMCP
|
|
7
|
+
from pydantic.fields import Field
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
# No need for unidiff import anymore as we're using block-based patching
|
|
11
|
+
|
|
12
|
+
MEMORY_FILE = "MEMORY.md"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
mcp = FastMCP(
|
|
16
|
+
name="Project Memory MCP",
|
|
17
|
+
instructions=f"""
|
|
18
|
+
This MCP is for storing and retrieving project information to/from an English memory file, named
|
|
19
|
+
`{MEMORY_FILE}` in the project directory.
|
|
20
|
+
|
|
21
|
+
The memory file should store all information about the project in short and concise manner. It should be
|
|
22
|
+
good for humans and AI agents to catch up on the project status and progress quickly. Should contain descriptions,
|
|
23
|
+
ongoing tasks, tasks to do, discoveries, references to files and other project resources, URLs where to get more
|
|
24
|
+
information, etc.
|
|
25
|
+
|
|
26
|
+
Rules:
|
|
27
|
+
- This must be read by `get_project_memory` tool in the beginning of the first request of every conversation
|
|
28
|
+
if the conversation is about a project and a full project path is provided.
|
|
29
|
+
- At the end of every of your answers the project memory must be updated using the `update_project_memory`
|
|
30
|
+
or `set_project_memory` tool if any relevant changes were made to the project or any useful information
|
|
31
|
+
was discovered or discussed.
|
|
32
|
+
- The `set_project_memory` tool must be used to set the whole project memory if `update_project_memory`
|
|
33
|
+
failed or there is no project memory yet.
|
|
34
|
+
- Never store any sensitive information in the memory file, e.g. personal information, company
|
|
35
|
+
information, passwords, access tokens, email addresses, etc!
|
|
36
|
+
- The memory file **must be in English**!
|
|
37
|
+
- You can sometimes make it shorter, always remove any information that is no longer relevant.
|
|
38
|
+
- Always remove any information that is no longer relevant from the memory file.
|
|
39
|
+
- If the user talks about "memory" or "project memory", you should use the tools of this MCP.
|
|
40
|
+
"""
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
allowed_directories = []
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def eprint(*args, **kwargs):
|
|
47
|
+
print(*args, file=sys.stderr, **kwargs)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main():
|
|
51
|
+
# Process command line arguments
|
|
52
|
+
global allowed_directories
|
|
53
|
+
parser = argparse.ArgumentParser(description="Project Memory MCP server")
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
'--allowed-dir',
|
|
56
|
+
action='append',
|
|
57
|
+
dest='allowed_dirs',
|
|
58
|
+
required=True,
|
|
59
|
+
help='Allowed base directory for project paths (can be used multiple times)'
|
|
60
|
+
)
|
|
61
|
+
args = parser.parse_args()
|
|
62
|
+
allowed_directories = [str(Path(d).resolve()) for d in args.allowed_dirs]
|
|
63
|
+
|
|
64
|
+
if not allowed_directories:
|
|
65
|
+
allowed_directories = [str(Path.home().resolve())]
|
|
66
|
+
|
|
67
|
+
eprint(f"Allowed directories: {allowed_directories}")
|
|
68
|
+
|
|
69
|
+
# Run the MCP server
|
|
70
|
+
mcp.run()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
main()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
#
|
|
78
|
+
# Tools
|
|
79
|
+
#
|
|
80
|
+
|
|
81
|
+
@mcp.tool()
|
|
82
|
+
def get_project_memory(
|
|
83
|
+
project_path: str = Field(description="The full path to the project directory")
|
|
84
|
+
) -> str:
|
|
85
|
+
"""
|
|
86
|
+
Get the whole project memory for the given project path in Markdown format.
|
|
87
|
+
This must be used in the beginning of the first request of every conversation.
|
|
88
|
+
|
|
89
|
+
The memory file contains vital information about the project such as descriptions,
|
|
90
|
+
ongoing tasks, references to important files, and other project resources.
|
|
91
|
+
|
|
92
|
+
:return: The project memory content in Markdown format
|
|
93
|
+
:raises FileNotFoundError: If the project path doesn't exist or MEMORY.md is missing
|
|
94
|
+
:raises PermissionError: If the project path is not in allowed directories
|
|
95
|
+
"""
|
|
96
|
+
pp = Path(project_path).resolve()
|
|
97
|
+
|
|
98
|
+
# Check if the project path exists and is a directory
|
|
99
|
+
if not pp.exists() or not pp.is_dir():
|
|
100
|
+
raise FileNotFoundError(f"Project path {project_path} does not exist")
|
|
101
|
+
# Check if it is inside one of the allowed directories
|
|
102
|
+
if not any(str(pp).startswith(base) for base in allowed_directories):
|
|
103
|
+
raise PermissionError(f"Project path {project_path} is not in allowed directories")
|
|
104
|
+
|
|
105
|
+
with open(pp / MEMORY_FILE, "r") as f:
|
|
106
|
+
return f.read()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@mcp.tool()
|
|
110
|
+
def set_project_memory(
|
|
111
|
+
project_path: str = Field(description="The full path to the project directory"),
|
|
112
|
+
project_info: str = Field(description="Complete project information in Markdown format")
|
|
113
|
+
):
|
|
114
|
+
"""
|
|
115
|
+
Set the whole project memory for the given project path in Markdown format.
|
|
116
|
+
|
|
117
|
+
Use this tool when:
|
|
118
|
+
- Creating a memory file for a new project
|
|
119
|
+
- Completely replacing an existing memory file
|
|
120
|
+
- When `update_project_memory` fails to apply patches
|
|
121
|
+
- When extensive reorganization of the memory content is needed
|
|
122
|
+
|
|
123
|
+
Guidelines for content:
|
|
124
|
+
- The project memory file **must be in English**!
|
|
125
|
+
- Should be concise yet comprehensive
|
|
126
|
+
- Remove outdated information
|
|
127
|
+
- Include project overview, components, status, and important references
|
|
128
|
+
|
|
129
|
+
:raises FileNotFoundError: If the project path doesn't exist
|
|
130
|
+
:raises PermissionError: If the project path is not in allowed directories
|
|
131
|
+
"""
|
|
132
|
+
pp = Path(project_path).resolve()
|
|
133
|
+
if not pp.exists() or not pp.is_dir():
|
|
134
|
+
raise FileNotFoundError(f"Project path {project_path} does not exist")
|
|
135
|
+
if not any(str(pp).startswith(base) for base in allowed_directories):
|
|
136
|
+
raise PermissionError(f"Project path {project_path} is not in allowed directories")
|
|
137
|
+
|
|
138
|
+
with open(pp / MEMORY_FILE, "w") as f:
|
|
139
|
+
f.write(project_info)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def validate_block_integrity(patch_content):
|
|
143
|
+
"""
|
|
144
|
+
Validate the integrity of patch blocks before parsing.
|
|
145
|
+
|
|
146
|
+
This function performs comprehensive validation of the patch format:
|
|
147
|
+
1. Checks for balanced markers (SEARCH, separator, REPLACE)
|
|
148
|
+
2. Verifies correct marker sequence
|
|
149
|
+
3. Detects nested markers inside blocks (which would cause errors)
|
|
150
|
+
|
|
151
|
+
All these checks happen before actual parsing to provide clear error
|
|
152
|
+
messages and prevent corrupted patches from being applied.
|
|
153
|
+
|
|
154
|
+
:param patch_content: The raw patch content to validate
|
|
155
|
+
:raises ValueError: With detailed message if any validation fails
|
|
156
|
+
"""
|
|
157
|
+
# Check marker balance
|
|
158
|
+
search_count = patch_content.count("<<<<<<< SEARCH")
|
|
159
|
+
separator_count = patch_content.count("=======")
|
|
160
|
+
replace_count = patch_content.count(">>>>>>> REPLACE")
|
|
161
|
+
|
|
162
|
+
if not (search_count == separator_count == replace_count):
|
|
163
|
+
raise ValueError(
|
|
164
|
+
f"Malformed patch format: Unbalanced markers - "
|
|
165
|
+
f"{search_count} SEARCH, {separator_count} separator, {replace_count} REPLACE markers"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Check marker sequence
|
|
169
|
+
markers = []
|
|
170
|
+
for line in patch_content.splitlines():
|
|
171
|
+
line = line.strip()
|
|
172
|
+
if line in ["<<<<<<< SEARCH", "=======", ">>>>>>> REPLACE"]:
|
|
173
|
+
markers.append(line)
|
|
174
|
+
|
|
175
|
+
# Verify correct marker sequence (always SEARCH, SEPARATOR, REPLACE pattern)
|
|
176
|
+
for i in range(0, len(markers), 3):
|
|
177
|
+
if i+2 < len(markers):
|
|
178
|
+
if markers[i] != "<<<<<<< SEARCH" or markers[i+1] != "=======" or markers[i+2] != ">>>>>>> REPLACE":
|
|
179
|
+
raise ValueError(
|
|
180
|
+
f"Malformed patch format: Incorrect marker sequence at position {i}: "
|
|
181
|
+
f"Expected [SEARCH, SEPARATOR, REPLACE], got {markers[i:i+3]}"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Check for nested markers in each block
|
|
185
|
+
sections = patch_content.split("<<<<<<< SEARCH")
|
|
186
|
+
for i, section in enumerate(sections[1:], 1): # Skip first empty section
|
|
187
|
+
if "<<<<<<< SEARCH" in section and section.find(">>>>>>> REPLACE") > section.find("<<<<<<< SEARCH"):
|
|
188
|
+
raise ValueError(f"Malformed patch format: Nested SEARCH marker in block {i}")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def parse_search_replace_blocks(patch_content):
|
|
192
|
+
"""
|
|
193
|
+
Parse multiple search-replace blocks from the patch content.
|
|
194
|
+
|
|
195
|
+
This function first validates the block integrity, then extracts all
|
|
196
|
+
search-replace pairs using either regex or line-by-line parsing as fallback.
|
|
197
|
+
It also checks that search and replace texts don't contain markers themselves,
|
|
198
|
+
which could lead to corrupted files.
|
|
199
|
+
|
|
200
|
+
:param patch_content: Raw patch content with SEARCH/REPLACE blocks
|
|
201
|
+
:return: List of tuples (search_text, replace_text)
|
|
202
|
+
:raises ValueError: If patch format is invalid or contains nested markers
|
|
203
|
+
"""
|
|
204
|
+
# Define the markers
|
|
205
|
+
search_marker = "<<<<<<< SEARCH"
|
|
206
|
+
separator = "======="
|
|
207
|
+
replace_marker = ">>>>>>> REPLACE"
|
|
208
|
+
|
|
209
|
+
# First validate patch integrity
|
|
210
|
+
validate_block_integrity(patch_content)
|
|
211
|
+
|
|
212
|
+
# Use regex to extract all blocks
|
|
213
|
+
pattern = f"{search_marker}\\n(.*?)\\n{separator}\\n(.*?)\\n{replace_marker}"
|
|
214
|
+
matches = re.findall(pattern, patch_content, re.DOTALL)
|
|
215
|
+
|
|
216
|
+
if not matches:
|
|
217
|
+
# Try alternative parsing if regex fails
|
|
218
|
+
blocks = []
|
|
219
|
+
lines = patch_content.splitlines()
|
|
220
|
+
i = 0
|
|
221
|
+
while i < len(lines):
|
|
222
|
+
if lines[i] == search_marker:
|
|
223
|
+
search_start = i + 1
|
|
224
|
+
separator_idx = -1
|
|
225
|
+
replace_end = -1
|
|
226
|
+
|
|
227
|
+
# Find the separator
|
|
228
|
+
for j in range(search_start, len(lines)):
|
|
229
|
+
if lines[j] == separator:
|
|
230
|
+
separator_idx = j
|
|
231
|
+
break
|
|
232
|
+
|
|
233
|
+
if separator_idx == -1:
|
|
234
|
+
raise ValueError("Invalid format: missing separator")
|
|
235
|
+
|
|
236
|
+
# Find the replace marker
|
|
237
|
+
for j in range(separator_idx + 1, len(lines)):
|
|
238
|
+
if lines[j] == replace_marker:
|
|
239
|
+
replace_end = j
|
|
240
|
+
break
|
|
241
|
+
|
|
242
|
+
if replace_end == -1:
|
|
243
|
+
raise ValueError("Invalid format: missing replace marker")
|
|
244
|
+
|
|
245
|
+
search_text = "\n".join(lines[search_start:separator_idx])
|
|
246
|
+
replace_text = "\n".join(lines[separator_idx + 1:replace_end])
|
|
247
|
+
|
|
248
|
+
# Check for markers in the search or replace text
|
|
249
|
+
if any(marker in search_text for marker in [search_marker, separator, replace_marker]):
|
|
250
|
+
raise ValueError(f"Block {len(blocks)+1}: Search text contains patch markers")
|
|
251
|
+
if any(marker in replace_text for marker in [search_marker, separator, replace_marker]):
|
|
252
|
+
raise ValueError(f"Block {len(blocks)+1}: Replace text contains patch markers")
|
|
253
|
+
|
|
254
|
+
blocks.append((search_text, replace_text))
|
|
255
|
+
|
|
256
|
+
i = replace_end + 1
|
|
257
|
+
else:
|
|
258
|
+
i += 1
|
|
259
|
+
|
|
260
|
+
if blocks:
|
|
261
|
+
return blocks
|
|
262
|
+
else:
|
|
263
|
+
raise ValueError("Invalid patch format. Expected block format with SEARCH/REPLACE markers.")
|
|
264
|
+
|
|
265
|
+
# Check for markers in matched content
|
|
266
|
+
for i, (search_text, replace_text) in enumerate(matches):
|
|
267
|
+
if any(marker in search_text for marker in [search_marker, separator, replace_marker]):
|
|
268
|
+
raise ValueError(f"Block {i+1}: Search text contains patch markers")
|
|
269
|
+
if any(marker in replace_text for marker in [search_marker, separator, replace_marker]):
|
|
270
|
+
raise ValueError(f"Block {i+1}: Replace text contains patch markers")
|
|
271
|
+
|
|
272
|
+
return matches
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@mcp.tool()
|
|
276
|
+
def update_project_memory(
|
|
277
|
+
project_path: str = Field(description="The full path to the project directory"),
|
|
278
|
+
patch_content: str = Field(description="Block-based patch content with SEARCH/REPLACE markers")
|
|
279
|
+
):
|
|
280
|
+
"""
|
|
281
|
+
Update the project memory by applying a block-based patch to the memory file.
|
|
282
|
+
|
|
283
|
+
Required block format:
|
|
284
|
+
```
|
|
285
|
+
<<<<<<< SEARCH
|
|
286
|
+
Text to find in the memory file
|
|
287
|
+
=======
|
|
288
|
+
Text to replace it with
|
|
289
|
+
>>>>>>> REPLACE
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
You can include multiple search-replace blocks in a single request:
|
|
293
|
+
```
|
|
294
|
+
<<<<<<< SEARCH
|
|
295
|
+
First text to find
|
|
296
|
+
=======
|
|
297
|
+
First replacement
|
|
298
|
+
>>>>>>> REPLACE
|
|
299
|
+
<<<<<<< SEARCH
|
|
300
|
+
Second text to find
|
|
301
|
+
=======
|
|
302
|
+
Second replacement
|
|
303
|
+
>>>>>>> REPLACE
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
This tool verifies that each search text appears exactly once in the file to ensure
|
|
307
|
+
the correct section is modified. If a search text appears multiple times or isn't
|
|
308
|
+
found, it will report an error.
|
|
309
|
+
|
|
310
|
+
:return: Success message with number of blocks applied
|
|
311
|
+
:raises FileNotFoundError: If the project path or memory file doesn't exist
|
|
312
|
+
:raises ValueError: If patch format is invalid or search text isn't unique
|
|
313
|
+
:raises RuntimeError: If patch application fails for any reason
|
|
314
|
+
"""
|
|
315
|
+
project_dir = Path(project_path).resolve()
|
|
316
|
+
if not project_dir.is_dir():
|
|
317
|
+
raise FileNotFoundError(f"Project path {project_path} does not exist or is not a directory")
|
|
318
|
+
memory_file = project_dir / MEMORY_FILE
|
|
319
|
+
if not memory_file.exists():
|
|
320
|
+
raise FileNotFoundError(
|
|
321
|
+
f"Memory file does not exist at {memory_file}. Use `set_project_memory` to set the whole memory instead."
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Read the current file content
|
|
325
|
+
with open(memory_file, 'r', encoding='utf-8') as f:
|
|
326
|
+
original_content = f.read()
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
# First, try to parse as block format
|
|
330
|
+
try:
|
|
331
|
+
# Parse multiple search-replace blocks
|
|
332
|
+
blocks = parse_search_replace_blocks(patch_content)
|
|
333
|
+
if blocks:
|
|
334
|
+
eprint(f"Found {len(blocks)} search-replace blocks")
|
|
335
|
+
|
|
336
|
+
# Apply each block sequentially
|
|
337
|
+
current_content = original_content
|
|
338
|
+
applied_blocks = 0
|
|
339
|
+
|
|
340
|
+
for i, (search_text, replace_text) in enumerate(blocks):
|
|
341
|
+
eprint(f"Processing block {i+1}/{len(blocks)}")
|
|
342
|
+
|
|
343
|
+
# Check exact match count
|
|
344
|
+
count = current_content.count(search_text)
|
|
345
|
+
|
|
346
|
+
if count == 1:
|
|
347
|
+
# Exactly one match - perfect!
|
|
348
|
+
eprint(f"Block {i+1}: Found exactly one exact match")
|
|
349
|
+
current_content = current_content.replace(search_text, replace_text)
|
|
350
|
+
applied_blocks += 1
|
|
351
|
+
elif count > 1:
|
|
352
|
+
# Multiple matches - too ambiguous
|
|
353
|
+
raise ValueError(f"Block {i+1}: The search text appears {count} times in the file. "
|
|
354
|
+
"Please provide more context to identify the specific occurrence.")
|
|
355
|
+
else:
|
|
356
|
+
# No match found
|
|
357
|
+
raise ValueError(f"Block {i+1}: Could not find the search text in the file. "
|
|
358
|
+
"Please ensure the search text exactly matches the content in the file.")
|
|
359
|
+
|
|
360
|
+
# Write the final content back to the file
|
|
361
|
+
with open(memory_file, 'w', encoding='utf-8') as f:
|
|
362
|
+
f.write(current_content)
|
|
363
|
+
|
|
364
|
+
return f"Successfully applied {applied_blocks} patch blocks to memory file"
|
|
365
|
+
except Exception as block_error:
|
|
366
|
+
# If block format parsing fails, log the error and try traditional patch format
|
|
367
|
+
eprint(f"Block format parsing failed: {str(block_error)}")
|
|
368
|
+
|
|
369
|
+
# If you still want to support traditional patches with whatthepatch or similar, add that code here
|
|
370
|
+
# For now, we'll just raise the error from block parsing
|
|
371
|
+
raise block_error
|
|
372
|
+
|
|
373
|
+
except Exception as e:
|
|
374
|
+
# If anything goes wrong, provide detailed error
|
|
375
|
+
raise RuntimeError(f"Failed to apply patch: {str(e)}")
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: project-mem-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An MCP Server to store and retreive project information from memory file
|
|
5
|
+
Author: PYNESYS LLC
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: fastmcp<3.0.0,>=2.2.0
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# Project Memory MCP
|
|
14
|
+
|
|
15
|
+
An MCP Server to store and retrieve project information from memory file. This allows AI agents (like Claude) to maintain persistent memory about projects between conversations.
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
Project Memory MCP provides a simple way to:
|
|
20
|
+
- Store project information in Markdown format
|
|
21
|
+
- Retrieve project information at the beginning of conversations
|
|
22
|
+
- Update project information using patches
|
|
23
|
+
|
|
24
|
+
The memory is stored in a `MEMORY.md` file in each project directory.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
### Using uvx
|
|
29
|
+
|
|
30
|
+
This method uses `uvx` (from the `uv` Python package manager) to run the server without permanent installation:
|
|
31
|
+
|
|
32
|
+
#### Prerequisites
|
|
33
|
+
|
|
34
|
+
Install `uvx` from [uv](https://docs.astral.sh/uv/installation/) if you don't have it already.
|
|
35
|
+
|
|
36
|
+
#### Set up MCP Client (Claude Desktop, Cursor, etc.)
|
|
37
|
+
|
|
38
|
+
Merge the following config with your existing config file (e.g. `claude_desktop_config.json`):
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"project-memory": {
|
|
44
|
+
"command": "uvx",
|
|
45
|
+
"args": [
|
|
46
|
+
"project-mem-mcp",
|
|
47
|
+
"--allowed-dir", "/Users/your-username/projects",
|
|
48
|
+
"--allowed-dir", "/Users/your-username/Documents/code"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> **Note:** Replace `/Users/your-username` with the actual path to your own projects and code directories.
|
|
56
|
+
|
|
57
|
+
### Install from Source
|
|
58
|
+
|
|
59
|
+
#### Prerequisites
|
|
60
|
+
|
|
61
|
+
- Python 3.11 or higher
|
|
62
|
+
- Pip package manager
|
|
63
|
+
|
|
64
|
+
#### Clone the repository
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
git clone https://github.com/your-username/project-mem-mcp.git
|
|
68
|
+
python -m venv venv
|
|
69
|
+
source venv/bin/activate
|
|
70
|
+
pip install -e .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### Set up MCP Client (Claude Desktop, Cursor, etc.)
|
|
74
|
+
|
|
75
|
+
Merge the following config with your existing config file (e.g. `claude_desktop_config.json`):
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"mcpServers": {
|
|
80
|
+
"project-memory": {
|
|
81
|
+
"command": "path/to/your/venv/bin/project-mem-mcp",
|
|
82
|
+
"args": [
|
|
83
|
+
"--allowed-dir", "/Users/your-username/projects",
|
|
84
|
+
"--allowed-dir", "/Users/your-username/Documents/code"
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
> **Note:** Replace `/Users/your-username` with the actual path to your own projects and code directories.
|
|
92
|
+
|
|
93
|
+
## Arguments
|
|
94
|
+
|
|
95
|
+
The `--allowed-dir` argument is used to specify the directories that the server has access to. You can use it multiple times to allow access to multiple directories. All directories inside the allowed directories are also allowed.
|
|
96
|
+
It is optional. If not provided, the server will only have access to the home directory of the user running the server.
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
## Usage
|
|
100
|
+
|
|
101
|
+
The MCP server is started by the client (e.g., Claude Desktop) based on the configuration you provide. You don't need to start the server manually.
|
|
102
|
+
|
|
103
|
+
### Tools
|
|
104
|
+
|
|
105
|
+
Project Memory MCP provides three tools:
|
|
106
|
+
|
|
107
|
+
#### get_project_memory
|
|
108
|
+
|
|
109
|
+
Retrieves the entire project memory in Markdown format. Should be used at the beginning of every conversation about a project.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
get_project_memory(project_path: str) -> str
|
|
113
|
+
```
|
|
114
|
+
- **project_path**: Full path to the project directory.
|
|
115
|
+
- Returns the content of the MEMORY.md file as a string.
|
|
116
|
+
- Raises `FileNotFoundError` if the project or memory file does not exist.
|
|
117
|
+
- Raises `PermissionError` if the project path is not in allowed directories.
|
|
118
|
+
|
|
119
|
+
#### set_project_memory
|
|
120
|
+
|
|
121
|
+
Sets (overwrites) the entire project memory. Use this when creating a new memory file, replacing the whole memory, or if `update_project_memory` fails.
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
set_project_memory(project_path: str, project_info: str)
|
|
125
|
+
```
|
|
126
|
+
- **project_path**: Full path to the project directory.
|
|
127
|
+
- **project_info**: Complete project information in Markdown format.
|
|
128
|
+
- Overwrites the MEMORY.md file with the provided content.
|
|
129
|
+
- Raises `FileNotFoundError` if the project path does not exist.
|
|
130
|
+
- Raises `PermissionError` if the project path is not in allowed directories.
|
|
131
|
+
|
|
132
|
+
#### update_project_memory
|
|
133
|
+
|
|
134
|
+
Updates the project memory by applying one or more block-based patches to the memory file. This is more efficient for small changes.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
update_project_memory(project_path: str, patch_content: str)
|
|
138
|
+
```
|
|
139
|
+
- **project_path**: Full path to the project directory.
|
|
140
|
+
- **patch_content**: Block-based patch content using SEARCH/REPLACE markers (see below).
|
|
141
|
+
- Each patch block must have the following format:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
<<<<<<< SEARCH
|
|
145
|
+
Text to find in the memory file
|
|
146
|
+
=======
|
|
147
|
+
Text to replace it with
|
|
148
|
+
>>>>>>> REPLACE
|
|
149
|
+
```
|
|
150
|
+
Multiple blocks can be included in a single request.
|
|
151
|
+
- Each search text must appear **exactly once** in the file, otherwise an error is raised.
|
|
152
|
+
- Raises `FileNotFoundError` if the project or memory file does not exist.
|
|
153
|
+
- Raises `ValueError` if the patch format is invalid or the search text is not unique.
|
|
154
|
+
- Raises `RuntimeError` if patch application fails for any reason.
|
|
155
|
+
|
|
156
|
+
### Example Workflow
|
|
157
|
+
|
|
158
|
+
1. Begin a conversation with LLM about a project
|
|
159
|
+
2. LLM uses `get_project_memory` to retrieve project information
|
|
160
|
+
3. Throughout the conversation, LLM uses `update_project_memory` to persist new information
|
|
161
|
+
4. If the update fails, LLM can use `set_project_memory` instead
|
|
162
|
+
|
|
163
|
+
#### Claude Desktop
|
|
164
|
+
|
|
165
|
+
if you use Claude Desktop, it is best to use the project feature.
|
|
166
|
+
|
|
167
|
+
Edit the project instructions:
|
|
168
|
+
- Add a line like this: "The path of the project is <project_path>"
|
|
169
|
+
- If it does not always use the memory, you can add a line like this: "Always use the project memory, it is not optional"
|
|
170
|
+
|
|
171
|
+
## Security Considerations
|
|
172
|
+
|
|
173
|
+
- Memory files should never contain sensitive information
|
|
174
|
+
- Project paths are validated against allowed directories
|
|
175
|
+
- All file operations are restricted to allowed directories
|
|
176
|
+
|
|
177
|
+
## Dependencies
|
|
178
|
+
|
|
179
|
+
- fastmcp (>=2.2.0, <3.0.0)
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/project_mem_mcp/__init__.py
|
|
5
|
+
src/project_mem_mcp/server.py
|
|
6
|
+
src/project_mem_mcp.egg-info/PKG-INFO
|
|
7
|
+
src/project_mem_mcp.egg-info/SOURCES.txt
|
|
8
|
+
src/project_mem_mcp.egg-info/dependency_links.txt
|
|
9
|
+
src/project_mem_mcp.egg-info/entry_points.txt
|
|
10
|
+
src/project_mem_mcp.egg-info/requires.txt
|
|
11
|
+
src/project_mem_mcp.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fastmcp<3.0.0,>=2.2.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
project_mem_mcp
|