DEV Community

Sudhakar Punniyakotti
Sudhakar Punniyakotti

Posted on

MCP the API Gateway for AI Agents

AI agents are revolutionising industries by automating complex tasks and enabling efficient workflows. However, the effectiveness of these agents more dependent on data, systems and other agents.

The Model Context Protocol (MCP) is a standardised protocol designed to integrate smooth communication between large language models (LLMs) and services, external systems and tools.

MCP

MCP promotes a modular and scalable architecture, enabling the development of sophisticated reusable AI systems capable of performing complex tasks efficiently.


Understanding AI Agents

Before diving into MCP, it's essential to grasp the foundational concepts of AI Agents:

AI Agents: Systems that coordinate one or multiple LLMs alongside various tools to execute a series of tasks cohesively while retaining context in memory.

Workflow: AI agents follow a structured workflow comprising:

  1. Perception: Data collection.
  2. Planning: Deciding on a plan based on goals.
  3. Execution: Taking actions using tools and external systems.
  4. Monitoring: Handling outcomes and errors.
  5. Learning: Incorporating feedback for improvement.

A significant challenge with AI agents is the necessity to individually create, integrate, and grant access to each tool and functionality. This often results in bespoke solutions tailored to specific use cases, limiting scalability and flexibility.


Current Challenges in AI Agents

Effective communication between AI agents and external systems faces several challenges,

  1. Inconsistent Message Formats: Different agents may use unique communication protocols, making integration difficult.

  2. Scalability Issues: Tight coupling between agents and tools can lead to bottlenecks, reducing scalability in complex tasks.

  3. Collaboration Barriers: The lack of standardised protocols and the duplication of services lead to inefficiencies and monolithic behaviours.


Why MCP is Crucial in AI

The Model Context Protocol addresses the challenges by introducing standardised communication, modularity, scalability, and enhanced collaboration capabilities.

1. Standardised Communication

MCP is an open protocol that standardises how applications provide context to AI models, particularly large language models (LLMs). Its compared to USB-C serves as a universal standard for connecting devices, MCP offers a unified method to connect AI agents and systems to a different services and tools.

2. Modularity and Scalability

Inspired by microservices architecture, MCP enables AI agents to be modular, scalable, and reusable across different systems. Developers can build specialised agents that perform specific tasks independently, ensuring these agents can operate autonomously and integrate seamlessly into broader workflows.

3. Enhanced Collaboration

MCP defines clear ownership and responsibilities for agents, fostering cross-functional teamwork. This clarity is essential for developing complex AI systems where multiple teams work on different project aspects. MCP ensures consistency and integration across the entire system, allowing each team to focus on their specialized modules or services.


How MCP Works

Its a traditional client-server architecture with added host application as user facing. Here's a basic structure:

|- MCP Host[Chat System or Web application]

  |- MCP Client[Remote Client using SSE]
    |- MCP Server A[Database]
    |- MCP Server B[Filesystem]
    |- MCP Server C[External System]

  |-MCP Client[Stdio Client in local]
    |-MCP Server D[API system]
Enter fullscreen mode Exit fullscreen mode

MCP Architecture

The MCP architecture has the following

  1. MCP Hosts: The user application like Chat system, Agents or web application and Programs like Claude Desktop or integrated development environments (IDEs) that access data through MCP.

  2. MCP Clients: Protocol clients maintaining one-to-one connections with servers.

  3. MCP Servers: Dedicated servers that expose specific capabilities / existing systems / files via MCP.

Core Components of MCP

  1. Resources: Resources represent data that MCP servers make available to clients (AI agents). These can include file contents, database records, API responses, live system data, images, and log files. Each resource is identified by a unique URI and can contain either text or binary data.

  2. Tools: Tools are functions that MCP servers expose, allowing clients to invoke them for performing actions. Ranging from simple computations to complex API interactions, MCP supports the discovery, invocation, and management of these tools.

  3. Prompts: Prompts are predefined templates in MCP that accept dynamic arguments, include context from resources, chain multiple interactions, and guide specific workflows. Servers can define reusable prompt templates and workflows that clients use to interact with LLMs efficiently.


