adding-mcp-to-an-ai-agent

Adding MCP to an AI Agent

In the previous articles, we gradually increased the capabilities of our AI Agent.

Our Agent can now:

  • Understand user requests
  • Use an AI model
  • Use tools
  • Maintain conversation context
  • Use memory
  • Retrieve information using RAG

Now we will introduce another important technology:

MCP – Model Context Protocol.

MCP provides a standardized way for AI applications to connect to external tools, data sources, and services. The OpenAI Agents SDK currently supports MCP servers through several connection methods, including local stdio servers and Streamable HTTP servers. (OpenAI GitHub)

What Is MCP?

MCP stands for:

Model Context Protocol

MCP is an open protocol designed to standardize how applications provide tools and context to AI models.

A simple way to understand MCP is to think of it as a standard connection between an AI application and external capabilities.

The official MCP documentation uses a useful analogy: MCP is similar to USB-C for AI applications — one standardized connection can be used to connect an application to different types of tools and data sources. (OpenAI GitHub)

Why Do We Need MCP?

Imagine that we build an AI Agent that needs to work with several external systems:

AI Agent
   |
   +---- File System
   |
   +---- Database
   |
   +---- GitHub
   |
   +---- Calendar
   |
   +---- Company Documents
   |
   +---- External APIs

Without a standard protocol, each connection could require a different implementation.

MCP provides a standardized way for AI applications to discover and use capabilities exposed by MCP servers.

A Simple MCP Architecture

A simplified architecture looks like this:

                     AI Agent
                         |
                         v
                     MCP Client
                         |
                         v
                    MCP Server
                         |
          +--------------+--------------+
          |              |              |
          v              v              v
      File System     Database      External API

The MCP client is the part of the AI application that connects to an MCP server.

The MCP server exposes tools or other context that the client can use.

MCP Client and MCP Server

It is important to understand these two terms.

MCP Client

The client is part of the AI application or Agent that connects to an MCP server.

In our case, the Agent application can act as an MCP client.

MCP Server

The MCP server provides capabilities to the client.

For example, an MCP server might expose tools for:

  • Reading files
  • Searching information
  • Accessing a database
  • Working with Git repositories
  • Calling an external service

The server does not necessarily have to be a large cloud application. It can also be a local process running on your computer.

MCP Is Not an AI Model

MCP is sometimes misunderstood as an AI model.

It is not.

MCP is a protocol.

The relationship is approximately:

LLM
 |
 v
AI Agent
 |
 v
MCP Client
 |
 v
MCP Server
 |
 v
External System

The LLM provides language understanding and reasoning.

The Agent manages the workflow.

MCP provides a standardized connection to external capabilities.

MCP and Tools

There is a strong relationship between MCP and tools.

An MCP server can expose tools that an Agent can use.

For example:

MCP Server
     |
     +---- search_files
     |
     +---- read_file
     |
     +---- write_file

The Agent can discover these tools and use them when appropriate.

The OpenAI Agents SDK documentation describes MCP server tool calling as an integrated part of the SDK, with MCP-backed tools attached to an Agent similarly to other tools. (OpenAI GitHub)

MCP vs a Normal Python Function

In our earlier article, we created a calculator tool directly in Python.

Conceptually:

AI Agent
   |
   v
Python Function
   |
   v
Calculator

The function belongs directly to our application.

With MCP, the architecture can instead be:

AI Agent
   |
   v
MCP Client
   |
   v
MCP Server
   |
   v
Calculator / External System

The capability can therefore be provided by a separate MCP server.

Why Is This Useful?

Imagine that you have an MCP server providing access to a company database.

You could potentially use the same MCP server from different AI applications.

For example:

             MCP Server
                  |
        +---------+---------+
        |                   |
        v                   v
    AI Agent A          AI Agent B

Both Agents can use the capabilities exposed by the MCP server.

This separation can make systems easier to reuse and maintain.

MCP Servers Can Be Local or Remote

MCP does not require everything to run in the cloud.

An MCP server can run locally on your computer.

For example:

Your Computer
|
+-- AI Agent
|
+-- MCP Client
|
+-- MCP Server
|
+-- Local Files

An MCP server can also be accessed remotely.

For example:

Your Computer
|
+-- AI Agent
      |
      v
   Internet
      |
      v
Remote MCP Server
      |
      v
External Service

The OpenAI Agents SDK currently supports local stdio MCP servers as well as Streamable HTTP MCP servers, among other integrations. (OpenAI GitHub)

MCP Transports

The word transport describes how the MCP client communicates with the MCP server.

