PDPA GraphRAG MCP Server
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., "@PDPA GraphRAG MCP Serverเก็บข้อมูลสุขภาพพนักงานได้ไหม ถ้าฝ่าฝืนมีโทษอะไร"
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.
PDPA GraphRAG + MCP
A GraphRAG agent over Thailand's Personal Data Protection Act (PDPA, B.E. 2562 / 2019). The 96 sections of the statute become a 190-node / 380-edge knowledge graph plus a dense + sparse vector index, exposed as five MCP tools that any agent can call. Everything runs locally with Docker.
This is the companion code for the article series "Why Vector RAG Answers Only Half of a Legal Question" (part 1 builds this system in five steps; parts 2 and 3 cover a graph bug and tool design for agents). Statutes do not repeat themselves, they point: a prohibition lives in one section, its exceptions in sub-clauses, and the penalty chapters later in a sentence that only says "whoever violates Section 26". Vector search finds the first half; the graph walk finds the second.
The example is Thai law, but the structure is the same for GDPR, HIPAA, the CCPA or Japan's APPI, and the embedding model is multilingual.
What's inside
mcp_server_rag.py FastMCP server (streamable-http, port 8100) exposing 5 tools
lightrag/ retrieval engine: Markdown chunker, NetworkX graph, BGE-M3 + Qdrant index, hybrid retrieval
data/
pdpa_full_text_v2.md the statute as Markdown (H1 Act -> H2 Chapter -> H3 Part -> H4 Section)
pdpa_knowledge_graph.json pre-built graph: 190 nodes / 380 edges, 11 node types, 26 edge types
scripts/
build_graph.py rule-based graph builder (sections + cross-references + curated nodes)
validate_graph.py counts nodes/edges from the data, checks connectivity and penalty reachability
labs/
core/ minimal MCP client (httpx), tool registry (MCP inputSchema -> OpenAI tools), LLM client
lab7/agent_loop.py the smallest possible agent loop, local tools only
lab8/agent_pdpa_mcp.py the same loop wired to this MCP server (the run shown in the article)
examples/mcp_curl.sh talk to the server with plain curl + jq
docs/ architecture and pipeline figures
Dockerfile, docker-compose.yml, requirements.txt, .env.examplelabs/ contains only the agent-side code; the course lab instructions are not part of this repo.
Related MCP server: Connapse
Architecture

