NOEMA-AI MCP Server

NOEMA-AI MCP Server

Exposes document ingestion, retrieval (vector, vectorless, hybrid), and multi-turn chat tools for a LangGraph-powered RAG pipeline with streaming answers.

Category
访问服务器

README

NOEMA-AI

An end-to-end Retrieval-Augmented Generation pipeline that ingests documents and answers queries grounded in their content. It supports three retrieval paths side by side — vector-based (embeddings + vector store), vector-less (keyword/BM25 structured retrieval), and hybrid (Reciprocal Rank Fusion of both) — orchestrated with LangChain and LangGraph, traced with LangSmith, and exposed to external clients via MCP (Model Context Protocol). It also supports multi-turn chat with session memory and streaming answers over Server-Sent Events.

Document  ─▶  Ingestion  ─▶  Chunking  ─▶  ┬─▶  Embeddings ─▶ Vector Store  ─┐
                                            └─▶  BM25 Index (vector-less)    ─┤─▶  LangGraph  ─▶  LLM  ─▶  Answer
                                                                              │      (retrieve node)
Query     ───────────────────────────────────────────────────────────────────┘

Screenshots

<!-- Drop your screenshot images into docs/screenshots/ using these exact filenames and they'll render automatically on GitHub — no other changes needed. Recommended: 1280px+ wide PNGs, dark mode (matches the UI theme). -->

Home page — landing view of the NOEMA UI Home page

Console — ask a question, watch retrieval + generation trace live Console view

Hybrid retrieval mode — fused vector + BM25 results with match source Hybrid retrieval

Chat — multi-turn conversation with session memory Chat with memory

API reference — full endpoint interface view API interface

Streaming answer — tokens arriving live over SSE Streaming answer