For our purposes, two important approaches are:

stdio

The Agent starts or communicates with a local MCP server process using standard input and output.

AI Agent
   |
 stdin/stdout
   |
MCP Server

This is useful for local MCP servers.

Streamable HTTP

The Agent communicates with an MCP server through HTTP.

AI Agent
   |
 HTTP
   |
MCP Server

The current OpenAI Agents SDK documentation recommends Streamable HTTP or stdio for new integrations, while older SSE-based MCP connections are considered legacy. (OpenAI GitHub)

Our First MCP Example

For our practical tutorial, we will keep the example simple.

Instead of immediately connecting to a complicated enterprise system, we will first understand the basic architecture.

Our Agent will connect to an MCP server.

The MCP server will expose a tool.

The Agent will then be able to use that tool.

The architecture will be:

User
 |
 v
AI Agent
 |
 v
MCP Client
 |
 v
MCP Server
 |
 v
MCP Tool
 |
 v
Result
 |
 v
AI Agent
 |
 v
User

A Simple Practical MCP Example

In the previous sections we looked at MCP conceptually. Now let’s connect a real Python Agent to a local MCP server.
To keep the example easy to understand, our MCP server will provide one simple tool: get_project_info.
The important thing is not the complexity of the tool. The important part is that the tool is provided by a separate MCP server rather than being implemented directly inside our Agent.

Create a very small MCP server
we add a new file: mcp_server.py

AI-Agent/
│
├── agent.py
├── mcp_server.py
├── articles/
├── rag_db/
└── memory.json

The mcp_server.py :

#mcp_server.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("my-server")

@mcp.tool()
def hello(name: str) -> str:
    return f"Hello {name}"

if __name__ == "__main__":
    mcp.run()

The important line in the code is:

@mcp.tool()

That tells the MCP server:: Make this Python function available as an MCP tool.
So unlike your existing:

@function_tool
def search_articles(...):

the MCP tool lives in a separate process.
Your existing RAG tool is part of the Agent application. Your MCP tool is provided by another application/process. That’s a useful distinction to teach readers.
Your current RAG implementation, for comparison, defines search_articles as a local Agent function tool.

Add MCP to a simple Agent:
For the article, don’t initially show the entire agent.py.
Instead, create a small example called: mcp_agent.py:

#mcp_agent.py
import asyncio
import sys
from pathlib import Path

from agents import Agent, Runner
from agents.mcp import MCPServerStdio


async def main():

    # Directory where this file is located
    project_dir = Path(__file__).resolve().parent

    # Python executable from the current virtual environment
    python_executable = sys.executable

    # Full path to the MCP server
    mcp_server_file = project_dir / "mcp_server.py"

    print("Starting MCP server...")
    print(f"Python: {python_executable}")
    print(f"MCP server: {mcp_server_file}")

    async with MCPServerStdio(
        name="Project MCP Server",

        params={
            "command": python_executable,
            "args": [
                str(mcp_server_file)
            ],
        },
    ) as server:

        print("MCP server connected.")

        agent = Agent(
            name="MCP Agent",

            instructions="""
            You are a helpful AI assistant.

            You have access to tools provided by
            an MCP server.

            Use the MCP tools when they are useful
            for answering the user's question.

            Do not invent information returned by
            the MCP tools.
            """,

            mcp_servers=[
                server
            ],
        )

        result = await Runner.run(
            agent,
            "Tell me about this AI Agent project."
        )

        print()
        print("Agent:")
        print(result.final_output)


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

This follows the current Agents SDK pattern: MCPServerStdio launches a local MCP server process, and that server is supplied to the Agent through mcp_servers=[server].
This is the most important in the above code:

async with MCPServerStdio(
    name="Project MCP Server",
    params={
        "command": "python",
        "args": ["mcp_server.py"],
    },
) as server:

MCPServerStdio creates a connection between our Agent and a local MCP server.

The command tells the Agent how to start the MCP server.

Here we use Python:

python mcp_server.py

The MCP server then communicates with the Agent through standard input and output.
The SDK documentation specifically describes MCPServerStdio as the option for local MCP servers that run as subprocesses and communicate over stdin/stdout.
And:

mcp_servers=[
    server
]

as:

This tells the Agent that the MCP server is available and that its tools can be used by the Agent. The SDK’s Agent supports an mcp_servers list specifically for MCP-backed tools.

Create requirment.txt in  the same directory as agent.py:

openai-agents
mcp<2
chromadb
sentence-transformers

How to Run the Example

Follow these steps to set up and run the MCP Agent example in PowerShell.