Typical Workflow with MCP

  1. Initialisation: Establishing connections and negotiating protocols and capabilities.

  2. Message Exchange: Facilitating tools, resources, and prompt interactions through standardised MCP messages.

  3. Termination: Graceful shutdown and resource cleanup upon completion.

Example Implementation: Maths MCP (Python)

Let's build an MCP system that provides resource, tools and prompt information in the below 10 steps

Setting Up the Environment

uv init maths-mcp
cd maths-mcp
uv venv
source .venv/bin/activate
uv add "mcp[cli]" httpx
Enter fullscreen mode Exit fullscreen mode

Implementing the Server with Stdio

from mcp.server.fastmcp import FastMCP
from pathlib import Path

mcp = FastMCP("Maths MCP Server")

@mcp.resource(
    uri="dir://ubuntu/url/docs",
    name="maths_docs",
    description="List of available documents"
)
def maths_docs() -> list[str]:
    """List the files in the user's desktop"""
    docs = Path.home() / "url/docs"
    return [str(f) for f in docs.iterdir()]

@mcp.tool(
  name="addition",
  description="Add two numbers"
)
async def addition(a: float, b: float) -> float:
    """
    Add two numbers
    """
    response = a + b
    return response

@mcp.tool(
  name="subtraction",
  description="Subtract two numbers"
)
async def subtraction(a: float, b: float) -> float:
    """
    Subtract two numbers
    """
    response = a - b
    return response

@mcp.prompt(
  name="maths_prompt",
  description="Prompt for maths use cases"
)
def maths_prompt(message: str) -> str:
    prompt = "Enter two numbers to perform an operation"
    return prompt

if __name__ == "__main__":
  mcp.run()
Enter fullscreen mode Exit fullscreen mode

Explanation

Two tools:

  1. addition: Provides addition tool
  2. subtraction: Provides subtraction tool

One resource:

  1. maths_docs: Provides maths docs url

One prompt:

  1. maths_prompt: Provides maths prompt

Example client code

Let's explore how a simple MCP client in Python can interact with the MCP server using stdio

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

# Create server parameters for stdio connection
server_params = StdioServerParameters(
    command="python",  # Executable
    args=["post/stdio_server.py"],  # Command line arguments
    env=None,  # Optional environment variables
)

async def run():
  async with stdio_client(server_params) as (read, write):
    async with ClientSession(
      read, write) as session:
      # Initialize the connection
      await session.initialize()

      # List available prompts
      prompts = await session.list_prompts()
      print("\n\n\nAvailable prompts:", prompts)

      # List available resources
      resources = await session.list_resources()
      print("\n\n\nAvailable resources:", resources)

      # List available tools
      tools = await session.list_tools()
      print("\n\n\nAvailable tools:", tools)

if __name__ == "__main__":
    import asyncio
    asyncio.run(run())
Enter fullscreen mode Exit fullscreen mode

Output

>python post/studio_client.py

Available prompts: meta=None nextCursor=None prompts=[Prompt(name='maths_prompt', description='Prompt for maths use cases', arguments=[PromptArgument(name='message', description=None, required=True)])]


Available resources: meta=None nextCursor=None resources=[Resource(uri=AnyUrl('dir://ubuntu/url/docs'), name='maths_docs', description='List of available documents', mimeType='text/plain', size=None, annotations=None)]


Available tools: meta=None nextCursor=None tools=[Tool(name='addition', description='Add two numbers', inputSchema={'properties': {'a': {'title': 'A', 'type': 'number'}, 'b': {'title': 'B', 'type': 'number'}}, 'required': ['a', 'b'], 'title': 'additionArguments', 'type': 'object'}), Tool(name='subtraction', description='Subtract two numbers', inputSchema={'properties': {'a': {'title': 'A', 'type': 'number'}, 'b': {'title': 'B', 'type': 'number'}}, 'required': ['a', 'b'], 'title': 'subtractionArguments', 'type': 'object'})]
Enter fullscreen mode Exit fullscreen mode

Explanation

