Blogs

MCP - Comprehensive Guide

Posted by JCharis AI

April 18, 2025, 11:09 a.m.


MCP - Comprehensive Guide

Important Clarification: The term "Model Context Protocol" (MCP) isn't a universally standardized, formally defined protocol like HTTP or TCP/IP that you'd find an RFC for. Instead, it's a conceptual framework or a design pattern for how applications should structure, manage, and pass contextual information to and from AI/ML models, especially in stateful or multi-turn interactions.

Think of it as a set of best practices and conventions for handling the "memory" or "awareness" of an AI model within an application.

This tutorial will define what we mean by MCP in this practical sense and then show you how to implement these concepts in Python.

Goal: To understand and implement a robust way to manage context for AI models in Python applications.

Table of Contents:

  1. What is "Model Context Protocol" (Conceptually)?
  2. Why is MCP Important?
  3. Core Components of an MCP
  4. Designing Your MCP: Key Considerations
  5. Python Implementation:
    • Basic Example: Simple Chatbot
    • Advanced Example: Chatbot with Tool Use & Retrieval Augmented Generation (RAG)
  6. Serializing and Deserializing Context
  7. Best Practices for MCP
  8. When to Evolve Your MCP
  9. Conclusion

1. What is "Model Context Protocol" (Conceptually)?

At its heart, an MCP is an agreement or a defined structure for:

  • What information (context) a model needs: This goes beyond the immediate user input. It can include conversation history, user preferences, session data, system state, retrieved documents, tool outputs, etc.
  • How this information is formatted: Often using structured data like JSON, dictionaries, or custom objects.
  • How this information is passed to the model: As part of the input payload.
  • How the model's output might update or contribute to the context: For subsequent interactions.
  • How the context is managed across turns or sessions: Persistence, truncation, summarization.

It's the "contract" between your application logic and your AI model regarding the information flow necessary for coherent, relevant, and effective model responses.


2. Why is MCP Important?

  • Statefulness: Enables models to "remember" past interactions, leading to more natural and coherent conversations or processes.
  • Personalization: Allows models to tailor responses based on user profiles, preferences, or history.
  • Efficiency: Prevents re-computation or re-fetching of information the model already "knows" or has access to via the context.
  • Complex Task Execution: Facilitates multi-step processes where the model needs to track progress and intermediate results (e.g., using tools, RAG).
  • Debugging & Observability: A well-defined context makes it easier to understand why a model behaved a certain way.
  • Modularity: Separates context management from the core model logic, making it easier to swap models or update application logic.

3. Core Components of an MCP

An MCP typically revolves around a "context object" or "context payload." This payload might include:

  1. Session Identifiers:

    • session_id: Unique ID for the current interaction session.
    • user_id: Unique ID for the user.
  2. Interaction History:

    • history: A list of previous user inputs and model responses, often with roles (e.g., {"role": "user", "content": "Hello"}). Essential for chatbots.
  3. User Profile & Preferences:

    • user_profile: { namelanguagepreferences, etc. }
  4. System State / Instructions:

    • system_prompt / persona: Instructions on how the model should behave.
    • current_time: If time-sensitive information is needed.
    • available_tools: List of tools the model can request to use.
  5. Task-Specific Data:

    • retrieved_documents (for RAG): Content fetched from a knowledge base.
    • tool_calls: Requests made by the model to use external tools.
    • tool_responses: Results from executed tools.
    • current_state: For state machines or multi-step workflows.
  6. Metadata:

    • context_version: Version of your MCP schema (useful for evolution).
    • request_id: For tracing.

4. Designing Your MCP: Key Considerations

  • Model Capabilities: What context can your chosen model effectively use? (e.g., LLMs are good with text history, some models might need structured data).
  • Application Needs: What information is crucial for your application's functionality?
  • Context Size Limits: Models have input token limits. Your MCP needs strategies for managing context size (truncation, summarization).
  • Data Structure:
    • Dictionaries/JSON: Flexible, easy to serialize, human-readable. Good starting point.
    • Typed Dictionaries (Python 3.8+ typing.TypedDict) / Pydantic Models: Offer type safety and validation, excellent for robustness.
    • Custom Classes: Provide encapsulation and methods for context manipulation.
  • Immutability vs. Mutability: Should parts of the context be immutable once set? How are updates handled? (Often, a new context object is created or a copy is modified).
  • Security & Privacy: Be mindful of PII. Redact or anonymize sensitive data before including it in the context if it's not strictly necessary for the model.
  • Versioning: As your application evolves, your context structure might need to change. Versioning helps manage backward/forward compatibility.

5. When to Evolve Your MCP

  • When new features require additional information for the model.
  • When you switch to a model with different input requirements or capabilities.
  • When you identify inefficiencies or limitations in the current context structure.
  • When performance issues arise due to large context sizes, requiring new management strategies.
  • When security or compliance requirements change.

Always manage these changes with versioning and, if necessary, migration strategies for existing stored contexts.

What is MCP?

MCP (Model Context Protocol) is an open, standardized protocol—originally developed by Anthropic—for enabling AI models (like Claude) to interact dynamically with real-world tools, APIs, and databases. MCP offers:

  • A universal interface for LLM-to-tool connections
  • A client-server architecture with clear role separation
  • Support for external tool invocation, data/resource access, and prompt templates
  • Multiple transport layers: stdio, WebSockets, HTTP SSE, UNIX sockets
  • Security, modularity, and scalability for AI app development[1][4][5][10]

