TNS
VOXPOP
As a JavaScript developer, what non-React tools do you use most often?
Angular
0%
Astro
0%
Svelte
0%
Vue.js
0%
Other
0%
I only use React
0%
I don't use JavaScript
0%
NEW! Try Stackie AI
AI / AI Agents / Software Development

Tutorial: Build a Simple MCP Server With Claude Desktop

We walk you through the step-by-step process of building an MCP server and integrating it with Claude Desktop.
May 7th, 2025 4:00pm by
Featued image for: Tutorial: Build a Simple MCP Server With Claude Desktop
Photo by Danielle-Claude Bélanger on Unsplash.

In the previous part of this series, I introduced the basic concepts of Model Context Protocol (MCP) — an open standard designed to streamline how AI models interact with external tools, data sources and resources. In this installment, I’ll walk you through the step-by-step process of building an MCP server and integrating it with Claude Desktop.

We’ll use Python and FastMCP 2.0 to build an MCP server that utilizes the FlightAware API. For more information on FlightAware, refer to the previous tutorial, where I integrated it with large language model (LLM) tools. Ensure that you have a valid FlightAware API key before proceeding.

In subsequent parts of this series, I’ll demonstrate how to extend this to work with AI agents built with OpenAI Agents SDK and Google Agent Development Kit (ADK).

Step 1: Setting Up the Environment

Let’s start by creating a virtual environment and installing the required Python modules:

python -m venv venv
source venv/bin/activate
pip install requests fastmcp pytz

Step 2: Building the MCP Server

First, import the required Python modules in the code:

import json
import os  
import requests
import pytz
from datetime import datetime, timedelta
from typing import Any, Callable, Set, Dict, List, Optional
from mcp.server.fastmcp import FastMCP

The next step is to create the MCP object from FastMCP:

mcp = FastMCP("Flight Server")

We’ll then create a function and decorate it with the @mcp.tool() annotation to explicitly identify it as a tool:

@mcp.tool()
def get_flight_status(flight):
    """Returns Flight Information"""
    AEROAPI_BASE_URL = "https://aeroapi.flightaware.com/aeroapi"
    AEROAPI_KEY="YOUR_API_KEY"
 
    def get_api_session():
        session = requests.Session()
        session.headers.update({"x-apikey": AEROAPI_KEY})
        return session
 
    def fetch_flight_data(flight_id, session):
        if "flight_id=" in flight_id:
            flight_id = flight_id.split("flight_id=")[1]    
        
        start_date = datetime.now().date().strftime('%Y-%m-%d')
        end_date = (datetime.now().date() + timedelta(days=1)).strftime('%Y-%m-%d')
        api_resource = f"/flights/{flight_id}?start={start_date}&end={end_date}"  # Fixed & to &
        
        try:
            response = session.get(f"{AEROAPI_BASE_URL}{api_resource}")
            response.raise_for_status()
            json_data = response.json()
            
            # Print the response for debugging
            print("API Response:", json.dumps(json_data, indent=2))
            
            if 'flights' not in json_data or len(json_data['flights']) == 0:
                return None
            
            return json_data['flights'][0]
        except Exception as e:
            print(f"Error fetching flight data: {e}")
            return None
 
    def utc_to_local(utc_date_str, local_timezone_str):
        utc_datetime = datetime.strptime(utc_date_str, '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=pytz.utc)
        local_timezone = pytz.timezone(local_timezone_str)
        local_datetime = utc_datetime.astimezone(local_timezone)
        return local_datetime.strftime('%Y-%m-%d %H:%M:%S')    
    
    session = get_api_session()
    flight_data = fetch_flight_data(flight, session)
    
    if flight_data is None:
        return json.dumps({
            'error': 'Failed to retrieve flight data. Check the API key and flight ID.',
            'flight': flight
        })
    
    try:
        dep_key = 'estimated_out' if 'estimated_out' in flight_data and flight_data['estimated_out'] else \
              'actual_out' if 'actual_out' in flight_data and flight_data['actual_out'] else \
              'scheduled_out'
        
        arr_key = 'estimated_in' if 'estimated_in' in flight_data and flight_data['estimated_in'] else \
              'actual_in' if 'actual_in' in flight_data and flight_data['actual_in'] else \
              'scheduled_in'    
        
        flight_details = f"""
Flight: {flight}
Source: {flight_data['origin']['city']}
Destination: {flight_data['destination']['city']}
Departure Time: {utc_to_local(flight_data[dep_key], flight_data['origin']['timezone'])}
Arrival Time: {utc_to_local(flight_data[arr_key], flight_data['destination']['timezone'])}
Status: {flight_data['status']}
"""
        return flight_details
    except Exception as e:
        print(f"Error processing flight data: {e}")
        return json.dumps({
            'error': f'Error processing flight data: {str(e)}',
            'flight': flight
        })

The last step is to create the main function to run the server if it’s executed directly:

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

That’s it! We’re done building our first MCP server. It’s time to put it to the test by integrating it with Claude Desktop.

Step 3: Integrating With Claude Desktop

Ensure you have the latest version of Claude Desktop. Open the Settings menu to access the Developer tab:

Click on the Edit Config button to access the MCP settings file named claude_desktop_config.json.

Open the file in your favorite editor and add the following content:

{
  "mcpServers": {
    "flight": {
      "command": "/Users/janakiramm/Demo/venv/bin/python",
      "args": [
        "/Users/janakiramm/Demo/flight_server.py"
      ]
    }
  }
}

Replace the path with the absolute path to your Python environment and MCP server and save the file.

Restart Claude, and you should now find your MCP server within the settings option.

Make sure it’s turned on. Now we’re all set to use our MCP server from Claude.

It’s time to test the MCP server by asking Claude about a flight schedule. Claude will ask for your permission to access a third-party service.

Once you allow it, Claude will invoke the MCP server and fetch the data, which acts as the context to the original prompt.

As we can see from the output, Claude invoked the MCP server to fetch the real-time status. Note that we are using the STDIO transport.

You can find the entire code in the Gist below:

In upcoming articles in this series, we’ll explore how to use other transport protocols, including streamable HTTP transport. Stay tuned!

Group Created with Sketch.
TNS owner Insight Partners is an investor in: OpenAI.
TNS DAILY NEWSLETTER Receive a free roundup of the most recent TNS articles in your inbox each day.