yomemoai-mcp 0.1.0__tar.gz → 0.1.2__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.
@@ -0,0 +1,40 @@
1
+ # Cursor best practice: proactive Yomemo long-term memory
2
+
3
+ After configuring the Yomemo MCP in Cursor, add the rule below to **Rules for AI**. The AI will then **proactively** call `save_memory` when it detects important information—no need to say "remember this" every time.
4
+
5
+ ---
6
+
7
+ ## Option 1: Cursor global / project Rules for AI
8
+
9
+ Open Cursor settings → **Rules for AI**, add a new rule, and paste the following:
10
+
11
+ ```
12
+ You are equipped with Yomemo.ai MCP.
13
+
14
+ ## When to use `save_memory`:
15
+ - **Tech Stack**: When we decide on a specific library or version.
16
+ - **Business Logic**: When I explain a complex internal rule.
17
+ - **Preferences**: If I tell you "I prefer using early returns in Go".
18
+
19
+ ## When to use `load_memories`:
20
+ - At the start of a new feature implementation, check if there's relevant context in the 'coding' or 'project-name' handle.
21
+
22
+ ## Feedback:
23
+ - After saving, just add a ✓ in your response. No need for a long confirmation.
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Option 2: Project-level rule file (.cursor/rules)
29
+
30
+ Create `.cursor/rules/` in your project root and add a file such as `yomemo-memory.mdc` with the same content above. The rule will then apply only in that project.
31
+
32
+ ---
33
+
34
+ ## What you get
35
+
36
+ - The AI will call `save_memory` when it recognizes **preferences, decisions, or reusable logic**.
37
+ - After saving, it will add a ✓ in its reply—smooth and low-friction.
38
+ - At the start of new chats or features, it will call `load_memories` when it needs historical context.
39
+
40
+ Use this together with your [yomemoai-mcp](https://github.com/yomemoai/python-yomemo-mcp) MCP setup.
@@ -1,8 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yomemoai-mcp
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Model Context Protocol (MCP) server for YoMemoAI - enables AI assistants to save and retrieve encrypted memories
5
- Author-email: lvxiangxiang <lvxiangxiang@shopline.com>
5
+ Project-URL: Homepage, https://github.com/yomemoai/python-yomemo-mcp
6
+ Project-URL: Documentation, https://github.com/yomemoai/python-yomemo-mcp#readme
7
+ Project-URL: Repository, https://github.com/yomemoai/python-yomemo-mcp
8
+ Project-URL: Issues, https://github.com/yomemoai/python-yomemo-mcp/issues
9
+ Author-email: xxlv <lvxiang119@gmail.com>
6
10
  License: MIT
7
11
  License-File: LICENSE
8
12
  Keywords: ai,claude,cursor,mcp,mcp-server,memory,yomemoai
@@ -44,8 +48,8 @@ A Model Context Protocol (MCP) server for YoMemoAI, enabling AI assistants to sa
44
48
  1. Clone the repository:
45
49
 
46
50
  ```bash
47
- git clone <repository-url>
48
- cd yomemoai-mcp
51
+ git clone https://github.com/yomemoai/python-yomemo-mcp.git
52
+ cd python-yomemo-mcp
49
53
  ```
50
54
 
51
55
  2. Install dependencies using uv:
@@ -99,6 +103,11 @@ After configuration, you can run the MCP server using:
99
103
  uv run memo-mcp
100
104
  ```
101
105
 
106
+ ### Cursor best practice (recommended)
107
+
108
+ Want the AI to **proactively** save memories when it detects preferences or decisions (not only when you say "remember")?
109
+ Add one rule in Cursor's Rules for AI—see **[CURSOR_RULES.md](./CURSOR_RULES.md)**.
110
+
102
111
  ### Integration with Cursor/Claude Desktop
103
112
 
104
113
  Add the following configuration to your MCP settings file:
