n1mem-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.
- n1mem_mcp-0.1.0/PKG-INFO +85 -0
- n1mem_mcp-0.1.0/README.md +70 -0
- n1mem_mcp-0.1.0/n1mem_mcp/__init__.py +9 -0
- n1mem_mcp-0.1.0/n1mem_mcp/__main__.py +14 -0
- n1mem_mcp-0.1.0/n1mem_mcp/server.py +227 -0
- n1mem_mcp-0.1.0/n1mem_mcp.egg-info/PKG-INFO +85 -0
- n1mem_mcp-0.1.0/n1mem_mcp.egg-info/SOURCES.txt +10 -0
- n1mem_mcp-0.1.0/n1mem_mcp.egg-info/dependency_links.txt +1 -0
- n1mem_mcp-0.1.0/n1mem_mcp.egg-info/entry_points.txt +2 -0
- n1mem_mcp-0.1.0/n1mem_mcp.egg-info/top_level.txt +1 -0
- n1mem_mcp-0.1.0/pyproject.toml +29 -0
- n1mem_mcp-0.1.0/setup.cfg +4 -0
n1mem_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: n1mem-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: N1Mem MCP server (BYOK, zero dependencies) — let Claude / Cursor / OpenClaw talk to N1Mem long-term memory via MCP
|
|
5
|
+
Author: N1Mem (powered by T1Mem engine)
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Project-URL: Homepage, https://www.n1mem.com
|
|
8
|
+
Keywords: mcp,memory,llm,agent,n1mem,t1mem,claude,cursor,openclaw
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# n1mem-mcp · N1Mem 记忆 MCP Server
|
|
17
|
+
|
|
18
|
+
> powered by T1Mem engine · BYOK(自带 Key)· 零依赖(仅标准库)
|
|
19
|
+
|
|
20
|
+
让 Claude / Cursor / OpenClaw 之类支持 MCP 的客户端,直接获得 N1Mem 的**长期记忆**能力。
|
|
21
|
+
|
|
22
|
+
- `memory_write`:写入一段记忆(持久化 + 建向量,写入即可召回)
|
|
23
|
+
- `memory_recall`:按自然语言问题召回记忆;返回的 `answer` 已基于命中记忆接地,`retrieved` 给出命中的明文记忆
|
|
24
|
+
- `memory_health`:查看服务健康与 provider 可用性
|
|
25
|
+
|
|
26
|
+
本 Server 是**薄层透传**:不含任何记忆逻辑,只把工具调用转发到 N1Mem C-2 API(`https://api.n1mem.com`)。因此零依赖、可塞进任意环境。
|
|
27
|
+
|
|
28
|
+
## 安装
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install n1mem-mcp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 获取 Key
|
|
35
|
+
|
|
36
|
+
试用 Key 当前由 N1Mem 团队发放(自助注册 / 定价页在路上)。拿到形如 `tk_xxx` 的 Key。
|
|
37
|
+
|
|
38
|
+
## 配置(Cursor / Claude / OpenClaw)
|
|
39
|
+
|
|
40
|
+
把下面片段加进你的 MCP 配置(`~/.cursor/mcp.json`、Claude 的 `claude_desktop_config.json`、或 OpenClaw 的 MCP 配置):
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"n1mem": {
|
|
46
|
+
"command": "n1mem-mcp",
|
|
47
|
+
"env": { "N1MEM_API_KEY": "tk_xxx" }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
或用模块方式启动(无需 `pip install` 也可,只要能 import):
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"mcpServers": {
|
|
58
|
+
"n1mem": {
|
|
59
|
+
"command": "python",
|
|
60
|
+
"args": ["-m", "n1mem_mcp"],
|
|
61
|
+
"env": { "N1MEM_API_KEY": "tk_xxx" }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 环境变量
|
|
68
|
+
|
|
69
|
+
| 变量 | 说明 | 默认 |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `N1MEM_API_KEY` | 你的试用 Key(必填) | 空 |
|
|
72
|
+
| `N1MEM_BASE_URL` | C-2 API 地址 | `https://api.n1mem.com` |
|
|
73
|
+
| `N1MEM_MCP_TIMEOUT` | 单条调用超时(秒) | `90` |
|
|
74
|
+
|
|
75
|
+
## 已知限制(诚实告知)
|
|
76
|
+
|
|
77
|
+
- 暂无 `delete` / `update` 工具(服务端未就绪,故不暴露);需要删记忆请联系团队。
|
|
78
|
+
- `health()` 可能返回 `status: degraded`:这是设计使然(部分上游 provider 未配置),核心记忆链路(存储 / 嵌入 / 写入 / 召回)正常。
|
|
79
|
+
|
|
80
|
+
## 与 `n1mem` 的区别
|
|
81
|
+
|
|
82
|
+
| 包 | 形态 | 用途 |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `n1mem` | Python SDK(函数调用) | 代码里直接 `m.ingest() / m.recall()` |
|
|
85
|
+
| `n1mem-mcp` | MCP Server(stdio) | 让 MCP 客户端(Claude/Cursor/OpenClaw)以工具方式调用记忆 |
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# n1mem-mcp · N1Mem 记忆 MCP Server
|
|
2
|
+
|
|
3
|
+
> powered by T1Mem engine · BYOK(自带 Key)· 零依赖(仅标准库)
|
|
4
|
+
|
|
5
|
+
让 Claude / Cursor / OpenClaw 之类支持 MCP 的客户端,直接获得 N1Mem 的**长期记忆**能力。
|
|
6
|
+
|
|
7
|
+
- `memory_write`:写入一段记忆(持久化 + 建向量,写入即可召回)
|
|
8
|
+
- `memory_recall`:按自然语言问题召回记忆;返回的 `answer` 已基于命中记忆接地,`retrieved` 给出命中的明文记忆
|
|
9
|
+
- `memory_health`:查看服务健康与 provider 可用性
|
|
10
|
+
|
|
11
|
+
本 Server 是**薄层透传**:不含任何记忆逻辑,只把工具调用转发到 N1Mem C-2 API(`https://api.n1mem.com`)。因此零依赖、可塞进任意环境。
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install n1mem-mcp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 获取 Key
|
|
20
|
+
|
|
21
|
+
试用 Key 当前由 N1Mem 团队发放(自助注册 / 定价页在路上)。拿到形如 `tk_xxx` 的 Key。
|
|
22
|
+
|
|
23
|
+
## 配置(Cursor / Claude / OpenClaw)
|
|
24
|
+
|
|
25
|
+
把下面片段加进你的 MCP 配置(`~/.cursor/mcp.json`、Claude 的 `claude_desktop_config.json`、或 OpenClaw 的 MCP 配置):
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"n1mem": {
|
|
31
|
+
"command": "n1mem-mcp",
|
|
32
|
+
"env": { "N1MEM_API_KEY": "tk_xxx" }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
或用模块方式启动(无需 `pip install` 也可,只要能 import):
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"n1mem": {
|
|
44
|
+
"command": "python",
|
|
45
|
+
"args": ["-m", "n1mem_mcp"],
|
|
46
|
+
"env": { "N1MEM_API_KEY": "tk_xxx" }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 环境变量
|
|
53
|
+
|
|
54
|
+
| 变量 | 说明 | 默认 |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `N1MEM_API_KEY` | 你的试用 Key(必填) | 空 |
|
|
57
|
+
| `N1MEM_BASE_URL` | C-2 API 地址 | `https://api.n1mem.com` |
|
|
58
|
+
| `N1MEM_MCP_TIMEOUT` | 单条调用超时(秒) | `90` |
|
|
59
|
+
|
|
60
|
+
## 已知限制(诚实告知)
|
|
61
|
+
|
|
62
|
+
- 暂无 `delete` / `update` 工具(服务端未就绪,故不暴露);需要删记忆请联系团队。
|
|
63
|
+
- `health()` 可能返回 `status: degraded`:这是设计使然(部分上游 provider 未配置),核心记忆链路(存储 / 嵌入 / 写入 / 召回)正常。
|
|
64
|
+
|
|
65
|
+
## 与 `n1mem` 的区别
|
|
66
|
+
|
|
67
|
+
| 包 | 形态 | 用途 |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `n1mem` | Python SDK(函数调用) | 代码里直接 `m.ingest() / m.recall()` |
|
|
70
|
+
| `n1mem-mcp` | MCP Server(stdio) | 让 MCP 客户端(Claude/Cursor/OpenClaw)以工具方式调用记忆 |
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""N1Mem MCP Server —— 让 Claude / Cursor / OpenClaw 通过 MCP 直连 N1Mem 记忆能力。
|
|
2
|
+
|
|
3
|
+
零依赖:仅用标准库实现最小 JSON-RPC 2.0 over stdio,薄层转发到 C-2 API。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .server import SERVER_NAME, SERVER_VERSION, handle, serve_stdio
|
|
7
|
+
|
|
8
|
+
__version__ = SERVER_VERSION
|
|
9
|
+
__all__ = ["SERVER_NAME", "SERVER_VERSION", "__version__", "serve_stdio", "handle"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Entry point for `python -m n1mem_mcp` and the `n1mem-mcp` console command.
|
|
2
|
+
|
|
3
|
+
无第三方依赖:仅用标准库做最小 JSON-RPC 2.0 over stdio。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .server import serve_stdio
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> None:
|
|
10
|
+
serve_stdio()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
main()
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""N1Mem MCP Server —— 让 Claude / Cursor / OpenClaw 通过 MCP 直连记忆能力。
|
|
2
|
+
|
|
3
|
+
设计取舍(写在最前面,避免后来人重复踩):
|
|
4
|
+
* **不引第三方 MCP SDK**:用最小 JSON-RPC 2.0 over stdio 实现,零依赖。
|
|
5
|
+
原因:本模块要能塞进 FC 代码包,且 MCP 协议在
|
|
6
|
+
initialize / tools/list / tools/call 三个方法上非常稳定,自己实现可控性更高。
|
|
7
|
+
* **薄层透传**:本模块不含任何记忆逻辑,只把工具调用转发到 C-2 API。
|
|
8
|
+
记忆能力在 T1Mem 引擎与 C-2 服务里,避免逻辑分叉、避免泄露引擎实现。
|
|
9
|
+
* **错误诚实**:上游 401 / 429 原样转成 MCP 工具错误;未就绪能力(delete/update)
|
|
10
|
+
**不暴露成工具**,避免客户端以为支持。
|
|
11
|
+
|
|
12
|
+
✅ 写入即持久化(D7 存储 + C-10 语义召回):memory_write 写入即可被召回。
|
|
13
|
+
✅ recall 已接地(P0 修复,2026-09-09):memory_recall 返回的 `answer` 是基于
|
|
14
|
+
召回记忆生成的,不再是「我没有你的记忆」这类拒答;`retrieved` 同时给出命中的
|
|
15
|
+
明文记忆,便于审计。
|
|
16
|
+
⚠ 已知限制(如实告知调用方):暂无 delete / update 工具(未就绪故不暴露)。
|
|
17
|
+
|
|
18
|
+
用法(Cursor / Claude / OpenClaw 配置片段)::
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"n1mem": {
|
|
23
|
+
"command": "n1mem-mcp",
|
|
24
|
+
"env": { "N1MEM_API_KEY": "tk_xxx" }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
或等价地用模块方式启动(无需安装命令)::
|
|
30
|
+
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"n1mem": {
|
|
34
|
+
"command": "python",
|
|
35
|
+
"args": ["-m", "n1mem_mcp"],
|
|
36
|
+
"env": { "N1MEM_API_KEY": "tk_xxx" }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
"""
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
import json
|
|
44
|
+
import os
|
|
45
|
+
import sys
|
|
46
|
+
import urllib.error
|
|
47
|
+
import urllib.request
|
|
48
|
+
from typing import Any
|
|
49
|
+
|
|
50
|
+
SERVER_NAME = "n1mem"
|
|
51
|
+
SERVER_VERSION = "0.1.0"
|
|
52
|
+
PROTOCOL_VERSION = "2024-11-05"
|
|
53
|
+
|
|
54
|
+
DEFAULT_BASE_URL = os.environ.get("N1MEM_BASE_URL", "https://api.n1mem.com")
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------------- 工具定义
|
|
57
|
+
|
|
58
|
+
TOOLS: list[dict] = [
|
|
59
|
+
{
|
|
60
|
+
"name": "memory_write",
|
|
61
|
+
"description": (
|
|
62
|
+
"把一段内容写入 N1Mem 长期记忆(持久化 + 生成向量,写入即可被召回)。"
|
|
63
|
+
),
|
|
64
|
+
"inputSchema": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"properties": {
|
|
67
|
+
"text": {"type": "string", "description": "要记住的内容"},
|
|
68
|
+
},
|
|
69
|
+
"required": ["text"],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "memory_recall",
|
|
74
|
+
"description": (
|
|
75
|
+
"按问题从 N1Mem 记忆中召回相关内容(关键词 + 语义融合,支持改写问法)。"
|
|
76
|
+
"返回体含 retrieved(召回的明文记忆)与 answer(已基于记忆接地的回答)。"
|
|
77
|
+
),
|
|
78
|
+
"inputSchema": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"properties": {
|
|
81
|
+
"prompt": {"type": "string", "description": "要问的问题 / 检索提示"},
|
|
82
|
+
"purpose": {"type": "string", "description": "用途标签,默认 recall",
|
|
83
|
+
"default": "recall"},
|
|
84
|
+
},
|
|
85
|
+
"required": ["prompt"],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "memory_health",
|
|
90
|
+
"description": "查看 N1Mem 服务健康状态与各 provider 可用性。",
|
|
91
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
92
|
+
},
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------- 上游调用
|
|
97
|
+
|
|
98
|
+
class UpstreamError(Exception):
|
|
99
|
+
def __init__(self, status: int, detail: str):
|
|
100
|
+
super().__init__(f"[{status}] {detail}")
|
|
101
|
+
self.status = status
|
|
102
|
+
self.detail = detail
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _call_api(path: str, body: dict | None = None, timeout: float | None = None) -> Any:
|
|
106
|
+
"""转发到 C-2 API。BYOK:key 只从环境变量取,绝不内联。
|
|
107
|
+
|
|
108
|
+
超时说明:C-2 的 ingest/recall 内部含一次上游 LLM 生成,默认 90s
|
|
109
|
+
(实测首调含冷启动可能 20-50s)。可用 N1MEM_MCP_TIMEOUT 覆盖。
|
|
110
|
+
"""
|
|
111
|
+
timeout = timeout or float(os.environ.get("N1MEM_MCP_TIMEOUT", "90"))
|
|
112
|
+
api_key = os.environ.get("N1MEM_API_KEY", "")
|
|
113
|
+
headers = {"Accept": "application/json"}
|
|
114
|
+
if api_key:
|
|
115
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
116
|
+
data = json.dumps(body, ensure_ascii=False).encode("utf-8") if body is not None else None
|
|
117
|
+
if data:
|
|
118
|
+
headers["Content-Type"] = "application/json"
|
|
119
|
+
req = urllib.request.Request(DEFAULT_BASE_URL + path, data=data,
|
|
120
|
+
headers=headers, method="POST" if data else "GET")
|
|
121
|
+
try:
|
|
122
|
+
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
123
|
+
raw = r.read().decode("utf-8", "replace")
|
|
124
|
+
return json.loads(raw) if raw else {}
|
|
125
|
+
except urllib.error.HTTPError as e:
|
|
126
|
+
detail = ""
|
|
127
|
+
try:
|
|
128
|
+
detail = e.read().decode("utf-8", "replace")
|
|
129
|
+
except Exception:
|
|
130
|
+
pass
|
|
131
|
+
raise UpstreamError(e.code, detail or e.reason) from e
|
|
132
|
+
except urllib.error.URLError as e:
|
|
133
|
+
raise UpstreamError(0, f"connection error: {e.reason}") from e
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _dispatch(name: str, args: dict) -> dict:
|
|
137
|
+
"""执行工具,返回 MCP content 结构。"""
|
|
138
|
+
if name == "memory_write":
|
|
139
|
+
text = (args or {}).get("text", "")
|
|
140
|
+
if not text:
|
|
141
|
+
raise ValueError("text 不能为空")
|
|
142
|
+
out = _call_api("/v1/ingest", {"text": text, "purpose": "recall"})
|
|
143
|
+
return {"ok": True, "endpoint": "/v1/ingest", "result": out}
|
|
144
|
+
if name == "memory_recall":
|
|
145
|
+
prompt = (args or {}).get("prompt", "")
|
|
146
|
+
if not prompt:
|
|
147
|
+
raise ValueError("prompt 不能为空")
|
|
148
|
+
out = _call_api("/v1/recall", {"prompt": prompt,
|
|
149
|
+
"purpose": (args or {}).get("purpose", "recall")})
|
|
150
|
+
return {"ok": True, "endpoint": "/v1/recall", "result": out}
|
|
151
|
+
if name == "memory_health":
|
|
152
|
+
return {"ok": True, "endpoint": "/health", "result": _call_api("/health")}
|
|
153
|
+
raise ValueError(f"unknown tool: {name}")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# ---------------------------------------------------------------- JSON-RPC
|
|
157
|
+
|
|
158
|
+
def _ok(id_: Any, result: dict) -> dict:
|
|
159
|
+
return {"jsonrpc": "2.0", "id": id_, "result": result}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _err(id_: Any, code: int, msg: str) -> dict:
|
|
163
|
+
return {"jsonrpc": "2.0", "id": id_, "error": {"code": code, "message": msg}}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def handle(msg: dict) -> dict | None:
|
|
167
|
+
"""处理一条 JSON-RPC 消息;notification 返回 None(不回包)。"""
|
|
168
|
+
id_ = msg.get("id")
|
|
169
|
+
method = msg.get("method", "")
|
|
170
|
+
|
|
171
|
+
if method == "initialize":
|
|
172
|
+
return _ok(id_, {
|
|
173
|
+
"protocolVersion": PROTOCOL_VERSION,
|
|
174
|
+
"capabilities": {"tools": {}},
|
|
175
|
+
"serverInfo": {"name": SERVER_NAME, "version": SERVER_VERSION},
|
|
176
|
+
})
|
|
177
|
+
if method == "tools/list":
|
|
178
|
+
return _ok(id_, {"tools": TOOLS})
|
|
179
|
+
if method == "tools/call":
|
|
180
|
+
params = msg.get("params") or {}
|
|
181
|
+
name = params.get("name", "")
|
|
182
|
+
try:
|
|
183
|
+
payload = _dispatch(name, params.get("arguments") or {})
|
|
184
|
+
return _ok(id_, {
|
|
185
|
+
"content": [{"type": "text",
|
|
186
|
+
"text": json.dumps(payload, ensure_ascii=False, indent=2)}],
|
|
187
|
+
"isError": False,
|
|
188
|
+
})
|
|
189
|
+
except UpstreamError as e:
|
|
190
|
+
# 上游 401/429 等原样透传,便于客户端区分是鉴权问题还是配额问题
|
|
191
|
+
return _ok(id_, {
|
|
192
|
+
"content": [{"type": "text",
|
|
193
|
+
"text": f"upstream error {e.status}: {e.detail}"}],
|
|
194
|
+
"isError": True,
|
|
195
|
+
})
|
|
196
|
+
except Exception as e: # noqa: BLE001
|
|
197
|
+
return _ok(id_, {
|
|
198
|
+
"content": [{"type": "text", "text": f"tool error: {e}"}],
|
|
199
|
+
"isError": True,
|
|
200
|
+
})
|
|
201
|
+
if method.startswith("notifications/") or id_ is None:
|
|
202
|
+
return None
|
|
203
|
+
return _err(id_, -32601, f"method not found: {method}")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def serve_stdio(stream_in=None, stream_out=None) -> None:
|
|
207
|
+
"""stdio 传输:一问一答,每行一条 JSON-RPC。"""
|
|
208
|
+
sin = stream_in or sys.stdin
|
|
209
|
+
sout = stream_out or sys.stdout
|
|
210
|
+
for line in sin:
|
|
211
|
+
line = line.strip()
|
|
212
|
+
if not line:
|
|
213
|
+
continue
|
|
214
|
+
try:
|
|
215
|
+
msg = json.loads(line)
|
|
216
|
+
except json.JSONDecodeError:
|
|
217
|
+
sout.write(json.dumps(_err(None, -32700, "parse error")) + "\n")
|
|
218
|
+
sout.flush()
|
|
219
|
+
continue
|
|
220
|
+
resp = handle(msg)
|
|
221
|
+
if resp is not None:
|
|
222
|
+
sout.write(json.dumps(resp, ensure_ascii=False) + "\n")
|
|
223
|
+
sout.flush()
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
if __name__ == "__main__":
|
|
227
|
+
serve_stdio()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: n1mem-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: N1Mem MCP server (BYOK, zero dependencies) — let Claude / Cursor / OpenClaw talk to N1Mem long-term memory via MCP
|
|
5
|
+
Author: N1Mem (powered by T1Mem engine)
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Project-URL: Homepage, https://www.n1mem.com
|
|
8
|
+
Keywords: mcp,memory,llm,agent,n1mem,t1mem,claude,cursor,openclaw
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# n1mem-mcp · N1Mem 记忆 MCP Server
|
|
17
|
+
|
|
18
|
+
> powered by T1Mem engine · BYOK(自带 Key)· 零依赖(仅标准库)
|
|
19
|
+
|
|
20
|
+
让 Claude / Cursor / OpenClaw 之类支持 MCP 的客户端,直接获得 N1Mem 的**长期记忆**能力。
|
|
21
|
+
|
|
22
|
+
- `memory_write`:写入一段记忆(持久化 + 建向量,写入即可召回)
|
|
23
|
+
- `memory_recall`:按自然语言问题召回记忆;返回的 `answer` 已基于命中记忆接地,`retrieved` 给出命中的明文记忆
|
|
24
|
+
- `memory_health`:查看服务健康与 provider 可用性
|
|
25
|
+
|
|
26
|
+
本 Server 是**薄层透传**:不含任何记忆逻辑,只把工具调用转发到 N1Mem C-2 API(`https://api.n1mem.com`)。因此零依赖、可塞进任意环境。
|
|
27
|
+
|
|
28
|
+
## 安装
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install n1mem-mcp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 获取 Key
|
|
35
|
+
|
|
36
|
+
试用 Key 当前由 N1Mem 团队发放(自助注册 / 定价页在路上)。拿到形如 `tk_xxx` 的 Key。
|
|
37
|
+
|
|
38
|
+
## 配置(Cursor / Claude / OpenClaw)
|
|
39
|
+
|
|
40
|
+
把下面片段加进你的 MCP 配置(`~/.cursor/mcp.json`、Claude 的 `claude_desktop_config.json`、或 OpenClaw 的 MCP 配置):
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"n1mem": {
|
|
46
|
+
"command": "n1mem-mcp",
|
|
47
|
+
"env": { "N1MEM_API_KEY": "tk_xxx" }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
或用模块方式启动(无需 `pip install` 也可,只要能 import):
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"mcpServers": {
|
|
58
|
+
"n1mem": {
|
|
59
|
+
"command": "python",
|
|
60
|
+
"args": ["-m", "n1mem_mcp"],
|
|
61
|
+
"env": { "N1MEM_API_KEY": "tk_xxx" }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 环境变量
|
|
68
|
+
|
|
69
|
+
| 变量 | 说明 | 默认 |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `N1MEM_API_KEY` | 你的试用 Key(必填) | 空 |
|
|
72
|
+
| `N1MEM_BASE_URL` | C-2 API 地址 | `https://api.n1mem.com` |
|
|
73
|
+
| `N1MEM_MCP_TIMEOUT` | 单条调用超时(秒) | `90` |
|
|
74
|
+
|
|
75
|
+
## 已知限制(诚实告知)
|
|
76
|
+
|
|
77
|
+
- 暂无 `delete` / `update` 工具(服务端未就绪,故不暴露);需要删记忆请联系团队。
|
|
78
|
+
- `health()` 可能返回 `status: degraded`:这是设计使然(部分上游 provider 未配置),核心记忆链路(存储 / 嵌入 / 写入 / 召回)正常。
|
|
79
|
+
|
|
80
|
+
## 与 `n1mem` 的区别
|
|
81
|
+
|
|
82
|
+
| 包 | 形态 | 用途 |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `n1mem` | Python SDK(函数调用) | 代码里直接 `m.ingest() / m.recall()` |
|
|
85
|
+
| `n1mem-mcp` | MCP Server(stdio) | 让 MCP 客户端(Claude/Cursor/OpenClaw)以工具方式调用记忆 |
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
n1mem_mcp/__init__.py
|
|
4
|
+
n1mem_mcp/__main__.py
|
|
5
|
+
n1mem_mcp/server.py
|
|
6
|
+
n1mem_mcp.egg-info/PKG-INFO
|
|
7
|
+
n1mem_mcp.egg-info/SOURCES.txt
|
|
8
|
+
n1mem_mcp.egg-info/dependency_links.txt
|
|
9
|
+
n1mem_mcp.egg-info/entry_points.txt
|
|
10
|
+
n1mem_mcp.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
n1mem_mcp
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "n1mem-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "N1Mem MCP server (BYOK, zero dependencies) — let Claude / Cursor / OpenClaw talk to N1Mem long-term memory via MCP"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "Proprietary" }
|
|
12
|
+
authors = [{ name = "N1Mem (powered by T1Mem engine)" }]
|
|
13
|
+
keywords = ["mcp", "memory", "llm", "agent", "n1mem", "t1mem", "claude", "cursor", "openclaw"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
19
|
+
]
|
|
20
|
+
dependencies = []
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
n1mem-mcp = "n1mem_mcp.__main__:main"
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://www.n1mem.com"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
include = ["n1mem_mcp*"]
|