DEV Community

Cover image for Build Your First MCP Server in 30 Minutes: Complete Guide for Claude and Cursor
Kuldeep Paul
Kuldeep Paul

Posted on

Build Your First MCP Server in 30 Minutes: Complete Guide for Claude and Cursor

Build Your First MCP Server in 30 Minutes: Complete Guide for Claude and Cursor

TL;DR

  • The Model Context Protocol (MCP) standardizes how artificial intelligence applications connect to external tools, databases, and APIs using JSON-RPC 2.0.
  • Developers can build your first MCP server in under 30 minutes using Python and the FastMCP framework without manual schema configuration.
  • Connecting an MCP server to Claude Desktop or Cursor requires adding a simple JSON configuration that declares the executable and environment.
  • Running MCP servers locally via standard input and output works for personal workflows, but team deployments require a centralized gateway.
  • An open-source gateway like Bifrost centralizes tool discovery, slashes token costs via code execution, and enforces endpoint governance with Bifrost Edge.

The Model Context Protocol (MCP) is an open specification designed to standardize how artificial intelligence models interact with external data sources, local files, and third-party tools. Learning how to build your first MCP server allows engineers to give assistants like Claude Desktop and Cursor direct access to custom databases, developer environments, and internal APIs without writing fragile custom integrations for every platform. Bifrost, an open-source AI gateway written in Go by Maxim AI, unifies model access and acts as an enterprise MCP gateway to manage, route, and govern these tool connections across organizations.

┌────────────────────────┐         JSON-RPC 2.0          ┌────────────────────────┐
│  Host (Claude, Cursor) │ ◄───────────────────────────► │    Custom MCP Server   │
└────────────────────────┘        (stdio / HTTP)         └────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

What is the Model Context Protocol?

The Model Context Protocol (MCP) is an open standard introduced by Anthropic that enables language models to securely discover, query, and execute capabilities provided by external services. Instead of relying on proprietary function-calling implementations for each distinct model provider, MCP provides a unified JSON-RPC 2.0 communication format that works uniformly across diverse AI hosts and client interfaces.

Before MCP emerged, integrating an AI model with an external service required writing custom API translation layers, formatting custom JSON schemas for tool definitions, and managing connection state manually. When switching from an OpenAI model to an Anthropic model, or from a terminal tool to an IDE assistant, developers often had to duplicate their tool schemas and execution code.

MCP establishes a clear client-server architecture consisting of three primary layers:

  1. Host Applications: The AI user interface or runtime environment, such as Claude Desktop, Cursor, Zed Editor, or a CLI coding assistant.
  2. MCP Clients: Internal connectors within the host application that maintain a 1:1 stateful or stateless connection with individual servers.
  3. MCP Servers: Lightweight programs that expose specific capabilities (tools, prompts, or dynamic resources) to the host via standardized communication channels.

MCP supports three primary capabilities:

  • Tools: Callable executable functions that the model can invoke to perform actions, such as executing a database query or fetching weather data.
  • Resources: Structured data or text that the host can read into the model context, similar to reading a local file or inspecting a log stream.
  • Prompts: Pre-configured conversation templates and workflows that users can invoke directly from the host interface.

Prerequisites and Environment Setup

Building and running a local MCP server requires minimal software. For this guide, you will need:

  • Python 3.10 or higher: FastMCP requires modern Python typing features.
  • uv or pip: uv is recommended for fast package resolution and isolated environment execution.
  • Claude Desktop or Cursor: A host application capable of registering MCP servers.
  • Node.js (Optional): Helpful if you wish to run the official MCP Inspector for diagnostic debugging.

To initialize a clean directory structure using uv, run the following commands in your terminal:

# Create and enter a new project directory
mkdir my-first-mcp-server
cd my-first-mcp-server

# Initialize the project and virtual environment
uv init
uv add "fastmcp>=0.4.0"
Enter fullscreen mode Exit fullscreen mode

If you prefer standard Python tooling, configure a virtual environment with venv:

python3 -m venv .venv
source .venv/bin/activate
pip install "fastmcp>=0.4.0"
Enter fullscreen mode Exit fullscreen mode

FastMCP is a high-level Python framework designed specifically to build compliant MCP servers with minimal boilerplate. It inspects Python type hints and docstrings to generate complete JSON Schema definitions automatically, eliminating the need to write manual JSON-RPC message handlers.


Step 1: Writing Your First MCP Server with FastMCP

To illustrate practical utility, we will build a local developer context server. It will provide two tools:

  1. calculate_hash: Generates MD5, SHA-1, or SHA-256 hashes of input strings.
  2. query_project_db: Queries a lightweight local SQLite database to retrieve application metrics.

