Skip to content
BotServBotServ
MCPMCP ServerModel Context ProtocolAI AgentsLocal AI

Build Your Own MCP Server

Create a custom MCP Server for local AI agents. Protocol, tools, transport, JSON-RPC and best practices.

S

schutzgeist

4 min read
Build Your Own MCP Server

Building Your Own MCP Server

What This Article Covers

  • What the Model Context Protocol (MCP) is.
  • How MCP servers are structured.
  • How to define tools, resources, and prompts.
  • Available transport mechanisms.
  • A simple example in TypeScript and Python.
  • Best practices and common pitfalls.

Introduction: Building Your Own MCP Server

The Model Context Protocol (MCP) is an open standard for connecting AI agents to external data sources and tools. An MCP server provides functions that an MCP client can invoke. By building your own MCP server, you can integrate company-specific databases, APIs, or local tools into AI workflows.

This article walks you through creating a simple MCP server from scratch. It’s written for developers and self-hosters who want to make their own tools accessible to agents.

What Is MCP?

MCP defines standardized communication between an MCP client and an MCP server. The client is typically an AI agent or chat interface. The server provides:

  • Tools: Functions the agent can call.
  • Resources: Data the agent can read.
  • Prompts: Templates for specific tasks.
  • Sampling: Reverse requests from the server to the client.

Communication happens over JSON-RPC 2.0.

Key Concepts

  • MCP Host: An application containing an MCP client, such as Claude Desktop or an agent framework.
  • MCP Client: Communicates with one or more MCP servers.
  • MCP Server: Provides tools, resources, and prompts.
  • Tool: An executable function with a name, description, and JSON schema.
  • Resource: An addressable data source, such as a file or database table.
  • Transport: The communication pathway, usually stdio or Server-Sent Events over HTTP.
  • JSON-RPC: A lightweight protocol for remote procedure calls.

Transport Mechanisms

stdio

The server runs as a subprocess of the client. Communication happens through standard input and output. This is particularly straightforward for local tools.

SSE over HTTP

The server runs as its own service. The client connects via HTTP and receives messages through Server-Sent Events. This works well for remote setups or multiple clients.

Structure of an MCP Server

An MCP server must provide the following:

  1. Initialization: Exchange protocol version and capabilities.
  2. Capabilities: Announce which tools, resources, and prompts are available.
  3. Tool Handlers: Execute functions for each tool.
  4. Resource Handlers: Return resources on demand.
  5. Error Handling: Send clear error messages to the client.

Simple Example in Python

This server offers a get_current_time tool that returns the current time.

# server.py
import json
import sys
from datetime import datetime


def send_message(msg):
    payload = json.dumps(msg)
    print(payload, flush=True)


def handle_request(req):
    method = req.get("method")
    id_ = req.get("id")

    if method == "initialize":
        return {
            "jsonrpc": "2.0",
            "id": id_,
            "result": {
                "protocolVersion": "2024-11-05",
                "capabilities": {"tools": {}},
                "serverInfo": {"name": "time-server", "version": "1.0.0"},
            },
        }

    if method == "tools/list":
        return {
            "jsonrpc": "2.0",
            "id": id_,
            "result": {
                "tools": [
                    {
                        "name": "get_current_time",
                        "description": "Returns the current time.",
                        "inputSchema": {
                            "type": "object",
                            "properties": {},
                        },
                    }
                ]
            },
        }

    if method == "tools/call":
        name = req["params"]["name"]
        if name == "get_current_time":
            now = datetime.now().isoformat()
            return {
                "jsonrpc": "2.0",
                "id": id_,
                "result": {
                    "content": [{"type": "text", "text": f"Current time: {now}"}]
                },
            }
        return {"jsonrpc": "2.0", "id": id_, "error": {"code": -32601, "message": "Tool not found"}}

    return {"jsonrpc": "2.0", "id": id_, "error": {"code": -32601, "message": "Method not found"}}


def main():
    for line in sys.stdin:
        line = line.strip()
        if not line:
            continue
        req = json.loads(line)
        resp = handle_request(req)
        send_message(resp)


if __name__ == "__main__":
    main()

The server reads JSON-RPC requests line by line from stdin and writes responses to stdout.

Setting Up in Claude Desktop

Once you’ve built the server file, you can register it in Claude Desktop. On macOS, the configuration lives at ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "time": {
      "command": "python3",
      "args": ["/path/to/server.py"]
    }
  }
}

After restarting Claude Desktop, the get_current_time tool becomes available.

MCP SDKs

Rather than building everything by hand, you can use SDKs:

  • Python SDK: mcp on PyPI.
  • TypeScript SDK: @modelcontextprotocol/sdk on npm.
  • Java SDK: mcp-java.

These SDKs handle protocol versions, transport, and JSON-RPC details for you.

Best Practices

  • Clear tool descriptions: The language model decides whether to use a tool based on its description.
  • Secure input validation: Validate every tool argument.
  • Meaningful error messages: Return errors that help the agent respond appropriately.
  • Statelessness: Tools should avoid hidden state where possible.
  • Permissions: The server should only perform operations it’s authorized to do.
  • Logging: Write output to stderr so stdout stays clean for JSON-RPC.

Common Pitfalls

  • Malformed JSON-RPC: Missing jsonrpc or id fields.
  • Polluted stdout: Debug output on stdout instead of stderr.
  • Missing capabilities: Client doesn’t know which tools are available.
  • Unclear tool descriptions: Model calls the wrong tool.
  • No input validation: Malicious input causes errors.
  • Transport mixing: Don’t mix stdio and HTTP in the same server.

Further Resources

FAQ: Building Your Own MCP Server

Do I need a special framework? No. An MCP server can be implemented in any language that speaks JSON-RPC. SDKs make it easier to get started.

Can an MCP server offer multiple tools? Yes. tools/list returns an array of all available tools.

How secure is an MCP server? It runs locally and only executes what’s defined in the code. Still, validate every input and restrict permissions.

What’s the difference between stdio and SSE? stdio is simpler for local processes; SSE suits network or container setups.

Can I run an MCP server in Docker? Yes. In that case, SSE over HTTP or a wrapper that provides stdio to the client works well.

Sources and Further Reading

Summary: Building Your Own MCP Server

Your own MCP server extends AI agents with specialized tools and data sources. The Model Context Protocol relies on JSON-RPC and supports both stdio and HTTP transport. Clear tool descriptions, secure input validation, clean error handling, and proper separation of stdout and stderr are essential. Once you understand the protocol, you can safely integrate company-specific functionality into local agent workflows.

Back to Blog
Share:

Related Posts