文档问答服务器

一个本地运行的MCP服务器,用于索引PDF文档并提供基于文档的问答服务,支持来源追溯。

  • by RACERNOX
  • Python
  • 本地部署
  • 更新于
在 GitHub 查看 RACERNOX/MCP_Nexla

使用说明

文档问答MCP服务器

一个本地可运行的MCP(模型上下文协议)服务器,对PDF文档进行索引并公开 query_documents 用于源归因的基础问答工具。内置FastMCP、ChromaDB、句子转换器和Gemini 2.5 Flash。

建筑

flowchart TD
    subgraph ingestion ["Ingestion Pipeline (run once via ingest.py)"]
        PDFs["data/pdfs/*.pdf"] --> Parser["parser.py<br/>PyMuPDF"]
        Parser -->|"pages with metadata"| Chunker["chunker.py<br/>sliding window"]
        Chunker -->|"overlapping chunks"| Embedder["embedder.py<br/>all-MiniLM-L6-v2"]
        Embedder --> ChromaDB["data/chroma_db/"]
    end

    subgraph server ["MCP Server (always on via mcp_server.py)"]
        Client["Claude / MCP Client"] -->|"query_documents(question)"| Server["server.py"]
        Client -->|"list_documents()"| Server
        Server --> Retriever["retriever.py<br/>cosine search"]
        Retriever --> ChromaDB2["data/chroma_db/"]
        ChromaDB2 -->|"top-k chunks"| LLM["llm.py<br/>Gemini 2.5 Flash"]
        LLM --> Server
        Server -->|"JSON: answer + sources"| Client
    end

    ChromaDB -.->|"persisted index"| ChromaDB2

先决条件

  • Python 3.10+
  • A免费 Gemini API密钥 (仅用于LLM答案,不用于嵌入)

设置

# Clone and enter the project
cd nexla-mcp-doc-qa

# Create virtual environment
python3 -m venv .venv
source .venv/bin/activate

# Install dependencies
pip install -r requirements.txt

# Set your API key
cp .env.example .env
# Edit .env and add your GEMINI_API_KEY

# Run ingestion (one-time, indexes all PDFs)
python3 ingest.py

# Start the MCP server
python3 mcp_server.py

项目结构

nexla-mcp-doc-qa/
├── mcp_server.py                # Entry point: runs the MCP server
├── ingest.py                    # Entry point: runs the ingestion pipeline
├── src/
│   ├── config.py                # All constants: paths, models, chunk params
│   ├── server.py                # FastMCP app + tool handlers
│   ├── ingestion/               # Offline — run once
│   │   ├── parser.py            # PyMuPDF: PDF → [{text, doc_name, page}]
│   │   ├── chunker.py           # Sliding window: pages → overlapping chunks
│   │   ├── embedder.py          # all-MiniLM-L6-v2 → ChromaDB storage
│   │   └── pipeline.py          # Orchestrator: parser → chunker → embedder
│   └── core/                    # Online — used at query time
│       ├── retriever.py         # Embed question → ChromaDB cosine search
│       └── llm.py               # Build prompt → Gemini 2.5 Flash → parse JSON
├── data/
│   ├── pdfs/                    # Input PDFs (5 AI/ML research papers)
│   └── chroma_db/               # Vector index (auto-created, gitignored)
├── requirements.txt
├── .env.example
└── .gitignore

索引文档

文档 描述
attention_is_all_you_need.pdf 原始变压器纸(Vaswani等人)
bert_paper.pdf BERT:深层双向变压器的预培训
gpt2_paper.pdf 语言模型是无监督多任务学习者(GPT-2)
rag_paper.pdf 知识密集型任务的检索增强生成
ai_index_report_2024.pdf AI指数报告——高效多模态LLMs调查

MCP工具

query_documents

询问有关索引PDF文档的问题。

输入:

{ "question": "What is the attention mechanism?" }

输出:

{
  "answer": "The attention mechanism allows the model to jointly attend to information from different representation subspaces at different positions. The Transformer uses multi-head attention, where queries, keys, and values are linearly projected h times, attention is applied in parallel, and the results are concatenated.",
  "sources": [
    { "doc_name": "attention_is_all_you_need.pdf", "page": 3 },
    { "doc_name": "attention_is_all_you_need.pdf", "page": 5 }
  ],
  "confidence": "high"
}

