Understanding the Model Context Protocol (MCP): A Comprehensive Guide

Subramanya M RaoSubramanya M Rao
14 min read

Introduction

In the rapidly evolving landscape of artificial intelligence, the ability of AI models to understand context and interact with external tools is becoming increasingly crucial. The Model Context Protocol (MCP) has emerged as a revolutionary framework designed to enhance AI language models by providing effective context management. This standardized protocol facilitates efficient communication between AI models and various data sources, raising the capability of these models to interact with the world around them. Whether you're a developer looking to create more powerful AI applications or a business seeking to leverage AI technologies, understanding MCP is essential in today's AI-driven world.

What is MCP ?

The Model Context Protocol (MCP) is a standardized framework designed to bridge the gap between Large Language Models (LLMs) like OpenAI's GPT-4 and Google's Gemini and the external tools, data sources, and functionalities they need to access. At its core, MCP focuses on enhancing the use of AI models by supplying vital contextual information required for decision-making and task execution.

It's important to understand that LLMs fundamentally function as token generators - they create text output based on probabilistic models. They are not autonomous systems that can independently perform actions like searching the web, accessing databases, or sending emails. These capabilities come from the application architecture surrounding the LLMs, not from the models themselves.

MCP serves as a standardized method for connecting these AI models with various data sources and tools, similar to how USB connections standardize how devices communicate with computers. This protocol defines how tools, resources, and other context are exposed to AI models in a consistent format, allowing for seamless integration across different applications.

Why Do We Need MCP ?

The need for MCP stems from several limitations in the current AI ecosystem:

  1. Context Limitations: LLMs have limited context windows and training cutoff dates. Without access to external information, they can't provide accurate responses about current events or specific data outside their training.

  2. Tool Fragmentation: Before MCP, developers had to create custom integrations for each tool they wanted an AI model to use. This led to fragmentation and inefficiency, with each application requiring its own implementation for identical tools.

  3. Standardization Issues: Different AI applications had different ways of exposing tools to language models, making it difficult to create tools that work across multiple AI systems.

  4. Contextual Intelligence: AI models need context to generate accurate and relevant responses. MCP addresses this by providing structured ways to incorporate real-time data inputs.

  5. Development Complexity: Without standardized protocols, developers had to manually code how specific tools should be used, increasing development time and complexity.

What Problems Does MCP Solve?

MCP addresses several critical challenges in the AI development ecosystem:

  1. Efficient Context Management: It simplifies the complexity of managing context for AI models, especially large ones that ordinarily struggle with keeping track of extensive information.

  2. Tool Integration Standardization: By providing a standardized framework for tool exposure, MCP eliminates the need for developers to create custom implementations for each tool across different AI applications.

  3. Enhanced AI Capabilities: With MCP, AI models can access up-to-date information and perform actions that would otherwise be impossible given their inherent limitations.

  4. Interoperability: MCP enables better interoperability between different AI systems and tools, creating a more cohesive ecosystem.

  5. Development Efficiency: Developers can leverage existing MCP servers and SDKs to quickly integrate tools without having to reinvent the wheel for each application.

How Can We Use MCP?

Implementing MCP involves understanding its server-client architecture and how they communicate:

MCP Server-Client Architecture

An MCP architecture consists of:

  1. MCP Servers: These execute requests from clients by offering tools and resources. They can run as local programs accessible via standard input/output or be hosted online, employing server-sent events for real-time communications.

  2. MCP Clients: These are typically AI applications that make requests to MCP servers to access tools and contextual information needed to complete tasks.

Implementation Guidelines

For effective implementation:

  1. Use MCP alongside existing APIs: MCP is not meant to replace existing API standards but to enhance them for AI interaction.

  2. Maintain high abstraction levels: Keep MCP servers at a high abstraction level to protect database integrity while allowing AI to operate effectively.

  3. Modular approach: Instead of making a single MCP server handle everything, adopt a modular approach with multiple servers for different functionalities.

  4. Leverage existing SDKs: Utilize available SDKs designed for MCP to save time and avoid the complexity of manual implementations.

Types of Things MCP Servers Can Expose

MCP servers provide various context primitives, which fall into four main categories:

  1. Tools: Functional utilities for executing tasks. These could be functions that allow an AI to search for information, send messages, update databases, or perform calculations.

  2. Resources: Attachments or data sources required by the AI. These might include documents, images, or other forms of data that provide context for the AI's response.

  3. Sampling: Inter-model queries that allow one AI model to consult with another model for specific inputs or verifications.

  4. Parameterized Prompts: Request templates that structure how the AI should approach certain types of queries, ensuring consistent and appropriate responses.