Knowledge graph: NetworkX, in memory. 96 Section nodes plus Definition, LawfulBasis, Right, Obligation, Penalty, Exemption and Principle nodes; the backbone edges are
REFERENCES_SECTION(122, section cites section) andCONTAINS_SECTION(119).Vector index: BGE-M3 (dense 1024-d + sparse lexical weights) in Qdrant with RRF fusion. Two collections:
pdpa_chunks(96 full sections) andpdpa_profiles(570 = every node and edge rendered as a short text, so vector search can land on graph nodes).MCP server: FastMCP,
stateless_http=True. Each tool's docstring is the description the agent reads before choosing a tool.LLM on the server side is used for exactly one thing, extracting search keywords. Answers are written by the agent's own model: the server retrieves, the agent reasons.
Quick start
Requirements: Docker with Compose, an OpenRouter API key, and about 5 GB of disk for the image (BGE-M3, ~2.3 GB, is baked in at build time so the container starts offline).
cp .env.example .env # put your OPENROUTER_API_KEY in .env
docker compose up --build # starts Qdrant + the MCP server; the first build downloads BGE-M3Startup is done when the log shows:
Parsed 96 sections (มาตรา), 96 chunks
Upserted 570 profile vectors
Upserted 96 chunk vectorsSmoke test with curl (needs jq):
./examples/mcp_curl.shA bare request without the Accept: application/json, text/event-stream header returns
-32600 Client must accept text/event-stream. That means the server is alive; the streamable-http
transport simply requires the header.
Run the agent against the server:
python -m venv .venv && source .venv/bin/activate
pip install -r requirements-agent.txt
python labs/lab8/agent_pdpa_mcp.py "เก็บข้อมูลสุขภาพพนักงานได้ไหม ถ้าฝ่าฝืนมีโทษอะไร"The statute and the agent's system prompt are in Thai, so Thai questions work best (the model
answers in Thai; change SYSTEM in labs/lab8/agent_pdpa_mcp.py to answer in another language).
A typical run makes 7 tool calls in 4 rounds: search_pdpa -> get_related_sections +
get_penalty -> get_section_text x4 -> answer citing Sections 26 / 79 / 84.
MCP tools
Tool | What it does |
| Hybrid retrieval: LLM keywords -> profile search -> 2-hop BFS -> chunk search -> context (JSON) |
| In- and out-edges of one section, grouped by relationship |
| BFS (max 3 hops) from the violated section to Penalty nodes. Pass the violated section ( |
| Full statute text of one section by id, deterministic (no vector search) |
| Node/edge counts and the schema of the graph |
Section ids are sec_<number> with Arabic digits (sec_26), even though the statute text uses Thai
numerals (มาตรา ๒๖).
How retrieval works

An LLM splits the question into low-level keywords (section numbers, named rights, penalties) and high-level keywords (principles, concepts), after LightRAG's dual-level scheme.
Profile search (
pdpa_profiles, dense + sparse, RRF) finds up to 3 seed nodes.Chunk search (
pdpa_chunks) fetches the full text of up to 5 sections.BFS from the seeds, 2 hops, following edges in both directions; every reached node is written as
[hop1][GRANTS_EXEMPTION] sec26_ex5c: ...so the model knows why it is there.The context is capped (8 profiles, 15 expanded nodes, 5 full sections) and returned as JSON. The server never writes the answer.
Rebuilding and validating the graph
python scripts/build_graph.py # -> build/ (never overwrites data/)
python scripts/validate_graph.py # checks data/pdpa_knowledge_graph.json
python scripts/validate_graph.py build/pdpa_knowledge_graph.jsonbuild_graph.py is rule-based: a regex finds section headings in the Markdown, a second regex finds
cross-references inside each section (REFERENCES_SECTION edges), and the semantic nodes
(definitions, rights, obligations, penalties, exemptions, principles, lawful bases) are curated lists
inside the script. It reproduces the base graph (163 nodes / 345 edges). The shipped
data/pdpa_knowledge_graph.json is that base graph plus hand-added exemption nodes (190 / 380).
Its metadata block still reports the older 163 / 343 counts; always count from the data, which is
what validate_graph.py does.
To use this on another statute, swap the heading pattern (Article\s+(\d+) for GDPR,
第([一二三四五六七八九十百]+)条 for Japan's APPI) and re-curate the semantic nodes.
Known limitations
Built for one statute. The graph lives in memory and both Qdrant collections are rebuilt on every start (no hot reload). For a corpus of laws use a graph database;
build_graph.pyalso emitspdpa_neo4j_import.cypher.Rule-based graph construction means porting to a new law is curation work, not configuration.
There is no formal evaluation set yet. Contributions of question/answer pairs with expected section citations are welcome.
The shipped graph has a known gap that affects
get_penaltyfor one section; it is the subject of part 2 of the article series and is left as is until that article is published.
Data
The statute text is the Personal Data Protection Act B.E. 2562 (2019), a public document, prepared from the version published by the Office of the Council of State of Thailand. The knowledge graph, code and figures are by the author.
Article series
Why Vector RAG Answers Only Half of a Legal Question (Thai: https://aekanunbigdata.medium.com/a0f1e09e3450 — English version coming)
One missing edge: why the agent cited the wrong penalty section (coming)
Why a GraphRAG agent needs a fifth tool (coming)
This server cannot be deployed
Maintenance
Related MCP Connectors
Cloud or self-hosted knowledge for AI agents: hybrid search, reranking, GraphRAG, scoped MCP tools.
Persistent memory and knowledge graphs for AI agents. Hybrid search, context checkpoints, and more.
Persistent memory and knowledge management for AI agents with semantic search and 50+ tools.
Universal persistent memory and knowledge retrieval layer for AI agents and LLMs.
Related MCP Servers
- FlicenseAqualityDmaintenanceEnables AI agents to query a local knowledge graph built from document collections using hybrid search (BM25 + vector fusion) and entity-relationship extraction. Supports privacy-first, offline operation with tools for semantic search, entity graph exploration, and corpus statistics.3-
- AlicenseAqualityCmaintenanceSelf-hosted knowledge backend for AI agents. Provides 11 MCP tools for hybrid vector + keyword search, container-isolated knowledge bases, and 4 storage connectors (S3, Azure Blob, MinIO, filesystem). Built with .NET, runs via Docker.1121MIT
- FlicenseNot gradedqualityCmaintenanceEnables document Q&A and knowledge retrieval through hybrid semantic and keyword search, with tools for document ingestion, chunking, summarization, PII redaction, and RAGAS-based evaluation.-
- AlicenseNot gradedqualityBmaintenanceEnables persona-grounded retrieval-augmented generation over your own documents by building a Neo4j knowledge graph and exposing tools for search, context, persona briefs, and graph queries via MCP.MIT