DEV Community

Cover image for MCP vs Direct APIs: A Side-by-Side Example
Chandra Shettigar
Chandra Shettigar

Posted on

MCP vs Direct APIs: A Side-by-Side Example

See exactly how MCP standardizes AI agent development

In our previous post, we introduced MCP as "HTTP for AI agents." In this post, let's see this in action with a bit more detailed example that shows the difference between building AI agents with traditional integrations vs. the MCP approach.

Important Context: MCP is still early-stage, and most services don't have MCP servers yet. This example shows the vision of where MCP is heading and the problems it aims to solve.

The Scenario: Building a Smart Travel Assistant

You're building an AI assistant that helps users plan trips. It needs to:

  • Get weather forecasts
  • Find nearby restaurants
  • Book hotels

Let's see how you'd build this today vs. with MCP.

The Traditional Approach: Integration Complexity

Weather Service Integration

import requests
import os

class WeatherService:
    def __init__(self):
        self.api_key = os.getenv('WEATHER_API_KEY')
        self.base_url = 'https://api.openweathermap.org/data/2.5'

    def get_current_weather(self, city):
        try:
            response = requests.get(
                f"{self.base_url}/weather",
                params={
                    'q': city,
                    'appid': self.api_key,
                    'units': 'metric'
                }
            )

            response.raise_for_status()
            data = response.json()

            # Transform to our format
            return {
                'temperature': data['main']['temp'],
                'description': data['weather'][0]['description'],
                'humidity': data['main']['humidity']
            }
        except requests.RequestException as error:
            print(f'Weather service failed: {error}')
            raise
Enter fullscreen mode Exit fullscreen mode

Restaurant Service Integration

class RestaurantService:
    def __init__(self):
        self.api_key = os.getenv('YELP_API_KEY')
        self.base_url = 'https://api.yelp.com/v3'

    def find_restaurants(self, location, cuisine):
        try:
            response = requests.get(
                f"{self.base_url}/businesses/search",
                params={
                    'location': location,
                    'categories': cuisine,
                    'limit': 10
                },
                headers={
                    'Authorization': f'Bearer {self.api_key}',
                    'Accept': 'application/json'
                }
            )

            response.raise_for_status()
            data = response.json()

            # Different data format, different transformation
            return [
                {
                    'name': business['name'],
                    'rating': business['rating'],
                    'address': business['location']['address1'],
                    'phone': business.get('phone', '')
                }
                for business in data['businesses']
            ]
        except requests.RequestException as error:
            print(f'Restaurant service failed: {error}')
            raise
Enter fullscreen mode Exit fullscreen mode

Hotel Service Integration

class HotelService:
    def __init__(self):
        self.api_key = os.getenv('BOOKING_API_KEY')
        self.base_url = 'https://api.booking.com/v1'

    def search_hotels(self, city, check_in, check_out):
        try:
            # Yet another authentication method
            response = requests.post(
                f"{self.base_url}/hotels/search",
                headers={
                    'X-API-Key': self.api_key,
                    'Content-Type': 'application/json'
                },
                json={
                    'destination': city,
                    'check_in': check_in,
                    'check_out': check_out,
                    'adults': 1
                }
            )

            # Different error handling pattern
            if response.status_code == 429:
                raise Exception('Rate limited - try again later')

            response.raise_for_status()
            data = response.json()

            # Another data transformation pattern
            return [
                {
                    'name': hotel['hotel_name'],
                    'price': hotel['price_per_night'],
                    'rating': hotel['guest_rating'],
                    'availability': hotel['available_rooms'] > 0
                }
                for hotel in data['results']
            ]
        except requests.RequestException as error:
            print(f'Hotel service failed: {error}')
            raise
Enter fullscreen mode Exit fullscreen mode

The AI Agent Code

class TravelAgent:
    def __init__(self):
        self.weather_service = WeatherService()
        self.restaurant_service = RestaurantService()
        self.hotel_service = HotelService()

    def plan_trip(self, destination, dates):
        try:
            # Each service has its own calling pattern
            weather = self.weather_service.get_current_weather(destination)
            restaurants = self.restaurant_service.find_restaurants(destination, 'restaurants')
            hotels = self.hotel_service.search_hotels(destination, dates['check_in'], dates['check_out'])

            return {
                'weather': weather,
                'restaurants': restaurants,
                'hotels': hotels
            }
        except Exception as error:
            print(f'Trip planning failed: {error}')
            raise
Enter fullscreen mode Exit fullscreen mode

What's Challenging About This Approach?

  • 3 different authentication patterns: API key in URL, Bearer token, custom header
  • 3 different error handling approaches: Different HTTP status codes, rate limiting
  • 3 different data transformation needs: Each API returns data in its own format
  • Integration maintenance burden: Updates to any API require changes across all consumers
  • Hard to extend: Adding a new service means learning another API pattern

Note: This could be improved with better architecture (adapters, factories, etc.), but the core complexity remains.

The MCP Vision: Standardized Communication

Reality Check: Most of these MCP servers don't exist yet - someone would need to build them. But here's what it would look like:

# Conceptual MCP approach - not production code
from mcp_client import MCPClient

