Coding Your Own Custom MCP Server in Python - Full Tutorial

NeuralNineAbout 5 min readJul 1, 2025Watch original
THE SUMMARYAI-generated

Key Concepts

  • MCP (Model Context Protocol): A standard for communication between AI tools and functionalities.
  • MCP Server: Provides specific functionalities (e.g., sending emails, creating GitHub issues).
  • MCP Client: Uses the functionalities provided by the MCP server (e.g., Cursor, CLion Desktop).
  • FastMCP: A framework for building MCP servers, similar to FastAPI.
  • Tools: Functions within an MCP server that perform specific actions.
  • Environment Variables: Used to store sensitive information like passwords and API keys securely.
  • JSON Configuration: Used to define and configure MCP servers for use in MCP clients.

MCP Server Implementation: Sending Emails

1. Setting up the Environment

  • Create a directory for the MCP server (e.g., mcp_mail_function).
  • Initialize a Python environment using uv init (or pip, virtualenv).
  • Install necessary packages: python-dotenv (for loading environment variables) and mcp-server (for MCP functionality). uv add python-dotenv mcp-server

2. Code Structure (main.py)

  • Imports: os, smtplib, email.message.EmailMessage, dotenv.load_dotenv, mcp_server.fastmcp.FastMCP.

  • MCP Instance: Create a FastMCP instance: mcp = FastMCP().

  • Load Environment Variables: load_dotenv() to load variables from a .env file.

  • Tool Definition: Use @mcp.tool decorator to define a function as an MCP tool.

    @mcp.tool
    def send_email(to: str, subject: str, body: str) -> str:
        # Implementation here
    
  • Function Parameters: Define parameters for the tool (e.g., to, subject, body as strings).

  • Return Type: Specify the return type of the tool (e.g., str for success/failure message).

3. Email Sending Logic

  • Retrieve Credentials: Get email account, password, SMTP server, and SMTP port from environment variables using os.getenv().
  • Create Email Message: Construct an EmailMessage object, setting the from, to, subject, and content.
  • Connect to SMTP Server: Use smtplib.SMTP to connect to the SMTP server and port.
  • Start TLS: Use server.starttls() to encrypt the connection.
  • Login: Authenticate with the email account and password using server.login().
  • Send Email: Send the email message using server.send_message().
  • Error Handling: Use a try...except block to catch exceptions and return an error message.

4. .env File

  • Create a .env file in the same directory as main.py.

  • Store sensitive information as key-value pairs:

    [email protected]
    MAIL_PASSWORD=your_password
    SMTP_SERVER=mail.yourserver.de
    SMTP_PORT=587
    

5. Running the MCP Server

  • Use if __name__ == "__main__": block to run the MCP server.
  • Call mcp.run(transport="stdio") to start the server using standard input/output.

6. JSON Configuration for Cursor

  • Create or modify the ~/.config/cursor/mcp.json file.

  • Define the MCP server with a name, command, and arguments:

    {
      "mcp_servers": {
        "mail_tool": {
          "command": "uv",
          "args": [
            "-d",
            "/home/neural9/documents/programming/neural9/tutorial/mcp_mail_function",
            "run",
            "main.py"
          ]
        }
      }
    }
    
  • Command: The command to execute the MCP server (e.g., uv).

  • Args: A list of arguments for the command, including the directory and the Python script.

MCP Server Implementation: Creating GitHub Issues

1. Setting up the Environment

  • Create a directory for the MCP server (e.g., mcp_issue_function).
  • Initialize a Python environment using uv init.
  • Install necessary packages: python-dotenv, mcp-server, and PyGithub. uv add python-dotenv mcp-server pygithub

2. Code Structure (main.py)

  • Imports: os, github.Github, dotenv.load_dotenv, mcp_server.fastmcp.FastMCP.

  • MCP Instance: Create a FastMCP instance: mcp = FastMCP().

  • Load Environment Variables: load_dotenv() to load variables from a .env file.

  • Tool Definition: Use @mcp.tool decorator to define a function as an MCP tool.

    @mcp.tool
    def create_issue(issue_title: str, issue_text: str) -> str:
        # Implementation here
    
  • Function Parameters: Define parameters for the tool (e.g., issue_title, issue_text as strings).

  • Return Type: Specify the return type of the tool (e.g., str for success/failure message).

3. GitHub Issue Creation Logic

  • Retrieve Access Token: Get the GitHub access token from environment variables using os.getenv().
  • Authenticate with GitHub: Create a Github object using the access token: g = Github(os.getenv("ACCESS_TOKEN")).
  • Get Repository: Get the repository object using g.get_repo("neural9/tutorial-mcp-repo").
  • Create Issue: Create a new issue using repo.create_issue(title=issue_title, body=issue_text).
  • Error Handling: Use a try...except block to catch exceptions and return an error message.

4. .env File

  • Create a .env file in the same directory as main.py.

  • Store the GitHub access token:

    ACCESS_TOKEN=your_github_access_token
    

5. Running the MCP Server

  • Use if __name__ == "__main__": block to run the MCP server.
  • Call mcp.run(transport="stdio") to start the server using standard input/output.

6. JSON Configuration for Cursor

  • Add a new tool definition to the ~/.config/cursor/mcp.json file:

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

General Notes and Troubleshooting

  • Whitespace in Paths: Avoid whitespace in the paths to your MCP server directories, as Cursor may not handle them correctly on Linux. Use hyphens or underscores instead.
  • Restart Cursor: After modifying the mcp.json file, restart Cursor for the changes to take effect.
  • Configuration Errors: Check the Cursor settings (Tools) for configuration errors.
  • Dependency Issues: Ensure all necessary packages are installed in the correct environment.
  • Permissions: Make sure the GitHub access token has the necessary permissions to create issues in the specified repository.
  • Case Sensitivity: Pay attention to case sensitivity, especially when importing modules (e.g., github.Github vs. GitHub).

Synthesis/Conclusion

The video demonstrates how to create custom MCP servers for extending the functionality of AI-powered coding tools like Cursor and CLion Desktop. By implementing MCP servers for sending emails and creating GitHub issues, the video illustrates the flexibility and power of the MCP standard. The key takeaways are the importance of using environment variables for security, the simplicity of the FastMCP framework, and the ability to integrate custom tools into existing workflows through JSON configuration. The demonstrated examples provide a foundation for building more complex and specialized MCP servers to automate various development tasks.

AI summaries can miss context or contain errors. Check important details against the original video.

Go a little deeper.

Have a question about this video? Load its transcript to open the video chat.