MCP for newbies

20.07.2025

Model Context Protocol (MCP) — is just an API designed for LLMs. Sure, LLMs can use traditional APIs, but it’s like asking a chef to cook in a broom closet because of:


    • Complex prompt engineering to teach API schemas

 

  • Handle authentication tokens like a responsible adult
  • Remember what happened three API calls ago
  • Deal with rate limits without having an existential crisis

Standard APIs are built for programming languages, not natural language. MCP solves this using simplified, LLM-friendly interfaces described in natural language.

MCP’s secret sauce is state management, reflected in the termcontextin ModelContextProtocol. Unlike traditional APIs (which forget you faster than a goldfish), MCP remembers context between calls. That is a common thing in websites (storing user session for example), but rarely present in APIs for scaling and other reasons.

Traditional API:

// [Request] → [Response] (Stateless)

POST /flights/search
{"from": "NYC", "to": "Paris", "date": "2024-03-15"}
// Returns results

POST /flights/book
{"flight_id": "AF123", "passenger": ???} // Wait, who was booking?

MCP:

// [Request + Prior Context] → [Response + Updated Context]

SEARCH_FLIGHTS to=Paris date=next_week
// System remembers you're in NYC from context

BOOK_FLIGHT flight=AF123
// System knows it's you, remembers the search context

This context-awareness is what makes MCP uniquely suited for LLM workflows. The name directly reflects its purpose – a protocol that gives models contextual awareness during API interactions.

So MCP focuses on intent rather than technical implementation and acts as an interpreter layer between LLMs and backend systems.

MCP replaces API-specific syntax with a consistent command structure:

[ACTION] [TOOL_NAME] [PARAMETERS]

#Example tool call: 

BOOK_FLIGHT to=Paris date=202312–01

No curly braces were harmed in the making of this command.

MCP replaces the traditional request-response pattern with something more conversational. Tools are defined in a standardized manifest format:

tools:
name: search_flights
description: “Find available flights”
params:
name: destination
type: string
name: date
type: date

name: book_flight
description: “Reserve a specific flight”
params:
name: flight_id
type: string

Notice what’s missing? No authentication headers. No complex nested objects. No 47-page API documentation. Just tools with clear purposes and simple parameters.

Key Differences: Traditional API vs. MCP

Press enter or click to view image in full size

Image by the author

As an example of an MCP, let’s create a simple GitHub MCP from the manifest above:

tools:
  - name: search_flights
    description: "Find available flights"
    params:
      - name: destination
type: string
      - name: date
type: date

  - name: book_flight
    description: "Reserve a specific flight"
    params:
      - name: flight_id
type: string

It can be called like this:

BOOK_FLIGHT to=Paris date=20231201

As you can see there are some advantages over using an API:

  • No Token Management: MCP handles authentication
  • Auto-Context: Remembers repo, user, previous issues
  • Simplified Commands: Natural language instead of JSON payloads
  • Error Handling: Standardized error messages the LLM can understand

Here is the code for this MCP server:

class FlightMCP:
def __init__(self, api_key: str):
        self.api_key = api_key
        self.context = {
"user_location": None,
"recent_searches": [],
"preferences": {}
        }

def get_tools(self):
return [
            {
"name": "search_flights",
"description": "Search for flights to a destination",
"params": [
                    {"name": "to", "type": "string"},
                    {"name": "date", "type": "string"},
                    {"name": "class", "type": "string", "optional": True}
                ]
            },
            {
"name": "book_flight",
"description": "Book a specific flight",
"params": [
                    {"name": "flight_id", "type": "string"},
                    {"name": "passengers", "type": "integer", "default": 1}
                ]
            }
        ]

def execute_tool(self, tool_name: str, params: dict):
# Context-aware execution
if tool_name == "search_flights":
# Use context to fill missing info
            from_location = params.get("from") or self.context.get("user_location")

            results = self._search_flights(
                from_loc=from_location,
                to_loc=params["to"],
                date=self._parse_natural_date(params["date"])
            )

# Update context for future calls
            self.context["recent_searches"].append({
"destination": params["to"],
"results": len(results)
            })

return {
"success": True,
"flights": results,
"next_actions": ["BOOK_FLIGHT", "SEARCH_HOTELS"]
            }

Here’s where MCP gets interesting — bidirectional communication. APIs can push updates to the LLM:

class FlightMCP:
def __init__(self):
        self.notification_handler = None
        self.monitored_flights = {}

def book_flight(self, flight_id: str):
# Book the flight
        booking = self._process_booking(flight_id)

# Start monitoring for changes
        self._monitor_flight(booking["confirmation_number"])

