Skip to content

Mastering Your Own Custom MCP Server: A Powerful Python Tutorial

  • mcp
mcp server tutorial

Welcome to an in-depth MCP Server Tutorial designed to empower you with the knowledge to create your own custom Model Context Protocol (MCP) servers in Python. This guide is based on insights from the tutorial video “Coding Your Own Custom MCP Server in Python – Full Tutorial” by NeuralNine (available at: https://www.youtube.com/watch?v=IuZk3j-D_C0).

In the evolving landscape of AI-powered development tools, seamlessly integrating custom functionalities into your workflow can significantly boost productivity. Imagine an AI assistant in your code editor that doesn’t just suggest code, but can also create GitHub issues, send emails, or interact with your internal APIs on command. This is precisely what custom MCP servers enable. By the end of this comprehensive guide, you’ll have built two practical MCP servers and integrated them with popular AI clients like Cursor or Claw Desktop, unlocking a new realm of automated possibilities.

1. The Power of MCP: Understanding Model Context Protocol

Before we dive into the code, let’s solidify our understanding of what MCP is and why it’s a game-changer for AI-assisted workflows.

What Exactly is MCP?
MCP, or Model Context Protocol, is a standardized communication protocol designed to bridge the gap between AI tools (specifically large language models or LLMs) and various external functionalities. Think of it as a universal language that allows your AI assistant to “talk” to your custom scripts and services.

At its core, MCP defines how an MCP client (like Cursor or Claw Desktop, often enhanced by an LLM) requests actions from an MCP server, which in turn provides specific functionalities. The server exposes “tools” – Python functions in our case – that the client can call. When an LLM within your client recognizes a user’s intent that aligns with a provided tool, it can automatically execute that tool.

Why Build Your Own Custom MCP Server?
The ability to craft your own custom MCP servers opens up a world of automation and personalization. Here’s why this MCP Server Tutorial is incredibly valuable:

  • Tailored Automation: Off-the-shelf AI tools are powerful, but they can’t do everything. Custom MCP servers let you automate tasks unique to your workflow, team, or project. Whether it’s filing bug reports in your specific project management tool, deploying a snippet of code, or generating a report, you can build a tool for it.
  • Enhanced Productivity: Reduce context switching and manual tasks. By integrating frequently performed actions directly into your AI-powered development environment, you can trigger complex operations with simple natural language commands, saving precious time and effort.
  • Seamless Integration: MCP ensures that your custom tools integrate smoothly with compatible AI clients. This means your new functionalities feel like native features, not clunky add-ons.
  • Problem-Solving Power: Your AI can not only help you identify problems but also take immediate action to resolve them. For instance, if an error pops up, your AI could be prompted to summarize it and automatically create a GitHub issue, notifying your team.
  • Extensibility: The possibilities are virtually limitless. You can connect your AI assistant to any API, database, or external service you can access programmatically.

In this MCP Server Tutorial, we’ll demonstrate two compelling examples: sending emails and creating GitHub issues. These examples serve as a springboard for you to imagine and implement far more complex and specialized tools for your daily tasks.

2. Laying the Foundation: Setting Up Your MCP Server Environment

A solid development environment is crucial for any project. This section will guide you through setting up your Python environment and managing dependencies for your custom MCP servers. While uv is used in the source video for its speed, we’ll also mention pip as a widely accessible alternative.

Prerequisites:

  • Python: Ensure Python 3.8+ is installed on your system. You can download it from Python.org.
  • Basic Python Knowledge: Familiarity with functions, decorators, and basic I/O will be helpful.
  • Command Line Fundamentals: You’ll be using your terminal to manage directories and install packages.
  • GitHub Account & Access Token (for GitHub server): If you plan to build the GitHub issue server, you’ll need a GitHub account and a Personal Access Token with appropriate repository permissions. More on this later.

Environment Setup Steps:

  1. Choose Your Environment Manager:

    • Option A: Using uv (Recommended for Speed):
      If you don’t have uv installed, you can get it with pip install uv or follow the instructions on its official documentation.

    • Option B: Using pip and venv (Standard Python Practice):
      Python’s built-in venv module is excellent for creating isolated environments. You’ll activate these environments before installing packages.

  2. Create Project Directories:
    It’s good practice to separate your custom MCP servers into distinct directories for better organization.

    mkdir mcp_mail_function
    mkdir mcp_issue_function
    
  3. Initialize Environment and Install Dependencies:

    • For the Email Server (mcp_mail_function):
      Navigate into the directory and initialize your chosen environment.

      Using uv:

      cd mcp_mail_function
      uv init
      uv add python-dotenv mcp-server
      

      Using pip and venv:

      cd mcp_mail_function
      python -m venv .venv        # Create a virtual environment
      source .venv/bin/activate   # Activate it (on Windows: .venv\Scripts\activate)
      pip install python-dotenv mcp-server
      

      (Remember to activate the environment every time you work on this server.)

    • For the GitHub Issue Server (mcp_issue_function):
      Repeat the process for the second directory.

      Using uv:

      cd ../mcp_issue_function
      uv init
      uv add python-dotenv mcp-server pygithub
      

      Using pip and venv:

      cd ../mcp_issue_function
      python -m venv .venv
      source .venv/bin/activate
      pip install python-dotenv mcp-server pygithub
      

      (Remember to activate the environment every time you work on this server.)

Why python-dotenv?
This package is crucial for handling sensitive information like API keys and passwords. It allows you to store environment variables in a .env file, which is then loaded into your application’s environment. This prevents you from hardcoding credentials directly into your source code, making your applications more secure and portable. Crucially, never commit your .env files to public version control repositories like GitHub!

With your environment set up, you’re ready to start coding your first custom MCP server.

3. Building Your First Custom MCP Server: The Email Sender

Our first MCP Server Tutorial example will demonstrate how to create a simple yet powerful MCP server that can send emails on command. This functionality can be incredibly useful for sending notifications, summaries, or reports directly from your AI development environment.

Goal: To create an MCP server that exposes a “send email” tool.

Steps:

  1. Create main.py:
    Inside your mcp_mail_function directory, create a new file named main.py. This will house the code for your email server.

  2. Import Necessary Libraries:
    At the top of main.py, add the following import statements:

    import os                 # For interacting with environment variables
    import smtplib            # For sending emails using SMTP protocol
    from email.message import EmailMessage # For constructing email messages
    from dotenv import load_dotenv     # For loading .env file
    from mcp_server.fastmcp import FastMCP # The core MCP server framework
    
    • smtplib is Python’s standard library for sending emails using the Simple Mail Transfer Protocol (SMTP).
    • EmailMessage helps in creating well-formatted email messages with headers (From, To, Subject) and content.
    • FastMCP is a convenient wrapper around mcp-server that provides a FastAPI-like syntax for defining tools, making it very intuitive for developers familiar with web frameworks.
  3. Load Environment Variables:

    load_dotenv()
    

    This single line, placed after imports, tells python-dotenv to look for a .env file in the current directory and load any key-value pairs found there as environment variables.

  4. Create MCP Instance:

    mcp = FastMCP()
    

    This initializes our MCP server application. It’s the central object that will manage and expose our tools.

  5. Define the send_email Tool:
    Now, let’s define the core functionality – the send_email tool. We’ll use the @mcp.tool decorator to register this Python function as an MCP tool.

    @mcp.tool
    def send_email(recipient: str, subject: str, body: str) -> str:
        """
        Sends an email to the specified recipient with the given subject and body.
        Expects MAIL_ACCOUNT, MAIL_PASSWORD, SMTP_SERVER, and SMTP_PORT in .env.
        """
        try:
            # Retrieve credentials and server info from environment variables
            mail_account = os.getenv("MAIL_ACCOUNT")
            mail_password = os.getenv("MAIL_PASSWORD")
            smtp_server = os.getenv("SMTP_SERVER")
            smtp_port = int(os.getenv("SMTP_PORT")) # Port should be an integer
    
            # Create the email message object
            message = EmailMessage()
            message["From"] = mail_account
            message["To"] = recipient
            message["Subject"] = subject
            message.set_content(body)
    
            # Establish a secure connection and send the email
            with smtplib.SMTP(smtp_server, smtp_port) as server:
                server.starttls() # Enable TLS encryption for security
                server.login(mail_account, mail_password) # Authenticate with the mail server
                server.send_message(message) # Send the prepared message
    
            return "Email sent successfully!"
        except Exception as e:
            # Return an error message if anything goes wrong
            return f"Failed to send email due to: {str(e)}"
    
    • Decorator (@mcp.tool): This is what transforms a regular Python function into an MCP-callable tool.
    • Function Signature: The type hints (recipient: str, subject: str, body: str, -> str) are crucial. MCP clients use these to understand what parameters your tool expects and what kind of result it returns. The LLM can then infer how to use your tool based on these definitions and its docstring.
    • Credential Retrieval: os.getenv() safely fetches sensitive information from your loaded .env file.
    • smtplib.SMTP: This initializes the SMTP client. server.starttls() is vital for encrypting your communication with the mail server, protecting your credentials and message content.
    • Error Handling: The try...except block ensures that if an issue occurs (e.g., incorrect credentials, network problem), the server returns a helpful error message instead of crashing.
  6. Add the Main Execution Block:
    This standard Python idiom ensures that your MCP server runs when the main.py file is executed directly.

    if __name__ == "__main__":
        mcp.run(transport="stdio")
    
    • mcp.run() starts the MCP server.
    • transport="stdio" specifies that the server will communicate with the client over standard input/output. This is a common and convenient way to run local MCP servers.
  7. Create .env File:
    In the mcp_mail_function directory (alongside main.py), create a file named .env. Add your email account details. Replace the placeholder values with your actual email service credentials.

    MAIL_ACCOUNT=your_sender_email@example.com
    MAIL_PASSWORD=your_email_password
    SMTP_SERVER=smtp.example.com  # e.g., smtp.gmail.com for Gmail, smtp-mail.outlook.com for Outlook
    SMTP_PORT=587                 # Common ports: 587 (TLS), 465 (SSL)
    

    Critical Security Reminder: As mentioned, never share this file or commit it to public repositories. For better security, consider using app-specific passwords or OAuth if your email provider supports them, rather than your main email password. You’ll need to look up the specific SMTP server and port for your email provider (e.g., “Gmail SMTP server” or “ProtonMail SMTP settings”).

With these steps complete, your first custom MCP server is ready to run!

4. Expanding Functionality: The GitHub Issue Creator MCP Server

Building on our first MCP Server Tutorial example, let’s create a second server that can automatically create GitHub issues. This is incredibly useful for turning an AI-suggested task directly into an actionable item in your project’s issue tracker.

Goal: To create an MCP server that can create new issues in a specified GitHub repository.

Steps:

  1. Create main.py:
    Inside your mcp_issue_function directory, create a new file named main.py.

  2. Import Necessary Libraries:

    import os                   # For interacting with environment variables
    from github import Github   # The PyGithub library for GitHub API interaction
    from dotenv import load_dotenv       # For loading .env file
    from mcp_server.fastmcp import FastMCP # The core MCP server framework
    
    • pygithub (imported as Github) is a powerful Python library that simplifies interaction with the GitHub API.
  3. Load Environment Variables:

    load_dotenv()
    

    This line will load the ACCESS_TOKEN we’ll define in the .env file.

  4. Create MCP Instance:

    mcp = FastMCP()
    

    Again, we initialize our FastMCP application.

  5. Define the create_issue Tool:
    Now, we define the create_issue function, decorated as an MCP tool.

    @mcp.tool
    def create_issue(issue_title: str, issue_text: str) -> str:
        """
        Creates a GitHub issue in the specified repository.
        Requires a GitHub Personal Access Token with 'repo' scope in the .env file.
        """
        try:
            # Initialize GitHub object with the access token
            access_token = os.getenv("ACCESS_TOKEN")
            if not access_token:
                raise ValueError("GitHub ACCESS_TOKEN not found in environment variables.")
    
            g = Github(access_token)
    
            # Get the repository object. Replace "your_username/your_repo_name"
            # with the actual path to your target GitHub repository.
            # Make sure your token has permissions for this specific repo.
            repo_name = os.getenv("GITHUB_REPO", "neural9/tutorial-mcp-repo") # Default for demo
            repo = g.get_repo(repo_name)
    
            # Create the issue
            issue = repo.create_issue(title=issue_title, body=issue_text)
    
            return f"GitHub issue '{issue.title}' created successfully! URL: {issue.html_url}"
        except Exception as e:
            return f"Failed to create GitHub issue due to: {str(e)}"
    
    • Github(access_token): This authenticates your pygithub client with your GitHub account using the provided Personal Access Token.
    • repo.get_repo("your_username/your_repo_name"): This fetches the specific repository where you want to create issues. You must replace "neural9/tutorial-mcp-repo" with your own repository’s path (e.g., "myorg/my-project-repo").
    • repo.create_issue(): This method creates the new issue with the provided title and body.
    • Return Value: The tool returns a success message including the URL of the newly created issue for user convenience.
  6. Add the Main Execution Block:
    Similar to the email server, this ensures the GitHub server runs when executed.

    if __name__ == "__main__":
        mcp.run(transport="stdio")
    
  7. Create .env File:
    In your mcp_issue_function directory, create a .env file and add your GitHub Personal Access Token.

    ACCESS_TOKEN=your_github_personal_access_token_here
    # Optionally, specify the target repository:
    # GITHUB_REPO=your_username/your_repo_name
    

    Generating a GitHub Personal Access Token:
    If you don’t have one, you’ll need to generate a fine-grained personal access token on GitHub. Go to your GitHub Settings > Developer settings > Personal access tokens > Fine-grained tokens. Create a new token, give it a descriptive name, and select the specific repository you want it to access. For permissions, you’ll generally need Contents (read & write for issues) and Issues (read & write). More details can be found in the GitHub documentation on personal access tokens.
    Again, do not commit this .env file to your repository!

You now have two functional custom MCP servers! The next step in this MCP Server Tutorial is to integrate them with your preferred AI client.

5. Connecting Your MCP Servers: Integration with Cursor & Claw Desktop

Having built your custom MCP servers, the final crucial step is to inform your AI clients (like Cursor or Claw Desktop) about their existence and how to run them. This is typically done via a JSON configuration file.

Goal: Configure Cursor or Claw Desktop to recognize and use your custom mail_tool and git_issue_tool.

Steps:

  1. Locate the MCP Configuration File:
    The location of this file can vary slightly by operating system and client version.

    • Cursor:

      • Linux/macOS: ~/.config/cursor/mcp.json
      • Windows: %APPDATA%\cursor\mcp.json (or similar, check Cursor’s documentation)
    • Claw Desktop:

      • Linux/macOS: ~/.config/clot/desktop_config.json or ~/.config/clot_desktop/config.json
      • Windows: Similar path under %APPDATA% or program files.

    If the file doesn’t exist, you can create it.

  2. Edit the Configuration File:
    Open the mcp.json (or desktop_config.json) file in your preferred text editor. This file defines a JSON object containing an "mcp_servers" key, which itself is a dictionary mapping server names to their execution commands.

    • Basic Empty Structure (if file is new):

      {
        "mcp_servers": {}
      }
      
    • Adding Your Servers:
      Now, add your mail_tool and git_issue_tool definitions within the "mcp_servers" object. Crucially, replace the paths in the "args" array with the absolute paths to your main.py files.

      {
        "mcp_servers": {
          "mail_tool": {
            "command": "uv",
            "args": [
              "run",
              "/home/your_username/documents/programming/neural9/tutorial/mcp_mail_function/main.py"
            ]
          },
          "git_issue_tool": {
            "command": "uv",
            "args": [
              "run",
              "/home/your_username/documents/programming/neural9/tutorial/mcp_issue_function/main.py"
            ]
          }
        }
      }
      

      Important Notes for Configuration:

      • Paths: Ensure you use the absolute path to your main.py file for each server. Relative paths often do not work correctly in this context. Replace /home/your_username/documents/programming/neural9/tutorial/ with the actual parent directory containing your mcp_mail_function and mcp_issue_function folders.

      • "command" and "args":

        • If you’re using uv, command should be uv and args should be ["run", "path/to/main.py"].
        • If you’re using pip and venv, and you’ve activated your virtual environment manually before starting Cursor/Claw, your command might be python and args would just be ["path/to/main.py"].
        • Alternatively, if you want the client to activate the virtual environment, you might need to specify the full path to the Python executable within the virtual environment (e.g., /home/user/.venv/bin/python) and then the path to your script. The uv run command handles this automatically.
      • No Whitespace in Paths (Especially Linux/macOS): As highlighted in the source material, avoid whitespace characters in any part of the path to your server files, especially on Linux/macOS, as this can lead to configuration errors in clients like Cursor. Use hyphens (-) or underscores (_) instead.

      • JSON Syntax: Be meticulous with JSON syntax – commas after each server definition (except the last one), correct curly braces and square brackets. A single misplaced comma or bracket can cause a “Configuration Error.”

  3. Restart Cursor/Claw Desktop:
    After saving the mcp.json file, it’s essential to fully close and restart your AI client for the changes to take effect. This allows the client to re-read its configuration files.

  4. Verify the Integration:

    • Once your client restarts, navigate to its settings or “Tools” section.
    • You should now see mail_tool and git_issue_tool listed. Ensure they show as “enabled” or “1 tool enabled.” If you see “Configuration Error” or “0 tools enabled,” refer to the troubleshooting section below.
    • Now, in a chat with your AI assistant within Cursor or Claw Desktop, you can prompt it to use your new tools. For the email tool, try something like: “Send an email to your_recipient@example.com with the subject ‘Project Update’ and the body ‘The project is progressing well.'” For the GitHub tool: “Create a GitHub issue titled ‘Refactor authentication module’ with the description ‘The current authentication code needs to be updated for better security practices.'”

    Your AI client, empowered by the LLM, will interpret your natural language request, identify the appropriate tool, and prompt you to run it, providing the necessary parameters. This completes the core MCP Server Tutorial process!

6. Troubleshooting Common MCP Server Issues

Even with careful following of an MCP Server Tutorial, you might encounter issues. Here are some common problems and their solutions:

  • “Configuration Error” in Cursor/Claw Desktop:

    • JSON Syntax: The most frequent culprit. Use an online JSON validator (search for “JSON validator online”) to check your mcp.json file for syntax errors like missing commas, unclosed brackets, or incorrect quotation marks.
    • Incorrect Path: Double-check that the absolute paths in your mcp.json file (within the "args" array) precisely point to your main.py files. Typos are common.
    • Whitespace in Path: As mentioned, ensure no folders in the path to your main.py files have spaces in their names. Rename them to use hyphens or underscores.
    • Client Restart: Always ensure you’ve completely restarted the AI client after making changes to mcp.json.
  • “Cannot import name ‘Github’ from ‘github'” (or similar import errors):

    • Missing Dependency: This means the required Python package (pygithub in this case) was not installed in the environment where your MCP server is running.

      • If using uv: Ensure you ran uv add pygithub in the correct directory.
      • If using pip and venv: Ensure you activated the virtual environment (source .venv/bin/activate) before running pip install pygithub.
    • Case Sensitivity: Python is case-sensitive. Ensure your import statement matches the package name exactly (e.g., from github import Github for pygithub). The error cannot import name github from github often means pygithub was imported with a lowercase ‘g’ where an uppercase ‘G’ was expected for the class name.

  • “Failed due to…” Errors (after running a tool):

    • Check .env: Verify that all environment variables (MAIL_ACCOUNT, MAIL_PASSWORD, ACCESS_TOKEN, etc.) in your .env files are correctly spelled and contain the right values. A missing or incorrect credential is a common issue.
    • Permissions:

      • For email: Ensure your MAIL_ACCOUNT has permission to send emails through the specified SMTP_SERVER. Some email providers require “less secure app access” or app-specific passwords.
      • For GitHub: Ensure your ACCESS_TOKEN has the necessary scopes (e.g., repo or issues read/write) for the target repository.
    • Network Issues: Your server needs to be able to reach the external services (SMTP server, GitHub API). Check your internet connection or firewall settings.
    • Console Output: When troubleshooting, manually run your main.py script from the terminal (e.g., uv run main.py or python main.py) in its respective directory. Any direct error messages from your Python code will appear there, which can provide more detailed insights than the general “Failed due to…” message from the client.
  • Tool Not Triggering / LLM Not Calling Tool:

    • Clear Prompts: Ensure your prompts to the AI client are clear and unambiguous about the action you want to perform. The LLM needs enough context to infer that a tool should be used.
    • Tool Description: While not explicitly required for simple tools, adding a clear docstring to your @mcp.tool function ("""Sends an email...""") helps the LLM understand its purpose better. For complex tools, a detailed docstring explaining inputs and outputs is highly beneficial.
    • Parameters: If the LLM tries to call your tool but fails, it might be passing incorrect parameters. Review the issue_title: str, issue_text: str, etc., in your function signatures to ensure they match what the LLM expects.

By systematically checking these points, you can resolve most issues encountered during your MCP Server Tutorial journey.

7. Beyond the Basics: Advanced Customization and Next Steps

This MCP Server Tutorial has provided a strong foundation for building custom MCP servers. The true power of this protocol lies in its extensibility. Here are ideas for advanced customization and how you can take your MCP server development further:

  • Multiple Tools per Server:
    You are not limited to one tool per main.py file. You can define multiple @mcp.tool functions within a single FastMCP instance in the same file. This is efficient for grouping related functionalities. For example, a “Project Management Tool” server could have create_issue, assign_task, update_status, etc.
  • Richer Tool Descriptions (Docstrings):
    While our examples used short docstrings, for more complex tools, a detailed docstring explaining the tool’s purpose, parameters, and expected output will significantly improve the AI’s ability to use it correctly. The mcp-server library automatically exposes these docstrings to the client.
  • Integrating with Other APIs and Services:
    The possibilities are boundless. Think about what repetitive tasks you perform daily that involve interacting with web services.

    • Communication: Integrate with Slack, Microsoft Teams, Discord (e.g., “Send a Slack message to #dev-updates: ‘New build deployed.'”).
    • Project Management: Connect to Jira, Trello, Asana (e.g., “Create a Jira ticket for bug in module X”).
    • Cloud Services: Interact with AWS, Google Cloud, Azure APIs (e.g., “Start EC2 instance my-dev-server-01“).
    • Internal Tools: If your company has internal APIs, you can build MCP tools to interact with them directly.
    • Data Retrieval: Create tools to fetch data from databases or external data sources and present it to your AI.
  • Deployment Considerations:
    For long-running or shared MCP servers, consider more robust deployment strategies:

    • Docker: Containerize your MCP server application. This ensures consistent environments across different machines.
    • Cloud Functions (AWS Lambda, Azure Functions, Google Cloud Functions): For event-driven or infrequently used tools, serverless functions can be a cost-effective and scalable deployment option. You’d expose them via HTTP endpoints, and your mcp.json would point to these external URLs, rather than local stdio processes.
    • Dedicated Servers: For highly critical or frequently used tools, running them on a dedicated server might be appropriate.
  • Error Handling and Logging:
    For production-ready tools, enhance your error handling. Implement detailed logging to track tool usage, successes, and failures. This is crucial for debugging and monitoring your custom functionalities.
  • Input Validation:
    While type hints (str, int) provide basic validation, for more complex tools, add explicit input validation within your tool functions to ensure that the parameters received from the AI client are valid and safe to process.

This MCP Server Tutorial is just the beginning. The more you experiment and integrate your specific workflows, the more indispensable your AI-powered development environment will become. The ability to customize and extend your tools is a significant step towards a truly personalized and hyper-efficient coding experience.

Conclusion

You’ve successfully navigated this comprehensive MCP Server Tutorial, learning how to build and integrate your own custom MCP servers in Python. From setting up the environment to crafting functional email and GitHub issue creation tools, you now possess the core knowledge to extend the capabilities of AI clients like Cursor and Claw Desktop.

The Model Context Protocol offers a powerful paradigm for enhancing developer productivity by allowing AI models to seamlessly interact with custom functionalities. By applying the principles outlined in this guide, you can automate repetitive tasks, streamline complex workflows, and unlock a new dimension of intelligent assistance in your daily coding life.

Remember the key takeaways: secure credential management with .env files, the simplicity of FastMCP for defining tools, and meticulous configuration of your client’s mcp.json. Now, go forth and build amazing things! Experiment, integrate, and transform your development environment into an even more intelligent and responsive partner.

“`


Discover more from teguhteja.id

Subscribe to get the latest posts sent to your email.

Tags:

Leave a Reply

WP Twitter Auto Publish Powered By : XYZScripts.com