RAG_POC/mcp-tools.md
This document defines the MCP tool surface for querying ProxySQL’s embedded RAG index. It is intended as a stable interface for AI agents. Internally, these tools query the SQLite schema described in schema.sql and the retrieval logic described in architecture-runtime-retrieval.md.
Design goals
doc_id: stable document identifier (e.g. posts:12345)chunk_id: stable chunk identifier (e.g. posts:12345#0)source_id / source_name: corresponds to rag_sourcesscore_fts (bm25; lower is better in SQLite’s bm25 by default)score_vec (distance or similarity, depending on implementation)score (normalized fused score; higher is better)Recommendation Normalize scores in MCP layer so:
score_fts_raw, distance_raw, etc. if helpfulAll tools should enforce caps, regardless of caller input:
k_max = 50candidates_max = 500query_max_bytes = 8192response_max_bytes = 5_000_000timeout_ms (per tool): 250–2000ms depending on tool typeTools must return a truncated boolean if limits reduce output.
Many tools accept the same filter structure. This is intentionally simple in v0.
{
"source_ids": [1,2],
"source_names": ["stack_posts"],
"doc_ids": ["posts:12345"],
"min_score": 5,
"post_type_ids": [1],
"tags_any": ["mysql","json"],
"tags_all": ["mysql","json"],
"created_after": "2022-01-01T00:00:00Z",
"created_before": "2025-01-01T00:00:00Z"
}
Notes
metadata_json values. Implementation can:
source_ids and source_names are provided, treat as intersection.rag.search_ftsKeyword search over rag_fts_chunks.
{
"query": "json_extract mysql",
"k": 10,
"offset": 0,
"filters": { },
"return": {
"include_title": true,
"include_metadata": true,
"include_snippets": false
}
}
include_snippets is requested (still bounded).{
"results": [
{
"chunk_id": "posts:12345#0",
"doc_id": "posts:12345",
"source_id": 1,
"source_name": "stack_posts",
"score_fts": 0.73,
"title": "How to parse JSON in MySQL 8?",
"metadata": { "Tags": "<mysql><json>", "Score": "12" }
}
],
"truncated": false,
"stats": {
"k_requested": 10,
"k_returned": 10,
"ms": 12
}
}
rag.search_vectorSemantic search over rag_vec_chunks.
{
"query_text": "How do I extract JSON fields in MySQL?",
"k": 10,
"filters": { },
"embedding": {
"model": "text-embedding-3-large"
}
}
{
"query_embedding": {
"dim": 1536,
"values_b64": "AAAA..." // float32 array packed and base64 encoded
},
"k": 10,
"filters": { }
}
query_text is provided, ProxySQL computes embedding internally (preferred for agents).query_embedding is provided, ProxySQL uses it directly (useful for advanced clients).{
"results": [
{
"chunk_id": "posts:9876#1",
"doc_id": "posts:9876",
"source_id": 1,
"source_name": "stack_posts",
"score_vec": 0.82,
"title": "Query JSON columns efficiently",
"metadata": { "Tags": "<mysql><json>", "Score": "8" }
}
],
"truncated": false,
"stats": {
"k_requested": 10,
"k_returned": 10,
"ms": 18
}
}
rag.search_hybridHybrid search combining FTS and vectors. Supports two modes:
{
"query": "json_extract mysql",
"k": 10,
"filters": { },
"mode": "fuse",
"fuse": {
"fts_k": 50,
"vec_k": 50,
"rrf_k0": 60,
"w_fts": 1.0,
"w_vec": 1.0
}
}
{
"query": "json_extract mysql",
"k": 10,
"filters": { },
"mode": "fts_then_vec",
"fts_then_vec": {
"candidates_k": 200,
"rerank_k": 50,
"vec_metric": "cosine"
}
}
fts_kvec_kchunk_idkcandidates_kk reranked results{
"results": [
{
"chunk_id": "posts:12345#0",
"doc_id": "posts:12345",
"source_id": 1,
"source_name": "stack_posts",
"score": 0.91,
"score_fts": 0.74,
"score_vec": 0.86,
"title": "How to parse JSON in MySQL 8?",
"metadata": { "Tags": "<mysql><json>", "Score": "12" },
"debug": {
"rank_fts": 3,
"rank_vec": 6
}
}
],
"truncated": false,
"stats": {
"mode": "fuse",
"k_requested": 10,
"k_returned": 10,
"ms": 27
}
}
rag.get_chunksFetch chunk bodies by chunk_id. This is how agents obtain grounding text.
{
"chunk_ids": ["posts:12345#0", "posts:9876#1"],
"return": {
"include_title": true,
"include_doc_metadata": true,
"include_chunk_metadata": true
}
}
{
"chunks": [
{
"chunk_id": "posts:12345#0",
"doc_id": "posts:12345",
"title": "How to parse JSON in MySQL 8?",
"body": "<p>I tried JSON_EXTRACT...</p>",
"doc_metadata": { "Tags": "<mysql><json>", "Score": "12" },
"chunk_metadata": { "chunk_index": 0 }
}
],
"truncated": false,
"stats": { "ms": 6 }
}
Hard limit recommendation
rag.get_docsFetch full canonical documents by doc_id (not chunks). Useful for inspection or compact docs.
{
"doc_ids": ["posts:12345"],
"return": {
"include_body": true,
"include_metadata": true
}
}
{
"docs": [
{
"doc_id": "posts:12345",
"source_id": 1,
"source_name": "stack_posts",
"pk_json": { "Id": 12345 },
"title": "How to parse JSON in MySQL 8?",
"body": "<p>...</p>",
"metadata": { "Tags": "<mysql><json>", "Score": "12" }
}
],
"truncated": false,
"stats": { "ms": 7 }
}
rag.fetch_from_sourceRefetch authoritative rows from the source DB using doc_id (via pk_json).
{
"doc_ids": ["posts:12345"],
"columns": ["Id","Title","Body","Tags","Score"],
"limits": {
"max_rows": 10,
"max_bytes": 200000
}
}
rag_documents to get source_id and pk_jsonrag_sources{
"rows": [
{
"doc_id": "posts:12345",
"source_name": "stack_posts",
"row": {
"Id": 12345,
"Title": "How to parse JSON in MySQL 8?",
"Score": 12
}
}
],
"truncated": false,
"stats": { "ms": 22 }
}
Security note
rag.admin.stats (recommended)Operational visibility for dashboards and debugging.
{}
{
"sources": [
{
"source_id": 1,
"source_name": "stack_posts",
"docs": 123456,
"chunks": 456789,
"last_sync": null
}
],
"stats": { "ms": 5 }
}
rag.admin.sync (optional in v0; required in v1)Kicks ingestion for a source or all sources. In v0, ingestion may run as a separate process; in ProxySQL product form, this would trigger an internal job.
{
"source_names": ["stack_posts"]
}
{
"accepted": true,
"job_id": "sync-2026-01-19T10:00:00Z"
}
rag_chunks/rag_documents for metadata.INVALID_ARGUMENT, LIMIT_EXCEEDED, INTERNAL)stats.ms in responsesThese MCP tools define a stable retrieval interface:
rag.search_fts, rag.search_vector, rag.search_hybridrag.get_chunks, rag.get_docs, rag.fetch_from_sourcerag.admin.stats, optionally rag.admin.sync