rag-mcp-azure
Click on "Install 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., "@rag-mcp-azureSearch for the termination clause in the contract."
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.
O.M. Health AI ā Medical RAG Assistant
A production-ready AI-powered medical assistant built on a full-stack RAG (Retrieval-Augmented Generation) architecture. The backend runs on Azure Container Apps, the frontend is deployed on Vercel. Designed for reliability, security, and a polished end-user experience.
š Live Demo (Frontend): https://om-health-ai.vercel.app
āļø Backend API (Azure): https://rag-mcp-azure.redsand-f0795bb6.francecentral.azurecontainerapps.io
ā ļø Notice: The backend API is currently deployed using an Azure Free Tier allocation. If the live demo is unresponsive due to expired cloud credits, please check out the Demo Video or follow the Quick Start instructions below to run the architecture locally in seconds using Docker.
šÆ Project Overview
What it does:
Answers medical questions in natural language using the MedQuAD dataset (16,000+ validated Q&A pairs from the NIH)
Retrieves the most relevant medical context from a FAISS vector index using MMR search (Maximal Marginal Relevance)
Synthesizes a clear, professional answer via Claude Haiku (Anthropic) through OpenRouter ā entirely server-side
Exposes a clean chat UI with dark mode, suggestion chips, and Markdown rendering
Also exposes a MCP (Model Context Protocol) endpoint for agentic AI integrations
Architecture philosophy:
Security by design: the LLM API key never reaches the browser ā all synthesis happens in the backend
Privacy by design: zero client-side storage of sensitive data, stateless request processing
Lightweight: CPU-only, no GPU, no heavy vector database ā optimized for cost-effective cloud deployment
Production-ready: automated CI/CD, tested endpoints, verified MCP integration
Related MCP server: RAG-MCP
š Tech Stack
Layer | Technology | Notes |
Backend framework | FastAPI + Uvicorn | Async HTTP, Pydantic validation |
RAG engine | LangChain + FAISS | MMR search, in-memory vector store |
Embeddings | HuggingFace | CPU-optimized, 33 MB |
LLM | Claude Haiku 4.5 via OpenRouter | Called server-side only |
HTTP client | httpx | Async OpenRouter calls from backend |
Knowledge base | MedQuAD dataset (NIH) | 16,000+ medical Q&A pairs, pre-built FAISS index |
Document storage | Azure Blob Storage | Source of truth for index files in production |
MCP server | MCP SDK v2, Streamable HTTP | Agentic tool interface for external AI agents |
Containerization | Docker |
|
Backend hosting | Azure Container Apps | Serverless, auto-scaling, managed ingress |
Container registry | Azure Container Registry (ACR) | Image storage |
Frontend hosting | Vercel | Static HTML/CSS/JS, global CDN |
CI/CD | GitHub Actions | Test ā build ā push ā deploy, idempotent |
šļø Architecture
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā USER BROWSER ā
ā frontend/ (HTML + CSS + JS) ā
ā Hosted on Vercel (static) ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā POST /chat { query }
ā¼
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā AZURE CONTAINER APPS (Backend) ā
ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā FastAPI app/main.py ā ā
ā ā ā ā
ā ā POST /chat āāāŗ rag_engine.search() (MMR, k=3) ā ā
ā ā ā ā ā
ā ā ā¼ ā ā
ā ā FAISS index (in-memory) ā ā
ā ā MedQuAD Q&A embeddings ā ā
ā ā ā ā ā
ā ā ā¼ ā ā
ā ā httpx āāāŗ OpenRouter API ā ā
ā ā Claude Haiku 4.5 ā ā
ā ā (OPENROUTER_API_KEY ā secret Azure) ā ā
ā ā ā ā ā
ā ā ā¼ ā ā
ā ā { "answer": "..." } ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā
ā Also exposes: /query (raw context), /reindex, /upload, /mcp ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
ā¼
Azure Blob Storage
(FAISS index files: index.faiss + index.pkl)Key security boundary: the OPENROUTER_API_KEY is stored as an Azure Container Apps secret and injected at runtime. It is never sent to the browser, never committed to source, and never logged.
šļø Project Structure
rag-mcp-azure/
āāā app/
ā āāā data/ # PDF fallback for local dev
ā āāā main.py # FastAPI app: /chat, /query, /reindex, /upload, MCP mount
ā āāā rag_engine.py # RAG pipeline: MMR search, Blob/local ingestion, FAISS
āāā frontend/
ā āāā index.html # Full-page UI: navbar, hero, chat card, features, FAQ
ā āāā style.css # Design system: CSS variables, dark mode, responsive
ā āāā app.js # Chat logic: POST /chat, Markdown rendering, dark mode toggle
āāā scripts/
ā āāā build_index_offline.py # Build FAISS index from medquad.csv locally
ā āāā upload_index_to_blob.py # Upload index files to Azure Blob Storage
ā āāā deploy-aca.sh # Manual Azure deployment script
āāā tests/
ā āāā test_api.py # REST endpoint tests (CI)
ā āāā test_mcp_integration.py # MCP client tests (manual, live server)
āāā .github/workflows/deploy.yml # CI/CD pipeline
āāā medquad.csv # Source dataset (NIH MedQuAD)
āāā Dockerfile
āāā requirements.txt
āāā README.mdš§ RAG Engine ā Key Design Decisions
Dataset: MedQuAD
The knowledge base is built from the MedQuAD (Medical Question Answering Dataset) published by the NIH. It contains over 16,000 question-answer pairs covering diseases, symptoms, treatments, and diagnostics across dozens of medical specialties.
Unlike a pure document ingestion pipeline, the FAISS index here embeds both the question and the answer text, giving the retrieval step richer semantic context to match against user queries.
MMR Search (Maximal Marginal Relevance)
The search() method in rag_engine.py uses MMR instead of plain cosine similarity:
results = self.vector_store.max_marginal_relevance_search(query, k=3, fetch_k=20)fetch_k=20: retrieve the top 20 candidates by similarityk=3: from those 20, select the 3 most diverse results
This prevents the top-3 results from being near-duplicate chunks (a common failure mode when multiple similar Q&A pairs exist in the dataset), and ensures the LLM receives varied, complementary context.
Pre-built Index Loading
In production, the FAISS index is pre-built offline from medquad.csv and stored in Azure Blob Storage (INDEX_CONTAINER_NAME). On startup, the engine downloads index.faiss + index.pkl directly ā no re-embedding at boot time, which keeps cold start under 30 seconds on 0.5 CPU.
š Security Architecture
API Key ā Server-Side Only
The most important security change from the initial prototype: the OpenRouter API key never leaves the server.
Old architecture | Current architecture | |
Who calls OpenRouter? | Browser (JavaScript) | Backend (Python/httpx) |
Where is the key? |
| Azure Container Apps secret |
Key visible in DevTools? | ā Yes | ā No |
Key in source code? | Risk | Never ā |
The key is injected at deploy time:
az containerapp secret set --name rag-mcp-azure --resource-group rg-rag-mcp-azure `
--secrets "openrouter-api-key=sk-or-v1-..."
az containerapp update --name rag-mcp-azure --resource-group rg-rag-mcp-azure `
--set-env-vars "OPENROUTER_API_KEY=secretref:openrouter-api-key"Full Security Checklist
ā OPENROUTER_API_KEY ā Azure Container Apps secret, never in source or env plaintext
ā BLOB_CONNECTION_STRING ā Azure Container Apps secret, re-applied on every CI deploy
ā GitHub Secrets ā Azure, ACR, and Blob credentials stored as Actions secrets
ā Service Principal ā deployment uses a scoped Azure AD SP, not the subscription owner
ā Pydantic validation ā all request bodies validated before processing
ā MCP DNS rebinding protection ā
TransportSecuritySettingsscoped to known hostsā CORS ā open for public demo (MedQuAD is public data); restrict for production use
ā Key rotation practiced ā Blob Storage key was rotated after accidental log exposure during early debugging
š„ļø Frontend ā UI/UX
The frontend is a static single-page application (HTML + CSS + JS, no framework) deployed on Vercel.
Features
Full-page layout ā navbar, hero section (2-column: pitch + live chat), features grid, security section, FAQ
Functional chat card ā real-time POST to
/chat, typing indicator, timestamped messagesMarkdown rendering ā bot responses rendered with headers, bold, lists, blockquotes (custom lightweight parser, no library)
Lucide Icons ā SVG icon library replacing all native emojis for consistent cross-platform rendering
Dark mode ā toggled via a settings dropdown in the navbar, persisted in
localStorage, applied viabody[data-theme="dark"]CSS variable overrides with smooth transitionSuggestion chips ā pre-filled question shortcuts that disappear after first use
Privacy by design ā zero
sessionStorage/localStorageusage for sensitive data; no API key ever stored client-side
Dark Mode Implementation
/* Light (default) */
:root {
--bg-color: #fdfdfd;
--text-main: #111827;
--white: #ffffff;
--bot-bg: #f3f4f6;
/* ... */
}
/* Dark */
body[data-theme="dark"] {
--bg-color: #111827;
--text-main: #f3f4f6;
--white: #1f2937;
--bot-bg: #374151;
/* ... */
}All colors in style.css use CSS variables ā no hardcoded hex values for interface elements ā ensuring the dark mode applies globally and consistently.
š” API Endpoints
POST /chat ā Primary endpoint
Full RAG + LLM pipeline. Retrieves context from FAISS, calls Claude Haiku via OpenRouter server-side, returns a synthesized medical answer.
Request:
{ "query": "What are the symptoms of hypertension?" }Response:
{ "answer": "Hypertension is often called the 'silent killer' because..." }POST /query
Returns raw FAISS context chunks without LLM synthesis. Used internally and for debugging/testing.
Response:
{
"query": "...",
"context_extrait": "Extrait 1:\n...\n\nExtrait 2:\n..."
}GET /health
{ "status": "ok" }POST /reindex
Rebuilds the FAISS index from Blob Storage (or local files). Call after uploading new documents.
POST /upload
Uploads a PDF and adds it to the in-memory index (ephemeral ā lost on restart or /reindex).
MCP /mcp-server/mcp
See MCP Protocol Integration below.
š MCP Protocol Integration
Model Context Protocol (MCP) is an open standard for connecting AI agents to external tools. O.M. Health AI exposes a search_documents tool over Streamable HTTP, allowing any MCP-compatible agent (Claude Desktop, custom agents) to query the medical knowledge base directly.
Endpoint
Environment | URL |
Local |
|
Production |
|
Exposed Tool
Tool | Input | Output |
|
| Top-3 MMR-ranked medical context chunks |
Python client example (verified end-to-end)
import asyncio
from mcp.client.streamable_http import streamable_http_client
from mcp import ClientSession
async def main():
url = "https://rag-mcp-azure.redsand-f0795bb6.francecentral.azurecontainerapps.io/mcp-server/mcp"
async with streamable_http_client(url) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool("search_documents", {"query": "symptoms of diabetes"})
for content in result.content:
if hasattr(content, "text"):
print(content.text)
asyncio.run(main())Claude Desktop config
{
"mcpServers": {
"om-health-ai": {
"url": "https://rag-mcp-azure.redsand-f0795bb6.francecentral.azurecontainerapps.io/mcp-server/mcp"
}
}
}DNS Rebinding Protection
The MCP SDK restricts the Host header to localhost by default. TransportSecuritySettings in app/main.py explicitly allow-lists the production Azure hostname ā without this, requests return 421 Misdirected Request.
š Quick Start
Local Development
git clone https://github.com/oumniya03/rag-mcp-azure.git
cd rag-mcp-azure
python -m venv .venv
.venv\Scripts\Activate # Windows
# source .venv/bin/activate # macOS/Linux
pip install -r requirements.txtSet environment variables (create a .env or export directly):
# Required for LLM synthesis
export OPENROUTER_API_KEY=sk-or-v1-...
# Optional: load FAISS index from Blob Storage instead of local medquad_index/
export INDEX_CONTAINER_NAME=medquad-index
export BLOB_CONTAINER_URL=<your-connection-string>Run the backend:
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000Open frontend/index.html directly in a browser, or serve it locally:
cd frontend && python -m http.server 3000Test the chat endpoint:
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"query": "What are the symptoms of high blood pressure?"}'š³ Docker
# Build
docker build -t om-health-ai:latest .
# Run (pass the API key at runtime)
docker run --rm -p 8000:8000 \
-e OPENROUTER_API_KEY=sk-or-v1-... \
om-health-ai:latestImage profile: python:3.12-slim base, CPU-only torch wheel, ~500 MB compressed on ACR.
āļø Azure Deployment
Production Resources
Resource | Name | Notes |
Container App |
| Backend API |
Resource Group |
| France Central |
ACA Environment |
| Shared environment |
ACR |
| Docker image registry |
Blob Storage |
| FAISS index + source PDFs |
Compute | 0.5 CPU / 1.0 Gi | Sufficient for CPU-only inference |
Required GitHub Secrets
Secret | Description |
| Service principal JSON ( |
| ACR admin username |
| ACR admin password |
| Blob Storage connection string (re-applied as Container App secret on every deploy) |
| OpenRouter API key (applied as Container App secret) |
CI/CD Pipeline (.github/workflows/deploy.yml)
On every push to main:
Install dependencies
Run REST tests (
pytest tests/ -v -m "not integration")Login to Azure (service principal)
Build & push Docker image to ACR
Idempotent deploy:
az containerapp updateif exists,az containerapp createotherwiseRe-apply both secrets (
blob-connection-string,openrouter-api-key) on every deploy
git push origin main # triggers the full pipelineBlob Storage ā FAISS Index
The pre-built FAISS index is stored in Blob Storage and loaded at startup (no re-embedding on cold start):
# Upload index files
az storage blob upload-batch `
--destination medquad-index `
--source medquad_index/ `
--account-name ragmcpstorage26 `
--auth-mode key
# Set the index container env var
az containerapp update --name rag-mcp-azure --resource-group rg-rag-mcp-azure `
--set-env-vars "INDEX_CONTAINER_NAME=medquad-index"š§Ŗ Testing
Automated (CI)
pytest tests/ -v -m "not integration"Covers /health, /query, /reindex, /upload ā runs on every push to main.
MCP Integration (manual, requires live server)
pytest tests/test_mcp_integration.py -v
# Against local instance:
$env:MCP_TEST_URL="http://localhost:8000/mcp-server/mcp"
pytest tests/test_mcp_integration.py -vQuick production smoke test
# Health
Invoke-RestMethod -Uri "https://rag-mcp-azure.redsand-f0795bb6.francecentral.azurecontainerapps.io/health"
# Chat
$body = @{query="What is hypertension?"} | ConvertTo-Json
Invoke-RestMethod -Method Post `
-Uri "https://rag-mcp-azure.redsand-f0795bb6.francecentral.azurecontainerapps.io/chat" `
-ContentType "application/json" -Body $bodyš§ Configuration Reference
Variable | Required | Description |
| Yes (production) | LLM API key ā Azure secret, never plaintext |
| No | Blob Storage connection string for PDF ingestion |
| No | Blob container name for pre-built FAISS index |
Local tweaks (app/rag_engine.py):
chunk_size/chunk_overlapā text splitter parameters (default: 500 / 50)k/fetch_kā MMR search parameters (default: k=3, fetch_k=20)Embedding model ā
HuggingFaceEmbeddings(model_name=...)
āļø Troubleshooting
Symptom | Likely cause | Fix |
|
| Set the Azure Container App secret |
Empty context from | No index loaded | Check startup logs for |
MCP | Host not in allow-list | Add host to |
MCP | MCP not started via | Ensure |
Docker build fails | Dependency conflict | Check Python 3.12 compatibility in |
GitHub Actions fails | Missing secrets | Verify all 5 secrets are set in repository settings |
š Performance Profile
Metric | Value |
Deployment CPU | 0.5 vCPU |
Deployment Memory | 1.0 Gi |
Embedding model |
|
Cold start | ~25ā30s (index download + model load) |
Inference | CPU-only, no GPU |
FAISS search | MMR, k=3 from fetch_k=20 |
LLM | Claude Haiku 4.5, temp=0.3, max_tokens=1000 |
š£ļø Roadmap
MedQuAD medical knowledge base
MMR search for diverse, non-redundant context
Server-side LLM synthesis (
/chatendpoint)API key security ā backend-only, Azure secret
Full-page frontend with dark mode and Lucide icons
MCP Streamable HTTP endpoint (verified end-to-end)
Pre-built FAISS index loaded from Blob Storage
Automated CI/CD with GitHub Actions
OIDC federated auth for CI/CD (replace ACR admin credentials)
Rate limiting on
/chatAPI key authentication for
/chatand/uploadAzure Application Insights (latency, error rate, usage)
Persist
/uploaddocuments to Blob StorageMulti-language support
š License
Provided as-is for educational and portfolio purposes.
š¤ Author
Oumniya Moutaouakil ā AI Engineer, LLM / Agentic AI & RAG Systems.
Project status: ā Production-ready ā backend on Azure Container Apps, frontend on Vercel, REST + MCP endpoints verified end-to-end.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
No tool schema history has been recorded yet.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
The CustomGPT.ai MCP server is a fully managed, RAG-powered endpoint that connects large language models with private knowledge bases and external data sources. It provides tools for retrieval-augmented generation queries (send_message), data ingestion (upload_file), and source listing, enabling AI agents to query private documents like PDFs with high accuracy and real-time citations.
MCP-Native LLM Orchestration Agent
The Remote MCP server acts as a standardized bridge between LLM applications (like Claude, ChatGPT, and Cursor) and external services, enabling AI agents to access external tools and resources. Its primary capability is providing a centralized search tool to discover other MCP servers and their respective tools. Unlike local implementations, it runs remotely with OAuth authentication and permission controls for security.
The Needle MCP server enables semantic search on documents stored in files like PDFs, DOCX, and XLSX by connecting AI applications to external data sources. It provides capabilities to create and manage document collections, perform natural language searches on stored content, and retrieve relevant information without requiring exact keyword matches.
Related MCP Servers
- FlicenseAqualityDmaintenanceA local RAG MCP server for PDF development experience, enabling document ingestion, semantic search, and Q\&A with source citations using TF-IDF and cosine similarity.3-
- FlicenseNot gradedqualityDmaintenanceA Retrieval Augmented Generation MCP server that ingests documents into a local vector database and enables semantic search queries.10-
- FlicenseNot gradedqualityBmaintenanceMCP server for a modular RAG system that enables natural language question answering over enterprise documents with intent-aware routing, adaptive retrieval, and citation-backed responses.-
- AlicenseNot gradedqualityCmaintenanceMCP server for a self-hosted RAG system that enables AI tools to search and retrieve grounded answers from locally ingested documents via MCP tools, with local embeddings and no API key required.MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/oumniya03/rag-mcp-azure'
If you have feedback or need assistance with the MCP directory API, please join our Discord server