return {
"success": True,
"confirmation": booking["confirmation_number"],
"monitoring": True
        }

def _monitor_flight(self, confirmation: str):
def check_status():
while confirmation in self.monitored_flights:
                status = self._get_flight_status(confirmation)

if status["changed"]:
                    self._send_notification({
"type": "flight_update",
"message": f"Your flight is now {status['new_status']}",
"data": status
                    })

                time.sleep(300)  # Check every 5 minutes

        threading.Thread(target=check_status, daemon=True).start()

Claude Desktop MCP Integration

Claude Desktop has built-in MCP support that allows you to connect external tools and data sources directly to your conversations with sandboxed execution with permission controls.

GetIgor Novikov’s stories in your inbox

Join Medium for free to get updates from this writer.

It comes with several pre-built MCPs:

  • Filesystem: Read/write local files
  • SQLite: Query databases
  • Web Search: Search the internet
  • Git: Repository operations

Tool Discovery

When you start Claude Desktop, it automatically launches configured MCP server, discovers available tools, makes them available in chat

It uses a configuration file (claude_desktop_config.json) to define available MCPs:

{
"mcpServers": {
"flights": {
"command": "python",
"args": ["flight_mcp_server.py"],
"env": {
"API_KEY": "your_flight_api_key"
}
},
"calendar": {
"command": "node",
"args": ["calendar_mcp.js"],
"env": {
"GOOGLE_CALENDAR_TOKEN": "your_token"
}
}
}
}

User: “Book me a flight to Tokyo next Tuesday”
Claude: I’ll search for flights to Tokyo for next Tuesday.
[Automatically uses SEARCH_FLIGHTS tool]
Found 3 flights. The best option is JAL 061 at 2 PM for $1,200.

[Uses BOOK_FLIGHT tool]

Booked! Confirmation #ABC123. I’ll monitor this flight and notify you of any changes.

Advanced MCP Concepts

MCPs can expose not just tools, but also resources (data sources):

resources:
  - name: "project_files"
    description: "Access to project source code"
    uri: "file:///project/src/"
  - name: "database_schema"
    description: "Current database structure"
    uri: "sqlite:///app.db"

MCP Composition:

Multiple MCPs working together:

{
"mcpServers": {
"flights": {...},
"hotels": {...},
"calendar": {...},
"expense_tracker": {...}
  }
}

The LLM orchestrates across all of them: “Book my usual San Francisco trip” triggers:

  1. Calendar MCP checks availability
  2. Flight MCP books based on preferences
  3. Hotel MCP reserves the regular spot
  4. Expense tracker logs everything

Security Considerations

MCP’s context persistence is powerful but dangerous:

  • Data isolation between users is critical
  • Context shouldn’t leak across sessions
  • Permissions need careful design
class SecureMCP:
def __init__(self):
self.contexts = {}  # User-isolated contexts

def get_context(self, user_id: str):
if user_id not in self.contexts:
self.contexts[user_id] = self._create_isolated_context()
return self.contexts[user_id]

When not to use MCP?

While MCP is brilliant for LLM integration, it’s not a magic wand that solves all API problems. Here are cases where you might want to stick with traditional approaches:

1.Non-LLM Consumers:If your API is primarily consumed by traditional software (mobile apps, web frontends, etc.), standard REST or GraphQL APIs are more appropriate. MCP’s natural language focus adds no value here.

2.Performance-Critical Systems:MCP’s stateful nature and simplified error handling introduce overhead. For high-frequency, low-latency systems (e.g., stock trading), every millisecond counts.

3.Simple, Stateless Operations:If your use case involves one-off requests without context (e.g., fetching weather data), MCP’s state management becomes unnecessary complexity.

4.Existing API Ecosystems:If you have well-established API gateways, client libraries, and documentation, retrofitting MCP might not justify the effort.

5.Security Constraints:MCP’s automatic context passing might violate strict data isolation requirements (e.g., multi-tenant systems). Traditional APIs offer finer-grained control.

6.Scaling: there is a good reason why APIs don’t have context — it is much easier to scale this way. If you really need scalable solution — MCP may be not a good way to do things.

What’s Next?

As context windows explode (looking at you, Gemini’s 10 million tokens), MCP might become less critical for some use cases. Why maintain state when you can just dump everything into context?

But MCP’s real value isn’t just state management — it’s the standardization. Just like REST gave us a common language for APIs, MCP gives us a common language for LLM integrations.

In summary: MCP is just another way to expose API, this time for LLMs.

Back

Share:

  • icon
  • icon
  • icon
Innova AI Chatbot