yomemoai-mcp 0.1.0__py3-none-any.whl → 0.1.3__py3-none-any.whl

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.
yomemoai_mcp/__init__.py CHANGED
@@ -1,2 +1,3 @@
1
- def hello() -> str:
2
- return "Hello from yomemoai-mcp!"
1
+ """YoMemoAI MCP Server - Model Context Protocol server for YoMemoAI."""
2
+
3
+ __version__ = "0.1.0"
yomemoai_mcp/client.py CHANGED
@@ -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"
yomemoai_mcp/server.py CHANGED
@@ -1,11 +1,30 @@
1
1
  import asyncio
2
- from mcp.server.fastmcp import FastMCP
3
- from .client import MemoClient
2
+ import json
3
+ import logging
4
4
  import os
5
+ import sys
6
+ from mcp.server.fastmcp import FastMCP
7
+ from .client import MemoClient, MemoRequestError
8
+
9
+ if "--version" in sys.argv or "-version" in sys.argv:
10
+ from importlib.metadata import version
11
+ print(version("yomemoai-mcp"))
12
+ sys.exit(0)
5
13
 
6
14
  from dotenv import load_dotenv
7
15
  load_dotenv()
8
16
 
17
+ # Configure logging
18
+ DEBUG = os.getenv("DEBUG", "false").lower() == "true"
19
+ LOG_LEVEL = logging.DEBUG if DEBUG else logging.INFO
20
+
21
+ logging.basicConfig(
22
+ level=LOG_LEVEL,
23
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
24
+ stream=sys.stderr
25
+ )
26
+ logger = logging.getLogger(__name__)
27
+
9
28
  mcp = FastMCP("yomemoai")
10
29
 
11
30
  API_KEY = os.getenv("MEMO_API_KEY", "")
@@ -33,37 +52,78 @@ if not private_pem.strip():
33
52
  client = MemoClient(API_KEY, private_pem, BASE_URL)
34
53
 
35
54
 
55
+ def _format_payload(payload: dict) -> dict:
56
+ if not payload:
57
+ return {}
58
+ return dict(payload)
59
+
60
+
36
61
  @mcp.tool()
37
62
  async def save_memory(content: str, handle: str = "general", description: str = "") -> str:
38
63
  """
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.
64
+ Store important information as a permanent memory. You should call this proactively when you
65
+ detect user preferences, important decisions, reusable logic, or context worth recalling later—
66
+ not only when the user explicitly says "remember" or "save". After saving successfully, reply with ✓.
41
67
 
42
68
  :param content: The actual text/information to be remembered. Be concise but maintain context.
43
69
  :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.
70
+ :param description: A brief, non-sensitive summary or tag for this memory (helps future identification and search).
45
71
  """
72
+ logger.debug(
73
+ f"save_memory called: handle={handle}, description={description}, content_length={len(content)}")
46
74
  try:
47
75
  result = client.add_memory(
48
76
  content, handle=handle, description=description)
49
- return f"Successfully archived in memory. ID: {result.get('memory_id')}"
77
+ logger.debug(f"add_memory response: {result}")
78
+
79
+ if "memory_id" not in result:
80
+ return f"Failed to save memory for handle: {handle}"
81
+
82
+ memory_id = result.get('memory_id')
83
+
84
+ return f"Successfully archived in memory. ID: {memory_id}"
85
+ except MemoRequestError as e:
86
+ logger.error(
87
+ f"Error saving memory: {type(e).__name__}: {str(e)}", exc_info=DEBUG)
88
+ request_info = {
89
+ "url": e.url,
90
+ "status_code": e.status_code,
91
+ "response_text": e.response_text,
92
+ "payload": _format_payload(e.payload),
93
+ "handle": handle,
94
+ "description": description,
95
+ "content_length": len(content),
96
+ }
97
+ # only debug mode return the request_info
98
+ if DEBUG:
99
+ return json.dumps(request_info, ensure_ascii=False)
100
+ return f"Failed to save your memory: {str(e)}"
50
101
  except Exception as e:
51
- return f"Failed to save memory: {str(e)}"
102
+ logger.error(
103
+ f"Error saving memory: {type(e).__name__}: {str(e)}", exc_info=DEBUG)
104
+ return (
105
+ f"Failed to save your memory: {str(e)},"
106
+ f"handle: {handle},description: {description},content: {content}"
107
+ )
52
108
 
53
109
 
54
110
  @mcp.tool()
55
111
  async def load_memories(handle: str = None) -> str:
