Build a Context-Aware RAG Assistant with MCP Integration

Build a smart document assistant that retrieves the right chunks of your data, pipes them through an MCP client, and manages context windows so the model always sees exactly what it needs.

What You'll Build

A context-aware RAG assistant that retrieves document chunks, connects via an MCP client, and manages your context window budget — all in one clean interface you can actually use.

  • A RAG retrieval panel that pulls the most relevant document chunks for any query
  • An MCP client connection layer that routes queries to a local or remote MCP server
  • A live context window budget bar that shows token usage and warns before overflow
  • A smart prompt assembler that slots retrieved chunks into the right positions in the context
  • A live query interface with chunk source labels and relevance scores for full transparency
  • An overflow protection mode that auto-trims low-relevance chunks when the budget is tight

What You'll Need

🤖

AI Tool

Claude (claude.ai) — recommended for its large context window and strong reasoning about system architecture. The free tier works fine for all five steps.

📝

Text Editor

VS Code or any editor that handles HTML, JavaScript, and JSON. You'll save your generated files locally and open them directly in a browser — no server required for most steps.

Step-by-Step Guide

Follow each step in order. Each prompt builds on the last, so don't skip ahead — every piece connects.

1

Build the Core UI Shell and Layout

Start by getting the full interface scaffolded. This gives you a three-panel layout: a document chunk list on the left, a query and response area in the center, and a context window budget tracker on the right. Getting the layout right first means every subsequent step slots into a clear home.

PROMPT — Copy & paste this into Claude
Build me a single-file HTML/CSS/JS app called "RAG Context Assistant". It needs a dark-themed three-panel layout: Left panel (25% width): titled "Retrieved Chunks" — shows a scrollable list of document chunks. Each chunk card shows: a source label (e.g. "doc-1.txt"), a relevance score badge (0.0–1.0), a short excerpt (2–3 lines), and a checkbox to include/exclude it from the context. Center panel (50% width): titled "Query & Response" — has a textarea at the top for the user's question, a blue "Send Query" button, and a large response area below that shows the AI answer with a subtle loading shimmer while waiting. Right panel (25% width): titled "Context Budget" — shows a horizontal progress bar labeled "Token Usage" that fills from green to amber to red as it approaches the limit. Below it: numeric readout like "3,420 / 8,192 tokens", a "Model Limit" dropdown (options: 8k, 32k, 128k, 200k), and a red warning banner that appears when usage exceeds 85% capacity. Use a dark slate background, white headings, and violet accent colors for interactive elements. Make all three panels visible simultaneously on a 1280px screen. Stub out the JS functions so the UI is interactive but doesn't need a real backend yet.

What to look for: Claude should produce a complete HTML file with all three panels visible and properly proportioned. Check that the token progress bar changes color as it fills, the chunk checkboxes are interactive, and the model limit dropdown updates the denominator in the token readout. The Send Query button should trigger the shimmer state.

2

Wire Up the RAG Chunk Retrieval Simulator

Now you'll add a realistic RAG retrieval simulation. Real RAG systems embed documents and do vector similarity search — but for this project, you'll build a keyword-weighted relevance scorer in pure JavaScript that mimics how retrieval actually works. This teaches you the shape of RAG output before you ever touch an embedding API.

PROMPT — Copy & paste this into Claude
Add a RAG retrieval simulator to the existing app. Here's exactly what I need: 1. A hardcoded knowledge base: create a JS array called `knowledgeBase` with 10 realistic document chunks. Use a mix of topics — some about "context windows", some about "MCP protocol", some about "vector embeddings", some about "retrieval augmented generation". Each chunk object should have: id, source (filename), content (3–5 sentences of realistic technical content), and a `tokens` property (an integer between 150–600). 2. A `retrieveChunks(query, topK)` function that: - Tokenizes the query into lowercase words - Scores each chunk by counting how many query words appear in its content (case-insensitive) - Normalizes the score to a 0.0–1.0 float (divide by max possible matches) - Returns the top `topK` chunks sorted by score descending - Adds a small random jitter (±0.05) to scores so results feel natural 3. When the user types in the query box and clicks "Send Query", call `retrieveChunks(query, 5)` and render the top 5 results into the left panel chunk cards. Show the relevance score rounded to 2 decimal places. Auto-check the top 3 chunks and leave the bottom 2 unchecked. 4. Below the chunk list, add a small note: "Showing top 5 of 10 chunks · Retrieval method: keyword similarity". Keep all existing UI intact.

What to look for: Type a query like "how does context window work" and watch the left panel populate with ranked chunks. The relevance scores should vary meaningfully based on your query words. Chunks about context windows should score higher for that query than chunks about embeddings. The top 3 should be auto-checked.

3

Add the MCP Client Connection Layer