1. Open PowerShell and go to the project folder

cd C:\Utvecklingprogram\AI\AI-Agent

2. Create the virtual environment

If you haven’t created the virtual environment yet, run:

py -3.12 -m venv .venv

3. Activate the virtual environment

.\.venv\Scripts\Activate.ps1

You should now see (.venv) in your PowerShell prompt.

4. Install the dependencies

Upgrade pip first:

python -m pip install --upgrade pip

Then install the dependencies from requirements.txt:

python -m pip install -r requirements.txt

5. Run the MCP Agent

### 5. Run the MCP Agent

Start the example with:

```powershell
python mcp_agent.py
```

You should see output similar to:

```text
Starting MCP server...
Python: C:\Utvecklingprogram\AI\AI-Agent\.venv\Scripts\python.exe
MCP server: C:\Utvecklingprogram\AI\AI-Agent\mcp_server.py
MCP server connected.

Agent:
...
```

The message `MCP server connected.` confirms that the MCP server has started successfully and that the agent has connected to it.

The example then runs the agent with the prompt defined in `mcp_agent.py`. When the agent finishes, PowerShell returns to the command prompt:

```text
(.venv) PS C:\Utvecklingprogram\AI\AI-Agent>
```

At this point, the example has completed its run.

**Important:** You do not need to start `mcp_server.py` separately. The MCP server is started automatically by `mcp_agent.py`.

### What happens next?

To use the agent with your own questions, you need to modify the prompt in `mcp_agent.py` or change the example to accept user input.

For example, you can replace the example prompt with a question relevant to the MCP tools provided by your server and run:

```powershell
python mcp_agent.py
```

The agent will start the MCP server, connect to it, process the prompt, display the response, and then exit.

Example of questions and outputs:

(.venv) PS C:\Utvecklingprogram\AI\AI-Agent> python mcp_agent.py
Starting MCP server...
Python: C:\Utvecklingprogram\AI\AI-Agent\.venv\Scripts\python.exe
MCP server: C:\Utvecklingprogram\AI\AI-Agent\mcp_server.py
MCP server connected.

MCP Agent is ready.
Type 'exit' to quit.

You: What tools do you have?

Agent:
I have access to a `hello` tool that accepts a name and returns a greeting.

You: What can you help me with?

Agent:
I can help with answering questions, explaining concepts, writing or editing, brainstorming, coding, summarizing, planning, research, and more. What would you like to work on?

You: anne

Agent:
Hello! How can I help?

You: what is my name

Agent:
I don’t know your name.

You: Mehrdad

Agent:
Hello Mehrdad! How can I help you today?

You: 

you can even run program from VS code terminal and gives the same result.

What happens when the user asks a question?

 when the user asks a question?

Your article could show:

User
  │
  │ "Tell me about this AI Agent project"
  ▼
Agent
  │
  │ decides MCP tool is useful
  ▼
MCP Client
  │
  │ stdio
  ▼
mcp_server.py
  │
  ▼
get_project_info()
  │
  ▼
Tool result
  │
  ▼
Agent / LLM
  │
  ▼
User

The important learning point is:

The Agent does not contain get_project_info() directly.

The MCP server owns that capability.

Connect MCP to the Existing Memory and RAG Agent

In the previous articles, we built an agent with long-term memory, conversation memory, and RAG.

The next step is to add MCP without throwing away that existing architecture.

The goal is not to create a completely separate MCP agent. Instead, we extend the existing agent so it can use both its local RAG tools and tools provided by an MCP server.

The Agent Before MCP

Our previous agent contained the local RAG tool:

agent = Agent(
    name="Memory and RAG Agent",

    instructions="...",

    tools=[
        search_articles
    ]
)

The search_articles function is a local tool. It is part of our Python application and searches the Chroma-based article knowledge base.

The agent therefore had access to:

Agent
 │
 ├── LLM
 │
 ├── Long-term Memory
 │
 ├── Conversation Memory
 │
 └── RAG
      │
      └── search_articles()

What Changes with MCP?

MCP adds another source of tools.

Instead of implementing every external capability directly inside our application, we can connect the agent to an MCP server.

In our example, the MCP server currently provides the hello(name) tool.

The architecture therefore becomes:

Agent
 │
 ├── LLM
 │
 ├── Long-term Memory
 │
 ├── Conversation Memory
 │
 ├── RAG
 │    └── search_articles()
 │
 └── MCP
      │
      └── MCP Server
           │
           └── hello(name)

The important point is that hello() does not live inside agent.py.

It belongs to the MCP server:

