Mastering MCP - Model Context Protocol: A Guide for AI Agent Developers
Welcome to the cutting edge of AI agent development! This document will guide you through the intricacies of the Model Context Protocol (MCP), a revolutionary open standard that allows AI agents to interact with external systems, tools, and data in a standardized, secure, and highly effective manner. By the end of this guide, you will be equipped to design, build, and deploy your own MCP servers and integrate them with popular AI tools like Ollama and development environments like Visual Studio Code.
1. Introduction to MCP - Model Context Protocol
What is MCP?
The Model Context Protocol (MCP) is an open standard, first introduced by Anthropic in November 2024, that defines a standardized way for Large Language Models (LLMs) and AI applications (referred to as “hosts”) to interact with external tools and data sources (provided by “MCP servers”). Think of MCP as a “universal adapter” or a “USB-C port for AI applications.” Just as USB-C allows various devices to connect and communicate seamlessly, MCP enables AI models to:
- Access real-time data: Overcome the limitation of LLMs having knowledge “frozen” at their training time.
- Perform actions: Go beyond generating text to actively interact with the digital world, such as querying databases, sending emails, or controlling other software.
- Maintain context: Share conversation history, user preferences, and other relevant information across different tools and sessions.
Before MCP, integrating AI models with external systems often involved custom, brittle integrations for each tool, leading to an “M×N integration problem” where N LLMs needed to connect to M tools. MCP solves this by providing a unified, standardized protocol for these interactions.
Why Learn MCP? (Benefits, Use Cases, Industry Relevance)
Learning MCP in 2025 is crucial for anyone looking to build advanced, capable AI agents. Here’s why:
- Unlock Agentic AI: MCP is foundational for true “agentic AI,” allowing models to autonomously manage multi-step tasks and proactively utilize external tools without constant human supervision.
- Standardized Integration: It provides a consistent framework for AI-tool integration, significantly reducing development effort and increasing reliability compared to custom API integrations. This means a tool provider implements an MCP server once, and any MCP-compatible AI client can use it.
- Enhanced Capabilities: AI agents powered by MCP can perform a wider range of complex tasks, from automating code-related workflows (e.g., GitHub Copilot checking issues, running tests) to managing personal productivity tools (Notion, Linear, Asana), and even engaging in multi-agent coordination for complex problem-solving.
- Reduced Hallucinations: By providing LLMs with direct, real-time access to authoritative external data sources, MCP helps to mitigate the problem of AI “hallucinations” (generating plausible but incorrect information).
- Improved Security and Control: The protocol emphasizes user consent, granular control over data access, and secure authentication methods (like OAuth), making it suitable for enterprise applications dealing with sensitive information.
- Growing Ecosystem: Major players like Anthropic, OpenAI, Google, and Microsoft (with Visual Studio Code and GitHub Copilot) are adopting and contributing to the MCP ecosystem, indicating its strong future trajectory as an industry standard.
A Brief History
- November 2024: Anthropic open-sources the Model Context Protocol, introducing the specification and SDKs, local MCP server support in Claude Desktop, and an open-source repository of MCP servers.
- Early 2025: Rapid adoption by various development tools, platforms (like Vercel, Cloudflare), and AI companies begins.
- March 2025: Significant updates to the MCP specification are released, including richer tool descriptors and the introduction of “Streamable HTTP” as a new transport mechanism.
- May 2025: VS Code announces early preview support for MCP in its agent mode, enhancing GitHub Copilot’s capabilities.
- June 2025: VS Code announces full support for the complete MCP specification, including authorization, prompts, resources, and sampling. MongoDB launches its MCP Server in public preview. The MCP C# SDK is updated to support the latest specification, including improved authentication, elicitation, and structured tool output.
- August 2025: MCP support becomes generally available (GA) in Visual Studio, further solidifying its role in professional development workflows.
Setting Up Your Development Environment
To begin building MCP servers and integrating them with AI agents, you’ll need a few essential tools. We’ll focus on a Python-based setup, which is currently a popular choice for MCP server development.
Prerequisites:
- Python 3.10 or higher: Ensure you have a recent version of Python installed. You can download it from python.org.
uv(optional, but recommended): A fast, Rust-based Python package manager. It can significantly speed up dependency management.- macOS (Homebrew):
brew install uv - Windows (Winget):
winget install --id=astral-sh.uv -e - Other platforms: Refer to the uv documentation.
- macOS (Homebrew):
- Code Editor: Visual Studio Code (VS Code) is highly recommended due to its excellent MCP integration and Copilot support.
- Download VS Code from code.visualstudio.com.
- Ollama (for local LLMs): An open-source tool to run large language models locally.
- Download from ollama.com.
Initial Setup Steps (Python Project):
- Create a Project Directory:
mkdir my-mcp-agent cd my-mcp-agent - Initialize
uvProject and Virtual Environment:This creates an isolated environment for your project dependencies.uv init uv venv source .venv/bin/activate # On Windows, use `.venv\Scripts\activate` - Install MCP Python SDK:The
uv add "mcp[cli]" httpxmcp[cli]package provides the SDK for building MCP servers and clients, along with command-line tools.httpxis a modern HTTP client often used for making web requests in asynchronous Python applications.
2. Core Concepts and Fundamentals of MCP
The Model Context Protocol operates on a client-host-server architecture, standardizing communication through specific primitives.
MCP Architecture Overview
- Host Application: This is the AI application or environment where the LLM resides and interacts with the user. Examples include the Claude Desktop app, AI-powered IDEs (like VS Code with GitHub Copilot), or custom chat interfaces. The host manages client instances and enforces policies.
- MCP Client: Embedded within the host, the client is responsible for establishing and managing the communication link with MCP servers. It translates the LLM’s requests into MCP messages and vice-versa, and handles tool discovery. Each client typically has a 1:1 relationship with a specific server.
- MCP Server: An external service that provides context, data, or capabilities to the LLM. Servers expose their functionalities through MCP “primitives.” They can be local processes or remote services.
- Transport Layer: The mechanism for communication between the client and server. MCP primarily uses JSON-RPC 2.0 messages. Common transports include:
- Standard Input/Output (stdio): Ideal for local development, where the server runs as a subprocess and communicates via stdin/stdout.
- Streamable HTTP (HTTP): Recommended for remote resources, supporting efficient, real-time data streaming over a single HTTP connection. This replaced earlier HTTP + Server-Sent Events (SSE) models.
MCP Primitives: Tools, Resources, and Prompts
MCP servers expose their functionalities through three core primitives:
2.1. Tools: Actions Your AI Can Take
Detailed Explanation: Tools are executable functions or actions that an AI model can invoke through an MCP server. These allow AI agents to perform tasks in external systems, going beyond just generating text. Each tool has a clear name, a description that helps the LLM understand its purpose, and an input schema defining the parameters it expects.
Code Example (Python FastMCP for a Weather Tool):
Let’s create a simple MCP server that provides a tool to fetch current weather data using the Open-Meteo API.
First, create a file named weather_server.py:
import json
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
import asyncio
# Initialize FastMCP server
mcp = FastMCP("weather-app")
# Constants for the Open-Meteo API
OPENMETEO_API_BASE = "https://api.open-meteo.com/v1"
USER_AGENT = "mcp-weather-agent/1.0"
# Helper function to make an asynchronous HTTP request
async def make_openmeteo_request(url: str) -> dict[str, Any] | None:
"""Make an asynchronous request to the Open-Meteo API with error handling."""
headers = {
"User-Agent": USER_AGENT,
"Accept": "application/json"
}
async with httpx.AsyncClient() as client:
try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status() # Raise an exception for HTTP errors (4xx or 5xx)
return response.json()
except httpx.HTTPStatusError as e:
print(f"HTTP error occurred: {e}")
return None
except httpx.RequestError as e:
print(f"An error occurred while requesting {e.request.url!r}: {e}")
return None
except Exception as e:
print(f"An unexpected error occurred: {e}")
return None
@mcp.tool()
async def get_current_weather(latitude: float, longitude: float) -> str:
"""
Get current weather for a specified location.
Args:
latitude: Latitude of the location (e.g., 52.52)
longitude: Longitude of the location (e.g., 13.41)
Returns:
A JSON string containing current weather data, or an error message.
"""
url = (
f"{OPENMETEO_API_BASE}/forecast?"
f"latitude={latitude}&longitude={longitude}&"
"current=temperature_2m,is_day,showers,cloud_cover,wind_speed_10m,wind_direction_10m,pressure_msl,snowfall,precipitation,relative_humidity_2m,apparent_temperature,rain,weather_code,surface_pressure,wind_gusts_10m"
)
data = await make_openmeteo_request(url)
if not data:
return json.dumps({"error": "Unable to fetch current weather data for this location."})
return json.dumps(data)
if __name__ == "__main__":
# Run the server using stdio transport for local development
# In a production setup, you might use HTTP transport for remote access
mcp.run(transport='stdio')
Explanation:
FastMCP("weather-app"): Initializes an MCP server instance named “weather-app.”@mcp.tool(): This decorator registers theget_current_weatherfunction as an MCP tool. The function’s name and its docstring are used by the MCP client to describe the tool to the LLM.- Arguments and Return Type Hinting: The type hints (
latitude: float,longitude: float,-> str) are crucial. MCP uses these to generate the tool’s input schema (JSON Schema), allowing the LLM to understand what parameters it needs to provide. The return type is typically astr(often a JSON string) for structured output. - Asynchronous Operations: The
asyncandawaitkeywords are used because MCP server handlers are often asynchronous, especially when performing I/O-bound operations like network requests.
Exercises/Mini-Challenges:
- Add a Forecast Tool: Extend
weather_server.pyto include a tool calledget_hourly_forecast(latitude: float, longitude: float, days: int)that fetches a multi-day hourly forecast. You’ll need to consult the Open-Meteo API documentation for the correct endpoint and parameters. - Error Handling for Specific Locations: Modify the
get_current_weathertool to provide more specific error messages if, for example, thelatitudeorlongitudevalues are out of a reasonable range (though the Open-Meteo API handles a lot of this already).
2.2. Resources: Context Your AI Can Access (Read-Only Data)
Detailed Explanation: Resources represent read-only data or content that an MCP server can provide to clients. Unlike tools, which perform actions, resources offer static or dynamically fetched data for the LLM to consume as context. They are often identified by a URI-based scheme. This allows AI agents to access relevant documents, code snippets, database records, or other forms of information.
Code Example (Python FastMCP for a Simple File Resource):
Let’s add a resource to our weather_server.py that lists available weather phenomena definitions.
# ... (previous code for imports, FastMCP initialization, constants) ...
import os
WEATHER_PHENOMENA = {
"0": "Clear sky",
"1": "Mainly clear",
"2": "Partly cloudy",
"3": "Overcast",
"45": "Fog",
"48": "Depositing rime fog",
"51": "Drizzle: Light",
"53": "Drizzle: Moderate",
"55": "Drizzle: Dense intensity",
"56": "Freezing Drizzle: Light",
"57": "Freezing Drizzle: Dense intensity",
"61": "Rain: Slight",
"63": "Rain: Moderate",
"65": "Rain: Heavy intensity",
"66": "Freezing Rain: Light",
"67": "Freezing Rain: Heavy intensity",
"71": "Snow fall: Slight",
"73": "Snow fall: Moderate",
"75": "Snow fall: Heavy intensity",
"77": "Snow grains",
"80": "Rain showers: Slight",
"81": "Rain showers: Moderate",
"82": "Rain showers: Violent",
"85": "Snow showers: Slight",
"86": "Snow showers: Heavy",
"95": "Thunderstorm: Slight or moderate",
"96": "Thunderstorm with slight hail",
"99": "Thunderstorm with heavy hail"
}
@mcp.resource("weather-app://phenomena-definitions")
def get_phenomena_definitions() -> str:
"""
Provides a list of weather phenomena codes and their descriptions.
This resource gives the AI agent context about different weather conditions.
"""
return json.dumps(WEATHER_PHENOMENA, indent=2)
# ... (previous code for get_current_weather tool) ...
if __name__ == "__main__":
mcp.run(transport='stdio')
Explanation:
@mcp.resource("weather-app://phenomena-definitions"): This decorator registers theget_phenomena_definitionsfunction as an MCP resource with a URI. This URI allows clients to request this specific piece of context.- URI Naming: Resource URIs often follow a scheme (e.g.,
protocol://identifier/path) to uniquely identify the data. - Data Format: The resource returns a JSON string, which is a common and easily parsable format for LLMs.
Exercises/Mini-Challenges:
- Dynamic Resource: Imagine you have a directory of local
.txtfiles containing weather historical data. Create a new resourceweather-app://historical-data/{filename}that reads the content of a specified file. Implement error handling for non-existent files. - Resource with Parameters: Create a resource
weather-app://city-info/{city_name}that, given a city name, returns basic information about that city (e.g., its general latitude and longitude, perhaps hardcoded for a few major cities).
2.3. Prompts: Reusable Guidance for Users (Templates)
Detailed Explanation: Prompts are pre-defined templates or guidelines that an MCP server can offer to an MCP client. These are not direct actions or raw data but structured messages that can be triggered by users (often via slash commands in chat interfaces) to guide the LLM’s behavior or encapsulate common workflows. They help reduce the burden of “prompt engineering” for end-users by providing optimized starting points.
Code Example (Python FastMCP for a Weather Summary Prompt):
Let’s add a prompt template to weather_server.py that helps users get a concise weather summary.
# ... (previous code for imports, FastMCP initialization, constants, weather phenomena, get_current_weather, get_phenomena_definitions) ...
@mcp.prompt()
async def summarize_weather_for_location(location: str) -> dict:
"""
Generate a comprehensive weather summary for a given location, including current conditions and forecast.
Args:
location: The city or region for which to generate the weather summary.
"""
# This is a simplified example. In a real scenario, this prompt would involve
# calling the get_current_weather tool, potentially the forecast tool, and then
# orchestrating the LLM to synthesize the information into a summary.
return {
"messages": [
{"role": "system", "content": "You are a helpful weather assistant. Use the available tools to provide a concise and informative weather summary."},
{"role": "user", "content": f"Please provide a detailed weather summary for {location}, including current temperature, conditions, and a brief outlook."},
{"role": "user", "content": f"Remember to use the 'weather-app.get_current_weather' tool to fetch live data for {location}."}
]
}
if __name__ == "__main__":
mcp.run(transport='stdio')
Explanation:
@mcp.prompt(): This decorator registerssummarize_weather_for_locationas an MCP prompt.- Prompt as a Template: The function returns a dictionary that can include a series of messages for the LLM. This allows the server to dictate a multi-turn conversation or specific instructions.
- Dynamic Arguments: The
locationargument allows the user to specify the target of the prompt. - Implicit Tool Invocation: While this example directly generates messages, in a more advanced scenario, the prompt could implicitly trigger the
get_current_weathertool and then process its output to form the final summary.
Exercises/Mini-Challenges:
- Interactive Prompt: Create a prompt that asks the user for a city and then explicitly instructs the AI to use the
get_current_weathertool with the provided city’s coordinates (you’d need a way to get coordinates from the city name, perhaps a simple hardcoded mapping or another tool). - Prompt with Resource Integration: Design a prompt that asks the user about a weather phenomenon and then leverages the
weather-app://phenomena-definitionsresource to provide a detailed explanation of that phenomenon.
3. Intermediate Topics
Moving beyond the basic primitives, let’s explore more advanced aspects of MCP.
3.1. Authentication and Authorization
Detailed Explanation: Security is paramount for AI agents interacting with external systems. MCP includes robust mechanisms for authentication and authorization.
- Authentication: Verifying the identity of the client connecting to the server. MCP often delegates this to existing identity providers (IdPs) like OAuth 2.0 and OpenID Connect.
- Authorization: Determining what actions an authenticated client is allowed to perform on the server. This can involve scopes (e.g.,
read-only,read-write) and granular permissions for specific tools or resources. - Secure Secret Management: Best practices dictate never hardcoding API keys or sensitive credentials directly into configuration files. Instead, use input variables or environment variables that are securely managed by the host application.
Example (Conceptual mcp.json for a remote server with OAuth):
When configuring an MCP server in an environment like VS Code, you might see configurations that handle OAuth.
{
"servers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"authentication": {
"type": "oauth",
"provider": "github" // Or other configured OAuth provider
}
}
}
}
For local servers, environment variables are typically used:
{
"inputs": [
{
"type": "promptString",
"id": "github_token",
"description": "GitHub Personal Access Token",
"password": true
}
],
"servers": {
"github-local": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${input:github_token}"
}
}
}
}
Explanation:
"inputs"section: Defines variables that the host application should prompt the user for (e.g.,github_token).password: trueensures sensitive input is handled securely."env"section: Maps the input variablegithub_tokento an environment variableGITHUB_PERSONAL_ACCESS_TOKENthat the Docker container (running the GitHub MCP server) will use.
Exercises/Mini-Challenges:
- Simulated Authorization: Modify your
weather_server.pyto include a simple authorization check. For example, if a specific environment variableMCP_AUTH_KEYis not set or doesn’t match a hardcoded value, theget_current_weathertool should return an “Unauthorized” error. - Read-Only Mode Concept: How would you design a tool (
delete_file) and then implement a “read-only” flag in your MCP server that prevents this tool from being executed if the flag is set? (No need for full implementation, just describe the logic).
3.2. Sampling (Server-Initiated LLM Interactions)
Detailed Explanation: Sampling is an advanced MCP feature (often in preview) that allows an MCP server to request completions from the client’s LLM. This means the server, instead of directly processing complex natural language or needing its own LLM access, can delegate language model tasks to the client’s AI. This is incredibly powerful for:
- Complex Reasoning: Enabling multi-agent coordination or sophisticated reasoning where the server needs the LLM’s help to decide its next step.
- Cost and Security Control: The client retains control over which LLM is used, its cost, and data privacy, as the LLM requests originate from the client.
- Dynamic Tool Selection: A server might use sampling to decide which internal tool to use based on a user’s complex request.
Conceptual Flow:
- User prompts AI client (Host).
- AI client sends part of the request to MCP server.
- MCP server determines it needs LLM assistance for a sub-task.
- MCP server makes a “sampling request” to the MCP client.
- MCP client sends this sub-request to its configured LLM (e.g., OpenAI, Ollama).
- LLM responds to the MCP client.
- MCP client passes the LLM’s response back to the MCP server.
- MCP server uses this response to continue processing its task.
3.3. Roots (Workspace Context)
Detailed Explanation:
Roots refers to the ability of the MCP client (especially in IDEs like VS Code) to provide the MCP server with information about the user’s workspace folders. This allows MCP servers to be context-aware, tailoring their tools and behavior to the project at hand. For example, a file system MCP server could restrict its operations to the current workspace root, enhancing security and relevance.
Example (VS Code mcp.json with implicit roots):
When configured in a .vscode/mcp.json, the VS Code client implicitly shares the workspace root with the MCP servers. A file system server could then use this.
# A simplified view of how roots might influence a filesystem server's behavior
@mcp.tool()
async def list_files_in_workspace(pattern: str = "*") -> List[str]:
"""
Lists files in the current workspace directory matching a pattern.
Requires workspace root information from the MCP client.
"""
# Pseudocode:
# workspace_roots = get_roots_from_mcp_client() # Client provides this automatically
# files = []
# for root in workspace_roots:
# files.extend(glob.glob(os.path.join(root, pattern)))
# return files
pass
4. Advanced Topics and Best Practices
4.1. Advanced Server Implementations
While FastMCP simplifies server creation, for highly custom or performance-critical servers, you might work closer to the MCP Python SDK’s Server class.
Key considerations:
- Lifespan Management: Manually managing the server’s lifecycle (startup, shutdown) and connections.
- Custom Transport: Implementing transports other than
stdio(e.g., HTTP for remote servers) directly. - Error Handling and Logging: Robust error handling, comprehensive logging, and metrics are essential for production-grade servers.
4.2. Best Practices for MCP Server Development
- Modular Tools/Resources: Design each tool and resource to be as focused and independent as possible. This improves reusability and maintainability.
- Clear Descriptions and Schemas: Provide precise
descriptionstrings for tools and resources, and accurate input/output schemas. LLMs rely heavily on these for proper invocation. - Asynchronous Operations: Use
asyncio(Python) or Promises (TypeScript/JavaScript) for I/O-bound tasks in your server handlers to prevent blocking and ensure responsiveness. - Security First:
- Input Validation: Always validate and sanitize all inputs received from the MCP client (which ultimately comes from the LLM). Assume all inputs are untrusted.
- Least Privilege: Design your server and its tools to operate with the minimum necessary permissions.
- Access Control: Implement robust access control logic if your tools interact with sensitive systems.
- No Secret Hardcoding: Utilize environment variables or secure input mechanisms for credentials.
- Observability: Implement logging, tracing, and monitoring for your MCP servers to diagnose issues and understand usage patterns. Tools like LangSmith can be integrated for this purpose.
- Versioning: Clearly version your MCP server and adhere to the MCP specification version you’re implementing. This ensures compatibility with clients.
- Comprehensive Testing: Thoroughly test each tool, resource, and prompt, including edge cases and error conditions.
4.3. Common Pitfalls to Avoid
- Blocking Operations: Avoid synchronous blocking calls within asynchronous handlers. This can lead to unresponsive servers.
- Insufficient Input Validation: Trusting LLM-generated inputs directly can lead to security vulnerabilities (e.g., prompt injection leading to malicious tool arguments).
- Poor Tool Descriptions: Vague or misleading tool descriptions can result in the LLM misinterpreting their purpose or misusing them.
- Overly Broad Permissions: Granting too many permissions to an MCP server or a specific tool can create security risks.
- Ignoring Network Considerations: For remote servers, consider latency, network reliability, and security of the transport layer.
5. Guided Projects
These projects will help you apply the concepts learned to build practical MCP servers.
Project 1: File Management MCP Server
Objective: Create an MCP server that allows an AI agent to perform basic file system operations (list, read, write) within a designated directory.
Problem Statement: An AI assistant needs to interact with local project files to help developers. It should be able to list files, read their content, and create new files.
Steps:
Set up the Project:
- Create a new Python project directory:
mcp-file-server - Initialize
uvand a virtual environment. - Install
mcp[cli]. - Create a
server.pyfile.
- Create a new Python project directory:
Define the Base Server:
- In
server.py, initializeFastMCP.
# server.py from mcp.server.fastmcp import FastMCP import os import json mcp = FastMCP("file-manager") # Define a base directory where the AI agent is allowed to operate. # IMPORTANT: In a real application, make this configurable and secure. BASE_DIR = os.path.abspath("./agent_files") os.makedirs(BASE_DIR, exist_ok=True) # Ensure the directory exists print(f"File Manager MCP Server operating in: {BASE_DIR}")- In
Implement
list_directoryTool:- This tool will list all files and subdirectories within
BASE_DIR.
@mcp.tool() def list_directory(path: str = ".") -> str: """ Lists all files and directories within a specified path relative to the base directory. Args: path: The relative path to list (defaults to current base directory). Returns: A JSON string with a list of file/directory names, or an error message. """ full_path = os.path.join(BASE_DIR, path) if not os.path.exists(full_path): return json.dumps({"error": f"Path '{path}' not found."}) if not os.path.isdir(full_path): return json.dumps({"error": f"Path '{path}' is not a directory."}) try: entries = os.listdir(full_path) return json.dumps({"contents": entries}) except Exception as e: return json.dumps({"error": f"Failed to list directory: {str(e)}"})- This tool will list all files and subdirectories within
Implement
read_file_contentTool:- This tool will read the content of a specified file.
@mcp.tool() def read_file_content(filepath: str) -> str: """ Reads the content of a specified file relative to the base directory. Args: filepath: The relative path to the file to read. Returns: The content of the file as a string, or an error message. """ full_path = os.path.join(BASE_DIR, filepath) if not os.path.exists(full_path): return json.dumps({"error": f"File '{filepath}' not found."}) if not os.path.isfile(full_path): return json.dumps({"error": f"Path '{filepath}' is not a file."}) # Security: Prevent path traversal if not os.path.commonpath([BASE_DIR, full_path]) == BASE_DIR: return json.dumps({"error": "Attempted path traversal detected. Operation denied."}) try: with open(full_path, 'r', encoding='utf-8') as f: content = f.read() return json.dumps({"content": content}) except Exception as e: return json.dumps({"error": f"Failed to read file: {str(e)}"})Implement
create_fileTool:- This tool will create a new file with specified content.
@mcp.tool() def create_file(filepath: str, content: str = "") -> str: """ Creates a new file with specified content relative to the base directory. If the file already exists, it will be overwritten. Args: filepath: The relative path for the new file. content: The content to write to the file. Defaults to an empty string. Returns: A success message or an error message. """ full_path = os.path.join(BASE_DIR, filepath) # Ensure parent directories exist os.makedirs(os.path.dirname(full_path), exist_ok=True) # Security: Prevent path traversal if not os.path.commonpath([BASE_DIR, full_path]) == BASE_DIR: return json.dumps({"error": "Attempted path traversal detected. Operation denied."}) try: with open(full_path, 'w', encoding='utf-8') as f: f.write(content) return json.dumps({"status": f"File '{filepath}' created/overwritten successfully."}) except Exception as e: return json.dumps({"error": f"Failed to create file: {str(e)}"})Run the Server:
- Add the
if __name__ == "__main__":block to run your server.
if __name__ == "__main__": mcp.run(transport='stdio')- Add the
Test the Server with MCP Inspector:
- Open your terminal in the project directory.
- Run:
npx @modelcontextprotocol/inspector python server.py(ensure Node.js is installed to runnpx). - This will open a web-based inspector where you can see your “file-manager” server and interact with its tools.
- Create a dummy file: Use the
create_filetool in the inspector to make atest.txtinside youragent_filesdirectory. - List files: Use
list_directoryto seetest.txt. - Read file: Use
read_file_contentontest.txt.
Project 2: Integrating MCP Server with Ollama and a Python Client
Objective: Build a Python client that uses an Ollama-hosted LLM to interact with your MCP File Management server.
Problem Statement: You want a local AI agent, powered by Ollama, to manage files using the MCP server you just created, without manually calling server tools.
Steps:
Ensure Ollama is Running:
- Make sure Ollama is installed (refer to setup section).
- Pull a model that supports tool calling (e.g.,
llama3.2,qwen2.5,qwen3:14b).
ollama run llama3.2 # Or your preferred model # Keep this terminal open and running Ollama in serve mode or `ollama run <model>`Create the Python Client:
- In the same project directory (
my-mcp-agent), createclient.py. - Install necessary libraries:(Note:
uv add openai mcpopenailibrary is used to interact with Ollama’s OpenAI-compatible API).
- In the same project directory (
Implement the Client Logic:
# client.py import asyncio import json from contextlib import AsyncExitStack from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from openai import AsyncOpenAI import os # Path to your MCP server script MCP_SERVER_SCRIPT = os.path.abspath("server.py") # Assumes server.py is in the same directory class MCPOllamaClient: def __init__(self, ollama_model: str = "llama3.2", ollama_endpoint: str = "http://localhost:11434/v1"): self.session = None self.exit_stack = AsyncExitStack() self.ollama_model = ollama_model self.ollama_client = AsyncOpenAI(base_url=ollama_endpoint, api_key="ollama") # api_key can be anything for local Ollama async def connect_to_mcp_server(self, server_script_path: str): """Connects to the local MCP server running via stdio.""" server_params = StdioServerParameters( command="python", args=[server_script_path], ) stdio_transport = await self.exit_stack.enter_async_context( stdio_client(server_params) ) read_stream, write_stream = stdio_transport self.session = await self.exit_stack.enter_async_context( ClientSession(read_stream, write_stream) ) await self.session.initialize() print("✅ Connected to MCP server!") async def get_ollama_tools(self): """Converts MCP tools to OpenAI function format for Ollama.""" if not self.session: raise RuntimeError("MCP session not initialized.") tools_result = await self.session.list_tools() # Ollama's API (via openai client) expects tools in this specific format return [ { "type": "function", "function": { "name": tool.name, "description": tool.description, "parameters": tool.inputSchema, }, } for tool in tools_result.tools ] async def process_query(self, query: str) -> str: """Processes a user query by leveraging Ollama and MCP tools.""" if not self.ollama_client or not self.session: raise RuntimeError("Client or MCP session not initialized.") available_mcp_tools = await self.get_ollama_tools() messages = [ {"role": "system", "content": "You are a helpful file management assistant. Use the 'file-manager' tools to assist the user with file operations."}, {"role": "user", "content": query} ] # First call to Ollama with available tools response = await self.ollama_client.chat.completions.create( model=self.ollama_model, messages=messages, tools=available_mcp_tools, tool_choice="auto", # Allows Ollama to decide if it needs a tool ) assistant_message = response.choices[0].message # Check if Ollama decided to call a tool if assistant_message.tool_calls: print(f"🤖 Ollama chose to use a tool: {assistant_message.tool_calls[0].function.name}") messages.append(assistant_message) # Add the assistant's tool call message # Execute each tool call requested by Ollama for tool_call in assistant_message.tool_calls: tool_name = tool_call.function.name tool_arguments = json.loads(tool_call.function.arguments) # Call the MCP server tool tool_result = await self.session.call_tool(tool_name, arguments=tool_arguments) # Add the tool's output back to the conversation for Ollama to process messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": tool_result.content[0].text, # Assuming text content }) # Second call to Ollama, now with the tool's results final_response = await self.ollama_client.chat.completions.create( model=self.ollama_model, messages=messages, ) return final_response.choices[0].message.content else: return assistant_message.content async def cleanup(self): """Cleans up the MCP client session.""" await self.exit_stack.aclose() print("🔴 MCP client disconnected.") async def main(): client = MCPOllamaClient() try: await client.connect_to_mcp_server(MCP_SERVER_SCRIPT) # Create a file for testing purposes await client.process_query("Create a file named 'notes.txt' with content 'This is a test note for the AI agent.'") print("\n---") # Example queries queries = [ "List all files in the current directory.", "What is the content of 'notes.txt'?", "Create a new file 'report/summary.txt' with the text 'Annual summary report. More details to follow.'" ] for query in queries: print(f"\n🤔 User: {query}") response = await client.process_query(query) print(f"🤖 Agent: {response}") print("\n---") finally: await client.cleanup() if __name__ == "__main__": asyncio.run(main())
Explanation:
MCPOllamaClient: This class orchestrates the interaction.AsyncOpenAI: Used to interface with Ollama. Ollama provides an OpenAI-compatible API endpoint, so theopenaiPython client can be used by pointingbase_urlto your Ollama server and settingapi_key="ollama"(or any string, as it’s not actually used by local Ollama).StdioServerParametersandstdio_client: These are used to launch yourserver.pyas a subprocess and establish communication over standard I/O.ClientSession: Manages the MCP communication session with your server.get_ollama_tools(): Fetches the tools exposed by your MCP server and formats them into thetoolsstructure expected by Ollama’s chat completion API.process_query():- Sends the user’s query to Ollama, providing it with the list of available MCP tools.
- If Ollama decides to use a tool (
assistant_message.tool_calls), the client extracts the tool name and arguments. - It then calls the corresponding MCP server tool (
self.session.call_tool). - The result from the MCP tool is fed back to Ollama in a “tool” role message, allowing the LLM to synthesize a final, informed response to the user.
Independent Problem-Solving Opportunity:
- Error Handling in Client: Enhance the
process_querymethod to gracefully handle errors returned by the MCP server tools (e.g., if a file is not found). How would you instruct the LLM to inform the user about the error and suggest corrective actions? - Persistent Storage for MCP Server: Currently, the
BASE_DIRis relative. Modify yourserver.pyandclient.pyso that theBASE_DIRfor thefile-managerserver can be specified by the client when it connects, or loaded from an environment variable for persistent storage.
Project 3: Integrating with VS Code (Using an Existing MCP Server)
Objective: Configure VS Code to use an existing MCP server (like the GitHub MCP server) and demonstrate its interaction with GitHub Copilot Chat.
Problem Statement: You want to enhance your GitHub Copilot experience in VS Code by allowing it to interact directly with your GitHub repositories (e.g., list issues, create pull requests) using an official MCP server.
Steps:
Prerequisites:
- VS Code (version 1.99 or later): Ensure you have the latest VS Code.
- GitHub Copilot Access: You need an active GitHub Copilot Business or Enterprise subscription.
- GitHub Account: Signed in to GitHub within VS Code.
Enable MCP Support in VS Code:
- MCP support is usually enabled by default in recent VS Code versions (1.102+). You can verify or enable it by going to
Settings(Ctrl+, or Cmd+,) and searching forchat.mcp.enabled.
- MCP support is usually enabled by default in recent VS Code versions (1.102+). You can verify or enable it by going to
Add the GitHub MCP Server (Remote Example): The GitHub MCP server is often available as a remote, hosted service.
- Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P).
- Type
MCP: Add Serverand press Enter. - Select
HTTP (HTTP or Server-Sent Events). - For the Server URL, enter
https://api.githubcopilot.com/mcp/. - You can use the default Server ID or provide a custom one (e.g.,
github). - Choose where to save the configuration (e.g.,
Workspace Settingsto create.vscode/mcp.jsonorGlobalforsettings.json). - VS Code will prompt you to authorize the MCP server with OAuth through your GitHub account. Click
Allowand select your account.
Example
mcp.json(created by VS Code):{ "servers": { "github": { "type": "http", "url": "https://api.githubcopilot.com/mcp/" // OAuth handled automatically by VS Code } } }Use MCP Tools in Copilot Chat:
- Open the Chat view in VS Code (Ctrl+Alt+I or Cmd+Alt+I).
- Ensure Agent mode is selected from the dropdown menu in the chat input box.
- Click the Tools icon (often a wrench or gear) in the top-left of the chat box.
- You should see a list of available tools, including those from your “github” MCP server (e.g.,
listPullRequests,getIssueDetails). You can select or deselect tools. - Enter a prompt that leverages these tools.
Example Interaction:
- Prompt:
List all open pull requests assigned to me in the 'my-awesome-project' repository. - Copilot: (Internally, Copilot recognizes the need to call the
github.listPullRequeststool, passes the appropriate arguments, and retrieves the data.) - Copilot’s Response: “Here are the open pull requests assigned to you in ‘my-awesome-project’:
- #123 - Feature: Implement user login
- #125 - Bugfix: Fix pagination issue in dashboard”
Troubleshooting Tips (Common Issues):
- “401 Unauthorized”: Ensure your OAuth flow was completed correctly or that your Personal Access Token (if used) has the correct scopes. Unset any lingering
GITHUB_TOKENenvironment variables if you’re switching between local and remote. - Tools don’t appear: Corporate proxies might block access to
api.githubcopilot.com. You may need to configure proxy settings or allowlist the domain. - Model times out: If you have too many tools enabled, reduce the number of selected tools in the tools picker.
- “401 Unauthorized”: Ensure your OAuth flow was completed correctly or that your Personal Access Token (if used) has the correct scopes. Unset any lingering
Independent Problem-Solving Opportunity:
- Local GitHub MCP Server with Docker: Instead of the remote GitHub MCP server, try setting up the local GitHub MCP server using Docker. You’ll need Docker installed and a GitHub Personal Access Token (PAT). Configure your
.vscode/mcp.jsonas shown in the web search results, specifically the “Local MCP server setup” section for Docker. How does the setup differ, and what are the benefits/drawbacks? - Custom Toolset: Imagine you only want Copilot to see a subset of GitHub tools (e.g., only
list_issuesandcreate_issue). How would you configure yourmcp.jsonto enable only these specific tools, based on the documentation or examples?
6. Bonus Section: Further Learning and Resources
The world of MCP and AI agents is constantly evolving. Here are some resources to continue your learning journey:
Recommended Online Courses/Tutorials:
- Official Model Context Protocol Documentation: This is your primary source for specifications, concepts, and basic tutorials. (See link below)
- KodeKloud MCP Tutorial: Build Your First MCP Server and Client from Scratch: A highly recommended YouTube video for hands-on learning.
- AI Engineering Academy: Beginners Guide to creating an MCP server: Another great step-by-step guide focusing on Python.
- dev.to / Medium Articles: Many developers share their experiences and tutorials on these platforms. Search for “Model Context Protocol,” “MCP Ollama,” “MCP VS Code.”
Official Documentation:
- Model Context Protocol Official Site: The central hub for the MCP specification, documentation, and community resources.
- GitHub Docs on MCP: Essential for understanding MCP integration with GitHub Copilot and setting up the GitHub MCP server.
- Anthropic API Docs (MCP section): Provides context on MCP’s origins and integration with Claude models.
- Microsoft Learn: Use MCP servers - Visual Studio (Windows): Specific documentation for Visual Studio integration.
Blogs and Articles:
- “The current state of MCP (Model Context Protocol)” by Elastic: Provides a good overview of MCP’s evolution, features, and future.
- “What is Model Context Protocol (MCP)? A guide” by Google Cloud: Explains MCP’s purpose and architecture from a broader AI perspective.
- “Build Your Own AI Agent with MCP: A Developer’s Guide to Model Context Protocol” on dev.to: A practical guide with Python examples.
- Medium articles on MCP with Ollama: Several articles cover this specific integration, providing valuable code examples and insights. Search for authors like “Mayur Jain” or “Mehul Gupta.”
YouTube Channels:
- KodeKloud: Offers hands-on tutorials on various tech topics, including MCP.
- AI Explained, FreeCodeCamp.org, David Shapiro: Often cover new AI technologies and concepts.
Community Forums/Groups:
- Official MCP GitHub Repository: Explore existing servers, SDKs, and contribute to discussions.
- Stack Overflow: For specific technical questions and troubleshooting.
- Relevant Discord Servers: Many AI and developer communities have Discord servers where you can ask questions and engage with other MCP developers. (e.g., LobeChat, Ollama communities).
Next Steps/Advanced Topics:
- Remote MCP Servers (HTTP/SSE): Explore deploying your MCP servers as remote services, rather than just locally via
stdio. This often involves setting up a web server (e.g., FastAPI in Python) and configuring proper authentication. - Custom Authentication Mechanisms: Implement more sophisticated authentication flows for your MCP servers beyond simple API keys.
- Dynamic Tool Registration: Research how MCP servers can dynamically expose tools based on context or user actions, rather than a fixed set.
- Integration with Other LLM Frameworks: Experiment with integrating MCP servers with other popular LLM orchestration frameworks (e.g., LangChain, Semantic Kernel in C#) beyond basic OpenAI API calls.
- Multi-Agent Systems: Explore how multiple AI agents, each connected to different MCP servers, can collaborate on complex tasks.
- Security Best Practices: Deep dive into securing MCP deployments, including topics like rate limiting, input sanitization, and vulnerability scanning for AI-enabled applications.
By actively engaging with these resources and continuing to experiment, you’ll stay at the forefront of AI agent development with the Model Context Protocol. Happy building!