This step introduces the MCP client concept. You'll build a simulated MCP client module that shows exactly how a real client would establish a session, send tool call requests, and receive results — mirroring the architecture from the MCP Client tutorial. Even simulated, this makes the data flow visible and teachable.

PROMPT — Copy & paste this into Claude
Add a simulated MCP client layer to the app. This should teach users what a real MCP client does without needing an actual server running. Here's what I need: 1. An `MCPClient` JavaScript class with these methods: - `connect(serverName)` — logs "[MCP] Connecting to {serverName}..." then after 600ms logs "[MCP] Session established. Protocol: MCP/1.0" - `listTools()` — returns a mock array of tool objects: [{name: "retrieve_chunks", description: "Fetch relevant document chunks by query", inputSchema: {query: "string", topK: "number"}}, {name: "count_tokens", description: "Estimate token count for a string", inputSchema: {text: "string"}}] - `callTool(toolName, params)` — simulates a tool call. For "retrieve_chunks" it calls the existing `retrieveChunks` function and wraps the result in {tool: toolName, result: [...], latencyMs: randomInt(80,300)}. For "count_tokens" it estimates tokens as Math.ceil(params.text.length / 4) wrapped similarly. - `disconnect()` — logs "[MCP] Session closed." 2. An MCP Activity Log panel: add a collapsible section at the bottom of the right panel titled "MCP Activity Log" with a dark console-style background (near-black). Each log entry shows timestamp, log level (INFO / TOOL_CALL / RESPONSE), and message in monospace font. Use green for INFO, cyan for TOOL_CALL, amber for RESPONSE. 3. Wire it up: when the user clicks "Send Query", the flow should be: a. MCPClient.connect("local-rag-server") b. MCPClient.callTool("retrieve_chunks", {query, topK: 5}) c. Render chunks from the tool response d. MCPClient.callTool("count_tokens", {text: assembled prompt text}) e. Update the token budget bar f. MCPClient.disconnect() All steps should appear sequentially in the activity log with realistic timestamps.

What to look for: Send a query and watch the MCP Activity Log fill in sequentially — connect, tool call, response, disconnect. The log entries should feel like a real protocol trace. Token count from the count_tokens tool should update the budget bar. This is the core architecture of a real MCP client made visible.

4

Build the Smart Context Window Manager

This is where the context window tutorial comes to life. You'll build a prompt assembler that constructs the full context from system prompt + retrieved chunks + user query, tracks the running token budget, and automatically trims low-relevance chunks when you're close to the model's limit. This is exactly what production RAG systems do.

PROMPT — Copy & paste this into Claude
Add a smart context window manager to the app. This is the core intelligence layer. Here's exactly what I need: 1. A `ContextAssembler` class with: - A fixed system prompt string (~200 tokens): "You are a helpful assistant with access to retrieved document chunks. Answer questions using ONLY the provided context. If the context doesn't contain the answer, say so clearly. Cite your sources by chunk ID." - `assemblePrompt(query, chunks, modelLimit)` method that: a. Starts with the system prompt b. Adds each checked chunk in order: "[CHUNK {id} | Source: {source} | Score: {score}]\n{content}" c. Appends the user query d. Tracks a running token count after each addition e. If adding a chunk would push total tokens over (modelLimit × 0.85), skips that chunk and logs a warning f. Returns {prompt: assembledString, tokensUsed: number, chunksIncluded: array, chunksDropped: array} 2. A "Prompt Preview" expandable section in the center panel (below the response area). When expanded, it shows the fully assembled prompt in a monospace code block with syntax highlighting: system prompt in violet, chunk blocks in cyan, user query in amber. 3. A "Dropped Chunks" warning: if any chunks were dropped due to token budget, show a rose-colored warning bar listing which chunk IDs were excluded and why ("Token budget: {n} tokens remaining — chunk {id} required {m} tokens"). 4. Update the token budget bar to reflect the assembled prompt's real token count, not just the retrieved chunks. 5. Add a "Reserve for Response" slider (range: 256–4096, default 1024) in the right panel. This value is subtracted from the model limit before assembly begins, ensuring the model always has room to generate a full answer. Keep all existing functionality working.

What to look for: Try setting the model limit to 8k and including all 5 chunks — some should get dropped with a rose warning explaining why. The Prompt Preview should show distinct colors for each section. The Reserve for Response slider should tighten the budget visibly. This is the real behavior of a production RAG context manager.

5

Connect a Real AI Response and Add Diagnostic Exports

Finally, tie everything together with a simulated (or optionally real) AI response and add export tools so you can study your sessions. You'll add a realistic streamed response simulator, a full session export to JSON, and a shareable prompt export — making this a genuinely useful diagnostic tool, not just a demo.