Features

  • Vector RAG — chunking → embeddings → persistent vector store (Chroma) → similarity search. Ships with a zero-dependency offline hash embedding by default, and a real local semantic embedding option (EMBEDDING_PROVIDER=sentence-transformers) for actual semantic search
  • Vector-less RAG — BM25 keyword ranking over raw chunks, no embedding model required
  • Hybrid RAG — fuses the vector and BM25 result lists with Reciprocal Rank Fusion (RRF), so a chunk that ranks well in either retriever surfaces, and one that ranks well in both surfaces first
  • Multi-turn chat with memory — POST /chat keeps a rolling window of prior turns per session, so follow-up questions ("what about the second one?") resolve against earlier turns instead of being answered in a vacuum
  • Streaming answers — GET /query/stream (Server-Sent Events) streams the answer token-by-token instead of waiting for the full response
  • Document management — GET /documents lists every indexed source; DELETE /documents/{source} removes it from every retrieval path at once
  • Query result caching — a short-lived in-memory TTL cache serves repeated identical questions without re-running retrieval + the LLM; automatically invalidated on ingest/delete
  • Persistence across restarts — the BM25 corpus and the ingested-document manifest are saved to PERSIST_DIR and reloaded on startup, so a restart no longer silently wipes what was ingested
  • LangGraph pipeline — ingestion, retrieval (vector / vectorless / hybrid), and generation modeled as composable graph nodes, with automatic fallback from vector to vector-less
  • LangSmith tracing — every LLM call and retrieval step is traceable/evaluatable when configured
  • LLM Gateway / Middleware — a single layer that routes requests, logs, and rate-limits before calls reach the LLM
  • Guardrails — input/output checks with normalized (whitespace/zero-width-character-resistant) prompt-injection pattern matching; still pattern-based, not a full classifier — see the note below
  • API key auth — optional X-API-Key header enforcement on /ingest, /query/*, /documents/*, and /chat/*, off by default so the app still runs with zero setup
  • MCP server — exposes ingest_document, delete_document, list_documents, query_vector_rag, query_vectorless_rag, query_hybrid_rag, and chat_with_memory as MCP tools for external LLM clients
  • Docker support — a Dockerfile and docker-compose.yml for one-command containerized deployment with a persistent volume
  • 52 unit/API tests covering the chunker, guardrails (including evasion attempts), the BM25 store, hybrid RRF fusion, the query cache, chat memory, and the FastAPI endpoints (including auth enforcement, document deletion, and chat sessions)

Project layout

noema/
├── app/
│   ├── config.py              # environment / settings
│   ├── main.py                 # FastAPI entrypoint (REST API)
│   ├── middleware.py           # LLM gateway / logging / rate-limit middleware
│   ├── guardrails.py           # input & output guardrails
│   ├── cache.py                 # TTL query-result cache
│   ├── chat.py                  # session-based conversational memory
│   ├── ingestion/
│   │   ├── loader.py           # load .txt / .pdf / .docx into raw text
│   │   └── chunker.py          # sliding-window chunking with overlap
│   ├── retrieval/
│   │   ├── embeddings.py       # embedding provider (API or local fallback)
│   │   ├── vector_store.py     # Chroma-backed Vector RAG
│   │   ├── vectorless_store.py # BM25-backed Vector-less RAG (+ persistence)
│   │   └── hybrid.py            # Reciprocal Rank Fusion of vector + BM25
│   ├── llm/
│   │   └── client.py           # LLM client (Groq / LLaMA 3.3 70B, mockable, streaming + history-aware)
│   ├── graph/
│   │   └── pipeline.py         # LangGraph state machine wiring it all together
│   ├── mcp/
│   │   └── server.py           # MCP server exposing the pipeline as tools
│   └── static/                 # web UI (served at "/" by app/main.py)
│       ├── index.html
│       └── assets/
│           ├── style.css
│           └── app.js
├── docs/screenshots/            # drop screenshot images here (see Screenshots above)
├── data/sample_docs/sample.txt # example document to ingest
├── scripts/ingest.py            # CLI: ingest a document from the command line
├── tests/                       # 52 tests: chunker, guardrails, stores, hybrid, cache, chat, API
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── .env.example

Setup

python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env                # then fill in your keys

Environment variables (.env)

Variable Purpose
GROQ_API_KEY Key for Groq-hosted LLaMA 3.3 70B calls (leave blank to use the built-in mock LLM for local testing)
LANGCHAIN_TRACING_V2 true to enable LangSmith tracing
LANGCHAIN_API_KEY LangSmith API key
LANGCHAIN_PROJECT LangSmith project name, e.g. noema
VECTOR_DB_PATH Local path for the persistent Chroma store, default ./chroma_db
EMBEDDING_PROVIDER hash (default, deterministic offline bag-of-words embedding, zero deps) · sentence-transformers (real local semantic embeddings — pip install sentence-transformers, downloads a small model on first use) · remote (placeholder for a hosted embedding API you wire in)
EMBEDDING_MODEL_NAME Model name used when EMBEDDING_PROVIDER=sentence-transformers, default all-MiniLM-L6-v2
API_KEY Leave blank to run the API in open dev mode. Set a value to require a matching X-API-Key header on /ingest and /query/*
PERSIST_DIR Where the BM25 corpus and ingested-document manifest are saved, default ./storage. Restoring on startup means a restart no longer wipes what was ingested
QUERY_CACHE_TTL_SECONDS / QUERY_CACHE_MAX_ENTRIES How long identical (mode, query, top_k) requests are served from cache, and how many entries the cache holds before evicting
CHAT_HISTORY_MAX_TURNS / CHAT_SESSION_TTL_SECONDS How many turns of conversation /chat keeps per session, and how long an idle session is kept before being swept
HYBRID_RRF_K Reciprocal Rank Fusion constant for /query/hybrid; higher values reduce how much rank position 1 vs 2 matters

Running it

1. Ingest a document

python scripts/ingest.py data/sample_docs/sample.txt

2. Start the API (and the UI)

uvicorn app.main:app --reload

Open http://127.0.0.1:8000 — this now serves the NOEMA web UI directly from the FastAPI app (app/static/). Drop a file on the ingest panel, pick a retrieval mode, and ask a question; the pipeline trace strip lights up as the request moves through guardrails → retrieval → generation, and results show which lane (vector / vector-less) answered along with the retrieved chunks and their scores.

Endpoints:

  • GET /health — liveness + uptime
  • GET /stats — indexed document list, BM25 chunk count, vector store availability, active chat sessions, query cache stats
  • GET /documents — list every indexed source with its chunk count
  • DELETE /documents/{source} — remove a document from every retrieval path
  • POST /ingest — upload/ingest a document
  • POST /query/vector — answer a query using Vector RAG
  • POST /query/vectorless — answer a query using Vector-less (BM25) RAG
  • POST /query/hybrid — answer a query using Reciprocal Rank Fusion of both
  • POST /query/graph — run the full LangGraph pipeline (vector / vectorless / hybrid, with fallback)
  • GET /query/stream — Server-Sent Events: streams the answer token-by-token
  • POST /chat — multi-turn conversational endpoint with session memory
  • GET /chat/{session_id}/history — read back a session's turns
  • DELETE /chat/{session_id} — clear a session

The UI is plain HTML/CSS/JS (no build step) so it runs from the same uvicorn process with no extra tooling. If you'd rather host it separately, set window.__DOCUMIND_API_BASE__ in app/static/index.html before app.js loads, and point it at wherever the API is running — CORS is already open on the API side.

3. Run the MCP server (so an MCP-compatible client can call this pipeline as tools)

python -m app.mcp.server

4. Or run it in Docker

docker compose up --build

This builds the image, mounts a named volume at /data for the BM25 corpus/Chroma store to persist across container restarts, and serves the API + UI on http://localhost:8000. Configuration still comes from .env (referenced via env_file in docker-compose.yml).

Notes on the current state

This is a working scaffold, not a black-box demo: the chunker, guardrails, BM25 retrieval, hybrid fusion, query cache, chat memory, and the FastAPI endpoints (52 tests total) run fully offline and are unit-tested. The vector store, LLM client, and MCP server are wired up with real integrations but need a GROQ_API_KEY (and, optionally, chromadb/LangSmith credentials) to hit real APIs. Without keys, the LLM client falls back to a mock responder so the pipeline still runs end-to-end for development and demos.

Known, honestly-stated limitations:

  • Embeddings: the default EMBEDDING_PROVIDER=hash is a bag-of-words hash, not real semantic search — good for wiring/testing the pipeline offline, not for retrieval quality. Set EMBEDDING_PROVIDER=sentence-transformers for real semantic embeddings (needs an install + a one-time model download).
  • Guardrails are regex/pattern-based prompt-injection checks, not a trained classifier — they catch common phrasings and simple spacing/unicode evasions, not a determined adversary. Swap in a managed guardrails service for anything beyond a demo.
  • Persistence is file-based, not a real database: the BM25 corpus and ingested-document manifest are now saved to PERSIST_DIR and reloaded on startup (previously they were in-memory only and a restart silently wiped them), but this is still a JSON file on local disk, not a proper store — it won't behave correctly behind multiple worker processes or hosts. Chat sessions and the rate limiter are still in-memory-only by design (short-lived, session-scoped state that doesn't need to survive a restart).
  • API auth is a single shared X-API-Key, not per-user auth — fine for a demo or an internal tool, not a substitute for real auth in a multi-tenant deployment.
  • The query cache is a simple in-memory TTL dict, not a distributed cache — fine for a single-process deployment, and it's invalidated on every ingest/delete so it can't serve a stale answer past a document change, but it won't be shared across multiple worker processes.

A dependency-free web UI sits in front of all of this (app/static/, served at /), and several functional bugs were fixed while wiring it up so the demo actually works out of the box: python-multipart was missing from requirements.txt (FastAPI needs it for file uploads — /ingest would 500 on boot without it), the BM25 vector-less path was silently dropping every hit on small corpora (with only one or two chunks indexed, BM25's IDF term can go negative for common words, so the old score <= 0 filter discarded true matches — it now ranks by score but only discards chunks that share no terms with the query), and the frontend was reading stats.vectorless_chunk_count / stats.ingested_documents while /stats returned differently-named fields, so the document list and chunk count in the UI always silently showed zero regardless of what had actually been ingested.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选