MCP内存服务器v9.0
用于持久内存和上下文管理的模型上下文协议(MCP)服务器 语义事件提取, 图支持的上下文扩展,以及 质量基准。提供语义搜索、工件存储和事件+实体上下文包。
______________________________________________________________________
哲学:全面回忆而非精确
MCP存储器被设计为 人类思维的延伸,而不仅仅是一种工作生产力工具。
核心原则
1.捕捉一切,不过滤任何东西
人脑每天处理大量的信息——对话、观察、想法、事实、情感、交易、动作、关系。一个真正的记忆系统应该捕捉到人类经验的全部,而不仅仅是“工作事件”
现代LLM(GPT-4o+、Claude Opus等)能够从任何内容中提取细微差别的类别。我们应该充分利用这种能力,而不是将其限制在固定的分类法中。
2.噪音是可以接受的,信息缺失是不可接受的
*“所有相关数据加上噪声比没有噪声的部分回忆要好。”*
人脑不断地处理噪音——无关的记忆与相关的记忆一起出现,我们自然会过滤掉。LLMs也可以做同样的事情。他们无法回忆起在提取过程中从未存储或过滤掉的信息。
更喜欢回忆而不是精确。 返回比所需更多的上下文,让消费LLM决定相关性。
3.让用法定义结构
与其预先定义僵化的类别,不如让系统进化:
- 事件:任何值得记住的事件-系统会建议分类,而不是相反
- 实体:参与事件的任何人、地点、事物、概念或抽象概念
- 关系:从事件中的共现和明确提及中出现
实体模式应根据收到的查询进行扩展。如果用户经常询问“健康”或“财务”,系统应该为这些领域开发更丰富的表示。
4.孤立事实之上的关联背景
记忆不是一个简单的列表,它是一个图表。每条信息都与以下内容相关:
- 谁 涉及(实体)
- 什么 发生(事件)
- 当 它发生了(暂时的)
- 哪里 它发生了(空间)
- 为什么 它很重要(因果关系)
- 还有什么 相关(关联链接)
Recall应该自由地遍历此图,返回完整的上下文网络,而不是孤立的匹配。
设计启示
| 传统方法 | MCP内存方法 |
|---|---|
| 固定事件类别 | 动态、LLM建议类别 |
| 提取时过滤噪声 | 召回时过滤噪声(或根本不过滤) |
| 精确优化检索 | 召回优化检索 |
| 模式优先设计 | 使用驱动的模式演变 |
| 以工作为中心的分类学 | 涵盖生活的分类学 |
______________________________________________________________________
V7的新功能
- 质量基准套件 -用于事件提取、实体解析、召回相关性和具有CI/CD重放模式的图形扩展的综合基准测试
- 结果评估 -基于LLM的简单结果测试,用于快速质量验证(约0.006美元/次)
- 简化的API(V5/V6) -
remember(),recall(),forget(),status()-四个直观的工具取代了17个
以前的版本
V4:图形扩展
- 图形支持的上下文扩展 -
recall(expand=true)回报related_context[]和entities[]使用图遍历(Postgres中的Apache AGE) - 质量调整默认值 -
graph_expand=true,graph_seed_limit=1,智能类别筛选器 - 降噪 -默认矢量距离截止过滤器低质量点击
特性
- 持久内存存储 -跨会话存储和检索记忆
- 语义搜索 -使用自然语言查询(OpenAI嵌入)查找记忆
- 人工制品摄入 -存储和分块大型文档(电子邮件、文档、代码)
- 混合搜索 -语义+关键字搜索与RRF排名相结合
- 历史跟踪 -仅附加每个会话的对话历史记录
- 事件提取 -从工件中提取结构化事件(承诺、决策、风险)
可用工具
V5+简化API(推荐)
| 工具 | 说明 |
|---|---|
remember | 通过自动分块、嵌入和事件提取来存储内容 |
recall | 具有可选图扩展和实体丰富功能的语义搜索 |
forget | 通过级联确认删除存储的内容 |
status | 检查系统运行状况和提取作业状态 |
传统工具(V1-V4)
Click to expand legacy tools (17 total)
内存工具
memory_store-以类型和信心存储记忆memory_search-记忆的语义搜索memory_list-列出所有存储的内存memory_delete-删除特定内存
历史记录工具
history_append-在会话历史记录中添加条目history_get-检索会话历史记录
工件工具
artifact_ingest-摄取并分割大型文档artifact_search-在工件中搜索artifact_get-按ID检索工件artifact_delete-删除工件
搜索工具
hybrid_search-使用源元数据进行跨集合搜索(工件+事件)embedding_health-检查OpenAI嵌入服务状态
事件工具
event_search-使用过滤器查询事件(类别、时间、工件)event_get-通过ID和证据获取单个事件event_list-列出工件的所有事件event_reextract-强制重新提取事件job_status-检查提取作业状态
快速开始
先决条件
- Python 3.11+
- ChromaDB在端口8001上运行
- PostgreSQL在端口5432上运行
- OpenAI API密钥
启动服务器
# Start with scripts (recommended)
cd .claude-workspace/deployment
echo "OPENAI_API_KEY=sk-proj-your-key" > .env
./scripts/env-up.sh prod
# Or manually:
cd .claude-workspace/implementation/mcp-server
pip install -r requirements.txt
python src/server.py
# Start event worker (separate terminal)
python -m src.worker服务器运行时间: http://localhost:3001/mcp/
环境港口
| 环境 | MCP | ChromaDB | PostgreSQL |
|---|---|---|---|
| 产品 | 3001 | 8001 | 5432 |
| 分期 | 3101 | 8101 | 5532 |
| 测试 | 3201 | 8201 | 5632 |
HTTPS访问(通过ngrok)
对于Claude AI网络访问,请使用ngrok:
ngrok http 3001这提供了一个HTTPS URL,如下所示: https://xxxx.ngrok-free.app/mcp/
客户端配置
光标IDE
编辑 ~/.cursor/mcp.json:
{
"mcpServers": {
"memory": {
"url": "http://localhost:3001/mcp/"
}
}
}克劳德桌面/Claude.ai
- 启动ngrok:
ngrok http 3001 - 打开 克劳德桌面版 或 Claude.ai (网络)
- 首选 设置 → 连接器
- 点击 添加自定义连接器
- 输入:
- 名字: memory - 统一资源定位符: https://your-ngrok-url.ngrok-free.app/mcp/
备注:Claude Desktop和Claude.ai需要HTTPS。使用ngrok公开您的本地服务器。
克劳德代码(CLI)
创建或编辑 .mcp.json 在项目根目录中:
{
"mcpServers": {
"memory": {
"type": "http",
"url": "http://localhost:3001/mcp/"
}
}
}重要的模式要求 (Claude Code比其他客户更严格):
| 字段 | 必填 | 备注 |
|---|---|---|
type | 是 | 必须是 "http" (不是 "url") |
url | 是 | 包括尾随斜线 |
常见错误:
"Does not adhere to MCP server configuration schema"-失踪type字段或使用无效类型,如"url"- 添加不支持的字段,如
"description"将导致验证错误
验证配置:
claude /doctor多服务器示例:
{
"mcpServers": {
"memory": {
"type": "http",
"url": "http://localhost:3001/mcp/"
},
"playwright": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@playwright/mcp@latest"]
}
}
}备注:Claude Code与 http://localhost -本地开发不需要HTTPS。MCP传输语义(在连接客户端之前阅读此内容)
此服务器使用 流式HTTP (长寿 上海证券交易所 引擎盖下)。
- 使用规范端点:始终使用尾随斜线配置客户端:
.../mcp/ - 客户可以致电
/mcp无论如何:一些客户端(包括Claude连接器验证)将向发送请求/mcp(没有尾随斜线)。服务器必须支持此功能,并将客户端路由到/mcp/安全。 - 重定向必须保留HTTPS:当暴露在代理(例如ngrok)后面时,来自以下位置的任何重定向
/mcp不得降级到http://...。服务器应:
- 处理 /mcp 直接,或 - 发布一个 相对的 重定向(Location: /mcp/)并尊重转发的原型/主机标头。
测试
自动化测试
# Full user simulation (22 tests)
python3 .claude-workspace/tests/e2e/full_user_simulation.py
# Event extraction validation
python3 .claude-workspace/tests/e2e/validate_event_extraction.py
# Test sample documents
cd .claude-workspace/tests/e2e/sample_docs
python test_samples.py all健康检查
curl http://localhost:3001/health质量基准(V7)
V7包括一个全面的基准套件,用于测量提取和检索质量。
快速质量检查
cd .claude-workspace/benchmarks
export $(grep OPENAI_API_KEY ../deployment/.env)
python outcome_eval.py它存储测试文档、连接查询,并使用GPT-4o-mini验证预期结果。成本:约0.006美元/次。
完整基准套件
# Replay mode (uses fixtures, no API calls - for CI/CD)
python tests/benchmark_runner.py --mode=replay
# Live mode (requires running services)
python tests/benchmark_runner.py --mode=live指标
| 度量 | 描述 | 目标 |
|---|---|---|
| 检索MRR | 第一相关结果排名 | 0.60 |
| 检索NDCG | 总体排名质量 | 0.65 |
| 事件F1 | 事件提取精度 | 0.70 |
| 实体F1 | 实体提取精度 | 0.70 |
| 图F1 | 图扩展精度 | 0.60 |
看 基准测试/README.md 了解详情。
图形可视化(V4:Apache AGE→ Neo4j浏览器)
V4实现了 阿帕奇时代 图(位于Postgres内部)用于上下文扩展。最方便的方式 *视觉上* 探索它是将其导出到CSV并加载到 Neo4j,然后使用 Neo4j浏览器.
这表明了什么(重要)
- Neo4j将显示您导出和导入的所有节点/边,不是Postgres/AGE的实时视图。
- 如果你的图表很大,更喜欢可视化 *子图* (限制)而不是试图一次渲染所有内容。
1) 导出AGE图(nur)从Postgres到CSV
此仓库的AGE图名称为 nur (参见 GraphService 默认)。
从repo根目录:
mkdir -p temp/neo4j_import
# Export nodes + edges from AGE to CSV (host files)
docker exec -i postgres-v4 psql -U events -d events -v ON_ERROR_STOP=1 temp/neo4j_import/entities.csv
LOAD 'age';
SET search_path = ag_catalog, public;
COPY (
SELECT
trim(both '"' from entity_id::text) AS entity_id,
trim(both '"' from canonical_name::text) AS canonical_name,
trim(both '"' from type::text) AS type,
nullif(trim(both '"' from coalesce(role::text,'')), '') AS role,
nullif(trim(both '"' from coalesce(organization::text,'')), '') AS organization
FROM cypher('nur', $$
MATCH (e:Entity)
RETURN e.entity_id AS entity_id,
e.canonical_name AS canonical_name,
e.type AS type,
e.role AS role,
e.organization AS organization
$$) AS (entity_id agtype, canonical_name agtype, type agtype, role agtype, organization agtype)
) TO STDOUT WITH (FORMAT csv, HEADER true);
SQL
docker exec -i postgres-v4 psql -U events -d events -v ON_ERROR_STOP=1 temp/neo4j_import/events.csv
LOAD 'age';
SET search_path = ag_catalog, public;
COPY (
SELECT
trim(both '"' from event_id::text) AS event_id,
trim(both '"' from category::text) AS category,
trim(both '"' from narrative::text) AS narrative,
trim(both '"' from artifact_uid::text) AS artifact_uid,
trim(both '"' from revision_id::text) AS revision_id,
nullif(trim(both '"' from coalesce(event_time::text,'')), '') AS event_time,
(trim(both '"' from confidence::text)) AS confidence
FROM cypher('nur', $$
MATCH (ev:Event)
RETURN ev.event_id AS event_id,
ev.category AS category,
ev.narrative AS narrative,
ev.artifact_uid AS artifact_uid,
ev.revision_id AS revision_id,
ev.event_time AS event_time,
ev.confidence AS confidence
$$) AS (event_id agtype, category agtype, narrative agtype, artifact_uid agtype, revision_id agtype, event_time agtype, confidence agtype)
) TO STDOUT WITH (FORMAT csv, HEADER true);
SQL
docker exec -i postgres-v4 psql -U events -d events -v ON_ERROR_STOP=1 temp/neo4j_import/acted_in.csv
LOAD 'age';
SET search_path = ag_catalog, public;
COPY (
SELECT
trim(both '"' from entity_id::text) AS entity_id,
trim(both '"' from event_id::text) AS event_id,
nullif(trim(both '"' from coalesce(role::text,'')), '') AS role
FROM cypher('nur', $$
MATCH (e:Entity)-[r:ACTED_IN]->(ev:Event)
RETURN e.entity_id AS entity_id,
ev.event_id AS event_id,
r.role AS role
$$) AS (entity_id agtype, event_id agtype, role agtype)
) TO STDOUT WITH (FORMAT csv, HEADER true);
SQL
docker exec -i postgres-v4 psql -U events -d events -v ON_ERROR_STOP=1 temp/neo4j_import/about.csv
LOAD 'age';
SET search_path = ag_catalog, public;
COPY (
SELECT
trim(both '"' from event_id::text) AS event_id,
trim(both '"' from entity_id::text) AS entity_id
FROM cypher('nur', $$
MATCH (ev:Event)-[:ABOUT]->(e:Entity)
RETURN ev.event_id AS event_id,
e.entity_id AS entity_id
$$) AS (event_id agtype, entity_id agtype)
) TO STDOUT WITH (FORMAT csv, HEADER true);
SQL
docker exec -i postgres-v4 psql -U events -d events -v ON_ERROR_STOP=1 temp/neo4j_import/possibly_same.csv
LOAD 'age';
SET search_path = ag_catalog, public;
COPY (
SELECT
trim(both '"' from entity_a_id::text) AS entity_a_id,
trim(both '"' from entity_b_id::text) AS entity_b_id,
(trim(both '"' from confidence::text)) AS confidence,
nullif(trim(both '"' from coalesce(reason::text,'')), '') AS reason
FROM cypher('nur', $$
MATCH (a:Entity)-[r:POSSIBLY_SAME]->(b:Entity)
RETURN a.entity_id AS entity_a_id,
b.entity_id AS entity_b_id,
r.confidence AS confidence,
r.reason AS reason
$$) AS (entity_a_id agtype, entity_b_id agtype, confidence agtype, reason agtype)
) TO STDOUT WITH (FORMAT csv, HEADER true);
SQL2) 在本地运行Neo4j(Docker)进行可视化
在安装了CSV的情况下启动Neo4j:
docker rm -f neo4j-local >/dev/null 2>&1 || true
docker run -d --name neo4j-local \
-p 7474:7474 -p 7687:7687 \
-e NEO4J_AUTH=none \
-v "$PWD/temp/neo4j_import:/import" \
neo4j:5打开Neo4j浏览器:
http://localhost:7474/browser/
3) 导入Neo4j(从CSV)
docker exec -i neo4j-local cypher-shell -a bolt://localhost:7687 (ev)
SET r.role = CASE WHEN row.role = '' THEN null ELSE row.role END;
LOAD CSV WITH HEADERS FROM 'file:///about.csv' AS row
MATCH (ev:Event {event_id: row.event_id})
MATCH (e:Entity {entity_id: row.entity_id})
MERGE (ev)-[:ABOUT]->(e);
LOAD CSV WITH HEADERS FROM 'file:///possibly_same.csv' AS row
MATCH (a:Entity {entity_id: row.entity_a_id})
MATCH (b:Entity {entity_id: row.entity_b_id})
MERGE (a)-[r:POSSIBLY_SAME]->(b)
SET r.confidence = CASE WHEN row.confidence = '' THEN null ELSE toFloat(row.confidence) END,
r.reason = CASE WHEN row.reason = '' THEN null ELSE row.reason END;
CYPHER4) 在Neo4j浏览器中可视化
运行:
MATCH (n)-[r]->(m)
RETURN n,r,m
LIMIT 50;确认计数:
MATCH (n) RETURN labels(n) AS labels, count(*) AS cnt;
MATCH ()-[r]->() RETURN type(r) AS rel, count(*) AS cnt;清理
docker rm -f neo4j-local建筑
┌─────────────────┐ ┌─────────────────┐
│ MCP Clients │────▶│ MCP Server │
│ (Cursor/Claude)│ │ (Streamable │
└─────────────────┘ │ HTTP) │
└────────┬────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ ChromaDB │ │ PostgreSQL │ │ Event Worker │
│ (Vector Store) │ │ (Events DB) │ │ (Background) │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└──────────────────────┼──────────────────────┘
│
┌────────▼────────┐
│ OpenAI API │
│ (Embeddings + │
│ Extraction) │
└─────────────────┘环境变量
核心
| 变量 | 默认值 | 描述 |
|---|---|---|
MCP_PORT | 3000 | 内部容器端口(外部:prod=3001,stage=3101,test=3201) |
OPENAI_API_KEY | (必需) | OpenAI API密钥 |
LOG_LEVEL | 信息 | 日志记录级别 |
色度数据库
| 变量 | 默认值 | 描述 |
|---|---|---|
CHROMA_HOST | 本地主机 | ChromaDB主机 |
CHROMA_PORT | 8001 | ChromaDB端口 |
V3:PostgreSQL
| 变量 | 默认值 | 描述 |
|---|---|---|
EVENTS_DB_DSN | (必填) | Postgres连接字符串 |
POSTGRES_POOL_MIN | 2 | 最小池连接数 |
POSTGRES_POOL_MAX | 10 | 最大池连接数 |
V3:事件提取
| 变量 | 默认值 | 描述 |
|---|---|---|
OPENAI_EVENT_MODEL | gpt-4o-mini | 提取模型 |
EVENT_MAX_ATTEMPTS | 5 | 最大重试次数 |
项目结构
mcp_memory/
├── .claude/ # Claude Code configuration
│ ├── docs/ # Workflow documentation
│ └── settings.json # Hooks configuration
├── .claude-workspace/ # Build artifacts
│ ├── implementation/ # Source code
│ │ └── mcp-server/ # MCP server implementation
│ │ ├── src/
│ │ │ ├── server.py # Main MCP server
│ │ │ ├── services/ # V3 services
│ │ │ ├── storage/ # Postgres client
│ │ │ ├── tools/ # Event tools
│ │ │ └── worker/ # Event extraction worker
│ │ └── migrations/ # SQL migrations
│ ├── tests/ # Test suites
│ │ └── e2e/ # End-to-end tests
│ └── deployment/ # Docker configs
├── CLAUDE.md # Project instructions
└── README.md # This file活动类别
V3提取了8种语义事件:
| 类别 | 描述 |
|---|---|
| 承诺 | 承诺、截止日期、可交付成果 |
| 执行 | 采取的行动、完成情况 |
| 决策 | 做出选择,设定方向 |
| 协作 | 会议、讨论、交接 |
| 质量风险 | 问题、阻碍因素、担忧 |
| 反馈 | 用户输入、评论、批评 |
| 更改 | 修改、枢轴、更新 |
| 利益相关者 | 参与人员、角色 |
源元数据
V3支持用于可信度推理的丰富源元数据。摄入人工制品时,您可以提供:
| 字段 | 值 | 目的 |
|---|---|---|
document_date | ISO日期(YYYY-MM-DD) | 文档编写时 |
source_type | 电子邮件、slack、meeting_notes、文档、政策、合同、聊天、文字记录、维基、工单 | 文档来源 |
document_status | 草稿、最终、批准、取代、存档 | 文档生命周期阶段 |
author_title | 自由文本(例如,“CEO”、“项目负责人”) | 作者的角色 |
distribution_scope | 私人、团队、部门、公司、公众 | 目标受众 |
这些元数据流向事件,使模型能够推理:
- 近期性:哪些信息是最新的
- 权威:谁作了发言,以何种身份发言
- 正式性:草稿与批准的文件
- 上下文:会议记录与官方政策
许可证
私人-保留所有权利。