Of these, tools and resources are the most widely utilized, though sampling and parameterized prompts highlight the evolving capabilities of MCP.

What is Reflection Ability and How Does it Differ from Other Protocols?

One of the standout features of MCP is its support for reflection. This capability allows AI clients to dynamically inquire about the tools and resources available on a server.

Reflection in MCP

Reflection enables clients to:

  • Discover what tools and resources are available on an MCP server

  • Learn about the functionality and parameters of each tool

  • Dynamically adapt to the capabilities of different servers

Comparison with Other Protocols

While MCP shares similarities with existing standards such as REST APIs, GraphQL, and gRPC, its reflection capabilities set it apart:

  1. REST APIs: Typically require detailed documentation for clients to understand available endpoints and parameters. There's no standardized way for clients to automatically discover these.

  2. GraphQL: Allows for some introspection of the schema but lacks the specific tooling orientation that MCP provides for AI model interaction.

  3. gRPC: Offers some reflection capabilities but requires additional code layers to connect with AI models effectively.

MCP's design presents it as a more robust choice for AI operations, as it mandates less overhead to establish communication and functional mapping layers. Its reflection capability is specifically designed with AI clients in mind, allowing for seamless discovery and utilization of tools.

Different Types of Clients for MCP

MCP supports various types of clients:

  1. AI Applications: These are the primary clients, such as ChatGPT, Claude, or custom applications using models like GPT-4 or Gemini.

  2. Development Environments: IDEs and development tools can use MCP to provide AI assistance with access to project resources.

  3. Browser Extensions: Extensions can provide MCP capabilities to web browsing, allowing AI to interact with web content.

  4. Desktop Applications: Standalone applications can integrate with MCP servers to provide AI capabilities with access to local resources

  5. Mobile Applications: AI-powered mobile apps can use MCP to extend their capabilities beyond the constraints of the device.

MCP Server Examples (according to the current syntax for the Model Context Protocol SDK)

Example 1: Weather Lookup MCP Server in TypeScript

Here's a basic example of an MCP server in TypeScript that provides a weather lookup tool:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// Initialize MCP server
const server = new McpServer({ 
  name: "Weather Service", 
  version: "1.0.0"
});

// Define a tool for getting weather data
server.tool("getWeather")
  .parameters(
    z.object({
      city: z.string().describe("The name of the city to get weather for")
    })
  )
  .handler(async ({ city }) => {
    // In a real implementation, this would call a weather API
    // This is a simplified example
    const weatherData = {
      temperature: 72,
      humidity: 65,
      conditions: "Partly Cloudy"
    };

    return [
      { 
        type: 'text', 
        text: `Weather in ${city}: ${weatherData.temperature}°F, ${weatherData.humidity}% humidity, ${weatherData.conditions}.` 
      }
    ];
  });

// Define a resource provider for city information
server.resourceProvider("cityInfo")
  .parameters(
    z.object({
      city: z.string().describe("The name of the city")
    })
  )
  .handler(async ({ city }) => {
    // Simplified example
    return [
      {
        type: 'text',
        text: `Information about ${city}: population, landmarks, etc.`
      }
    ];
  });

// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);

Example 2: MongoDB CRUD Operations MCP Server in TypeScript

Here's an example of an MCP server that allows performing CRUD operations on a MongoDB database:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { MongoClient, ObjectId } from 'mongodb';

// MongoDB connection details
const MONGO_URI = 'mongodb://localhost:27017';
const DB_NAME = 'mcp_example';
const COLLECTION_NAME = 'users';

// Initialize MCP server
const server = new McpServer({
  name: "User Management Service",
  version: "1.0.0"
});

// MongoDB client
let client: MongoClient;
let collection: any;

// Connect to MongoDB
async function connectToMongo() {
  client = new MongoClient(MONGO_URI);
  await client.connect();
  const db = client.db(DB_NAME);
  collection = db.collection(COLLECTION_NAME);
  console.log('Connected to MongoDB');
}

// Define a user interface
interface User {
  _id?: string;
  name: string;
  email: string;
  age: number;
}

// Create user tool
server.tool("createUser")
  .description("Create a new user in the database")
  .parameters(
    z.object({
      name: z.string().describe("User name"),
      email: z.string().describe("User email"),
      age: z.number().describe("User age")
    })
  )
  .handler(async ({ name, email, age }) => {
    try {
      const result = await collection.insertOne({ name, email, age });
      return [
        {
          type: 'text',
          text: JSON.stringify({
            success: true,
            userId: result.insertedId.toString()
          })
        }
      ];
    } catch (error) {
      return [
        {
          type: 'text',
          text: JSON.stringify({
            success: false,
            error: error.message
          })
        }
      ];
    }
  });