list_documents

列出所有索引文档,包括页数和块数。

输出:

[
  { "doc_name": "ai_index_report_2024.pdf", "pages": 36, "chunks": 58 },
  { "doc_name": "attention_is_all_you_need.pdf", "pages": 15, "chunks": 20 },
  { "doc_name": "bert_paper.pdf", "pages": 16, "chunks": 31 },
  { "doc_name": "gpt2_paper.pdf", "pages": 24, "chunks": 43 },
  { "doc_name": "rag_paper.pdf", "pages": 19, "chunks": 29 }
]

响应架构

字段 类型 描述
answer string 基于文档上下文的固定答案
sources 阵列 [{doc_name, page}] --贡献的每一个来源
confidence 字符串 "high" (直接陈述), "medium" (推断), "none" (未找到)

交互示例

1.单文档问题

查询: “什么是Transformer架构?”

{
  "answer": "The Transformer is a model architecture that eschews recurrence and instead relies entirely on an attention mechanism to draw global dependencies between input and output. It uses stacked self-attention and point-wise, fully connected layers for both the encoder and decoder.",
  "sources": [
    { "doc_name": "attention_is_all_you_need.pdf", "page": 3 },
    { "doc_name": "attention_is_all_you_need.pdf", "page": 5 }
  ],
  "confidence": "high"
}

2.多文档问题

查询: “BERT和GPT-2在训练前目标上有何不同?”

{
  "answer": "BERT uses a masked language model (MLM) objective, where random tokens are masked and the model predicts them using bidirectional context. GPT-2 uses a standard left-to-right language model objective, predicting the next token given all previous tokens. BERT is bidirectional while GPT-2 is unidirectional.",
  "sources": [
    { "doc_name": "bert_paper.pdf", "page": 3 },
    { "doc_name": "bert_paper.pdf", "page": 13 },
    { "doc_name": "gpt2_paper.pdf", "page": 3 }
  ],
  "confidence": "high"
}

3.范围外问题

查询: “谷歌的股价是多少?”

{
  "answer": "The answer to this question was not found in the indexed documents.",
  "sources": [],
  "confidence": "none"
}

Claude桌面配置

将此添加到您的Claude桌面 claude_desktop_config.json:

{
  "mcpServers": {
    "document-qa": {
      "command": "python3",
      "args": ["mcp_server.py"],
      "cwd": "/absolute/path/to/nexla-mcp-doc-qa"
    }
  }
}

MCP检验员测试

# Install MCP Inspector
npx @modelcontextprotocol/inspector python3 mcp_server.py

冷启动试验

# Delete the vector index and re-ingest from scratch
rm -rf data/chroma_db/
python3 ingest.py
python3 mcp_server.py

技术栈

组件 技术 为什么
MCP框架 FastMCP 3.1.1 Python,最小样板
PDF解析 PyMuPDF(fitz) 快速,处理复杂布局
嵌入 all-MiniLM-L6-v2 免费,本地,无API依赖关系
矢量存储 ChromaDB 嵌入式,持久到磁盘,零配置
LLM Gemini 2.5 Flash 免费层,1M上下文窗口

设计决策

  • API嵌入之上的本地嵌入: 切换自 text-embedding-004 (已弃用) all-MiniLM-L6-v2.本地运行,零成本,无速率限制,嵌入模型与LLM完全解耦——Gemini只看到检索到的文本,从不看到向量。
  • 单词级重叠组块: 具有50个单词重叠的500个单词块保留了块边界处的上下文。基于单词(而不是基于字符),以避免拆分中间单词。
  • 误食: 重新 ingest.py 如果ChromaDB集合已填充,则跳过。删除 data/chroma_db/ 强制进行彻底的重新索引。
  • 结构化JSON响应: 每个工具都返回带有源属性的可解析JSON,因此MCP客户端可以提供带有引用的答案。

Vibe编码——人工智能辅助开发过程

工具和设置

工具 角色
光标IDE 主要开发环境
克劳德(人类学) Cursor内的AI编码助手,用于代理模式

