笔记管理服务

一个本地笔记管理服务,提供智能笔记管理、Markdown存储和强大的搜索功能。

  • by namp10010
  • Python
  • 本地部署
  • 更新于
在 GitHub 查看 namp10010/notes-mcp

使用说明

备注MCP服务器

一种本地笔记模型上下文协议(MCP)服务器,提供智能笔记管理,具有标记存储和强大的搜索功能。

特性

  • Markdown存储:笔记存储为带有frontmatter元数据的markdown文件
  • 智能索引:基于CSV的索引,用于快速检索和搜索
  • 强大的搜索功能:具有相关性评分和模糊匹配的全文搜索
  • 语义搜索:使用嵌入的AI驱动的基于意义的搜索(查找概念上相似的注释)
  • 混合搜索:结合关键字和语义搜索以获得最佳结果
  • 基于ULID的命名:按时间顺序排序的唯一标识符
  • 数据库就绪:结构设计便于迁移到关系数据库
  • MCP工具:笔记管理的四个核心工具

快速开始

安装

pip install -e .

配置

创建配置文件 .notes_config.yaml:

notes:
  directory: "~/notes"
  max_file_size: 10485760  # 10MB
  auto_rebuild_index: true
  index_rebuild_interval: 3600  # 1 hour

search:
  max_results: 50
  fuzzy_threshold: 0.8
  stop_words_file: "stopwords.txt"

security:
  validate_paths: true
  sanitize_input: true

运行服务器

本地开发

# Activate virtual environment
source venv/bin/activate

# Run the MCP server
notes-mcp

MCP服务器配置

服务器可以通过以下方式配置:

  1. 配置文件: .notes_config.yaml (推荐)
  2. 环境变量:覆盖配置文件设置
  3. 命令行参数:运行时覆盖

配置文件示例:

notes:
  directory: "~/.notes"
  max_file_size: 10485760  # 10MB
  auto_rebuild_index: true
  index_rebuild_interval: 3600  # 1 hour

search:
  max_results: 50
  fuzzy_threshold: 0.8
  stop_words_file: "stopwords.txt"

security:
  validate_paths: true
  sanitize_input: true

环境变量:

export NOTES_DIR="~/my-notes"
export SEARCH_MAX_RESULTS=100
export SEARCH_FUZZY_THRESHOLD=0.7

MCP服务器集成

克劳德桌面版

  1. 安装软件包:

    cd /path/to/notes-mcp
    pip install -e .
    
  2. 配置Claude桌面:

    • 打开克劳德桌面设置
    • 添加MCP服务器配置:
    {
      "mcpServers": {
        "notes-mcp": {
          "command": "notes-mcp",
          "args": [],
          "env": {
            "NOTES_DIR": "~/.notes"
          }
        }
      }
    }
    
  3. 重新启动克劳德桌面 加载MCP服务器

帆板运动

  1. 安装软件包:

    cd /path/to/notes-mcp
    pip install -e .
    
  2. 配置Windsurf:

    • 打开Windsurf设置
    • 添加MCP服务器:
    {
      "name": "notes-mcp",
      "command": "notes-mcp",
      "args": [],
      "env": {
        "NOTES_DIR": "~/notes"
      }
    }
    
  3. 重新启动Windsurf 启用MCP工具

其他MCP客户端

服务器遵循MCP规范,可以与任何兼容MCP的客户端集成。使用以下服务器配置:

{
  "name": "notes-mcp",
  "command": "notes-mcp",
  "args": [],
  "env": {
    "NOTES_DIR": "~/notes"
  }
}

用法示例

创建笔记

# Using the MCP tools in Claude
create_note(
    title="Project Planning",
    content="# Project Planning\n\n## Goals\n- Define objectives\n- Set timeline",
    tags=["planning", "project"]
)

搜索笔记

# Find notes by content
search_note(query="planning")

# Find notes by tags
find_note(tags=["project", "urgent"])

# Get recent notes
find_note(limit=10)

管理笔记

# Get a specific note
get_note(identifier="01H8X9V2P3R5Y7T8Q0W2E4R6T8Y0U2I3")

# Update a note
# (Note: This would be implemented through the MCP tools)

服务器命令

这 notes-mcp 命令支持多个选项:

# Run with custom config file
notes-mcp --config /path/to/config.yaml

# Run with custom notes directory
notes-mcp --notes-dir /path/to/notes

# Show server status
notes-mcp --status

# Rebuild index
notes-mcp --rebuild-index

# Validate index integrity
notes-mcp --validate-index

MCP工具

create_note

使用自动索引创建新笔记。

