DEV Community

Aisalkyn Aidarova
Aisalkyn Aidarova

Posted on

Lab: From LLM RAG MCP

STAGE 1 — LLM
User → LLM → Answer
              ↑
       model knowledge only


STAGE 2 — RAG
User → Retrieval → Internal Runbook
          ↓
         LLM → Answer
               ↑
        private knowledge


STAGE 3 — MCP
User → LLM → MCP Client → MCP Server → DevOps Tool
                                         ↓
                                   live/simulated data
Enter fullscreen mode Exit fullscreen mode

Use this scenario throughout:

We are DevOps engineers supporting payment-service.

And ask:

"Why is payment-service failing?"


Part 0 — Create the project

Students create:

mkdir llm-rag-mcp-lab
cd llm-rag-mcp-lab

python3 -m venv venv
source venv/bin/activate
Enter fullscreen mode Exit fullscreen mode

Create:

llm-rag-mcp-lab/
│
├── 01_llm.py
├── 02_rag.py
├── 03_mcp_server.py
├── 04_mcp_client.py
│
├── knowledge/
│   └── production_runbook.txt
│
└── requirements.txt
Enter fullscreen mode Exit fullscreen mode

The important teaching point is that every stage adds one capability.


PART 1 — Demonstrate the LLM itself

Before using RAG or MCP, make the LLM answer something it cannot possibly know.

Create:

01_llm.py
Enter fullscreen mode Exit fullscreen mode

For a classroom lab, you can use an API-backed model or a local model. The important experiment is the same.

Conceptually:

question = """
What database does our internal payment-service use,
and what should we check if it returns HTTP 500?
"""

response = llm(question)

print(response)
Enter fullscreen mode Exit fullscreen mode

Ask students before running it

"Will the LLM know our company's payment-service?"

No.

It may answer:

The payment service may use PostgreSQL, MySQL,
or another database.

For HTTP 500 errors, check:
- application logs
- database connectivity
- environment variables
...
Enter fullscreen mode Exit fullscreen mode

It can give a reasonable answer.

But ask:

"Where did PostgreSQL come from?"

It doesn't know.

Maybe PostgreSQL. Maybe MySQL. Maybe DynamoDB.

This demonstrates the first concept:

                  LLM
                   │
         ┌─────────┴─────────┐
         │                   │
      KNOWS              DOESN'T KNOW
         │                   │
   Kubernetes          our passwords
   Terraform           our runbooks
   Python              today's deployment
   AWS concepts        our Jira tickets
                       our production state
Enter fullscreen mode Exit fullscreen mode

Experiment that makes this very obvious

Ask:

What is the database hostname for
JumpToTech payment-service production?
Enter fullscreen mode Exit fullscreen mode

The model cannot reliably know it.

Student conclusion #1

Write this on the board:

LLM = reasoning/generation over the information available in its context and learned model behavior.

It does not automatically know your private environment.

Now tell them:

"We need to give our AI company knowledge."

That introduces RAG naturally.


PART 2 — Demonstrate RAG

Now create private information that the LLM has never seen.

Create:

knowledge/production_runbook.txt
Enter fullscreen mode Exit fullscreen mode

Put:

JUMPToTECH BANKING PLATFORM
PRODUCTION RUNBOOK

Application:
payment-service

Environment:
production

Kubernetes Cluster:
banking-prod-eks

AWS Region:
us-east-1

Database:
PostgreSQL

Database Host:
payments-prod.internal

Common Incident:
Payment service returns HTTP 500 when
DB_HOST is configured incorrectly.

Troubleshooting procedure:

1. Check payment-service pod status.
2. Check application logs.
3. Verify DB_HOST.
4. Test connectivity to PostgreSQL.
5. Check Kubernetes Secret payment-db-secret.

Rollback command:

helm rollback payment-service 3
Enter fullscreen mode Exit fullscreen mode

Tell students:

"We invented this information five seconds ago. The LLM was never trained on it."

That's what makes the demonstration powerful.


Step 2.1 — Ask the LLM again

Same question:

What database does our payment-service use?
Enter fullscreen mode Exit fullscreen mode

Without providing the document, the LLM still doesn't know.

Now we build retrieval.


Step 2.2 — Understand RAG before coding

Take the document:

production_runbook.txt
Enter fullscreen mode Exit fullscreen mode

Split it:

            DOCUMENT
                │
                ▼
             CHUNKS