PROMPT — Copy & paste this into Claude
Add the final layer to complete the RAG Context Assistant: 1. Simulated streaming response: when the user sends a query, generate a realistic response that: - References chunk IDs from the context (e.g. "According to [CHUNK 3]...") - Streams word by word into the response area with a 30ms delay between words (use setInterval) - After streaming completes, shows a "Response complete" badge with total response tokens counted and added to the budget bar - If no chunks were retrieved (query returned all zero scores), shows: "No relevant context found. I cannot answer this question from the available documents." 2. A session summary card that appears after each query in the right panel showing: - Query sent - Chunks retrieved / chunks included / chunks dropped - Total tokens used (prompt + response) - Model limit used - MCP tool calls made (count) - Latency (sum of simulated MCP latencies) 3. An "Export Session" button (amber colored) in the top-right header that downloads a JSON file named `rag-session-{timestamp}.json` containing: the full query, the assembled prompt, all chunk metadata, the response text, and the session summary. 4. A "Copy Prompt" button next to the Prompt Preview that copies just the assembled prompt to clipboard. 5. A settings gear icon in the header that opens a small modal letting the user change the app title and set a "Max Chunks to Retrieve" value (1–10, default 5). Make sure the entire app still works as one self-contained HTML file with no external dependencies beyond a CDN-hosted font if needed.

What to look for: The response should stream in word-by-word and cite chunk IDs naturally. The session summary card is key — it shows the full cost of one RAG query in tokens and latency. Export a session JSON and open it to verify all the fields are present. The settings modal should persist your max chunks value across queries.

Common Issues

The token budget bar doesn't update after adding chunks

This usually means the ContextAssembler isn't being called after chunk selection changes. Ask Claude: "Make the token budget bar recalculate every time a chunk checkbox is toggled, using the current checked state." The assembler should run on checkbox change, not just on query send.

All chunks score 0.0 for my query

Your query words aren't matching anything in the knowledge base content. This is actually a great RAG lesson — the system returns nothing when there's no overlap. Try queries using words that appear in the chunk content like "embedding", "context", "retrieval", or "MCP". Or ask Claude to expand the knowledge base with more varied content.

The MCP Activity Log timestamps are all the same

The simulated delays need to be awaited properly with async/await. Ask Claude: "Refactor the MCPClient methods to be async and add sequential awaited delays so each log entry appears after the previous one completes, not all at once." This makes the log read like a real protocol trace.

The Prompt Preview doesn't show different colors for each section

The syntax highlighting depends on string detection — if the assembled prompt format changed, the color patterns won't match. Ask Claude: "Update the Prompt Preview renderer to split the assembled prompt at the CHUNK markers and user query delimiter, then wrap each section in a colored span rather than using regex on the raw string."

The JSON export is missing fields or malformed

If the session state object isn't fully populated before export, you'll get nulls or missing fields. Ask Claude: "Add a validateSession() check before export that confirms all required fields exist and logs any missing ones to the MCP Activity Log before triggering the download."

What You Learned

🔍

RAG in Practice

You built a retrieval pipeline that scores, ranks, and selects document chunks based on query relevance — the exact pattern used in production RAG systems before you ever touch an embedding API.

🔌

MCP Client Architecture

You built a structured MCP client class that mirrors real protocol behavior — connect, list tools, call tools, disconnect — with an activity log that makes the invisible communication layer visible.

📏

Context Window Management

You learned how real systems track token budgets across system prompt, retrieved context, and response reservation — and built overflow protection that drops low-value chunks rather than crashing.

⚙️

Prompt Assembly as Engineering

You saw how a final LLM prompt isn't just something you type — it's assembled programmatically from multiple sources, each with a token cost and a priority. That's a core production AI skill.

Tips for Going Further

01

Connect to a Real Embedding API

Replace the keyword scorer with real cosine similarity using OpenAI's text-embedding-ada-002 or a local model via Ollama. Ask Claude to add a fetch call to the embeddings endpoint and swap the scoring function — the rest of the app stays identical.

02

Upgrade to a Real MCP Server

Point the MCPClient at an actual local MCP server using stdio transport. The client class you built already has the right shape — you just swap the simulated delays for real WebSocket or stdio messages.

03

Add a Multi-Model Context Comparison

Add a side-by-side mode that assembles the same query for two different model limits (e.g. 8k vs 128k) and shows which chunks get dropped in the smaller window. Great for understanding why model choice affects retrieval quality.

04

Load Your Own Documents

Add a file upload input that accepts .txt files and chunks them automatically (every 500 tokens). Ask Claude to add a simple chunker function that splits on paragraph breaks first, then hard-cuts at the token limit if a paragraph is too long.

05

Build a Chunk Priority Ranking System

Add manual priority tags (High / Medium / Low) to each chunk card. Update the ContextAssembler to always include High-priority chunks first regardless of relevance score, then fill the remaining budget with scored chunks. This models how real systems mix curated and dynamic context.

More projects

One of 38 hands-on projects.

All projects Prompting & Workflows tutorials