@mcp.tool()
def hello(name: str) -> str:
    return f"Hello {name}"

The agent connects to the server and receives access to the tools it provides.


The new agent.py:

import asyncio
import json
import os
import sys
from pathlib import Path

from agents import Agent, Runner, SQLiteSession, function_tool
from agents.mcp import MCPServerStdio

import chromadb
from sentence_transformers import SentenceTransformer


# ============================================================
# CONFIGURATION
# ============================================================

MEMORY_FILE = "memory.json"

# Folder containing your articles
ARTICLES_FOLDER = "articles"

# Persistent RAG database
RAG_DATABASE = "rag_db"

# Chroma collection name
RAG_COLLECTION = "articles"

# Number of article chunks to retrieve
RAG_TOP_K = 5

# Embedding model
EMBEDDING_MODEL = "all-MiniLM-L6-v2"


# ============================================================
# LONG-TERM MEMORY
# ============================================================

def load_memory():

    if not os.path.exists(MEMORY_FILE):
        return {}

    with open(
        MEMORY_FILE,
        "r",
        encoding="utf-8"
    ) as file:

        return json.load(file)


def save_memory(memory):

    with open(
        MEMORY_FILE,
        "w",
        encoding="utf-8"
    ) as file:

        json.dump(
            memory,
            file,
            indent=4,
            ensure_ascii=False
        )


memory = load_memory()


# ============================================================
# RAG SYSTEM
# ============================================================

class ArticleRAG:

    def __init__(self):

        print("Loading RAG embedding model...")

        self.embedding_model = SentenceTransformer(
            EMBEDDING_MODEL
        )

        # Persistent Chroma database
        self.client = chromadb.PersistentClient(
            path=RAG_DATABASE
        )

        self.collection = (
            self.client
            .get_or_create_collection(
                name=RAG_COLLECTION
            )
        )

        print("RAG system ready.")

    # --------------------------------------------------------
    # Split article into chunks
    # --------------------------------------------------------

    def chunk_text(
        self,
        text,
        chunk_size=500,
        overlap=100
    ):

        words = text.split()

        chunks = []

        start = 0

        while start < len(words):

            end = start + chunk_size

            chunk = " ".join(
                words[start:end]
            )

            if chunk.strip():

                chunks.append(chunk)

            start += chunk_size - overlap

        return chunks

    # --------------------------------------------------------
    # Add one article
    # --------------------------------------------------------

    def add_article(
        self,
        article_id,
        title,
        text
    ):

        chunks = self.chunk_text(text)

        if not chunks:
            return

        embeddings = (
            self.embedding_model
            .encode(chunks)
            .tolist()
        )

        ids = []
        metadatas = []

        for i in range(len(chunks)):

            ids.append(
                f"{article_id}_chunk_{i}"
            )

            metadatas.append(
                {
                    "article_id": article_id,
                    "title": title,
                    "chunk": i
                }
            )

        # Upsert means that running the program again
        # will update existing article chunks instead
        # of creating duplicates.
        self.collection.upsert(
            ids=ids,
            documents=chunks,
            embeddings=embeddings,
            metadatas=metadatas
        )

        print(
            f"RAG: indexed '{title}' "
            f"({len(chunks)} chunks)"
        )

    # --------------------------------------------------------
    # Load articles from folder
    # --------------------------------------------------------

    def load_articles(
        self,
        folder=ARTICLES_FOLDER
    ):

        if not os.path.exists(folder):

            print(
                f"RAG: article folder '{folder}' "
                f"does not exist."
            )

            return

        article_files = [
            file
            for file in os.listdir(folder)
            if file.lower().endswith(".txt")
        ]

        if not article_files:

            print(
                "RAG: no .txt articles found."
            )

            return

        for filename in article_files:

            path = os.path.join(
                folder,
                filename
            )

            try:

                with open(
                    path,
                    "r",
                    encoding="utf-8"
                ) as file:

                    text = file.read()

                article_id = os.path.splitext(
                    filename
                )[0]

                self.add_article(
                    article_id=article_id,
                    title=filename,
                    text=text
                )

            except Exception as error:

                print(
                    f"RAG: could not load "
                    f"{filename}: {error}"
                )

    # --------------------------------------------------------
    # Search articles
    # --------------------------------------------------------

    def search(
        self,
        query,
        top_k=RAG_TOP_K
    ):

        # Nothing to search
        if self.collection.count() == 0:

            return []

        query_embedding = (
            self.embedding_model
            .encode([query])
            .tolist()
        )

        results = self.collection.query(
            query_embeddings=query_embedding,
            n_results=top_k
        )

        documents = results.get(
            "documents",
            [[]]
        )[0]

        metadatas = results.get(
            "metadatas",
            [[]]
        )[0]

        retrieved = []

        for document, metadata in zip(
            documents,
            metadatas
        ):

            retrieved.append(
                {
                    "text": document,
                    "title": metadata.get(
                        "title",
                        "Unknown article"
                    ),
                    "article_id": metadata.get(
                        "article_id",
                        ""
                    )
                }
            )

        return retrieved