56
112
  """
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.
113
+ Retrieve previously stored memories. Call this when the user asks what you remember, or when you
114
+ need historical context (preferences, past decisions, project details) to answer accurately.
60
115
 
61
- :param handle: Optional filter. If the user specifies a category (e.g., 'about my job'),
62
- extract and provide the relevant handle.
116
+ :param handle: Optional filter. If the user specifies a category (e.g., 'work', 'project-x'),
117
+ use the relevant handle; otherwise omit to load across handles.
63
118
  """
119
+ logger.debug(f"load_memories called: handle={handle}")
64
120
  try:
65
121
  memories = client.get_memories(handle=handle)
122
+ logger.debug(f"Retrieved {len(memories)} memories")
123
+
66
124
  if not memories:
125
+ logger.info(
126
+ f"No memories found for handle: {handle if handle else 'all'}")
67
127
  return f"No memories found under the handle: {handle if handle else 'all'}."
68
128
 
69
129
  output = ["### Retrieved Memories:"]
@@ -72,8 +132,11 @@ async def load_memories(handle: str = None) -> str:
72
132
  output.append(
73
133
  f"Handle: [{m.get('handle')}]\nContent: {m.get('content')}\n---"
74
134
  )
135
+ logger.info(f"Successfully loaded {len(memories)} memories")
75
136
  return "\n".join(output)
76
137
  except Exception as e:
138
+ logger.error(
139
+ f"Error retrieving memories: {type(e).__name__}: {str(e)}", exc_info=DEBUG)
77
140
  return f"Error retrieving memories: {str(e)}"
78
141
 
79
142
  if __name__ == "__main__":
@@ -1,8 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yomemoai-mcp
3
- Version: 0.1.0
3
+ Version: 0.1.3
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
 
@@ -0,0 +1,9 @@
1
+ yomemoai_mcp/__init__.py,sha256=gDi0t9iKR423OZlwrrtwDaED3ck06XMe_y8mcxisBl0,95
2
+ yomemoai_mcp/client.py,sha256=oZNfRW3ppfSRkWvG3ej-w56lUyfKsc6jOjvV_IY4JxM,6960
3
+ yomemoai_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ yomemoai_mcp/server.py,sha256=60n1uXeWV9gEmnimjZNdK4xCixSTrNYAqZ8zAwugeoc,5242
5
+ yomemoai_mcp-0.1.3.dist-info/METADATA,sha256=ceULHtnbq_kf1FAC4y1pYjrypbIMxaHQCgDUhqZL3yE,7551
6
+ yomemoai_mcp-0.1.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
7
+ yomemoai_mcp-0.1.3.dist-info/entry_points.txt,sha256=uV0479NK4ppOUV1T9VSwFTLi87F4deaRjbL8s43R8uI,100
8
+ yomemoai_mcp-0.1.3.dist-info/licenses/LICENSE,sha256=UVt2r_C3iy7rNKJAj_EcLcmcZdXONxJ01oJNsIEqTfw,1066
9
+ yomemoai_mcp-0.1.3.dist-info/RECORD,,
@@ -1,4 +1,3 @@
1
1
  [console_scripts]
2
2
  memo-mcp = yomemoai_mcp.server:mcp.run
3
- yomemo = yomemoai_mcp.server:mcp.run
4
3
  yomemoai-mcp = yomemoai_mcp.server:mcp.run
@@ -1,9 +0,0 @@
1
- yomemoai_mcp/__init__.py,sha256=I2FoPD8dKHgF95vpefoGxZkQ9gi9yeYkEM2ZObyUQ48,58
2
- yomemoai_mcp/client.py,sha256=vNPnYJAYpdZlnYXBKyyx618-sAtNSzZNTRXENw4G8uo,4850
3
- yomemoai_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- yomemoai_mcp/server.py,sha256=3J-RLInsVz2ALTQV1YyAQti9mG7Y5YPfYXXO_sYprzg,2973
5
- yomemoai_mcp-0.1.0.dist-info/METADATA,sha256=04U52HSfVF9ODHRLU04tpkjp3m4BkllM_82EoZlvmbs,7032
6
- yomemoai_mcp-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
7
- yomemoai_mcp-0.1.0.dist-info/entry_points.txt,sha256=gWU6hmHA4oQMCYGqffv-f2YamGj-ERq8np2uuCSOhuI,137
8
- yomemoai_mcp-0.1.0.dist-info/licenses/LICENSE,sha256=UVt2r_C3iy7rNKJAj_EcLcmcZdXONxJ01oJNsIEqTfw,1066
9
- yomemoai_mcp-0.1.0.dist-info/RECORD,,