DAY 4

MCP Meets Groq: Build Your Agent's Brain

Learn how to integrate MCP with Groq to give your AI agent superpowers. Discover the potential of this powerful combination and how it can revolutionize your AI projects.

⏱ 15 mins
⚡ +50 XP

Day 4 — MCP Meets Groq: Build Your Agent's Brain

MCP & AI Agents — Agent Bootcamp Part 2 — RohithBuilds

This is the day it all clicks together. You'll build an MCP client — a Python program that connects to the server you've built over the last three days, asks Groq what to do, and runs whatever tool Groq picks. No if/else router. No manual prompt explaining each tool. Groq sees your real tools and decides for itself.

Step 1 — Today's Big Picture

Here's the flow you're building:

  1. You send a message (e.g. "How many days until GATE 2027?")
  2. Your client tells Groq what tools are available — straight from your MCP server
  3. Groq decides: does this need a tool? If so, which one, with what arguments?
  4. Your client calls that tool on your MCP server via session.call_tool()
  5. The result goes back to Groq, which writes the final, natural-language answer

💡 About async/await

Every line that talks to your MCP server uses await, and the code lives inside async def functions run with asyncio.run(). Don't worry about mastering async theory today — just follow the pattern. This is how almost all real AI agent code is written.

Create a new file client.py in the same folder as server.py — we'll build it up piece by piece.

Step 2 — Connect to Your Server & List Tools

Add this to client.py:

import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

server_params = StdioServerParameters(
    command="python",
    args=["server.py"],
)

async def main():
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools_result = await session.list_tools()
            print("Tools available on this server:\n")
            for tool in tools_result.tools:
                print(f"- {tool.name}: {tool.description}")

asyncio.run(main())

Run it:

python client.py

Expected Output

Terminal listing all 4 tools (exam_countdown, add_note, list_notes, delete_note) with descriptions

stdio_client just launched server.py as a background process and talked to it over MCP — the same server you've been testing in Inspector all week.

Step 3 — Convert MCP Tools to Groq's Format

Groq needs tool definitions in a specific JSON format. The good news: MCP's tool.inputSchema is already a JSON schema, so converting is mostly relabeling. Add this near the top of client.py, and add import json to your imports:

def mcp_tools_to_groq_format(mcp_tools):
    groq_tools = []
    for tool in mcp_tools:
        groq_tools.append({
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.inputSchema,
            }
        })
    return groq_tools

Now update the loop inside main() to use it:

tools_result = await session.list_tools()
groq_tools = mcp_tools_to_groq_format(tools_result.tools)
print(json.dumps(groq_tools, indent=2))

Run python client.py again.

Expected Output

Terminal showing JSON tool definitions in Groq's function-calling format, generated from MCP tools

That's the exact JSON Groq will use to understand what your tools do and what arguments they need — generated automatically from your @mcp.tool() functions.

Step 4 — Ask Groq: Does This Need a Tool?

Add Groq to your imports and set up the client — same pattern as Part 1, Day 2:

import os
from dotenv import load_dotenv
from groq import Groq

load_dotenv()
groq_client = Groq(api_key=os.getenv("GROQ_API_KEY"))

Now replace the print(json.dumps(...)) line with a real question for Groq:

messages = [
    {"role": "user", "content": "How many days until GATE 2027? The exam is on 2027-02-08."}
]

response = groq_client.chat.completions.create(
    model="llama-3.3-70b-versatile",
    messages=messages,
    tools=groq_tools,
)
reply = response.choices[0].message

if reply.tool_calls:
    for call in reply.tool_calls:
        print(f"Tool: {call.function.name}")
        print(f"Arguments: {call.function.arguments}")
else:
    print("No tool needed. Direct answer:", reply.content)

Expected Output

Terminal showing Groq choosing the exam_countdown tool with extracted arguments

Groq read your question, looked at the tools you gave it, and decided exam_countdown was the right one — extracting exam_name and exam_date from your plain-English sentence. It hasn't run anything yet. That's your job next.

Step 5 — Execute the Tool Call via MCP

Update the if reply.tool_calls: block to actually call the tool on your MCP server:

if reply.tool_calls:
    for call in reply.tool_calls:
        args = json.loads(call.function.arguments)
        print(f"Calling {call.function.name}({args})")

        result = await session.call_tool(call.function.name, arguments=args)
        print("Result:", result.content[0].text)
else:
    print("No tool needed. Direct answer:", reply.content)

Expected Output

Terminal showing the exam_countdown tool being called via MCP and returning a real countdown result

Read that output carefully: Groq chose the tool and the arguments, but session.call_tool() — talking to your server.py from Day 1 — actually ran the Python code and did the date math. Two systems, one job.

Step 6 — Send the Result Back to Groq

Right now you get a raw tool result, not a natural answer. Fix that by sending the result back to Groq for a final response. Replace the if reply.tool_calls: block once more:

if reply.tool_calls:
    messages.append({
        "role": "assistant",
        "content": reply.content,
        "tool_calls": [
            {
                "id": call.id,
                "type": "function",
                "function": {
                    "name": call.function.name,
                    "arguments": call.function.arguments,
                },
            }
            for call in reply.tool_calls
        ],
    })

    for call in reply.tool_calls:
        args = json.loads(call.function.arguments)
        print(f"Calling {call.function.name}({args})")
        result = await session.call_tool(call.function.name, arguments=args)
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": result.content[0].text,
        })

    final = groq_client.chat.completions.create(
        model="llama-3.3-70b-versatile",
        messages=messages,
    )
    print("Rohi:", final.choices[0].message.content)
else:
    print("Rohi:", reply.content)

Expected Output

Terminal showing Rohi giving a natural-language final answer that incorporates the MCP tool's result

That's a complete tool-use round trip — but it only handles one message. Let's make it a real conversation.

Step 7 — The Complete Agent (Chat Loop)

Replace the entire contents of client.py with this final version. It wraps everything you just built in a persistent chat loop — one connection to your server, many messages:

import asyncio
import json
import os
from dotenv import load_dotenv
from groq import Groq
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

load_dotenv()
groq_client = Groq(api_key=os.getenv("GROQ_API_KEY"))

server_params = StdioServerParameters(
    command="python",
    args=["server.py"],
)


def mcp_tools_to_groq_format(mcp_tools):
    groq_tools = []
    for tool in mcp_tools:
        groq_tools.append({
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.inputSchema,
            }
        })
    return groq_tools


async def main():
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            tools_result = await session.list_tools()
            groq_tools = mcp_tools_to_groq_format(tools_result.tools)

            messages = [
                {
                    "role": "system",
                    "content": (
                        "You are Rohi, a helpful assistant for a CS student. "
                        "Use the available tools whenever they help answer the question."
                    ),
                }
            ]

            print("Rohi (MCP-powered) is ready. Type 'quit' to exit.\n")

            while True:
                user_input = input("You: ")
                if user_input.lower() == "quit":
                    break

                messages.append({"role": "user", "content": user_input})

                response = groq_client.chat.completions.create(
                    model="llama-3.3-70b-versatile",
                    messages=messages,
                    tools=groq_tools,
                )
                reply = response.choices[0].message

                if reply.tool_calls:
                    messages.append({
                        "role": "assistant",
                        "content": reply.content,
                        "tool_calls": [
                            {
                                "id": call.id,
                                "type": "function",
                                "function": {
                                    "name": call.function.name,
                                    "arguments": call.function.arguments,
                                },
                            }
                            for call in reply.tool_calls
                        ],
                    })

                    for call in reply.tool_calls:
                        args = json.loads(call.function.arguments)
                        print(f"  -> calling {call.function.name}({args})")
                        result = await session.call_tool(call.function.name, arguments=args)
                        messages.append({
                            "role": "tool",
                            "tool_call_id": call.id,
                            "content": result.content[0].text,
                        })

                    final = groq_client.chat.completions.create(
                        model="llama-3.3-70b-versatile",
                        messages=messages,
                    )
                    final_text = final.choices[0].message.content
                    messages.append({"role": "assistant", "content": final_text})
                    print(f"Rohi: {final_text}\n")
                else:
                    messages.append({"role": "assistant", "content": reply.content})
                    print(f"Rohi: {reply.content}\n")


if __name__ == "__main__":
    asyncio.run(main())

Step 8 — Test Your MCP-Powered Agent

Run it:

python client.py

Try a conversation that exercises multiple tools, for example:

  • How many days until GATE 2027 on 2027-02-08?
  • Add a note: revise OS notes tonight
  • What notes do I have?

Expected Output

Terminal showing a full chat session where Rohi calls exam_countdown, add_note, and list_notes across different messages

Look at what just happened: the same server you tested manually in Inspector all week is now being driven entirely by an AI model, choosing tools on its own, message by message.

Step 9 — Troubleshooting

⚠️ Common Issues

  • Groq never returns tool_calls: double-check the model is exactly llama-3.3-70b-versatile — not every Groq model supports tool calling.
  • "Connection closed" or the agent hangs: make sure server.py runs without errors on its own first (python test_local.py from Day 1-3 still works).
  • JSON errors on tool arguments: print call.function.arguments before json.loads() — it should be a JSON string like {"exam_name": "GATE 2027", "exam_date": "2027-02-08"}.
  • Wrong note ID in delete_note: remember IDs keep incrementing even after deletions — run list_notes first to check current IDs.

✅ Day 4 Complete

Here is what you accomplished today:

Key Takeaways

  • Built a real MCP client that connects to your server ✅
  • Listed tools programmatically and converted them to Groq's format ✅
  • Watched Groq choose tools and arguments from plain English ✅
  • Executed those tool calls live via MCP ✅
  • Fed results back to Groq for natural-language answers ✅
  • Built a full chat loop — your MCP-powered agent is alive ✅

What Is Coming Tomorrow

On Day 5 you will:

  • Wrap today's agent in a Flask web app
  • Build a browser-based chat UI — no terminal required
  • Handle your async MCP agent inside Flask's request/response cycle
  • Chat with your MCP-powered Rohi from a real web page

See you there! 🚀

← Previous Lesson