# ============================================================
# CREATE RAG DATABASE
# ============================================================

rag = ArticleRAG()

# Load/index articles when the program starts
rag.load_articles()


# ============================================================
# RAG TOOL FOR THE AGENT
# ============================================================

@function_tool
def search_articles(query: str) -> str:
    """
    Search the article knowledge base.

    Use this tool whenever the user's question could be
    answered using information from the stored articles.

    Args:
        query: The question or topic to search for.
    """

    results = rag.search(
        query,
        top_k=RAG_TOP_K
    )

    if not results:

        return (
            "No relevant information was found "
            "in the article knowledge base."
        )

    output = []

    for i, result in enumerate(
        results,
        start=1
    ):

        output.append(
            f"""
SOURCE {i}
ARTICLE: {result['title']}

{result['text']}
"""
        )

    return "\n".join(output)


# ============================================================
# MAIN
# ============================================================

async def main():

    # --------------------------------------------------------
    # MCP SERVER
    # --------------------------------------------------------

    project_dir = Path(__file__).resolve().parent

    python_executable = sys.executable

    mcp_server_file = project_dir / "mcp_server.py"

    print()
    print("==============================")
    print("Memory + RAG + MCP Agent")
    print("==============================")
    print()

    print("Starting MCP server...")
    print(f"Python: {python_executable}")
    print(f"MCP server: {mcp_server_file}")

    # Start the MCP server and keep it connected
    # while the agent is running.
    async with MCPServerStdio(
        name="Project MCP Server",

        params={
            "command": python_executable,
            "args": [
                str(mcp_server_file)
            ],
        },
    ) as server:

        print("MCP server connected.")
        print()

        # ----------------------------------------------------
        # AGENT
        # ----------------------------------------------------

        agent = Agent(

            name="Memory, RAG and MCP Agent",

            instructions="""
You are a helpful AI assistant.

You have access to several capabilities:

1. Long-term user memory
2. Conversation memory through the session
3. An article knowledge base through the
   search_articles tool
4. Tools provided by an MCP server

==================================================
RAG RULES
==================================================

- When the user's question is related to information
  that could be contained in the articles, use the
  search_articles tool.

- Use the retrieved article information as your
  primary source for questions about the articles.

- Do not invent information that is not supported
  by the retrieved articles.

- If the article search does not contain enough
  information, clearly tell the user that the
  information was not found in the article
  knowledge base.

==================================================
MCP RULES
==================================================

- Use MCP tools when they are useful for answering
  the user's question.

- The MCP tools are provided by an external MCP server.

- Do not pretend that an MCP tool exists if it is
  not available.

- Do not invent information returned by MCP tools.

==================================================
MEMORY RULES
==================================================

- Long-term user memory is provided in the prompt.

- Conversation memory is maintained through the
  session.

- Use remembered information when it is relevant.

- If the user tells you something useful about
  themselves, acknowledge it naturally.

==================================================
GENERAL BEHAVIOR
==================================================

Choose the appropriate capability based on the
user's request.

You may combine information from memory, RAG,
MCP tools, and your general knowledge when
appropriate.

Always answer clearly and helpfully.
""",

            # Existing local RAG tool
            tools=[
                search_articles
            ],

            # New MCP connection
            mcp_servers=[
                server
            ]
        )

        # ----------------------------------------------------
        # SESSION
        # ----------------------------------------------------

        session = SQLiteSession(
            "memory_demo"
        )

        print("Agent is ready.")
        print("Type 'exit' to stop.")
        print(
            "Type 'remember:' followed by "
            "information to save it."
        )
        print()

        # ----------------------------------------------------
        # INTERACTIVE LOOP
        # ----------------------------------------------------

        while True:

            user_input = input(
                "You: "
            ).strip()

            # ------------------------------------------------
            # EXIT
            # ------------------------------------------------

            if user_input.lower() == "exit":

                print()
                print("Goodbye!")

                break

            # ------------------------------------------------
            # EMPTY INPUT
            # ------------------------------------------------

            if not user_input:

                continue

            # ------------------------------------------------
            # SAVE LONG-TERM MEMORY
            # ------------------------------------------------

            if user_input.lower().startswith(
                "remember:"
            ):

                information = (
                    user_input[9:].strip()
                )

                if information:

                    memory.setdefault(
                        "user_information",
                        []
                    ).append(
                        information
                    )

                    save_memory(
                        memory
                    )

                    print(
                        "Agent: I will remember that."
                    )

                print()

                continue

            # ------------------------------------------------
            # LOAD LONG-TERM MEMORY
            # ------------------------------------------------

            memory_text = json.dumps(
                memory,
                indent=2,
                ensure_ascii=False
            )

            # ------------------------------------------------
            # CREATE PROMPT
            # ------------------------------------------------

            prompt = f"""
Here is information remembered about the user:

{memory_text}

Use this information when it is relevant.

The agent has access to an article knowledge base
through the search_articles tool.

The agent also has access to tools provided by
an MCP server.

If the user's question relates to the articles,
use the search_articles tool.

If the user's question can be answered using an
MCP tool, use the appropriate MCP tool.

User message:

{user_input}
"""

            # ------------------------------------------------
            # RUN AGENT
            # ------------------------------------------------

            try:

                result = await Runner.run(
                    agent,
                    prompt,
                    session=session
                )

                print()
                print(
                    "Agent:",
                    result.final_output
                )

            except Exception as error:

                print()
                print(
                    "Agent error:",
                    error
                )

            print()