参数:

  • title (必填):备注标题
  • content (必填):Markdown内容
  • tags (可选):用户定义标签
  • friendly_name (可选):自定义友好名称
  • date (可选):自定义日期

get_note

通过ULID或文件名检索特定注释。

参数:

  • identifier (必填):ULID或文件名
  • include_metadata (可选):包括前端元数据
  • include_content (可选):包括注释正文

find_note

按日期范围、标签或友好名称模式查找笔记。

参数:

  • date_from (可选):开始日期(YYYYMMDDHHmmss)
  • date_to (可选):结束日期(YYYYMMDDHHmmss)
  • tags (可选):按标签筛选
  • friendly_name_pattern (可选):用于友好名称的正则表达式模式
  • limit (可选):最大结果(默认值:50)

search_note

在笔记内容和元数据中进行全文搜索。

参数:

  • query (必填):搜索查询
  • search_in (可选):搜索目标[“内容”、“标题”、“标签”、“摘要”]
  • fuzzy (可选):启用模糊匹配(默认值:true)
  • limit (可选):最大结果(默认值:50)

网络观众

用于浏览笔记的轻量级web界面。

快速开始

# Install frontend dependencies
just web-install

# Option 1: Development mode (with hot reload)
# Terminal 1: Start backend
just web-server

# Terminal 2: Start frontend dev server
just web-dev
# Open http://localhost:5173

# Option 2: Production mode
just web
# Open http://localhost:8000

特性

  • 基于卡片的布局显示最后12个注释
  • 模糊匹配的全文搜索
  • 按标签筛选
  • 按日期或标题排序
  • 带语法高亮显示的Markdown渲染
  • 带有词频的统计面板

技术栈

  • 前端:Svelte+Vite(通过电容器/Tauri便携式到手机/台式机)
  • 后端:FastAPI
  • 标记语言:marked.js

看 WEB.md 详细文档。

文件结构

注释以文件名格式存储: <ULID>_<YYYYMMDDHHmmss>_<friendly_name>.md

例子: 01H8X9V2P3R5Y7T8Q0W2E4R6T8Y0U2I3_20260120143000_meeting_notes.md

每张便条都包含前言:

---
title: Project Meeting Notes
date: 2026-01-20
tags: project;meeting;urgent
ulid: 01H8X9V2P3R5Y7T8Q0W2E4R6T8Y0U2I3
---

# Project Meeting Notes

Meeting content in markdown format...

索引格式

这 .notes_index.csv 文件包含用于快速搜索的元数据:

ulid|date_key|friendly_name|title|tags|frequent_words|summary|file_path|created_at|updated_at|last_accessed|file_size|checksum

建筑

服务器完全基于文件,没有数据库。存储分为三层:

存储层

  1. Markdown文件 --每个音符都是 .md 带有YAML frontmatter(标题、标签、时间戳)和编码ULID、日期键和友好名称的文件名的文件。
  2. CSV文本索引 (.notes_index.csv)--一个平面CSV文件,镜像笔记元数据,无需读取每个markdown文件即可快速查找。由文件锁保护,用于并发写入。
  3. FAISS语义索引 (.semantic_index.faiss + .semantic_metadata.json)--通过以下方式生成的向量嵌入 bge-small-en-v1.5 (384 dim)存储在FAISS中 IndexFlatL2 指数。可选;在以下情况下优雅地禁用 faiss 或 sentence-transformers 未安装。