整个项目是使用Cursor的AI代理作为配对编程伙伴构建的。我做了 不 从ChatGPT复制粘贴或编写一次性提示。相反,我将人工智能视为初级工程师——我设置了架构,给出了分阶段的指令,审查了每一个输出,并在出现错误时进行了纠正。


我如何指导人工智能——我的激励策略

我做了 不 写一个巨大的提示,比如 “给我建一个RAG系统。” 这会产生平庸、不可测试的代码。相反,我使用了 分阶段、计划优先的方法:

第一步:在接触任何代码之前,我写了一个构建计划。

在生成一行代码之前,我创建了一个详细的计划文档(document_q&a_mcp_server.plan.md)其中规定:

  • 确切的文件夹结构
  • 技术栈,每种选择都有其基本原理
  • 4个构建阶段,每个阶段都有具体的可交付成果
  • 建筑流程图(美人鱼)
  • 响应架构
  • 要避免的陷阱(幂等摄入、速率限制、抗幻觉提示)

这个计划变成了“合同”——每个AI提示都引用了它。AI没有决定架构;我做到了。

第二步:逐步指示,而不是“构建一切”

我一次给人工智能一个阶段,并在进入下一个阶段之前验证每个阶段都有效:

阶段 我提示了什么 我评论了什么
第一阶段——脚手架 创建文件夹结构、config.py、requirements.txt、.gitignore、.env.example 已验证所有路径均正确解析,并已检查 config.py 集中每个常量
第2阶段——摄入 为摄取管道编写parser.py、chunker.py、embedder.py、pipeline.py 冉 python3 ingest.py,验证每个文档的块计数,检查ChromaDB是否已填充
第3阶段——核心+MCP 使用query_documents和list_documents工具编写retriever.py、llm.py、server.py 通过MCP Inspector使用3种查询类型进行测试,检查JSON响应格式
第四阶段——波兰语 “编写README,包括架构、设置、工具文档、示例交互、Vibe编码部分” 阅读每一行,更正索赔,添加缺失部分

步骤3:每个阶段后都有明确的验证提示。

在每个阶段之后,我不仅继续前进,我还问: “验证此阶段是否正常工作,错误处理是否正确,管道是否正确。” 这捕获了真正的错误(记录如下)。


人工智能在哪里表现出色(我保留了它的产出)

  1. 清洁模块分离。 AI自然为每个文件模块生成一个函数(parser.py, chunker.py等等),具有清晰的输入/输出。这种关注点分离正是我想要的——每个模块都是可独立测试的。

  2. 正确的库API。 PyMuPDF(fitz.open(), page.get_text()),ChromaDB(PersistentClient, collection.add()),快速MCP(@mcp.tool())——人工智能知道这些API,并在第一次尝试时就生成了工作代码。

  3. 误食。 AI包括一个签到 embedder.py:如果ChromaDB集合已经有数据,则跳过重新嵌入。我没有要求这个;它主动添加了它。这可以防止意外的数据复制,这是一个真正的生产问题。

  4. 抗幻觉系统提示。 系统提示 llm.py 有明确的规则: “仅从提供的上下文中回答。如果没有找到,请诚实地说出来。” 人工智能构建了置信水平(high / medium / none)JSON响应模式正确。

  5. Markdown代码围栏剥离。 Gemini有时会封装JSON ```json ... ``` 阻碍。AI补充道 _parse_response() 在JSON解析之前剥离这些数据——这是一个微妙但重要的细节。


我在哪里超越或纠正了AI

以下是我在开发过程中所做的真实、具体的更正:

1.嵌入模型弃用是最大的支点

最初的计划使用了谷歌的 text-embedding-004 通过 google-generativeai 包裹。在第2阶段测试中,摄入崩溃:

models/text-embedding-004 is not found for API version v1beta

人工智能的第一个修复是错误的——它试图更新模型名称前缀。真正的问题是 google-generativeai SDK已完全弃用。我推回:

  • 第一轮: AI建议删除 models/ 前缀→ 仍然失败
  • 第二轮: AI迁移到新 google-genai SDK → 型号名称 text-embedding-004 也是日落
  • 第三轮: AI尝试 gemini-embedding-001 → 仍然存在API问题
  • 我的决定: 我问 “如果我们使用句子转换器(全MiniLM-L6-v2)怎么办?它适用于双子座吗?” 人工智能证实,嵌入模型和LLM在RAG中是解耦的。双子座只看到文本,从不看到向量。所以我把开关转到 100%本地嵌入这消除了API对整个摄入管道的依赖性、成本和速率限制。

