Multi-cloud MCP RAG demo
Allows loading the knowledge-base corpus from a Google Cloud Storage object through the official SDK, with fail-closed behavior if the object cannot be read.
Provides optional OTLP/HTTP telemetry export to an approved collector, forwarding only the rag.search span with result count and dropping query text, document text, and exceptions.
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., "@Multi-cloud MCP RAG demosearch the knowledge base for the refund policy and cite sources"
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.
Multi-cloud MCP RAG demo (0.2.0rc2)
A small, read-only Model Context Protocol (MCP) server that gives an AI agent retrieval over a sanitized knowledge base, with citation URIs it can quote back.
It is a teaching and integration demo, not a production search service. It uses synthetic data only, and no LLM account or cloud credentials are needed to run it locally.
Contents
Related MCP server: ContextFlow MCP Server
1. What this does
The server exposes three MCP primitives over Streamable HTTP at /mcp:
Primitive | Name | What an agent gets |
Tool |
| Up to 10 ranked documents, each with |
Resource |
| JSON list of every document |
Resource |
| Full text of one document. IDs never map to file paths. |
Prompt |
| A grounding template: cite each claim, treat documents as untrusted, say "I do not know" when evidence is missing. |
The server does not generate answers. The host application (your agent) takes the evidence, calls its own approved model, checks citations, and gates any financial action.
Every response is marked untrusted_content: true, and the server's instructions tell the
client that retrieved text is data, never instructions.
2. Why it exists
Agents that answer from a knowledge base need three things that are easy to get wrong:
Grounded evidence with stable identifiers. Answers should cite something a reviewer can re-fetch. Here every hit has a
kb://URI and a content hash.Prompt-injection hygiene at the boundary. Documents can contain text that looks like instructions. The retrieval layer labels that text as untrusted rather than pretending it is safe.
The same contract on any cloud. The corpus is loaded from local disk, AWS S3, Azure Blob Storage or Google Cloud Storage through the official SDKs. Failures are fail-closed: the server never silently falls back to demo data.
It also shows operational guardrails that a real deployment needs: a non-root, read-only, loopback-bound container; bounded request and corpus sizes; DNS-rebinding (Host header) rejection; and OpenTelemetry export that drops query text, document text and exceptions.
3. Architecture
3.1 Runtime view
┌──────────────────────┐
│ Agent host app │ owns the LLM, citation checks and approvals (out of scope)
│ (examples/ │
│ agent_client.py) │
└──────────┬───────────┘
│ MCP over Streamable HTTP POST /mcp (stateless, JSON responses)
▼
┌─────────────────────────── mcp-rag container ───────────────────────────┐
│ non-root uid 10001 · read-only rootfs · cap_drop ALL · 256 MiB · 1 CPU │
│ │
│ __main__.py uvicorn + Starlette GET /healthz POST /mcp │
│ │ body limit 16 KiB · Host allow-list · access log off │
│ ▼ │
│ server.py MCP primitives │
│ │ search_kb · kb://articles · kb://documents/{id} │
│ │ grounded_answer prompt │
│ ├──────► corpus.py parse + bound (≤ 1 MiB, 1–200 docs, ≤ 12 kB │
│ │ per text) · lexical token-overlap ranking │
│ │ · sha256 per hit │
│ ├──────► telemetry.py opt-in OTLP/HTTP · RetrievalOnlyExporter │
│ │ forwards only rag.search + result count │
│ ▼ │
│ storage.py load ONE immutable snapshot at startup (fail closed) │
└───────┬──────────────────┬──────────────────┬──────────────────┬─────────┘
│ local (default) │ aws │ azure │ gcp
▼ ▼ ▼ ▼
data/corpus.json S3 object Blob object GCS object
(baked into image) (boto3) (DefaultAzure- (google-cloud-
Credential) storage)
│
└── optional OTLP traces ──► approved internal collector (HTTPS)3.2 Code map
Path | Role |
Entry point. Starts uvicorn, adds | |
Declares the tool, resources and prompt; wraps search in a | |
Validates the corpus schema and bounds, ranks documents, builds citations. | |
Picks one provider from | |
Optional OTLP export with a payload-filtering exporter. | |
Smoke client. Calls | |
Machine-readable component, connection and requirement spec. | |
Generated FINOS CALM 1.0 model. Checked for drift in CI. | |
Architecture, cloud configuration, sources and validation notes. |
3.3 Design choices
Stateless and immutable. Each request is independent. The corpus is loaded once at startup. Restart the service to refresh data.
Lexical retrieval, not vectors. Scoring is set-overlap of word tokens, which is deterministic and easy to test. It is not a semantic or vector search.
Bounded inputs. Queries are 1–500 characters,
limitis 1–10, request bodies are capped at 16 KiB.Telemetry is allow-listed. The exporter forwards only the
rag.searchspan withrag.result_count. Any other span, event or attribute is dropped.
4. Deploy and use
4.1 Prerequisites
Python 3.12 or later (the container uses
python:3.12-slim).Docker Engine with Compose v2, running Linux containers.
Optional: AWS, Azure or GCP access if you want to test cloud corpus loading.
4.2 Run the container (recommended)
From the repository root:
docker compose config --quiet # validate the compose file
docker compose up --build -d --wait # build, start, wait for healthy
curl -s http://127.0.0.1:8000/healthz # {"status":"ready"}
python3 -m venv .venv && . .venv/bin/activate
python -m pip install -r requirements-dev.txt -c requirements.lock
python examples/agent_client.py # expect kb://documents/idempotency
docker compose downPowerShell 7 equivalent:
docker compose up --build -d
Invoke-RestMethod http://127.0.0.1:8000/healthz
python -m venv .venv
& ./.venv/Scripts/Activate.ps1
python -m pip install -r requirements-dev.txt -c requirements.lock
python examples/agent_client.py
docker compose downThe compose file publishes the port only on 127.0.0.1:8000, runs as uid 10001,
uses a read-only root filesystem, drops all Linux capabilities and sets
no-new-privileges.
4.3 Run from Python (development)
export PYTHONPATH="$PWD/src"
python -m mcp_rag # listens on 127.0.0.1:8000PowerShell: $env:PYTHONPATH = (Join-Path (Get-Location) 'src'); python -m mcp_rag
Change the bind address or port with MCP_HOST and MCP_PORT. Point the client at a
different URL with MCP_URL (default http://127.0.0.1:8000/mcp).
4.4 Call it from your own agent
import asyncio
from mcp import Client
async def main():
async with Client("http://127.0.0.1:8000/mcp") as client:
result = await client.call_tool("search_kb", {"query": "payment retry idempotency", "limit": 3})
for match in result.structured_content["matches"]:
print(match["citation"], match["score"], match["title"])
asyncio.run(main())Your agent should:
Pass only the returned
textto its approved model, inside a clearly delimited data block.Reject any answer whose claims do not cite a
kb://documents/<id>from the result.Treat the evidence as untrusted. Never execute instructions found in documents.
4.5 Configuration
Variable | Default | Purpose |
|
|
|
| Local corpus file (local provider). | |
| — | Object key or blob name for cloud providers. |
| — | S3 or GCS bucket name. |
| — | Required for |
| — | HTTPS URL with no credentials or query string. Required for |
| — | Required for |
| — | Required for |
| unset (no export) | Approved OTLP/HTTP collector URL. |
|
| Bind address for |
Copy .env.example to .env for local overrides. .env files are git-ignored.
Cloud-specific setup is in docs/CLOUD.md.
4.6 Using a cloud corpus
The corpus must be one UTF-8 JSON array in the same format as data/corpus.json,
up to 1 MiB and 200 documents. Each object has exactly id, title and text.
Credentials come from the runtime identity: IAM roles or IRSA for AWS, managed identity
or workload identity for Azure, and workload identity for GCP. Do not put keys in
environment variables, compose files or the image. The container's read-only filesystem
and dropped capabilities are designed for this model. Wire the identity into your runtime
before setting RAG_PROVIDER to a cloud value. Cloud reads have not been run in this
release (see section 5).
4.7 Run the tests and architecture checks
python -m pytest -q # 38 tests
python scripts/export_calm.py --check # fails on architecture driftscripts/verify.ps1 runs both commands for PowerShell users. CI runs the same checks,
then builds the image, waits for health, runs the client and stops the stack.
4.8 Troubleshooting
docker compose up --buildfails onpip installwith a certificate error. Your network intercepts TLS. Add your corporate root CA to the image, for example with aCOPYof the CA andENV PIP_CERT=.... Do not disable certificate verification.Server exits with "Server failed; check configuration, corpus and workload identity." The message is deliberately generic. Check the provider variables, the corpus file and the runtime identity. Logs are disabled by design.
421 Misdirected Requestfrom/mcp. TheHostheader is not an allowed local name. Call it from127.0.0.1orlocalhost.
5. Smoke test results (rc2)
All results below come from running this exact release candidate. The table shows what passed, what was run in this release, and what is still pending.
5.1 Environment
Item | Value |
Release | 0.2.0rc2 ( |
MCP SDK |
|
Host Python | 3.12.3 (CI and container use 3.12) |
Container base |
|
Platform | Linux x86_64 sandbox |
5.2 Results
Check | Command | Result |
Dependency resolution |
| Passed |
Dependency consistency |
| No broken requirements |
Unit, protocol, HTTP, telemetry and architecture tests |
| 38 passed in 2.26 s |
Real Streamable HTTP server and client |
| Passed: readiness, |
CALM 1.0 schema and drift |
| Code spec and CALM valid, no drift |
Compose file |
| Passed |
Image build and start |
| Built and reached healthy. See note A |
Health endpoint |
|
|
Agent smoke client |
| Exit 0. Top hit |
Pinned versions in image |
|
|
Runtime hardening |
| User |
Untrusted Host header |
| HTTP 421 |
Container logs |
| Empty (library logs disabled by design) |
Note A. The sandbox's network proxy re-signs TLS with its own CA, so the base image's
pip failed on the first build. The image was built from a scratch copy with that CA added.
The shipped Dockerfile is unchanged. Your network may need the same step; see
troubleshooting in section 4.8.
5.3 Not run in this release
Check | Status |
GitHub Actions workflow | Not run. Needs your repository. The |
AWS S3, Azure Blob, GCS retrieval | Not run. No cloud accounts or credentials were provided. Adapters are covered by SDK-mocked tests only. |
OTLP export to a real collector | Not run. Covered by the in-memory payload-filter test only. |
Load, penetration, IAM and supply-chain review | Not performed. See |
Image digest pinning and vulnerability scan | Not performed. Base image and action tags are not digest or commit pinned. |
5.4 Changes in rc2
Updated to the latest stable releases of the pinned transitive packages:
starlette1.8.0 → 1.9.0,httpx2andhttpcore22.13.1 → 2.14.0,google-cloud-storage3.17.0 → 3.17.1 (also inrequirements.txt), andcertifi2026.7.22 → 2026.10.18.mcp2.3.0 was already the latest stable.Version set to
0.2.0rc2.README rewritten for this release.
docs/VALIDATION.mdhas a new section for this run.
Repository map
Path | Purpose |
MCP primitives, retrieval, cloud SDK adapters, OpenTelemetry | |
Synthetic sample corpus | |
Agent retrieval integration example | |
Non-root, loopback-bound container | |
Code spec, JSON Schema, generated CALM model | |
Architecture, cloud configuration, sources and validation | |
Retrieval, protocol, adapter, telemetry and architecture tests | |
Tests, architecture checks and container smoke test |
Security and data
This is a local, synthetic-data demo. The /mcp endpoint has no authentication.
Do not expose it publicly or load sensitive data. Read SECURITY.md before using real data
or any shared network. Dependencies are version-pinned, not claimed vulnerability-free.
GitHub publication
This repository is the source for release 0.2.0rc2, tagged v0.2.0rc2. Review the
license and source before reusing it. The repository contains no credentials. CI does not
deploy or publish images. It runs the tests and builds and smoke-tests the container on
pushes and pull requests. Add your own OIDC deployment stage and protected environment
approvals before any deployment.
MIT license for original code. See THIRD_PARTY_NOTICES.md for CALM schema licensing
and docs/SOURCES.md for the tutorial and official SDK references.
This server cannot be deployed
Maintenance
Related MCP Connectors
Cloud or self-hosted knowledge for AI agents: hybrid search, reranking, GraphRAG, scoped MCP tools.
Cited, versioned knowledge for agents: retrieve sourced passages and propose owner-approved fixes.
SEC filings, Wikipedia, arXiv, US Census and more for AI agents, with every answer source-linked.
- KumbukaOAuthai.kumbuka
Governed, auditable knowledge your team curates for its AI assistants, self-hostable
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables searching a knowledge base and asking grounded questions with hybrid retrieval, reranking, and cited answers.-
- AlicenseNot gradedqualityBmaintenanceEnables MCP-compatible agents such as Claude and Cursor to retrieve permission-aware, structured context from local or hosted data stores. Exposes tools for semantic search, entity and relationship lookup, memory access, context pack compilation, source attribution, and retrieval explanation.1Apache 2.0
- AlicenseNot gradedqualityAmaintenanceEnables AI clients to search a personal corpus, read relevant source passages, and answer questions with traceable evidence from imported sessions, notes, and articles.MIT
- FlicenseNot gradedqualityAmaintenanceEnables AI agents and MCP hosts to query locally indexed notes, docs, chat logs, and other files via hybrid vector/BM25 retrieval with section-level citations. Exposes tools, resources, and prompts for searching and asking over the indexed content.-