Create a file named server.py in your project folder and add the following implementation:

"""
Developer Context MCP Server
Demonstrates tool exposure and resource management using FastMCP.
"""

import hashlib
import sqlite3
from typing import Dict, List
from fastmcp import FastMCP

# Initialize the MCP Server with a human-readable title
mcp = FastMCP(
    name="DevContextServer",
    description="A developer context server providing utility tools and database access."
)

# Setup a demo SQLite database in memory
def init_db():
    conn = sqlite3.connect("project_metrics.db")
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS error_logs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            service VARCHAR(50),
            error_code VARCHAR(20),
            message TEXT
        )
    """)
    cursor.execute("SELECT COUNT(*) FROM error_logs")
    if cursor.fetchone()[0] == 0:
        cursor.executemany("""
            INSERT INTO error_logs (service, error_code, message) VALUES (?, ?, ?)
        """, [
            ("auth-api", "AUTH_401", "JWT signature verification failed"),
            ("billing-worker", "PAY_500", "Stripe gateway timeout during charge capture"),
            ("inference-router", "LLM_429", "Upstream rate limit reached on Anthropic endpoint")
        ])
    conn.commit()
    conn.close()

init_db()

@mcp.tool()
def calculate_hash(text: str, algorithm: str = "sha256") -> str:
    """
    Computes a cryptographic digest for a given text string.

    Args:
        text: The input string to hash.
        algorithm: Hashing algorithm ('sha256', 'sha1', 'md5'). Defaults to 'sha256'.
    """
    algo = algorithm.lower().strip()
    if algo == "sha256":
        return hashlib.sha256(text.encode("utf-8")).hexdigest()
    elif algo == "sha1":
        return hashlib.sha1(text.encode("utf-8")).hexdigest()
    elif algo == "md5":
        return hashlib.md5(text.encode("utf-8")).hexdigest()
    else:
        raise ValueError(f"Unsupported algorithm '{algorithm}'. Choose sha256, sha1, or md5.")

@mcp.tool()
def query_project_db(service_name: str) -> List[Dict[str, str]]:
    """
    Retrieves logged error events for a specific backend service.

    Args:
        service_name: Name of the service to query (e.g., 'auth-api', 'billing-worker', 'inference-router').
    """
    conn = sqlite3.connect("project_metrics.db")
    cursor = conn.cursor()
    cursor.execute(
        "SELECT service, error_code, message FROM error_logs WHERE service = ?",
        (service_name.strip(),)
    )
    rows = cursor.fetchall()
    conn.close()

    return [
        {"service": row[0], "error_code": row[1], "message": row[2]}
        for row in rows
    ]

if __name__ == "__main__":
    # Start the server using standard input/output (stdio) transport
    mcp.run(transport="stdio")
Enter fullscreen mode Exit fullscreen mode

How the Code Functions

  • The FastMCP Class: Instantiates the protocol engine. The name provided appears in host debug logs and capability registries.
  • The @mcp.tool() Decorator: Registers the standard Python function as an MCP tool.
  • Type Annotations & Docstrings: FastMCP parses the function signature (str, List[Dict]) along with the docstring argument explanations to generate JSON Schema definitions. The language model uses these descriptions to decide when and how to call your tool.
  • The stdio Transport: The server reads JSON-RPC requests from standard input (stdin) and writes responses to standard output (stdout). This avoids opening local TCP ports or configuring web servers.

A close-up view of an open notebook beside a precision mechanical instrument, with small geometric gears fitting togethe


Step 2: Testing Your Server with the MCP Inspector

Before connecting the server to an IDE or desktop application, it is best practice to verify that the tool schemas compile correctly. The official MCP project provides an interactive browser-based testing utility known as the MCP Inspector.

Run the inspector by invoking npx with your Python executable:

# Verify using the MCP Inspector
npx @modelcontextprotocol/inspector uv run server.py
Enter fullscreen mode Exit fullscreen mode

If you are using a standard virtual environment rather than uv:

npx @modelcontextprotocol/inspector /path/to/my-first-mcp-server/.venv/bin/python server.py
Enter fullscreen mode Exit fullscreen mode

The inspector launches a local web UI (usually at http://localhost:5173). In the interface:

  1. Navigate to the Tools tab.
  2. Click List Tools to confirm that both calculate_hash and query_project_db are visible.
  3. Select query_project_db, pass auth-api as the service_name, and click Run Tool.
  4. Confirm that the JSON-RPC response contains the expected records from your SQLite database.
┌─────────────────────────────────────────────────────────────┐
│                       MCP Inspector                         │
├─────────────────────────────────────────────────────────────┤
│ Available Tools:                                            │
│   • calculate_hash(text, algorithm="sha256")                │
│   • query_project_db(service_name)                          │
│                                                             │
│ Executing: query_project_db("auth-api")                     │
│ Result: [{"service": "auth-api", "error_code": "AUTH_401"}] │
└─────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Step 3: Connecting Your Server to Claude Desktop

Claude Desktop provides first-class support for MCP servers running over local stdio. Claude starts the server process as a background child process when the application boots.

Configuration File Locations

The configuration file is stored as a JSON document in your operating system user directory:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json

Adding the Configuration

Open the file in a text editor (or click Claude > Settings > Developer > Edit Config within the app) and insert the configuration:

{
  "mcpServers": {
    "developer-context": {
      "command": "/Users/username/.local/bin/uv",
      "args": [
        "run",
        "--directory",
        "/absolute/path/to/my-first-mcp-server",
        "server.py"
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

If you are using standard Python without uv, specify the absolute path to your virtual environment interpreter directly:

{
  "mcpServers": {
    "developer-context": {
      "command": "/absolute/path/to/my-first-mcp-server/.venv/bin/python",
      "args": [
        "/absolute/path/to/my-first-mcp-server/server.py"
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Important: Always provide absolute paths for the command, --directory, and script arguments. Desktop applications do not inherit standard shell environment paths like .bashrc or .zshrc.

Verification in Claude

  1. Completely quit and restart Claude Desktop.
  2. Look for the small hammer icon at the bottom-right corner of the chat input box.
  3. Click the icon to verify that calculate_hash and query_project_db are listed.
  4. Test the integration with a natural language prompt: > "What errors have been logged recently for the auth-api service? Also generate a SHA-256 hash of the error code."

Claude will trigger the appropriate tools, inspect the database, compute the digest, and present the structured answer.


Step 4: Connecting Your Server to Cursor

Cursor supports MCP connections natively, allowing developers to bring external tools into Composer and chat sessions.

Project-Level vs. Global Configuration

Cursor supports two configuration scopes:

  • Project Scope (.cursor/mcp.json): Placed in the root directory of your workspace. Ideal for sharing repository-specific tools with team members.
  • Global Scope (~/.cursor/mcp.json): Placed in your user home directory to make tools accessible across all workspaces.

Create a .cursor/mcp.json file inside your project root with the following contents:

{
  "mcpServers": {
    "developer-context": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/absolute/path/to/my-first-mcp-server",
        "server.py"
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
my-project/
├── .cursor/
│   └── mcp.json
├── src/
└── server.py
Enter fullscreen mode Exit fullscreen mode

Verification in Cursor

  1. Open Cursor Settings and navigate to Features > MCP.
  2. Confirm that developer-context displays a green status indicator.
  3. Open Cursor Composer (Command + I or Ctrl + I) or the Chat panel (Command + L or Ctrl + L).
  4. Prompt the assistant: > "Check our project database for billing-worker errors and summarize the root cause."

Cursor will invoke query_project_db, read the error records, and synthesize the technical resolution.


Local Subprocess vs. Production MCP Architecture

Running MCP servers locally via stdio subprocesses is effective for personal experimentation. However, as teams expand AI tooling across multiple developers, local setups reveal significant architectural limitations.

Architectural Dimension Local Process (stdio) Centralized Gateway Architecture
Process Model Spawned child process per host Centralized, persistent microservice cluster
Credential Management Stored in cleartext JSON on laptops Managed centrally via Vault or Secrets Manager
Network Reach Local host filesystem and loopback only VPC-internal, cloud-native HTTP/SSE endpoints
Audit & Visibility No centralized logging Unified request logs, audit trails, and latency metrics
Token Efficiency Multi-turn back-and-forth tool calls Code Mode execution (up to 92.8% token reduction)
Access Governance Unrestricted tool execution Granular RBAC and per-key tool filtering

A structured architectural junction showing multiple fiber-optic pathways converging from separate points into a unified


Scaling MCP: Centralized Gateways and Endpoint Governance

When scaling beyond a single developer laptop, three major operational challenges emerge:

  1. Credential Proliferation: Providing developers with access to Jira, GitHub, or database MCP servers often leads to distributing sensitive API keys directly onto local developer machines.
  2. Context Window Saturation: Loading dozens of tool schemas into every prompt consumes thousands of tokens before conversation even starts, driving up latency and inference costs.
  3. Shadow AI and Tooling Blind Spots: Security teams have zero visibility into which third-party MCP servers are running locally on employee laptops.

Centralizing Tools with an MCP Gateway

To address infrastructure complexity, engineering teams use Bifrost as a centralized MCP gateway. Bifrost sits between AI applications and your MCP tool ecosystem, acting as both an MCP client and an MCP server:

┌─────────────────────────────────┐
│ AI Clients (Claude, Cursor, etc)│
└────────────────┬────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────────────┐
│                      Bifrost MCP Gateway                        │
│  - Virtual Keys & Rate Limits   - OAuth 2.0 Auth Broker         │
│  - Tool Whitelisting & RBAC     - Code Mode (92% Token Savings) │
└───────┬───────────────────────────────┬─────────────────────────┘
        │                               │
        ▼                               ▼
┌──────────────────────┐    ┌──────────────────────┐
│ Internal MCP Servers │    │ External SaaS Tools  │
└──────────────────────┘    └──────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Instead of configuring dozens of local servers in claude_desktop_config.json, engineers configure their tools once in Bifrost. Bifrost then provides advanced operational capabilities:

  • Unified Tool Connectivity: Connects upstream MCP servers over STDIO, HTTP, or SSE protocols.
  • Autonomous Tool Execution: Enables Agent Mode, allowing models to execute approved tools iteratively without manual back-and-forth roundtrips.
  • Dramatic Token Reduction: Features Code Mode, where the model writes Python scripts to orchestrate multiple tools in a single execution step, reducing input token usage by up to 92.8% and cutting latency by 40%.
  • Enterprise Access Governance: Implements MCP tool filtering and virtual keys to control exactly which tools are visible to specific users or teams.
  • Enterprise Authentication: Manages upstream MCP authentication with support for OAuth 2.0, PKCE, and per-user token exchange, preventing credential leakage to developer machines.

Endpoint AI Governance with Bifrost Edge

Beyond routing, Bifrost applies governance and security controls (virtual keys, budgets, guardrails, audit logs) centrally, and Bifrost Edge extends that same governance and security to AI traffic on employee machines, with endpoint enforcement and MCP governance to discover and restrict unapproved MCP tools across a fleet.

Currently in alpha, Bifrost Edge is deployed across macOS, Windows, and Linux devices via enterprise MDM solutions like Jamf or Microsoft Intune. It inventories the local MCP servers running inside desktop apps like Claude Desktop, Cursor, and terminal coding agents, giving security administrators a centralized catalog to approve or block MCP servers fleet-wide.


Frequently Asked Questions

What is the difference between an MCP tool and an MCP resource?

An MCP tool is an executable function that performs actions or computations and returns results dynamically to the language model. An MCP resource is a read-only data source (such as a documentation file, database table schema, or system log) that the client exposes as contextual information for the model to reference.

Can I build an MCP server in languages other than Python?

Yes. MCP is an open specification based on standard JSON-RPC 2.0 messages. Anthropic and the open-source community maintain official SDKs for TypeScript/JavaScript, Python, Kotlin, and Java. Any programming language capable of reading from standard input and output or serving HTTP endpoints can implement an MCP server.

Why does Claude Desktop fail to connect to my local MCP server?

The most frequent cause is relative system paths in claude_desktop_config.json. Desktop applications execute in non-interactive environments that do not inherit user shell aliases or updated $PATH variables. Ensure that the command and script arguments use absolute paths (e.g., /Users/username/.local/bin/uv instead of uv).

How do I secure credentials when deploying MCP servers across a team?

Avoid hardcoding API keys in local JSON configuration files. Instead, deploy MCP servers behind an enterprise gateway like Bifrost. Bifrost handles credential storage through integrations with HashiCorp Vault, AWS Secrets Manager, or Google Secret Manager, resolving credentials server-side and injecting them dynamically at runtime.

What is the advantage of using HTTP transport instead of stdio?

Standard input/output (stdio) transport requires the MCP client to spawn a local process on the user machine. HTTP (or Streamable HTTP) transport allows the server to run as an independent, scalable remote web service hosted on cloud infrastructure, making it accessible to multiple users simultaneously without requiring local Python environments.


Sources


Next Steps

Building your first MCP server bridges the gap between language models and actual infrastructure. While local stdio setups offer a fast on-ramp for individual developers, scaling tools across an enterprise demands centralized governance, rate-limiting, and robust token optimization.

Engineering teams evaluating enterprise MCP deployment can request a Bifrost demo to see centralized tool management in action or explore the open-source Bifrost repository to start running high-performance AI infrastructure today.

Top comments (0)