# ============================================================
# START PROGRAM
# ============================================================

if __name__ == "__main__":

    asyncio.run(main())

Updating agent.py

We now modify our existing agent.py to connect the MCP server to the same agent that already has memory and RAG.

First, we add the MCP imports:

import sys
from pathlib import Path

from agents.mcp import MCPServerStdio

These allow the agent application to start and communicate with our MCP server.

Starting the MCP Server

Inside main(), we locate the Python executable from the active virtual environment and the mcp_server.py file:

project_dir = Path(__file__).resolve().parent

python_executable = sys.executable

mcp_server_file = project_dir / "mcp_server.py"

We then start the MCP server:

async with MCPServerStdio(
    name="Project MCP Server",

    params={
        "command": python_executable,
        "args": [
            str(mcp_server_file)
        ],
    },
) as server:

The server remains connected while the agent is running.


The Updated Agent

The most important change is the Agent definition.

Previously, the agent had only the local RAG tool:

agent = Agent(
    name="Memory and RAG Agent",

    tools=[
        search_articles
    ]
)

Now it has both the existing RAG tool and the MCP server:

agent = Agent(

    name="Memory, RAG and MCP Agent",

    instructions="""
You are a helpful AI assistant.

You have access to several capabilities:

1. Long-term user memory
2. Conversation memory through the session
3. An article knowledge base through the
   search_articles tool
4. Tools provided by an MCP server

Use the search_articles tool when the user's
question relates to information in the articles.

Use MCP tools when they are useful for answering
the user's question.

Do not invent information returned by tools.
""",

    tools=[
        search_articles
    ],

    mcp_servers=[
        server
    ]
)

The key addition is:

mcp_servers=[
    server
]

Our existing RAG tool remains unchanged:

tools=[
    search_articles
]

This is important because MCP does not replace RAG.

It adds another capability to the agent.


What Changed in the Code?

The changes can be summarized in four steps.

1. Add MCP imports

We added:

import sys
from pathlib import Path

from agents.mcp import MCPServerStdio

2. Locate the MCP server

We identify the project directory, Python executable, and MCP server:

project_dir = Path(__file__).resolve().parent

python_executable = sys.executable

mcp_server_file = project_dir / "mcp_server.py"

3. Start the MCP server

We use MCPServerStdio to start mcp_server.py automatically:

async with MCPServerStdio(
    name="Project MCP Server",

    params={
        "command": python_executable,
        "args": [
            str(mcp_server_file)
        ],
    },
) as server:

This means the user does not need to start mcp_server.py separately.

4. Give the Agent access to the MCP server

We add:

mcp_servers=[
    server
]

while keeping:

tools=[
    search_articles
]

The result is an agent with both local and MCP capabilities.


The New Architecture

After this change, our application looks like this:

                         AI Agent
                            │
              ┌─────────────┼─────────────┐
              │             │             │
             LLM          Memory          Tools
                            │             │
                    ┌───────┴───────┐     │
                    │               │     │
              Long-term       Conversation
               Memory            Memory
                                          │
                                  ┌───────┴───────┐
                                  │               │
                                 RAG             MCP
                                  │               │
                         search_articles()   MCP Server
                                                  │
                                                  │
                                             hello(name)