class TravelAgentMCP:
    def __init__(self):
        self.mcp_client = MCPClient()

        # Connect to MCP servers (these would need to be built/deployed)
        self.services = {
            'weather': 'mcp://weather-service',
            'restaurants': 'mcp://yelp-service', 
            'hotels': 'mcp://booking-service'
        }

    def plan_trip(self, destination, dates):
        try:
            # Same calling pattern for all services
            weather = self.mcp_client.call_tool(
                self.services['weather'], 
                'get_current_weather', 
                {'city': destination}
            )

            restaurants = self.mcp_client.call_tool(
                self.services['restaurants'],
                'find_restaurants',
                {'location': destination, 'cuisine': 'restaurants'}
            )

            hotels = self.mcp_client.call_tool(
                self.services['hotels'],
                'search_hotels',
                { 
                    'city': destination, 
                    'check_in': dates['check_in'], 
                    'check_out': dates['check_out']
                }
            )

            return {'weather': weather, 'restaurants': restaurants, 'hotels': hotels}

        except Exception as error:
            # Standardized error handling across all MCP services
            print(f'Trip planning failed: {error}')
            raise
Enter fullscreen mode Exit fullscreen mode

The Key Differences

Traditional Approach

  • Multiple integration patterns to learn and maintain
  • Service-specific error handling and authentication
  • Direct coupling between agent and API formats
  • Repetitive integration work across different projects

MCP Approach

  • One integration pattern that works for all MCP services
  • Standardized error handling and authentication
  • Abstraction layer between agent and underlying APIs
  • Reusable integration work - write MCP server once, use everywhere

The Reality: Complexity Transfer, Not Elimination

Important: MCP doesn't eliminate integration complexity - it centralizes and standardizes it.

Someone still needs to:

  • Build MCP servers for each service (weather, restaurants, hotels)
  • Handle authentication to underlying APIs
  • Transform data formats within the MCP server
  • Manage MCP server reliability and performance

The advantage: This work is done once per service rather than once per consumer application.

Adding a New Service

Traditional way: Each application team writes their own integration:

# Team A writes this
class FlightServiceA:
    # 50 lines of integration code

# Team B writes this  
class FlightServiceB:
    # Another 50 lines, slightly different
Enter fullscreen mode Exit fullscreen mode

MCP way: One team writes an MCP server, everyone uses it:

# One team builds: mcp://flight-service (50+ lines in the MCP server)
# Every team uses: one line
self.services['flights'] = 'mcp://flight-service'
Enter fullscreen mode Exit fullscreen mode

Tool Discovery: The Dynamic Advantage

One of MCP's most powerful features is tool discovery:

# Conceptual example
tools = self.mcp_client.list_tools(self.services['weather'])

print(tools)
# Output:
# [
#   {'name': 'get_current_weather', 'description': 'Get current weather for a city'},
#   {'name': 'get_forecast', 'description': 'Get 5-day weather forecast'},
#   {'name': 'get_weather_alerts', 'description': 'Get severe weather alerts'}
# ]
Enter fullscreen mode Exit fullscreen mode

This enables AI agents to dynamically discover capabilities and adapt their behavior - something that's much harder with traditional integrations.

The Deeper Advantage: Enabling AI Intelligence

The real power of MCP lies in standardization, enabling smarter AI behavior. When all tools follow the same interface pattern, LLMs can:

  • Dynamically choose tools based on user prompts
  • Adapt to workflow context (customer support vs development workflows)
  • Combine tools intelligently across different domains
  • Learn new capabilities without code changes

Traditional integrations require developers to hard-code which API to use when. With MCP, the AI agent can make these decisions contextually and dynamically.

Current State vs Future Vision

Today:

  • MCP protocol exists and is being adopted
  • Few production MCP servers available
  • Early adopters building their own MCP servers
  • Mainly useful for organizations building multiple AI agents

Future Vision:

  • Marketplace of MCP servers for common services
  • AI agents that dynamically discover and combine capabilities
  • Ecosystem effects where adding one MCP server benefits all agents

The Bottom Line

Traditional integration: Each team builds custom connections - like every household having different electrical outlets.

MCP integration: Standardized protocol that enables ecosystem growth - like having universal electrical outlets that any device can use.

The question isn't whether MCP eliminates complexity (it doesn't), but whether standardizing that complexity creates enough ecosystem value to justify the transition.


Next up: We'll build our first MCP server and see exactly how to make any service AI-agent ready.

Key Takeaways

  • MCP standardizes communication rather than eliminating complexity
  • Someone still builds the integrations - just once per service instead of per consumer
  • Tool discovery enables dynamic AI behavior that's hard with traditional APIs
  • Early stage but promising - ecosystem effects grow with adoption
  • Consider MCP when building for multiple AI agents or planning for future AI capabilities

⚠️ Important Note: The AI space evolves incredibly fast. MCP is still in early stages and may evolve significantly - what we know today might look different in a few months. The key is building your foundation in AI concepts and staying adaptable as the ecosystem matures. Keep learning, keep experimenting!

📝 Code Note: The code examples in this series are simplified for illustration purposes. Actual MCP implementations may require additional setup, error handling, and configuration. Always refer to official MCP documentation for production code.

Ready to try building an MCP server yourself? The next post will show you exactly how to do it!

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.