课程: 人工智能无法诊断出未经训练的弃用。我必须确定根本原因(弃用的API,而不是代码错误),并提出架构中枢。

2.ChromaDB中的异常类型错误

AI写道 retriever.py 抓住 ValueError 当集合不存在时。测试表明ChromaDB实际上提高了 chromadb.errors.NotFoundError。我通过运行错误处理测试并将回溯反馈给AI来发现这一点,然后AI将其修复以捕获 Exception 带着一个 RuntimeError 重新提高。

3.阶段3中缺少错误路径

在第三阶段“完成”后,我明确地问: “检查第3阶段的错误处理。” AI发现了三个缺口:

  • llm.py: response.text 可能是 None 如果Gemini的安全过滤器阻止了处理的响应
  • llm.py:没有 try/except 在...周围 generate_content() 调用网络故障会使整个MCP服务器崩溃
  • server.py:两个MCP工具都没有错误。包装异常会终止MCP连接,而不是返回JSON错误

这些是真正的错误,会导致生产失败。当我提示进行审查时,人工智能发现了它们,但在最初的实施中没有包括它们。

4.MCP检查器配置

AI给出了连接MCP检查器的错误指示。它最初建议手动输入代理令牌和配置,而正确的方法很简单:

npx @modelcontextprotocol/inspector python3 mcp_server.py

我必须通过读取实际的MCP检查器输出并纠正连接方法来调试它。

5.我添加的Web UI,不在原计划中

最初的计划只要求安装MCP服务器。通过MCP Inspector进行测试后,我意识到视觉演示会更具吸引力。我指示AI构建一个FastAPI+HTML web UI,包括:

  • 置信度计
  • 源分布可视化
  • 证据块查看器
  • 使用实时索引上传PDF

人工智能构建了用户界面,但我驱动了每一个功能请求和用户体验决策。


什么不起作用

  1. 广泛的提示。 当我问 “构建摄入管道” AI生成了可工作但无错误路径的代码。它只在我特别要求时添加了适当的错误处理 “验证错误处理是否正确。” 除非你把它推到边缘情况,否则人工智能会为快乐的道路进行优化。

  2. API知识陈旧。 AI不知道 text-embedding-004 已弃用或 google-generativeai 被替换为 google-genai。我必须向它提供真实的错误消息和回溯,以便它进行修复。

  3. 配置多于解释。 在设置MCP检查员时,AI给出了技术上错误的分步说明。我必须调试实际的工具并纠正工作流程。


我的观点:正向部署工程工作流中的AI工具

人工智能是一种力量倍增器,而不是工程判断的替代品。

以下是我从构建这个项目中学到的:

  • 建筑必须来自人类。 在任何AI生成的代码存在之前,我编写了计划,选择了技术栈,定义了模块边界,并决定了响应模式。AI填写了实现细节,但结构是我的。

  • AI最擅长“已知模式”代码。 PDF解析、ChromaDB集成、FastMCP工具定义——这些都是AI很好处理的记录良好的模式。它节省了我阅读文档和编写样板的时间。

  • 人工智能在“未知未知”漏洞方面表现最差。 弃用的API、微妙的异常类型不匹配、安全过滤器边缘情况——这些都需要真正的调试和真正的错误输出。人工智能无法预测它们;只有在我向它出示证据后,它才能修复它们。

  • 逐步>一次全部。 一次性推动整个项目会产生一个脆弱的巨石。使用验证检查点分阶段构建可以及早发现错误并保持代码库的干净。

  • 90/10法则适用。 人工智能生成了大约90%的工作代码。其他约10%的错误路径、API迁移、到本地嵌入的体系结构枢轴-花费了约50%的总开发时间,并且完全由人驱动。这10%才是真正的工程所在。

在一个前沿部署的角色中,我会像这样使用人工智能:首先计划,快速生成,无情地验证,并拥有每一个决定。速度增益是真实的,但质量标准仍然取决于工程师。