This is the key architectural lesson.

Our existing Memory + RAG Agent remains the foundation. MCP is added as another tool-access layer.

The agent can therefore decide which capability is appropriate for a particular request:

Question about an article
        ↓
   RAG tool
        ↓
 search_articles()


Request requiring an MCP capability
        ↓
    MCP tool
        ↓
   MCP Server

This is one of the main advantages of the architecture: different capabilities can be provided through different mechanisms while remaining available to the same AI agent.

For this example, the MCP server provides only hello(name). In a more realistic application, the MCP server could expose tools for databases, APIs, files, Git repositories, business systems, or other external services.

Run agent.py from the VS code terminal:

1. Open the VS Code terminal
In VS Code, choose Terminal → New Terminal.
Make sure you’re in:
(.venv) PS C:\Utvecklingprogram\AI\AI-Agent>

2. Activate the virtual environment

.\.venv\Scripts\Activate.ps1
You should see:

(.venv) PS C:\Utvecklingprogram\AI\AI-Agent>

3. Run the combined agent

python agent.py

Output:

(.venv) PS C:\Utvecklingprogram\AI\AI-Agent> python agent.py
Loading RAG embedding model...
Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
Loading weights: 100%|█████████████████████████████████████████████████| 103/103 [00:00<00:00, 2926.72it/s]
RAG system ready.
RAG: indexed 'article1.txt' (1 chunks)
RAG: indexed 'article2.txt' (1 chunks)
RAG: indexed 'article3.txt' (1 chunks)
RAG: indexed 'article4.txt' (1 chunks)

==============================
Memory + RAG + MCP Agent
==============================

Starting MCP server...
Python: C:\Utvecklingprogram\AI\AI-Agent\.venv\Scripts\python.exe
MCP server: C:\Utvecklingprogram\AI\AI-Agent\mcp_server.py
MCP server connected.

Agent is ready.
Type 'exit' to stop.
Type 'remember:' followed by information to save it.

You: 

MCP in the OpenAI Agents SDK

The OpenAI Agents SDK provides built-in support for MCP servers.

For example, the Python SDK includes MCP server classes for different connection methods. (OpenAI GitHub)

Conceptually, the Agent can be configured like this:

from agents import Agent

agent = Agent(
    name="MCP Assistant",
    instructions="Use the MCP tools when they are useful.",
    mcp_servers=[mcp_server],
)

The important part is:

mcp_servers=[mcp_server]

This tells the Agent that an MCP server is available.

The actual server configuration depends on whether we are using a local or remote MCP server.

A Local MCP Server

One of the simplest approaches for learning MCP is a local server using stdio.

The architecture is:

VS Code
 |
 v
Python Agent
 |
 v
MCP Client
 |
 v
Local MCP Server
 |
 v
Local Resource

The MCP server can expose tools that operate on an authorized set of resources.

For example, a filesystem MCP server could expose tools for working with files.

MCP and the File System

Imagine an MCP server provides these tools:

read_file
write_file
list_files

Our Agent could then potentially perform tasks such as:

List the files in my project.

or:

Read the project documentation.

or:

Create a new text file.

The important point is that the Agent does not need to implement every file operation itself.

The MCP server provides those capabilities.

MCP Tool Discovery

One of the useful aspects of MCP is that an Agent can discover the tools provided by an MCP server.

Conceptually:

Agent
 |
 | "What tools are available?"
 |
 v
MCP Server
 |
 +---- read_file
 |
 +---- list_files
 |
 +---- search_files

The Agent can then use the appropriate tool when needed.

The OpenAI Agents SDK supports MCP tool discovery and also provides mechanisms for filtering which tools are exposed to an Agent. (OpenAI GitHub)

Tool Filtering

This is especially important for security.

Suppose an MCP server exposes:

read_file
write_file
delete_file

Perhaps our Agent only needs to read files.

We can expose only:

read_file

The current Agents SDK supports tool filtering for MCP servers, allowing developers to restrict which tools are exposed to an Agent. (OpenAI GitHub)

This gives us an important security principle:

An Agent should receive only the tools and permissions it actually needs.

MCP and Security

MCP can make Agents much more powerful.

But greater capability also means greater responsibility.

Imagine giving an Agent access to:

Email
Database
File System
Git
Cloud Infrastructure

The Agent could potentially perform significant actions.

