local-notebook
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@local-notebooksearch my 弱電SOP notebook for cable tray spacing, cite page numbers"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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: fable MCP server
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 時會自動建立。
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.
- docs2mcpOAuthcom.docs2mcp
Query your own PDFs and documents from any MCP client. Every answer cites the page it came from.
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
- AlicenseAqualityBmaintenanceEnables Claude to search, recall, and remember its own past conversations by indexing them into a local SQLite vault, providing direct access to the full context of previous sessions.1113MIT
- AlicenseNot gradedqualityBmaintenanceEnables Claude Code to search and retrieve from a local knowledge base of markdown notes using hybrid semantic+keyword search, keeping data entirely offline.4 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables natural language interaction with a personal knowledge base stored locally on your computer, supporting semantic search, note reading, and writing through Claude Code or mobile apps.11MIT