┌────────────────────────────┐
│ payment-service            │
│ environment: production    │
└────────────────────────────┘

┌────────────────────────────┐
│ Database: PostgreSQL       │
│ Host: payments-prod...     │
└────────────────────────────┘

┌────────────────────────────┐
│ HTTP 500 occurs when       │
│ DB_HOST is incorrect       │
└────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Then:

Chunks
   ↓
Embeddings
   ↓
Vectors
   ↓
Vector/Search Store
Enter fullscreen mode Exit fullscreen mode

Student asks:

Why is payment-service returning HTTP 500?
Enter fullscreen mode Exit fullscreen mode

Convert the question to a searchable representation and find the most relevant chunk.

Conceptually:

Question
   ↓
Retrieval
   ↓

Result #1
"HTTP 500 occurs when DB_HOST
is configured incorrectly."

   ↓

Question + Result
   ↓
LLM
Enter fullscreen mode Exit fullscreen mode

Now the model has evidence.


Step 2.3 — Make retrieval visible

This is critical for teaching.

Don't just display the final answer.

Your program should print something like:

===============================
USER QUESTION
===============================

Why is payment-service returning HTTP 500?


===============================
RETRIEVED CONTEXT
===============================

Common Incident:
Payment service returns HTTP 500 when
DB_HOST is configured incorrectly.

Troubleshooting:
Verify DB_HOST.
Test PostgreSQL connectivity.


===============================
LLM ANSWER
===============================

According to the production runbook,
a common cause is an incorrectly configured
DB_HOST.

Check DB_HOST and verify connectivity
to PostgreSQL.
Enter fullscreen mode Exit fullscreen mode

Now stop.

Ask students:

"Where did the answer come from?"

Not just the LLM.

Question
   ↓
RETRIEVE
   ↓
AUGMENT
   ↓
GENERATE
Enter fullscreen mode Exit fullscreen mode

That's literally the name:

R = Retrieval
A = Augmented
G = Generation
Enter fullscreen mode Exit fullscreen mode

Student conclusion #2

RAG retrieves relevant information and adds it to the LLM's context before generation.


PART 3 — Demonstrate why RAG still isn't enough

Now ask:

How many payment-service pods are currently running?
Enter fullscreen mode Exit fullscreen mode

Your runbook doesn't contain that.

Even with RAG:

RAG
 ↓
Search documents
 ↓
???
Enter fullscreen mode Exit fullscreen mode

Why?

Because this isn't really a static-document question.

You need current system information.

Normally a DevOps engineer might run:

kubectl get pods
Enter fullscreen mode Exit fullscreen mode

This gives us the transition to tools/MCP.

Tell students:

"Reading knowledge is different from interacting with an external system."


PART 4 — Demonstrate MCP

For the first class, do not connect production AWS/EKS.

Create a simulated DevOps tool.

Your MCP server exposes something conceptually like:

get_pods()
Enter fullscreen mode Exit fullscreen mode

And it returns:

payment-service-7d8f9    Running
payment-service-2ks91    Running
payment-service-8sa21    CrashLoopBackOff
Enter fullscreen mode Exit fullscreen mode

Architecture:

                   AI APPLICATION
                         │
                         ▼
                       LLM
                         │
                  "I need pod data."
                         │
                         ▼
                    MCP CLIENT
                         │
                  MCP protocol
                         │
                         ▼
                    MCP SERVER
                         │
                    get_pods()
                         │
                         ▼
                 Kubernetes/API
Enter fullscreen mode Exit fullscreen mode

For the classroom, the bottom can initially be simulated.


Step 4.1 — Make the MCP server obvious

Have the server expose three safe tools:

get_pods()

get_logs(pod)

get_deployment(service)
Enter fullscreen mode Exit fullscreen mode

For example:

get_pods()
Enter fullscreen mode Exit fullscreen mode

returns:

NAME                        STATUS
payment-service-abc         Running
payment-service-def         Running
payment-service-xyz         CrashLoopBackOff
Enter fullscreen mode Exit fullscreen mode

And:

get_logs("payment-service-xyz")
Enter fullscreen mode Exit fullscreen mode

returns:

ERROR:
connection to payments-prod.internal:5432 failed

FATAL:
database connection refused
Enter fullscreen mode Exit fullscreen mode

Step 4.2 — Let the LLM choose the tool

Student asks:

Why is payment-service failing?
Enter fullscreen mode Exit fullscreen mode

Now trace every operation on screen:

USER
│
│ Why is payment-service failing?
│
▼
LLM

"I need current information."

│
▼
MCP TOOL CALL

get_pods()

│
▼

payment-service-xyz
CrashLoopBackOff

│
▼
LLM

"I need logs."

│
▼
MCP TOOL CALL

get_logs("payment-service-xyz")

│
▼

database connection refused

│
▼
LLM

"The payment-service pod is crashing
because it cannot connect to the database."
Enter fullscreen mode Exit fullscreen mode

This is where students usually understand MCP.

The LLM didn't magically execute Kubernetes commands.

A tool was made available through the integration.


PART 5 — The best experiment: compare all three

Now put three terminals/windows next to each other.

Window 1 — LLM

Ask:

Why is payment-service failing?
Enter fullscreen mode Exit fullscreen mode

Result:

Possible causes include:
CPU,
memory,
database,
networking,
configuration...
Enter fullscreen mode Exit fullscreen mode

Tell students:

Generic reasoning.


Window 2 — RAG

Same question.

Retrieved:

Company Runbook:

HTTP 500 can occur because of incorrect DB_HOST.
Enter fullscreen mode Exit fullscreen mode

Answer:

According to the runbook,
check DB_HOST and PostgreSQL connectivity.
Enter fullscreen mode Exit fullscreen mode

Tell students:

Company knowledge.


Window 3 — MCP

Same question.

AI calls:

get_pods()
Enter fullscreen mode Exit fullscreen mode

Result:

CrashLoopBackOff
Enter fullscreen mode Exit fullscreen mode

Then:

get_logs()
Enter fullscreen mode Exit fullscreen mode

Result:

database connection refused
Enter fullscreen mode Exit fullscreen mode

Answer:

The current payment-service pod is failing
because its database connection is refused.
Enter fullscreen mode Exit fullscreen mode

Tell students:

Tool/system information.


PART 6 — Then combine RAG + MCP

This is the final "aha" moment.

Student asks:

Payment-service is failing.

Investigate the problem and tell me
how our company runbook says to fix it.
Enter fullscreen mode Exit fullscreen mode

Now both capabilities are required.

                         USER
                          │
                          ▼
                         LLM
                     /          \
                    /            \
                   ▼              ▼
                 MCP             RAG
                  │               │
          Current system       Company
             state             knowledge
                  │               │
                  ▼               ▼
             get_pods()       Runbook
                  │
                  ▼
             get_logs()
                  │
                  └──────┬────────┘
                         ▼
                        LLM
                         │
                         ▼
                    FINAL ANSWER
Enter fullscreen mode Exit fullscreen mode

MCP tells us:

Current pod:
CrashLoopBackOff

Current logs:
database connection refused
Enter fullscreen mode Exit fullscreen mode

RAG tells us:

Company procedure:

1. Verify DB_HOST
2. Test PostgreSQL connectivity
3. Check payment-db-secret
4. If required:
   helm rollback payment-service 3
Enter fullscreen mode Exit fullscreen mode

LLM combines them:

The current payment-service pod is in
CrashLoopBackOff because the database
connection is being refused.

According to the production runbook:

1. Verify DB_HOST.
2. Check payment-db-secret.
3. Test connectivity to PostgreSQL.
4. If the issue was introduced by the latest
   deployment, rollback payment-service.
Enter fullscreen mode Exit fullscreen mode

Now you can ask:

"Which component generated the explanation?"

LLM.

"Which component found our company's troubleshooting procedure?"

RAG.

"Which component let the AI application obtain current pod/log information through exposed tools?"

MCP/tool integration.

That's the distinction you want students to leave class understanding.


One important improvement for your class

I would actually make this a real executable lab, not just conceptual Python.

We can build it so students have:

llm-rag-mcp-lab/

01-llm/
   app.py

02-rag/
   app.py
   runbook.txt

03-mcp/
   server.py
   client.py

04-complete-ai-devops/
   app.py
Enter fullscreen mode Exit fullscreen mode

And when they run:

python app.py
Enter fullscreen mode Exit fullscreen mode

they see clearly labeled output:

🤖 LLM
🔎 RAG RETRIEVAL
🔧 MCP TOOL CALL
📄 TOOL RESULT
🤖 FINAL ANSWER
Enter fullscreen mode Exit fullscreen mode

Top comments (0)