搜索管道

                                 ┌─────────┐
                                 │  Query  │
                                 └────┬────┘
                                      │
                        ┌─────────────┴──────────────┐
                        ▼                            ▼
              ┌─────────────────┐          ┌─────────────────┐
              │  Keyword Search │          │ Semantic Search │
              └────────┬────────┘          └────────┬────────┘
                       │                            │
                       ▼                            ▼
            ┌─────────────────────┐      ┌─────────────────────┐
            │ Load all notes from │      │ Encode query with   │
            │ CSV index into      │      │ bge-small-en-v1.5   │
            │ memory              │      │ (384-dim embedding) │
            └─────────┬───────────┘      └──────────┬──────────┘
                      │                             │
                      ▼                             ▼
            ┌─────────────────────┐      ┌─────────────────────┐
            │ Per note, per field:│      │ FAISS IndexFlatL2   │
            │                     │      │ brute-force L2      │
            │ fuzzy=false:        │      │ nearest-neighbour   │
            │   substring match   │      │ search              │
            │   +0.5 word-boundary│      └───────────┬─────────┘
            │                     │                  │
            │ fuzzy=true:         │                  ▼
            │   partial_ratio     │      ┌──────────────────────┐
            │   token_set_ratio   │      │ Convert L2 distance  │
            │   best of two / 100 │      │ to similarity (0–1): │
            └─────────┬───────────┘      │                      │
                      │                  │ sim = 1 - (d / 10.0) │
                      ▼                  │ score = sim × 2.0    │
            ┌─────────────────────┐      └───────────┬──────────┘
            │ Apply field weights:│                  │
            │                     │                  │
            │ title        ×3.0   │                  │
            │ source_dir   ×3.0   │                  │
            │ source_repo  ×3.0   │                  │
            │ tags         ×2.0   │                  │
            │ summary      ×1.5   │                  │
            │ content      ×1.0   │                  │
            └─────────┬───────────┘                  │
                      │                              │
                      ▼                              │
            ┌─────────────────────┐                  │
            │ Recency boost:      │                  │
            │                     │                  │
            │ factor = 1 - (days  │                  │
            │   since update /365)│                  │
            │ score += score      │                  │
            │   × factor × 0.2    │                  │
            │ (max +20% boost)    │                  │
            └─────────┬───────────┘                  │
                      │                              │
                      ▼                              ▼
              keyword_score                  semantic_score
                      │                              │
                      └──────────┬───────────────────┘
                                 │
                                 ▼
                  ┌────────────────────────────┐
                  │     Hybrid merge           │
                  │                            │
                  │ combined = keyword × 0.5   │
                  │          + semantic × 0.5  │
                  │                            │
                  │ (weights are configurable) │
                  └─────────────┬──────────────┘
                                │
                                ▼
                  ┌────────────────────────────┐
                  │ Sort by combined score     │
                  │ (descending), return top N │
                  └────────────────────────────┘

这两条路径独立运行,并在最后合并:

  • 关键字路径:从CSV索引中加载所有笔记,使用精确子字符串匹配或模糊匹配对每个字段进行评分(thefuzz 图书馆与 partial_ratio + token_set_ratio),应用字段权重(标题/source_dir/source_repo×3,标签×2,摘要×1.5,内容×1)和最近度提升(最大+20%)。
  • 语义路径:使用索引时使用的相同句子变换器模型对查询进行编码,对FAISS索引执行暴力L2最近邻搜索,并将距离转换为归一化的相似性得分。
  • 合并:使用加权加法将两条路径的每个音符的分数合并,然后按分数降序排序。只有一条路径找到的注释仍然会出现(缺少的路径贡献0)。

重新索引

NoteManager.rebuild_index() 精心策划全面重建:

  1. 截断CSV并重新扫描所有 *.md 从notes目录中提取文件,解析frontmatter并计算校验和。
  2. 获取新构建的笔记列表并将其传递给 SemanticEngine.rebuild_index(),通过重新编码每个音符,从头开始创建一个新的FAISS索引。

A. validate_index 命令检测孤立文件(在磁盘上,但不在CSV中)和丢失文件(在CSV中,但已从磁盘中删除)。

已知限制

此实现专为个人使用而设计,最多可包含几百个音符。以下限制适用:

扩展

  • 每次搜索都会将整个CSV加载到内存中 并线性扫描。超过1000个音符时,性能会明显下降。
  • 更新和删除重写整个CSV文件。 没有就地行编辑。
  • add_note 扫描完整的CSV 在附加之前进行重复检查——每次插入O(n)。
  • 无分页 上 get_all_notes --总是将所有内容加载到内存中。
  • 无缓存 --每次重复搜索都会重新读取和解析CSV。

指标一致性

  • CSV索引和FAISS索引是独立更新的,没有交易保证。两次写入之间的崩溃可能会使它们不同步。
  • get_all_notes() 不获取文件锁,因此读取可以与正在进行的写入竞争。
  • 重新索引要么全有,要么全无;没有仅检测更改文件的增量同步。

语义搜索

  • IndexFlatL2 每个查询的暴力破解次数为O(n)。FAISS提供了近似指标(试管婴儿、HNSW),可以提高大型采集的性能。
  • 使用L2距离,但句子变换器模型通常针对余弦相似性进行优化。规范嵌入或切换到 IndexFlatIP 会更准确。

模糊搜索

  • 用途 thefuzz (纯Python Levenshtein)在长文本字段上运行缓慢,尤其是 content。大型文档不会短路。
  • 模糊阈值是一个单一的全局值,没有逐场调整。

故障排除

常见问题

服务器无法启动

# Check if MCP library is installed
pip install mcp>=1.25.0

# Verify the server can start
notes-mcp --status