// Read user tool
server.tool("getUser")
  .description("Get a user by their ID")
  .parameters(
    z.object({
      userId: z.string().describe("ID of the user to retrieve")
    })
  )
  .handler(async ({ userId }) => {
    try {
      const user = await collection.findOne({ _id: new ObjectId(userId) });
      if (!user) {
        return [
          {
            type: 'text',
            text: JSON.stringify({
              success: false,
              error: 'User not found'
            })
          }
        ];
      }

      return [
        {
          type: 'text',
          text: JSON.stringify({
            success: true,
            user: {
              _id: user._id.toString(),
              name: user.name,
              email: user.email,
              age: user.age
            }
          })
        }
      ];
    } catch (error) {
      return [
        {
          type: 'text',
          text: JSON.stringify({
            success: false,
            error: error.message
          })
        }
      ];
    }
  });

// Update user tool
server.tool("updateUser")
  .description("Update a user by their ID")
  .parameters(
    z.object({
      userId: z.string().describe("ID of the user to update"),
      updates: z.object({
        name: z.string().optional().describe("New name"),
        email: z.string().optional().describe("New email"),
        age: z.number().optional().describe("New age")
      }).describe("Fields to update")
    })
  )
  .handler(async ({ userId, updates }) => {
    try {
      const result = await collection.updateOne(
        { _id: new ObjectId(userId) },
        { $set: updates }
      );

      if (result.matchedCount === 0) {
        return [
          {
            type: 'text',
            text: JSON.stringify({
              success: false,
              error: 'User not found'
            })
          }
        ];
      }

      return [
        {
          type: 'text',
          text: JSON.stringify({
            success: true
          })
        }
      ];
    } catch (error) {
      return [
        {
          type: 'text',
          text: JSON.stringify({
            success: false,
            error: error.message
          })
        }
      ];
    }
  });

// Delete user tool
server.tool("deleteUser")
  .description("Delete a user by their ID")
  .parameters(
    z.object({
      userId: z.string().describe("ID of the user to delete")
    })
  )
  .handler(async ({ userId }) => {
    try {
      const result = await collection.deleteOne({ _id: new ObjectId(userId) });

      if (result.deletedCount === 0) {
        return [
          {
            type: 'text',
            text: JSON.stringify({
              success: false,
              error: 'User not found'
            })
          }
        ];
      }

      return [
        {
          type: 'text',
          text: JSON.stringify({
            success: true
          })
        }
      ];
    } catch (error) {
      return [
        {
          type: 'text',
          text: JSON.stringify({
            success: false,
            error: error.message
          })
        }
      ];
    }
  });

// List users tool
server.tool("listUsers")
  .description("List all users with optional pagination")
  .parameters(
    z.object({
      limit: z.number().optional().describe("Maximum number of users to return"),
      skip: z.number().optional().describe("Number of users to skip")
    })
  )
  .handler(async ({ limit = 10, skip = 0 }) => {
    try {
      const users = await collection.find({})
        .skip(skip)
        .limit(limit)
        .toArray();

      return [
        {
          type: 'text',
          text: JSON.stringify({
            success: true,
            users: users.map(user => ({
              _id: user._id.toString(),
              name: user.name,
              email: user.email,
              age: user.age
            }))
          })
        }
      ];
    } catch (error) {
      return [
        {
          type: 'text',
          text: JSON.stringify({
            success: false,
            error: error.message
          })
        }
      ];
    }
  });

// Initialize and start the server
async function start() {
  try {
    await connectToMongo();
    const transport = new StdioServerTransport();
    await server.connect(transport);
    console.log('MCP server running');
  } catch (err) {
    console.error('Failed to start MCP server:', err);
    if (client) {
      await client.close();
    }
    process.exit(1);
  }
}

start();

This MongoDB MCP server allows an AI model to:

  1. Create new users in the database

  2. Retrieve user information by ID

  3. Update user details

  4. Delete users

  5. List users with pagination

This demonstrates how MCP can be used to provide AI models with structured access to databases without exposing the underlying implementation details or connection information.

If you're interested in the implementation details using TypeScript, you can check out these resources. However, always refer to the official documentation here because the SDK and syntax change frequently.

Building a Single-File MCP Server in TypeScript

Build Your First MCP Server with TypeScript

How to Connect and Try Out MCP