In this client example:

  1. Initialisation: The client initialises the connection with the server by ClientSession.
  2. Listing Tools: It retrieves a list of available tools that the server exposes.
  3. Listing Resources: It retrieves a list of available resources that the server exposes.
  4. Listing Prompts: It retrieves a list of available prompts that the server exposes.

This interaction demonstrates how MCP ensures that clients can dynamically discover and utilise the capabilities provided by servers, making AI agents both flexible and powerful.


SSE Server Code

On the same folder create sse_server.py and import the studio server

from starlette.applications import Starlette
from mcp.server.sse import SseServerTransport
from mcp.server import Server
from starlette.requests import Request
from starlette.routing import Mount, Route
import uvicorn
from stdio_server import mcp

def create_starlette_app(mcp_server: Server, *, debug: bool = False) -> Starlette:
  """Create a Starlette application that can server the provied mcp server with SSE."""
  sse = SseServerTransport("/messages/")

  async def handle_sse(request: Request) -> None:
    async with sse.connect_sse(request.scope, request.receive, request._send,
    ) as (read_stream, write_stream):
      await mcp_server.run(read_stream, write_stream, mcp_server.create_initialization_options(),)

  return Starlette(
    debug=debug,
    routes=[
      Route("/sse", endpoint=handle_sse),
      Mount("/messages/", app=sse.handle_post_message),
    ],
  )

if __name__ == "__main__":
  mcp_server = mcp._mcp_server
  starlette_app = create_starlette_app(mcp_server, debug=True)

  uvicorn.run(starlette_app, host="0.0.0.0", port=8000)
Enter fullscreen mode Exit fullscreen mode

Explanation

In this sse server example:

The stdio_server is imported and using Starlette the endpoints /sse and /messages are exposed. Now run the server over http

Execution

post/sse_server.py
INFO:     Started server process [7553]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

Enter fullscreen mode Exit fullscreen mode

So now the MCP server is exposed in port 8000, this can be proxied by Apache or Nginx but in this example lets consider its in same host.

SSE Client Code

from mcp.client.session import ClientSession
from mcp.client.sse import sse_client
import asyncio

async def client_logic():
  async with sse_client(url="http://0.0.0.0:8000/sse") as (read, write):
    async with ClientSession(read, write) as session:
      await session.initialize()
      # List available tools
      tools = await session.list_tools()
      print("\n\n\nAvailable tools:", tools)

      resources = await session.list_resources()
      print("\n\n\nAvailable resources:", resources)

      prompts = await session.list_prompts()
      print("\n\n\nAvailable prompts:", prompts)

def main():
  asyncio.run(client_logic())

if __name__ == "__main__":
  main()
Enter fullscreen mode Exit fullscreen mode

Output

>python post/sse_client.py

Available tools: meta=None nextCursor=None tools=[Tool(name='addition', description='Add two numbers', inputSchema={'properties': {'a': {'title': 'A', 'type': 'number'}, 'b': {'title': 'B', 'type': 'number'}}, 'required': ['a', 'b'], 'title': 'additionArguments', 'type': 'object'}), Tool(name='subtraction', description='Subtract two numbers', inputSchema={'properties': {'a': {'title': 'A', 'type': 'number'}, 'b': {'title': 'B', 'type': 'number'}}, 'required': ['a', 'b'], 'title': 'subtractionArguments', 'type': 'object'})]

Available resources: meta=None nextCursor=None resources=[Resource(uri=AnyUrl('dir://ubuntu/url/docs'), name='maths_docs', description='List of available documents', mimeType='text/plain', size=None, annotations=None)]

Available prompts: meta=None nextCursor=None prompts=[Prompt(name='maths_prompt', description='Prompt for maths use cases', arguments=[PromptArgument(name='message', description=None, required=True)])]
Enter fullscreen mode Exit fullscreen mode

Summary

The Model Context Protocol (MCP) represents a possible solution to improve AI integrations, scaling by providing a standardised framework that ensures seamless connections between AI Agents, tools and systems.

MCP enhancing modularity, scalability, and collaboration, MCP is essential for building reusable and efficient AI systems.


References

Top comments (0)