@@ -225,9 +234,6 @@ yomemoai-mcp/
225
234
 
226
235
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
227
236
 
228
- ## Contributing
229
-
230
- [Add contribution guidelines here]
231
237
 
232
238
  ## Support
233
239
 
@@ -8,6 +8,19 @@ This document describes how to publish `yomemoai-mcp` to PyPI so that users can
8
8
  2. **TestPyPI Account** (recommended for testing): Create an account at [TestPyPI](https://test.pypi.org/)
9
9
  3. **API Token**: You'll need an API token to publish packages
10
10
 
11
+ ### Important: TestPyPI vs PyPI Accounts
12
+
13
+ **TestPyPI and PyPI are separate systems**, but you can use the **same username and password** for both:
14
+
15
+ - **Same credentials work**: If you register on TestPyPI, you can use the same username and password to register on PyPI
16
+ - **Separate accounts**: They are technically separate accounts, but sharing credentials is common and recommended
17
+ - **Email conflicts**: If you get an "email already in use" error on PyPI:
18
+ - You may have already registered on PyPI before (check your email for activation links)
19
+ - Or someone else is using that email (unlikely but possible)
20
+ - **Solution**: Try password reset on PyPI using the same email, or use a different email
21
+
22
+ **Recommended approach**: Register on both TestPyPI and PyPI using the same username and password for consistency.
23
+
11
24
  ## Step 1: Create API Token
12
25
 
13
26
  1. Log in to [PyPI](https://pypi.org/)
@@ -41,6 +54,7 @@ uv build
41
54
  ```
42
55
 
43
56
  This will create a `dist/` directory containing:
57
+
44
58
  - `yomemoai_mcp-0.1.0-py3-none-any.whl` (wheel distribution)
45
59
  - `yomemoai-mcp-0.1.0.tar.gz` (source distribution)
46
60
 
@@ -67,17 +81,21 @@ uv publish --publish-url https://test.pypi.org/legacy/
67
81
 
68
82
  Test that the package can be installed and run:
69
83
 
84
+ **Important**: TestPyPI may not have all dependencies. Use `--extra-index-url` to fetch dependencies from PyPI while getting the package from TestPyPI:
85
+
70
86
  ```bash
71
- # Test installation from TestPyPI
72
- uvx --index-url https://test.pypi.org/simple/ yomemoai-mcp --help
87
+ # Test installation from TestPyPI (dependencies from PyPI)
88
+ uvx --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ yomemoai-mcp --help
73
89
 
74
90
  # Or test in a clean environment
75
91
  uv venv test-env
76
92
  source test-env/bin/activate # On Windows: test-env\Scripts\activate
77
- pip install --index-url https://test.pypi.org/simple/ yomemoai-mcp
93
+ pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ yomemoai-mcp
78
94
  yomemoai-mcp --help
79
95
  ```
80
96
 
97
+ **Note**: The `--extra-index-url` flag tells the package manager to also check PyPI for dependencies that might not be available on TestPyPI.
98
+
81
99
  ## Step 5: Publish to PyPI
82
100
 
83
101
  Once testing is successful, publish to the real PyPI:
@@ -130,6 +148,7 @@ curl https://pypi.org/pypi/yomemoai-mcp/json | jq
130
148
  When you need to publish a new version:
131
149
 
132
150
  1. **Update version** in `pyproject.toml`:
151
+
133
152
  ```toml
134
153
  version = "0.1.1" # or 0.2.0, 1.0.0, etc.
135
154
  ```
@@ -145,6 +164,7 @@ When you need to publish a new version:
145
164
  ## Version Numbering
146
165
 
147
166
  Follow [Semantic Versioning](https://semver.org/):
167
+
148
168
  - **MAJOR** (1.0.0): Breaking changes
149
169
  - **MINOR** (0.1.0): New features, backward compatible
150
170
  - **PATCH** (0.1.1): Bug fixes, backward compatible
@@ -21,8 +21,8 @@ A Model Context Protocol (MCP) server for YoMemoAI, enabling AI assistants to sa
21
21
  1. Clone the repository:
22
22
 
23
23
  ```bash
24
- git clone <repository-url>
25
- cd yomemoai-mcp
24
+ git clone https://github.com/yomemoai/python-yomemo-mcp.git
25
+ cd python-yomemo-mcp
26
26
  ```
27
27
 
28
28
  2. Install dependencies using uv:
@@ -76,6 +76,11 @@ After configuration, you can run the MCP server using:
76
76
  uv run memo-mcp
77
77
  ```
78
78
 
79
+ ### Cursor best practice (recommended)
80
+
81
+ Want the AI to **proactively** save memories when it detects preferences or decisions (not only when you say "remember")?
82
+ Add one rule in Cursor's Rules for AI—see **[CURSOR_RULES.md](./CURSOR_RULES.md)**.
83
+
79
84
  ### Integration with Cursor/Claude Desktop
80
85
 
81
86
  Add the following configuration to your MCP settings file:
@@ -202,9 +207,6 @@ yomemoai-mcp/
202
207
 
203
208
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
204
209
 
205
- ## Contributing
206
-
207
- [Add contribution guidelines here]
208
210
 
209
211
  ## Support
210
212
 
@@ -0,0 +1,27 @@
1
+ # Project Description for GitHub/PyPI
2
+
3
+ **yomemoai-mcp** is a Model Context Protocol (MCP) server that enables AI assistants like Claude and Cursor to seamlessly save and retrieve encrypted memories through YoMemoAI's secure storage platform.
4
+
5
+ ## Key Features
6
+
7
+ - **End-to-End Encryption**: All memories are encrypted client-side using RSA-OAEP and AES-GCM before being stored, ensuring your data remains private even from the service provider
8
+ - **Persistent Memory**: AI assistants can remember important information, user preferences, and conversation context across sessions
9
+ - **Organized Storage**: Memories can be categorized using handles (tags/categories) for easy retrieval and organization
10
+ - **MCP Integration**: Built on the Model Context Protocol standard, making it compatible with any MCP-enabled AI assistant
11
+ - **Easy Installation**: Available on PyPI and can be installed with a single command: `uvx yomemoai-mcp`
12
+
13
+ ## Use Cases
14
+
15
+ - Save important user preferences and context that AI assistants should remember
16
+ - Store conversation history and key information for future reference
17
+ - Organize memories by topics, projects, or categories using handles
18
+ - Enable AI assistants to maintain long-term memory across multiple sessions
19
+
20
+ ## Technical Highlights
21
+
22
+ - Hybrid encryption architecture (RSA + AES-GCM) for secure data storage
23
+ - FastMCP framework for efficient MCP server implementation
24
+ - Type-safe Python implementation with full type hints
25
+ - Simple configuration via environment variables
26
+
27
+ Perfect for developers and users who want to give their AI assistants persistent, encrypted memory capabilities.
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "yomemoai-mcp"
3
- version = "0.1.0"
3
+ version = "0.1.2"
4
4
  description = "Model Context Protocol (MCP) server for YoMemoAI - enables AI assistants to save and retrieve encrypted memories"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
7
7
  authors = [
8
- { name = "lvxiangxiang", email = "lvxiangxiang@shopline.com" }
8
+ { name = "xxlv", email = "lvxiang119@gmail.com" }
9
9
  ]
10
10
  requires-python = ">=3.12"
11
11
  keywords = ["mcp", "mcp-server", "yomemoai", "memory", "ai", "claude", "cursor"]
@@ -26,9 +26,14 @@ dependencies = [
26
26
  "requests>=2.32.5",
27
27
  ]
28
28
 
29
+ [project.urls]
30
+ Homepage = "https://github.com/yomemoai/python-yomemo-mcp"
31
+ Documentation = "https://github.com/yomemoai/python-yomemo-mcp#readme"
32
+ Repository = "https://github.com/yomemoai/python-yomemo-mcp"
33
+ Issues = "https://github.com/yomemoai/python-yomemo-mcp/issues"
34
+
29
35
  [project.scripts]
30
36
  yomemoai-mcp = "yomemoai_mcp.server:mcp.run"
31
- yomemo = "yomemoai_mcp.server:mcp.run"
32
37
  memo-mcp = "yomemoai_mcp.server:mcp.run"
33
38
 
34
39
  [build-system]
@@ -0,0 +1,3 @@
1
+ """YoMemoAI MCP Server - Model Context Protocol server for YoMemoAI."""
2
+
3
+ __version__ = "0.1.0"
@@ -1,5 +1,6 @@
1
1
  import base64
2
2
  import json
3
+ import logging
3
4
  import time
4
5
  import requests
5
6
  from typing import Dict, List, Optional, Tuple
@@ -9,6 +10,24 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
9
10
  from cryptography.hazmat.backends import default_backend
10
11
  import os
11
12
 
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class MemoRequestError(Exception):
17
+ def __init__(
18
+ self,
19
+ message: str,
20
+ url: str,
21
+ payload: Dict,
22
+ status_code: Optional[int] = None,
23
+ response_text: Optional[str] = None,
24
+ ):
25
+ super().__init__(message)
26
+ self.url = url
27
+ self.payload = payload
28
+ self.status_code = status_code
29
+ self.response_text = response_text
30
+
12
31
 
13
32
  class MemoClient:
14
33
  """
@@ -98,9 +117,20 @@ class MemoClient:
98
117
  )
99
118
 
100
119
  def add_memory(self, content: str, handle: str = "", description: str = "", metadata: Dict = None):
120
+ # Ensure handle is not empty (API requires it)
121
+ if not handle or not handle.strip():
122
+ handle = "general"
123
+
124
+ # Remove spaces from handle (API doesn't allow spaces)
125
+ handle = handle.replace(" ", "-").lower()
126
+
101
127
  token_size = len(content)
128
+ logger.debug(
129
+ f"Adding memory: handle={handle}, content_length={token_size}, description={description}")
102
130
 
103
131
  packed = self.pack_data(content.encode('utf-8'))
132
+ logger.debug(f"Packed data length: {len(packed)}")
133
+
104
134
  payload = {
105
135
  "ciphertext": packed,
106
136
  "description": description,
@@ -110,10 +140,42 @@ class MemoClient:
110
140
  "from": "yomemoai-mcp",
111
141
  }
112
142
  }
113
- resp = self.session.post(
114
- f"{self.base_url}/api/v1/memory", json=payload)
115
- resp.raise_for_status()
116
- return resp.json()
143
+
144
+ url = f"{self.base_url}/api/v1/memory"
145
+ logger.debug(f"POST {url}")
146
+
147
+ try:
148
+ resp = self.session.post(url, json=payload)
149
+ logger.debug(f"Response status: {resp.status_code}")
150
+
151
+ # Provide better error messages
152
+ if not resp.ok:
153
+ try:
154
+ error_detail = resp.json().get("error", resp.text)
155
+ except Exception:
156
+ error_detail = resp.text
157
+ logger.error(f"API error {resp.status_code}: {error_detail}")
158
+ raise MemoRequestError(
159
+ f"API error {resp.status_code}: {error_detail}",
160
+ url=url,
161
+ payload=payload,
162
+ status_code=resp.status_code,
163
+ response_text=resp.text,
164
+ )
165
+
166
+ result = resp.json()
167
+ logger.debug(f"Success response: {result}")
168
+ return result
169
+ except requests.exceptions.RequestException as e:
170
+ logger.error(f"Request failed: {type(e).__name__}: {str(e)}")
171
+ raise MemoRequestError(
172
+ f"Request failed: {type(e).__name__}: {str(e)}",
173
+ url=url,
174
+ payload=payload,
175
+ ) from e
176
+ except Exception as e:
177
+ logger.error(f"Unexpected error: {type(e).__name__}: {str(e)}")
178
+ raise
117
179
 
118
180
  def get_memories(self, handle: Optional[str] = None) -> List[Dict]:
119
181
  url = f"{self.base_url}/api/v1/memory"
@@ -0,0 +1,138 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ import sys
5
+ from mcp.server.fastmcp import FastMCP
6
+ from .client import MemoClient, MemoRequestError
7
+ import os
8
+
9
+ from dotenv import load_dotenv
10
+ load_dotenv()
11
+
12
+ # Configure logging
13
+ DEBUG = os.getenv("DEBUG", "false").lower() == "true"
14
+ LOG_LEVEL = logging.DEBUG if DEBUG else logging.INFO
15
+
16
+ logging.basicConfig(
17
+ level=LOG_LEVEL,
18
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
19
+ stream=sys.stderr
20
+ )
21
+ logger = logging.getLogger(__name__)
22
+
23
+ mcp = FastMCP("yomemoai")
24
+
25
+ API_KEY = os.getenv("MEMO_API_KEY", "")
26
+ PRIV_KEY_PATH = os.getenv("MEMO_PRIVATE_KEY_PATH", "private.pem")
27
+ BASE_URL = os.getenv("MEMO_BASE_URL", "https://api.yomemo.ai")
28
+
29
+ if not API_KEY:
30
+ raise ValueError("MEMO_API_KEY environment variable is required")
31
+
32
+ if not os.path.exists(PRIV_KEY_PATH):
33
+ raise FileNotFoundError(
34
+ f"Private key file not found: {PRIV_KEY_PATH}. "
35
+ f"Please set MEMO_PRIVATE_KEY_PATH environment variable or place your private key at {PRIV_KEY_PATH}"
36
+ )
37
+
38
+ try:
39
+ with open(PRIV_KEY_PATH, "r") as f:
40
+ private_pem = f.read()
41
+ except Exception as e:
42
+ raise IOError(f"Failed to read private key from {PRIV_KEY_PATH}: {e}")
43
+
44
+ if not private_pem.strip():
45
+ raise ValueError(f"Private key file {PRIV_KEY_PATH} is empty")
46
+
47
+ client = MemoClient(API_KEY, private_pem, BASE_URL)
48
+
49
+
50
+ def _format_payload(payload: dict) -> dict:
51
+ if not payload:
52
+ return {}
53
+ return dict(payload)
54
+
55
+
56
+ @mcp.tool()
57
+ async def save_memory(content: str, handle: str = "general", description: str = "") -> str:
58
+ """
59
+ Store important information as a permanent memory. You should call this proactively when you
60
+ detect user preferences, important decisions, reusable logic, or context worth recalling later—
61
+ not only when the user explicitly says "remember" or "save". After saving successfully, reply with ✓.
62
+
63
+ :param content: The actual text/information to be remembered. Be concise but maintain context.
64
+ :param handle: A short, unique category or tag (e.g., 'work', 'personal', 'project-x'). Defaults to 'general'.
65
+ :param description: A brief, non-sensitive summary or tag for this memory (helps future identification and search).
66
+ """
67
+ logger.debug(
68
+ f"save_memory called: handle={handle}, description={description}, content_length={len(content)}")
69
+ try:
70
+ result = client.add_memory(
71
+ content, handle=handle, description=description)
72
+ logger.debug(f"add_memory response: {result}")
73
+
74
+ if "memory_id" not in result:
75
+ return f"Failed to save memory for handle: {handle}"
76
+
77
+ memory_id = result.get('memory_id')
78
+
79
+ return f"Successfully archived in memory. ID: {memory_id}"
80
+ except MemoRequestError as e:
81
+ logger.error(
82
+ f"Error saving memory: {type(e).__name__}: {str(e)}", exc_info=DEBUG)
83
+ request_info = {
84
+ "url": e.url,
85
+ "status_code": e.status_code,
86
+ "response_text": e.response_text,
87
+ "payload": _format_payload(e.payload),
88
+ "handle": handle,
89
+ "description": description,
90
+ "content_length": len(content),
91
+ }
92
+ # only debug mode return the request_info
93
+ if DEBUG:
94
+ return json.dumps(request_info, ensure_ascii=False)
95
+ return f"Failed to save your memory: {str(e)}"
96
+ except Exception as e:
97
+ logger.error(
98
+ f"Error saving memory: {type(e).__name__}: {str(e)}", exc_info=DEBUG)
99
+ return (
100
+ f"Failed to save your memory: {str(e)},"
101
+ f"handle: {handle},description: {description},content: {content}"
102
+ )
103
+
104
+
105
+ @mcp.tool()
106
+ async def load_memories(handle: str = None) -> str:
107
+ """
108
+ Retrieve previously stored memories. Call this when the user asks what you remember, or when you
109
+ need historical context (preferences, past decisions, project details) to answer accurately.
110
+
111
+ :param handle: Optional filter. If the user specifies a category (e.g., 'work', 'project-x'),
112
+ use the relevant handle; otherwise omit to load across handles.
113
+ """
114
+ logger.debug(f"load_memories called: handle={handle}")
115
+ try:
116
+ memories = client.get_memories(handle=handle)
117
+ logger.debug(f"Retrieved {len(memories)} memories")
118
+
119
+ if not memories:
120
+ logger.info(
121
+ f"No memories found for handle: {handle if handle else 'all'}")
122
+ return f"No memories found under the handle: {handle if handle else 'all'}."
123
+
124
+ output = ["### Retrieved Memories:"]
125
+ for m in memories:
126
+ timestamp = m.get('created_at', 'N/A')
127
+ output.append(
128
+ f"Handle: [{m.get('handle')}]\nContent: {m.get('content')}\n---"
129
+ )
130
+ logger.info(f"Successfully loaded {len(memories)} memories")
131
+ return "\n".join(output)
132
+ except Exception as e:
133
+ logger.error(
134
+ f"Error retrieving memories: {type(e).__name__}: {str(e)}", exc_info=DEBUG)
135
+ return f"Error retrieving memories: {str(e)}"
136
+
137
+ if __name__ == "__main__":
138
+ mcp.run()
@@ -1018,27 +1018,6 @@ wheels = [
1018
1018
  { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548 },
1019
1019
  ]
1020
1020
 
1021
- [[package]]
1022
- name = "python-memo-mcp"
1023
- version = "0.1.0"
1024
- source = { editable = "." }
1025
- dependencies = [
1026
- { name = "cryptography" },
1027
- { name = "fastmcp" },
1028
- { name = "mcp" },
1029
- { name = "python-dotenv" },
1030
- { name = "requests" },
1031
- ]
1032
-
1033
- [package.metadata]
1034
- requires-dist = [
1035
- { name = "cryptography", specifier = ">=46.0.3" },
1036
- { name = "fastmcp", specifier = ">=0.1.0" },
1037
- { name = "mcp", specifier = ">=1.25.0" },
1038
- { name = "python-dotenv", specifier = ">=1.2.1" },
1039
- { name = "requests", specifier = ">=2.32.5" },
1040
- ]
1041
-
1042
1021
  [[package]]
1043
1022
  name = "python-multipart"
1044
1023
  version = "0.0.21"
@@ -1297,28 +1276,28 @@ wheels = [
1297
1276
 
1298
1277
  [[package]]
1299
1278
  name = "sse-starlette"
1300
- version = "3.1.2"
1279
+ version = "3.2.0"
1301
1280
  source = { registry = "https://pypi.org/simple" }
1302
1281
  dependencies = [
1303
1282
  { name = "anyio" },
1304
1283
  { name = "starlette" },
1305
1284
  ]
1306
- sdist = { url = "https://files.pythonhosted.org/packages/da/34/f5df66cb383efdbf4f2db23cabb27f51b1dcb737efaf8a558f6f1d195134/sse_starlette-3.1.2.tar.gz", hash = "sha256:55eff034207a83a0eb86de9a68099bd0157838f0b8b999a1b742005c71e33618", size = 26303 }
1285
+ sdist = { url = "https://files.pythonhosted.org/packages/8b/8d/00d280c03ffd39aaee0e86ec81e2d3b9253036a0f93f51d10503adef0e65/sse_starlette-3.2.0.tar.gz", hash = "sha256:8127594edfb51abe44eac9c49e59b0b01f1039d0c7461c6fd91d4e03b70da422", size = 27253 }
1307
1286
  wheels = [
1308
- { url = "https://files.pythonhosted.org/packages/b7/95/8c4b76eec9ae574474e5d2997557cebf764bcd3586458956c30631ae08f4/sse_starlette-3.1.2-py3-none-any.whl", hash = "sha256:cd800dd349f4521b317b9391d3796fa97b71748a4da9b9e00aafab32dda375c8", size = 12484 },
1287
+ { url = "https://files.pythonhosted.org/packages/96/7f/832f015020844a8b8f7a9cbc103dd76ba8e3875004c41e08440ea3a2b41a/sse_starlette-3.2.0-py3-none-any.whl", hash = "sha256:5876954bd51920fc2cd51baee47a080eb88a37b5b784e615abb0b283f801cdbf", size = 12763 },
1309
1288
  ]
1310
1289
 
1311
1290
  [[package]]
1312
1291
  name = "starlette"
1313
- version = "0.51.0"
1292
+ version = "0.52.1"
1314
1293
  source = { registry = "https://pypi.org/simple" }
1315
1294
  dependencies = [
1316
1295
  { name = "anyio" },
1317
1296
  { name = "typing-extensions", marker = "python_full_version < '3.13'" },
1318
1297
  ]
1319
- sdist = { url = "https://files.pythonhosted.org/packages/e7/65/5a1fadcc40c5fdc7df421a7506b79633af8f5d5e3a95c3e72acacec644b9/starlette-0.51.0.tar.gz", hash = "sha256:4c4fda9b1bc67f84037d3d14a5112e523509c369d9d47b111b2f984b0cc5ba6c", size = 2647658 }
1298
+ sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702 }
1320
1299
  wheels = [
1321
- { url = "https://files.pythonhosted.org/packages/18/c4/09985a03dba389d4fe16a9014147a7b02fa76ef3519bf5846462a485876d/starlette-0.51.0-py3-none-any.whl", hash = "sha256:fb460a3d6fd3c958d729fdd96aee297f89a51b0181f16401fe8fd4cb6129165d", size = 74133 },
1300
+ { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272 },
1322
1301
  ]
1323
1302
 
1324
1303
  [[package]]
@@ -1473,6 +1452,27 @@ wheels = [
1473
1452
  { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591 },
1474
1453
  ]
1475
1454
 
1455
+ [[package]]
1456
+ name = "yomemoai-mcp"
1457
+ version = "0.1.1"
1458
+ source = { editable = "." }
1459
+ dependencies = [
1460
+ { name = "cryptography" },
1461
+ { name = "fastmcp" },
1462
+ { name = "mcp" },
1463
+ { name = "python-dotenv" },
1464
+ { name = "requests" },
1465
+ ]
1466
+
1467
+ [package.metadata]
1468
+ requires-dist = [
1469
+ { name = "cryptography", specifier = ">=46.0.3" },
1470
+ { name = "fastmcp", specifier = ">=0.1.0" },
1471
+ { name = "mcp", specifier = ">=1.25.0" },
1472
+ { name = "python-dotenv", specifier = ">=1.2.1" },
1473
+ { name = "requests", specifier = ">=2.32.5" },
1474
+ ]
1475
+
1476
1476
  [[package]]
1477
1477
  name = "zipp"
1478
1478
  version = "3.23.0"
@@ -1,2 +0,0 @@
1
- def hello() -> str:
2
- return "Hello from yomemoai-mcp!"
@@ -1,80 +0,0 @@
1
- import asyncio
2
- from mcp.server.fastmcp import FastMCP
3
- from .client import MemoClient
4
- import os
5
-
6
- from dotenv import load_dotenv
7
- load_dotenv()
8
-
9
- mcp = FastMCP("yomemoai")
10
-
11
- API_KEY = os.getenv("MEMO_API_KEY", "")
12
- PRIV_KEY_PATH = os.getenv("MEMO_PRIVATE_KEY_PATH", "private.pem")
13
- BASE_URL = os.getenv("MEMO_BASE_URL", "https://api.yomemo.ai")
14
-
15
- if not API_KEY:
16
- raise ValueError("MEMO_API_KEY environment variable is required")
17
-
18
- if not os.path.exists(PRIV_KEY_PATH):
19
- raise FileNotFoundError(
20
- f"Private key file not found: {PRIV_KEY_PATH}. "
21
- f"Please set MEMO_PRIVATE_KEY_PATH environment variable or place your private key at {PRIV_KEY_PATH}"
22
- )
23
-
24
- try:
25
- with open(PRIV_KEY_PATH, "r") as f:
26
- private_pem = f.read()
27
- except Exception as e:
28
- raise IOError(f"Failed to read private key from {PRIV_KEY_PATH}: {e}")
29
-
30
- if not private_pem.strip():
31
- raise ValueError(f"Private key file {PRIV_KEY_PATH} is empty")
32
-
33
- client = MemoClient(API_KEY, private_pem, BASE_URL)
34
-
35
-
36
- @mcp.tool()
37
- async def save_memory(content: str, handle: str = "general", description: str = "") -> str:
38
- """
39
- Store important information, user preferences, or conversation context as a permanent memory.
40
- Use this tool when the user explicitly asks to 'remember', 'save', or 'keep track of' something.
41
-
42
- :param content: The actual text/information to be remembered. Be concise but maintain context.
43
- :param handle: A short, unique category or tag (e.g., 'work', 'personal', 'project-x'). Defaults to 'general'.
44
- :param description: A brief summary of what this memory is about to help with future identification. don't include any sensitive information.
45
- """
46
- try:
47
- result = client.add_memory(
48
- content, handle=handle, description=description)
49
- return f"Successfully archived in memory. ID: {result.get('memory_id')}"
50
- except Exception as e:
51
- return f"Failed to save memory: {str(e)}"
52
-
53
-
54
- @mcp.tool()
55
- async def load_memories(handle: str = None) -> str:
56
- """
57
- Retrieve previously stored memories or context.
58
- Use this tool when the user asks 'what do you remember about...', 'check my notes on...', or when
59
- you need historical context to answer a question accurately.
60
-
61
- :param handle: Optional filter. If the user specifies a category (e.g., 'about my job'),
62
- extract and provide the relevant handle.
63
- """
64
- try:
65
- memories = client.get_memories(handle=handle)
66
- if not memories:
67
- return f"No memories found under the handle: {handle if handle else 'all'}."
68
-
69
- output = ["### Retrieved Memories:"]
70
- for m in memories:
71
- timestamp = m.get('created_at', 'N/A')
72
- output.append(
73
- f"Handle: [{m.get('handle')}]\nContent: {m.get('content')}\n---"
74
- )
75
- return "\n".join(output)
76
- except Exception as e:
77
- return f"Error retrieving memories: {str(e)}"
78
-
79
- if __name__ == "__main__":
80
- mcp.run()
File without changes
File without changes