注释未出现在搜索中

# Rebuild the index
notes-mcp --rebuild-index

# Validate index integrity
notes-mcp --validate-index

权限错误

  • 确保notes目录存在并且可写
  • 检查文件权限: chmod 755 ~/notes

MCP客户端连接问题

  • 验证服务器是否正在运行: notes-mcp --status
  • 检查stderr输出是否有错误(日志转到stderr)
  • 确保MCP客户端配置中的命令路径正确

语义搜索

语义搜索根据意义而不仅仅是关键字来查找笔记。它使用AI嵌入来理解查询的上下文和意图。

运作原理

  • 创建或更新注释时,其内容将转换为矢量嵌入
  • 嵌入内容存储在FAISS索引中(.semantic_index.faiss)
  • 搜索查询也被转换为嵌入,并与注释嵌入进行比较
  • 结果按语义相似性排序

启用语义搜索

默认情况下启用语义搜索。要配置:

search:
  enable_semantic: true           # Enable/disable semantic search
  semantic_model: "bge-small-en-v1.5"  # Embedding model to use
  semantic_weight: 0.5            # Weight in hybrid search (0-1)
  keyword_weight: 0.5             # Weight in hybrid search (0-1)

查询示例

无语义搜索(仅关键字):

Query: "project planning"
Results: Only notes containing exact words "project" or "planning"

使用语义搜索:

Query: "project planning"
Results:
  - "Project planning" (keyword match)
  - "Quarterly roadmap and objectives" (semantically similar)
  - "Team goal-setting session" (semantically similar)
  - "Sprint retrospective notes" (loosely related)

演出

  • 第一次搜索:约2-3秒(从缓存加载模型)
  • 后续搜索:50-100ms
  • 存储空间:FAISS索引中每张钞票约1 KB
  • 型号:33 MB(缓存在 ~/.cache/huggingface)

禁用语义搜索

要仅使用关键字搜索(如果内存有限,速度会更快):

search_engine = SearchEngine(config=config)
search_engine.enable_semantic = False
results = search_engine.search_notes("query")

调试模式

运行测试脚本以验证服务器功能:

python test_mcp_server.py

发展

项目结构

notes-mcp/
├── notes_mcp/
│   ├── __init__.py
│   ├── server.py          # MCP server implementation
│   ├── note.py            # Note model
│   ├── note_manager.py    # High-level note operations
│   ├── index_manager.py   # CSV index management
│   ├── search_engine.py   # Search and text analysis
│   ├── config.py          # Configuration management
│   ├── utils.py           # Utility functions
│   └── web/               # Web viewer
│       ├── app.py         # FastAPI backend
│       └── frontend/      # Svelte frontend
├── tests/                 # Test suite (146 tests)
├── examples/              # Usage examples
└── requirements.txt

运行测试

# Run all tests
pytest

# Run with coverage report
pytest --cov=notes_mcp --cov-report=html

# Run specific test file
pytest tests/test_search_engine.py -v

代码覆盖率

当前覆盖范围: 88% (不包括CLI工具)

pytest --cov=notes_mcp --cov-report=term-missing

API 参考

Python API

from notes_mcp.note_manager import NoteManager
from notes_mcp.search_engine import SearchEngine
from notes_mcp.config import Config

# Initialise
config = Config()
note_manager = NoteManager(config)
search_engine = SearchEngine(note_manager, config)

# Create a note
note = note_manager.create_note(
    title="My Note",
    content="Note content here",
    tags=["tag1", "tag2"]
)

# Search notes
results = search_engine.search_notes("query", fuzzy=True)
for result in results:
    print(f"{result.note.title}: {result.score}")

# Find notes by criteria
notes = search_engine.find_notes(
    tags=["project"],
    date_from="20260101",
    date_to="20261231"
)

搜索引擎方法

方法 说明
search_notes(query, search_in, fuzzy, limit) 带有相关性评分的全文搜索
find_notes(date_from, date_to, tags, pattern, limit) 按条件筛选笔记
fuzzy_match(text, query, threshold) 计算模糊匹配分数
advanced_search(query, tags, date_from, date_to, fuzzy, boost_tags, limit) 搜索与增强相结合

文本分析器方法

方法 说明
tokenize_text(text) 将文本拆分为标记
remove_stop_words(words) 过滤掉常用词
extract_keywords(text, max_keywords) 提取重要关键字
generate_summary(text, max_sentences) 创建文本摘要
calculate_word_frequency(text) 统计单词出现次数

许可证

MIT许可证-有关详细信息,请参阅许可证文件。