MCP Architecture Overview

ComponentDescription
HostApplication where the user interacts (e.g., Claude Desktop, IDE)
ClientConnector managing a single connection to an MCP server
ServerExposes tools/resources/prompts for the AI model to use

Key Concepts:

  • Tools: Functions LLMs can call to perform actions (e.g., weather API, GitHub fetch)
  • Resources: Read-only data sources exposed for context (akin to GET endpoints)
  • Prompts: Predefined templates for structured interaction[5][10]

Installation and Setup

1. Create a Project

python -m venv mcpenv
source mcpenv/bin/activate
pip install "mcp[cli]" requests python-dotenv

Or use uv for dependency management:

uv init mcp-demo
uv add "mcp[cli]" requests python-dotenv

[1][4][9]

2. Directory Structure

mcp-demo/
│
├── server.py       # MCP Server exposing tools/resources
├── client.py       # MCP Client connecting to server
├── .env            # Secrets and API keys (for example integrations)

MCP Server: Exposing a Tool (Python Example)

Example 1: Hello World MCP Server

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

mcp = FastMCP("HelloWorld MCP Server")

@mcp.tool()
def say_hello(name: str) -> str:
    """Say hello to the user"""
    return f"Hello, {name}! Welcome to MCP."

if __name__ == "__main__":
    mcp.serve_stdio()
  • This exposes a tool called say_hello that takes a name and returns a greeting.
  • FastMCP is the main server abstraction from the MCP Python SDK

MCP Client: Calling a Tool (Python Example)

Example 2: MCP Client for Calling Server Tools

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

async def main():
    # Define how to start the server (as a subprocess)
    server_params = StdioServerParameters(
        command="python",
        args=["server.py"]
    )
    # Connect to the server
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()
            print("Available Tools:", [t.name for t in tools.tools])
            # Call the say_hello tool
            result = await session.call_tool("say_hello", {"name": "MCP User"})
            print("Result from say_hello:", result.content.text)

if __name__ == "__main__":
    asyncio.run(main())
  • This client starts the server (as a subprocess), connects via stdio, lists available tools, and invokes the say_hello tool with an argument.
  • Output: Result from say_hello: Hello, MCP User! Welcome to MCP.[2][4][9]

Extending MCP: Real-World Integrations

Integrate with GitHub and Notion

You can build an MCP server that exposes tools for:

  • Fetching Pull Request details from GitHub
  • Saving reviews to Notion

Install additional dependencies:

pip install notion-client

Set up .env with your API keys:

GITHUB_TOKEN=your_github_token
NOTION_API_KEY=your_notion_api_key
NOTION_PAGE_ID=your_notion_page_id


Sample: Exposing a GitHub Tool

import requests
import os
from mcp.server.fastmcp import FastMCP
from dotenv import load_dotenv

load_dotenv()
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")

mcp = FastMCP("GitHub MCP Server")

@mcp.tool()
def get_pr_titles(repo: str) -> str:
    """Fetches PR titles from a GitHub repo."""
    headers = {'Authorization': f'token {GITHUB_TOKEN}'}
    response = requests.get(f"https://api.github.com/repos/{repo}/pulls", headers=headers)
    if response.status_code != 200:
        return f"Failed to fetch PRs: {response.status_code}"
    data = response.json()
    return "\n".join([pr["title"] for pr in data])

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

Using Resources and Prompts

Resources are like context providers:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Resource Example")

@mcp.resource("config://app")
def get_config() -> str:
    return "App config version 1.0"

@mcp.resource("users://{user_id}/profile")
def get_user_profile(user_id: str) -> str:
    return f"Profile for user {user_id}"

[4][5]


Client: Advanced Operations

  • Listing all resources:
    resources = await session.list_resources()
    print([r.resource_id for r in resources.resources])
    
  • Reading a resource:
    content, mime_type = await session.read_resource("users://123/profile")
    print(content)
    
  • Listing prompts:
    prompts = await session.list_prompts()
    print([p.name for p in prompts.prompts])

9. Conclusion

The "Model Context Protocol" is less about a rigid specification and more about a disciplined approach to managing the information flow surrounding your AI models. By thoughtfully designing how context is structured, passed, and updated, you can build more intelligent, coherent, and powerful AI applications.

Python, with its flexible data structures and excellent libraries like Pydantic, provides a fantastic environment for implementing robust MCPs. Start with simple dictionaries or Pydantic models, and let your application's needs guide the evolution of your context management strategy. Remember that a well-designed context is key to unlocking the full potential of your AI models.


By JCharisAI

  • No tags associated with this blog post.

NLP Analysis
  • Sentiment: positive
  • Subjectivity: positive
  • Emotions: joy
  • Probability: {'anger': 1.5605883754463902e-227, 'disgust': 0.0, 'fear': 1.78353935858717e-226, 'joy': 1.0, 'neutral': 0.0, 'sadness': 0.0, 'shame': 0.0, 'surprise': 2.9305729863767305e-252}
Comments
insert_chart