local-notebook
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
local-notebook
本機版 NotebookLM 替代工具(第一版)。把 PDF / TXT / MD(含逐字稿)匯入 SQLite, 透過 MCP server 讓 Claude Code 查詢,回答時附上可追查的原始出處 (檔名 + 頁碼 / 行號 / 時間戳)。
PDF / TXT / MD / 逐字稿
↓ (src/ingest.py,唯讀開啟原始檔,只複製不修改)
Python 匯入
↓
SQLite + FTS5 (data/notebook.db)
↓
MCP Server (src/server.py)
↓
Claude Code 查詢
↓
回答 + 原始來源 + 頁碼/時間戳第一版範圍(依原始規格):不做向量資料庫、不做 OCR、不做 Docker,全部跑在
.venv 內,不需要系統管理員權限。
1. 需求與已驗證環境
【確認】以下是本次實際建置與測試所用的環境(在本機沙箱內執行並以 pytest
全數通過,共 22 項測試,其中 1 項因沒有 CJK 字型而 skip):
Python 3.11.15
SQLite 3.45.1(內建於該 Python,支援
fts5的trigramtokenizer)mcp2.2.0(pip install mcp目前抓到的版本;此版把舊版FastMCP類別 改名為MCPServer,import 路徑是from mcp.server.mcpserver import MCPServer。 如果你的環境裝到 1.x 版mcp,需要改回from mcp.server.fastmcp import FastMCP,API 用法相同(.tool()裝飾器、.run()),但本專案的src/server.py是針對 2.x 寫的,尚未針對 1.x 測試。)pypdf6.19.0pytest9.1.1(測試用)
【確認】目標環境 Windows 11 + Python 3.12.10(pypdf 6.19.0)也已實際跑過
pytest:151 項通過、1 項 skip(同樣是缺 CJK 字型的那項;含網頁 UI 的測試)。
Related MCP server: ai-lab
2. 安裝(Windows,比照你原本的規劃)
cd D:\local-notebook
py -3.12 -m venv .venv
.venv\Scripts\pip install --upgrade pip
.venv\Scripts\pip install -e .
.venv\Scripts\pip install -e ".[dev]" # 想跑測試才需要專案檔案、虛擬環境和套件都裝在 D:\local-notebook\ 底下。Python/pip 執行時
仍可能寫入使用者層級的暫存位置(例如 %TEMP%、pip 快取
%LOCALAPPDATA%\pip\Cache),但不碰:
C:\Program FilesWindows Service
系統 PATH / Registry
Docker / PostgreSQL
全域
pip install(沒加-e .就用系統 pip 的話才會裝到全域,這裡用的是 專案自己的.venv)
若公司電腦的權限有疑慮,先用你原本規劃的三步驟測試:
py -3.12 -m venv D:\python_test
D:\python_test\Scripts\python.exe -m pip --version
D:\python_test\Scripts\python.exe -m pip install pytest三步都成功,代表一般 Python 開發環境可用。
3. 使用方式
3.1 匯入檔案
.venv\Scripts\python -m src.ingest --notebook "弱電SOP" --tags "SOP,弱電" `
C:\path\to\CableTray-SOP.pdf C:\path\to\meeting_transcript.txt整個資料夾一次匯入時,不要直接寫 C:\path\to\*.pdf。PowerShell 不會替外部
程式展開萬用字元,Python 收到的是字面上的 *.pdf,只會印出一行
[跳過] 找不到檔案,什麼都沒匯入(已實測)。請先用 Get-ChildItem 展開:
$files = Get-ChildItem "C:\path\to\*.pdf" | ForEach-Object FullName
.venv\Scripts\python -m src.ingest --notebook "弱電SOP" --tags "SOP,弱電" $files--notebook:notebook 名稱,不存在會自動建立。--tags:逗號分隔的標籤,存在 notebook 上(notebook 已存在時,有給--tags會覆蓋舊標籤,沒給則保留)(v1 只存一個字串欄位,不做多對多標籤)。--force:即使檔案內容雜湊(sha256)跟已匯入的重複,也重新匯入並取代舊紀錄(刪舊與寫新在同一個交易內,中途失敗會 rollback,舊資料不會遺失)。支援的副檔名:
.pdf.txt.md。檔案不存在、型別不支援、或 pypdf 判定 PDF 損毀(PyPdfError)時會印出[跳過]並繼續下一個檔案。其他錯誤(例如資料庫 錯誤,或 pypdf 對某些畸形 PDF 丟出的一般例外)仍會中止整批匯入;中止前已 rollback,且這次匯入剛複製進originals/的檔案會被刪除,不留孤兒檔。notebook 名稱含 Windows 不允許的字元(
< > : " / \ | ? *)時,originals/底下的資料夾名會把它們換成_;名稱是 Windows 裝置名(CON、NUL、COM1等)時資料夾名前面會加_。資料庫裡的 notebook 名稱不受影響。【確認】原始檔只會被「唯讀開啟」計算雜湊與擷取文字,然後用
shutil.copy2複製一份到data/originals/<notebook>/底下,不會修改、 不會搬移原始檔(tests/test_ingest_and_search.py::test_original_file_never_modified有驗證匯入前後原始檔的位元組完全一致)。同一個 notebook 內,同雜湊的檔案預設會被跳過(
status: skipped_duplicate), 避免重複灌資料。
3.2 啟動 MCP Server
.venv\Scripts\python -m src.server這個指令本身不會印出東西就繼續掛著等待——它是用 stdio 跟 MCP client(例如 Claude Desktop / Claude Code)溝通,不是要你手動互動。正常關閉方式是讓呼叫它 的 client 結束連線。
Claude 啟動 server 時,Python 必須找得到 src 套件,也就是專案根目錄
(src\ 的上一層)要在搜尋路徑上。在其他資料夾直接執行 python -m src.server
會出現 ModuleNotFoundError: No module named 'src'(已實測;editable 安裝加進
搜尋路徑的是 src\ 本身,不是專案根目錄)。
所以設定裡用 PYTHONPATH 指定專案根目錄,不依賴 client 啟動 server 時的工作
目錄。以下以專案在 F:\github\local-notebook\local-notebook 為例,請換成你實際
的路徑。
Claude Code(一行指令註冊):
claude mcp add local-notebook -e PYTHONPATH=F:\github\local-notebook\local-notebook -- F:\github\local-notebook\local-notebook\.venv\Scripts\python.exe -m src.serverClaude Desktop(claude_desktop_config.json;Claude Code 的 .mcp.json 也是
同樣的寫法):
{
"mcpServers": {
"local-notebook": {
"command": "F:\\github\\local-notebook\\local-notebook\\.venv\\Scripts\\python.exe",
"args": ["-m", "src.server"],
"env": { "PYTHONPATH": "F:\\github\\local-notebook\\local-notebook" }
}
}
}【確認】在 C:\ 底下設好上面的 PYTHONPATH 再執行,src.server 可以正常匯入,
資料庫也指向專案裡的 data\notebook.db(資料庫路徑由程式檔位置決定,和工作
目錄無關)。
【未驗證】還沒有在 Claude Code/Claude Desktop 裡實際註冊並呼叫過。舊版範例用的
cwd 欄位已拿掉,因為不確定 client 會不會讀它,改用 PYTHONPATH 就不需要它。
註冊後在 Claude 裡問一句「列出我的 notebook」,就能確認有沒有接通。
3.2.1 網頁 UI(匯入、搜尋、匯出)
雙擊專案根目錄的 start-ui.bat,瀏覽器會自動開啟 http://127.0.0.1:8765。
也可以在 PowerShell 執行:
.venv\Scripts\python.exe -m src.ui畫面上可以:
建立 notebook,或更新已存在 notebook 的 tags(tags 留空會保留原值)。
拖放或選擇
.pdf.txt.md匯入。每個檔案各自顯示結果:已匯入、重複已跳過、或失敗原因;一個檔案失敗不影響其他檔案。搜尋,並把結果匯出成 CSV(Excel 可直接開啟中文)或 Markdown(附檔名、頁碼、行號、時間戳)。
注意:
只接受本機連線(
127.0.0.1),區網其他電腦連不進來。8765 埠已被使用時會顯示錯誤;通常代表 UI 已經開著,直接開啟上面的網址即可。
關閉瀏覽器不會停止伺服器,要關閉
start-ui.bat開出的命令視窗。UI 匯入不支援
--force;需要強制重新匯入時用命令列。CSV 中以
=、+、-、@等開頭的內容前面會加',避免 Excel 當成公式執行。匯入失敗時畫面只顯示簡短原因,不顯示本機路徑;完整錯誤記在命令視窗裡。
UI、命令列、MCP 共用同一個
data\notebook.db,任一邊匯入的資料其他兩邊都查得到。
3.3 MCP 工具一覽
工具 | 參數 | 回傳 |
| 無 | 所有 notebook:id、name、tags、created_at、source_count、chunk_count |
|
| 該 notebook 內所有來源檔:id、filename、file_type、file_hash、imported_at、chunk_count |
|
| 符合的段落:chunk_id、seq、page、line_start、line_end、timestamp、text、snippet、source_file、source_id、notebook、score |
|
| 單一段落完整原文 + 來源;查無資料回傳 |
|
| 來源檔中繼資料 + 其所有段落(含 80 字預覽) |
每筆 search / get_chunk 結果都帶有 page(PDF)或 line_start/line_end
timestamp(TXT/MD,逐字稿才會有 timestamp),讓 Claude 回答時可以寫成 「根據 CableTray-SOP.pdf 第 12 頁……」或「根據 meeting.txt 第 3 行 (00:01:10)……」。
注意 PDF 頁碼:
page是 PDF 檔案從第一頁開始數的頁序(src/ingest.py的chunk_pdf逐頁編號),不一定等於頁面上印出的頁碼。文件有封面、目錄、 羅馬數字前置頁時,兩者會不同。正式引用工程規範前,請開啟原始 PDF 核對 印刷頁碼與條文編號。
4. 執行測試
.venv\Scripts\pip install -e ".[dev]"
.venv\Scripts\python -m pytest tests\ -v【確認】實際執行結果(Windows 11 + Python 3.12.10):151 個測試通過、1 個 skip(見下方明細),涵蓋:
tests/test_ingest_and_search.py(7 項):TXT/MD 分段、原始檔未被修改、 重複匯入跳過與--force覆蓋、get_chunk/get_source往返、 notebook 之間互不干擾、不支援的副檔名會丟出錯誤、list_notebooks計數正確。tests/test_pdf_ingest.py(2 項):用fpdf2產生真的 PDF(一份純英文、 一份用系統 Noto CJK 字型產生的繁體中文 PDF)餵給pypdf驗證逐頁分段、 頁碼、以及底下第 5 節提到的 NUL byte 問題確實被修好。tests/test_mcp_server.py(1 項):直接呼叫真正的MCPServer.call_tool()把 5 個工具都跑過一次。tests/test_mcp_stdio_subprocess.py(1 項):真的把python -m src.server開成子行程,用mcp.ClientSession走 stdio 協定 跟它對話(list_tools+ 呼叫list_notebooks、search),這是唯一會 抓到「python -m src.server這個進入點本身跑不起來」或「有東西污染了 stdout 導致 JSON-RPC 協定壞掉」這類問題的測試。tests/test_optimizations.py(11 項):審查時找到的 bug 的回歸測試,包括list_notebooks段落數不被放大、limit<=0、notebook 名稱含非法字元或 Windows 裝置名、損毀 PDF 不中斷整批、--tags更新、--force失敗時保留 舊資料,以及匯入失敗不留孤兒檔。tests/test_ui_app.py(127 項,含參數化展開):網頁 UI 的 API,用 StarletteTestClient走真正的 HTTP 路徑。涵蓋 Host/Origin/token 檢查、notebook 與 tags、上傳檔名清理與路徑不外洩、逐檔錯誤隔離、搜尋參數驗證、CSV(BOM、 公式防護)與 Markdown 匯出,以及前端不使用innerHTML。tests/test_ui_launcher.py(3 項):埠號被佔用時不開瀏覽器並回報錯誤; 開瀏覽器的那一刻 server 一定已經接受連線。
fpdf2 與 fonttools 只有跑測試才需要(用來產生測試用的假 PDF),
ingest.py / server.py 本身完全不依賴它們。若你的機器上沒有系統 CJK 字型,
test_pdf_traditional_chinese_extraction_and_search 會自動 skip(不會判定失敗),
其餘測試不受影響。
5. 已知限制(實測發現,不是猜的)
FTS5
trigramtokenizer 對短查詢無效 【確認】trigramtokenizer 是把內容切成重疊的 3 字元窗格來建索引,直接在 這個沙箱用 sqlite3 驗證:對內容"電纜橋架施工規範 Cable Tray"查詢"橋架"(2 個中文字)回傳 0 筆,查詢"橋架施"(3 個字)回傳 1 筆; 英文查詢"Ca"(2 字元)也是 0 筆,"Cable"才找得到。 這代表很多常見的兩字中文詞(例如「規範」「載重」)用 FTS5 搜不到,即使 內文明明有出現。 處理方式:src/search.py的search()已經加了判斷——查詢字串去除 前後空白後若少於 3 個字元,改用 SQLLIKE '%...%'子字串比對(結果的snippet與score會是null,因為 LIKE 沒有 bm25 排序);3 字元以上 才走原本的 FTS5 trigram 路徑。這個行為由tests/test_ingest_and_search.py::test_txt_ingest_chunking_and_search實際驗證(對「橋架」查詢會確實找到兩個含此詞的段落)。pypdf 從某些內嵌 CJK 字型的 PDF 擷取文字時,字元間會夾雜 NUL byte(
\x00) 【確認】用fpdf2+ 系統內的 Noto Sans CJK 字型產生一份含繁體中文的 PDF,pypdf.extract_text()擷取出來的結果是"\x00第\x00一\x00頁..."這種每個字中間插一個 NUL byte 的形式,不是乾淨的 中文字串。 處理方式:src/ingest.py的_clean_pdf_text()在每一頁擷取後都會 先.replace("\x00", "")再存進資料庫,tests/test_pdf_ingest.py::test_pdf_traditional_chinese_extraction_and_search有驗證清理後存入的文字與搜尋結果都不含 NUL byte。 【無法驗證】這個現象是不是所有中文 PDF 的通病,還是只跟這次測試用的 字型/產生方式有關——我沒有另一份「正常」的中文 PDF 可以對照測試。已加上 的清理是保險措施,不代表所有中文 PDF 擷取出來的文字品質都沒問題,正式 匯入你手上的 PDF 之後,建議用get_source/get_chunk抽查幾段內容, 確認擷取出來的文字跟原文一致。(已修正)原始檔複製目的地曾經跟資料庫路徑脫鉤:早期版本裡,
ingest.py決定「原始檔要複製到哪個originals/資料夾」是看環境變數LOCAL_NOTEBOOK_DB,跟實際傳進來的 SQLite 連線指向哪個.db檔案無關。 寫測試時就是因為這樣,才發現用自訂路徑開連線(測試常見寫法)時,原始檔 卻被複製進了專案本身的data/originals/,而不是測試用的暫存資料夾。 已改成db.originals_dir_for(conn):用PRAGMA database_list直接讀這個 連線實際打開的資料庫檔案路徑,再取其同層的originals/,保證data/notebook.db跟data/originals/永遠對應同一份資料庫,不會因為呼叫方式不同而分裂。修好之後重新整批跑測試,專案的data/目錄在測試前後都是空的(測試只會寫進各自的暫存目錄)。沒有 OCR:掃描圖檔式的 PDF(頁面上沒有文字層)會被整頁跳過,
ingest_file回傳的chunk_count可能是 0 或比實際頁數少很多,不會報錯, 但你不會拿到那些頁的任何內容——這是規格明講「第一版不做 OCR」的預期行為, 不是 bug。PDF 一頁一個 chunk,不做細切:符合規格「第一版不做向量資料庫」的簡化 設計;一頁內容很長時,
search()回傳的text會是整頁,snippet欄位可以用來快速定位關鍵字在頁面中的位置。沒有語意搜尋:
search是關鍵字(trigram 子字串)比對,不是向量相似度, 同義詞或换句話說的查詢不會自動找到相關段落——這也是規格明講的第一版範圍。
專案結構
local-notebook/
├─ .venv/
├─ data/
│ ├─ originals/ # 匯入時複製進來的原始檔(依 notebook 分資料夾)
│ └─ notebook.db # SQLite(metadata + 擷取文字 + FTS5 索引)
├─ src/
│ ├─ db.py # schema、連線、notebook 建立
│ ├─ ingest.py # CLI:匯入 PDF/TXT/MD
│ ├─ search.py # list_notebooks / list_sources / search / get_chunk / get_source 的實作
│ ├─ server.py # MCP server,把上面幾個函式包成 5 個 tool
│ └─ ui/
│ ├─ app.py # 網頁 UI 的 API(只包裝 db / ingest / search)
│ ├─ launcher.py # 綁定埠號、啟動 server、就緒後開瀏覽器
│ ├─ __main__.py # python -m src.ui
│ └─ static/ # index.html / app.js / style.css
├─ tests/
│ ├─ test_ingest_and_search.py
│ ├─ test_pdf_ingest.py
│ ├─ test_mcp_server.py
│ ├─ test_mcp_stdio_subprocess.py
│ ├─ test_optimizations.py
│ ├─ test_ui_app.py
│ └─ test_ui_launcher.py
├─ samples/
│ └─ sample_transcript.txt # 範例逐字稿,可用來快速試跑 ingest
├─ start-ui.bat # 雙擊啟動網頁 UI
├─ pyproject.toml
└─ README.mddata/notebook.db 與 data/originals/ 不會隨程式碼一起打包(都是執行時才
產生的資料),第一次執行 src/ingest.py 時會自動建立。
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
Search and reason over your Obsidian-style Markdown vault, right from ChatGPT.
Parse, extract, split, and ask over digital PDFs (text layer, no OCR) from Cursor and Claude.
Read-only search of your Sortio knowledge graph (files and entities) for Claude and ChatGPT.
Convert PDFs, Office docs, websites and audio into AI-ready Markdown, then search and ask your docs.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables Claude Desktop to search and query personal document collections (PDF, Word, Markdown, text) using semantic search and conversational AI with full context preservation across exchanges.MIT
- AlicenseNot gradedqualityCmaintenanceEnables Claude Code to search and retrieve from a local knowledge base of markdown notes using hybrid semantic+keyword search, keeping data entirely offline.7 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables Claude to search locally indexed exported chat history and project docs, retrieving full messages and conversation IDs via full-text search.1MIT
- AlicenseNot gradedqualityCmaintenanceEnables Claude to search and read the user's local document collection (PDF, Word, Excel, Markdown, plain text) during a conversation by subject, by file name/path, or by exact word, and to retrieve file contents page by page. It also supports finding folders relevant to a topic and checking indexing status, with everything running locally on the user's machine.MIT