Therefore, we need to consider:

  • Authentication
  • Authorization
  • Tool permissions
  • Access control
  • Human approval
  • Sensitive information
  • Logging
  • Monitoring

For example, an Agent might be allowed to:

Read files       ✓
Search documents ✓
Delete files     ✗
Send email       ✗

until a human explicitly approves the more sensitive actions.

MCP and Human Approval

Some actions should require human confirmation.

For example:

User:
Send this email to 5,000 customers.

The Agent may prepare the email, but the application could require a human to approve the action before it is sent.

The workflow becomes:

User
 |
 v
AI Agent
 |
 v
Prepare Action
 |
 v
Human Approval
 |
 +---- No ----> Stop
 |
 +---- Yes ---> Execute Tool

This is an important design pattern for production AI Agents.

MCP vs RAG

MCP and RAG can work together, but they are not the same thing.

RAG focuses on retrieving relevant information from a knowledge source.

Question
   |
   v
Search Knowledge
   |
   v
Relevant Documents
   |
   v
LLM

MCP provides a standardized way for an application to connect to external tools and context.

AI Agent
   |
   v
MCP
   |
   +---- Tool
   +---- Data Source
   +---- Service

An MCP server could even expose a search capability that an Agent uses as part of a broader RAG workflow.

MCP vs Tools

Tools are capabilities that an Agent can call.

MCP provides a standardized protocol for exposing tools and context to AI applications.

So:

Tool
 |
 +-- A capability an Agent can use

MCP
 |
 +-- A standardized way to expose
     tools and context

MCP is therefore not a replacement for tools.

It is a standardized way of connecting applications and Agents to tools and context.

Our Agent Architecture So Far

We have now built our conceptual Agent step by step.

At the beginning:

Agent
 |
 +-- LLM

Then:

Agent
 |
 +-- LLM
 |
 +-- Tools

Then:

Agent
 |
 +-- LLM
 |
 +-- Tools
 |
 +-- Memory

Then:

Agent
 |
 +-- LLM
 |
 +-- Tools
 |
 +-- Memory
 |
 +-- RAG

Now:

Agent
 |
 +-- LLM
 |
 +-- Tools
 |
 +-- Memory
 |
 +-- RAG
 |
 +-- MCP
       |
       +-- External Tools
       +-- External Data
       +-- External Services

This is starting to look like a real-world Agent architecture.

MCP in Enterprise Applications

MCP can be particularly interesting in enterprise environments.

Imagine a company has:

Customer Database
       |
Document System
       |
Git Repository
       |
Internal APIs
       |
Calendar
       |
Ticket System

MCP can provide standardized connections between AI applications and these external capabilities.

A possible architecture is:

                         AI Agent
                            |
                         MCP Client
                            |
          +-----------------+-----------------+
          |                 |                 |
          v                 v                 v
      MCP Server        MCP Server        MCP Server
       Documents         Database          Git
          |                 |                 |
          v                 v                 v
      Documents          Data            Repository

This makes MCP particularly interesting when building larger Agent systems.

What We Have Learned

In this article, we introduced Model Context Protocol (MCP).

We learned that:

  • MCP stands for Model Context Protocol.
  • MCP is an open protocol.
  • MCP standardizes connections between AI applications and external capabilities.
  • An MCP server can expose tools and context.
  • An Agent can connect to MCP servers.
  • MCP servers can be local or remote.
  • MCP can work with tools, memory, and RAG.
  • Tool filtering and permissions are important.
  • Sensitive actions may require human approval.

The basic idea is:

AI Agent
   |
   v
MCP Client
   |
   v
MCP Server
   |
   +---- Tools
   +---- Data
   +---- Services

Conclusion

MCP provides an important building block for modern AI Agent systems.

Instead of developing a completely different integration for every external service, developers can use a standardized protocol for connecting AI applications to tools and context.

When combined with the capabilities we have already explored, our Agent is becoming significantly more powerful:

                         AI Agent
                            |
        +-------------------+-------------------+
        |                   |                   |
        v                   v                   v
       LLM               Memory               Tools
                            |
                            v
                           RAG
                            |
                            v
                           MCP
                            |
              +-------------+-------------+
              |             |             |
              v             v             v
           Data          Services       Tools

We are now moving from a simple AI Agent toward a more complete Agent architecture.

In the next article, we will explore another important capability:

Planning and Reasoning in AI Agents.

We will see how an Agent can break a complex goal into smaller steps, decide what actions are needed, use tools, and work toward a final result.

Planning and Reasoning in AI Agents

← Back to AI Agents – Step-by-Step

← Back to Home Page