There are several ways to connect to and experiment with MCP:

  1. Using MCP SDKs: Various SDKs are available for different programming languages that simplify the process of creating and connecting to MCP servers.

  2. Building Custom Clients: You can build custom clients that connect to MCP servers using standard protocols like HTTP with server-sent events.

  3. Existing AI Platforms: Some AI platforms already support MCP integration, allowing you to connect your tools to their systems.

Different Ways to Connect to MCP

MCP currently supports two main transport mechanisms for communication:

  1. Standard I/O Transport: In this mode, a client runs the MCP server locally and communicates with it through standard input/output. This is useful for development and testing or when the server needs access to local resources.

  2. Server-Sent Events over HTTP: For remote communication, MCP uses server-sent events through HTTP. This allows clients to connect to remote MCP servers and receive real-time updates.

New Streamable HTTP Transport (Proposed as of 19/03/25)

There's an ongoing evolution in MCP transport mechanisms with a proposed RFC to replace HTTP+SSE with a new "Streamable HTTP" transport (PR #206). This proposal offers several significant benefits:

  • Stateless servers are now possible — eliminating the requirement for high availability long-lived connections

  • Plain HTTP implementation — MCP can be implemented in a plain HTTP server without requiring SSE

  • Infrastructure compatibility — it's "just HTTP," ensuring compatibility with middleware and infrastructure

  • Backwards compatibility — this is an incremental evolution of the current transport

  • Flexible upgrade path — servers can choose to use SSE for streaming responses when needed

Tradeoffs Between SSE and Streamable HTTP

Server - Sent Events ( SSE )Streamable HTTP
Requires long-lived connectionsSupports stateless operation
Better for real-time updatesMore compatible with existing infrastructure
Can be blocked by some proxiesWorks with standard HTTP infrastructure
More complex server implementationSimpler implementation for basic servers
Built-in reconnection handlingRequires custom reconnection logic
Real-time by designCan use streaming when needed

The proposed Streamable HTTP approach offers more flexibility while maintaining compatibility with existing implementations, making MCP more accessible for a wider range of applications and infrastructure setups.

Debugging MCP

Debugging MCP implementations can be approached through several methods:

  1. MCP Inspector: The official MCP Inspector tool is specifically designed for debugging MCP servers. It allows you to:

    • Connect to MCP servers

    • Browse available tools and resources

    • Test tool executions with different parameters

    • Monitor requests and responses

    • View detailed error information

  2. Logging: Implement comprehensive logging in your MCP server to track requests, responses, and any errors that occur.

  3. Development Tools: Use development tools specific to your programming language and environment to debug your MCP server implementation.

  4. Request/Response Inspection: Monitor the raw requests and responses between clients and servers to identify issues.

  5. Testing Frameworks: Create test cases that verify the functionality of your MCP server and tools.

Real-World Use Cases of MCP

  1. Enterprise AI Integration: Companies are using MCP to connect their AI systems with internal tools and databases, allowing for more powerful and context-aware AI assistants.

  2. Software Development: MCP servers are being used to provide AI assistance in development environments, helping developers access documentation, code examples, and project-specific information.

  3. Data Analysis: By connecting AI models to data sources through MCP, analysts can perform more sophisticated data exploration and analysis.

  4. Customer Service: AI chatbots enhanced with MCP can access customer information, order history, and support resources to provide more helpful and personalized assistance.

Organizations like Slack and Google are developing their own MCP servers, leading to an ecosystem of interconnected MCP servers and resources for developers.

A very creative use case I discovered is this:

3D Modeling with Blender: The BlenderMCP open-source project connects Blender to Claude AI through the Model Context Protocol, allowing Claude to directly interact with and control Blender. This integration enables prompt-assisted 3D modeling, scene creation, and manipulation.

Resources for Learning More About MCP

To deepen your understanding of MCP, consider exploring these resources:

  1. PulseMCP Resources:

    • PulseMCP Servers - A collection of available MCP servers that you can use with your AI applications

    • PulseMCP Clients - Information about different MCP clients and how to implement them

    • MCP App Store - A marketplace for MCP applications and tools

  2. Video Tutorials:

  3. Open Source Projects:

    • BlenderMCP - An example of integrating MCP with 3D modeling software
  4. Official Documentation:

  5. Developer Communities:

    • Forums and communities focused on AI integration

    • Discord servers and Slack channels dedicated to MCP development

0
Subscribe to my newsletter

Read articles from Subramanya M Rao directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Subramanya M Rao
Subramanya M Rao

CS Undergrad pursuing Web Development. Keen Learner & Tech Enthusiast.