Model Context Protocol: Technical Guide for Developers

Complete technical guide to Model Context Protocol (MCP). Learn how to install, configure, and create your own MCP servers with TypeScript and Python code examples. Includes integrations with GitHub, PostgreSQL, Filesystem, and more.

Model Context Protocol: Technical Guide for Developers
Robert Cojocaru
demo image

What is Model Context Protocol (MCP)?

Model Context Protocol (MCP) is an open standard created by Anthropic that enables LLMs to connect with external tools in a standardized way. It works as a client-server protocol where AI models can execute actions on external systems.

MCP Architecture

MCP follows a client-server architecture:

ComponentDescriptionExample
HostApplication that initiates connectionsClaude Desktop, IDEs
ClientMaintains 1:1 connection with serverBuilt into host
ServerExposes resources and toolsGitHub MCP, Filesystem MCP

The Three Primitives

  • Tools: Functions the model can execute
  • Resources: Data the model can read
  • Prompts: Reusable context templates

SDK Installation

TypeScript/Node.js

npm install @modelcontextprotocol/sdk

Python

pip install mcp

Create Your First MCP Server

Basic TypeScript Server

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new Server(
  { name: "my-server", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

// Define a tool
server.setRequestHandler("tools/list", async () => ({
  tools: [{
    name: "greet",
    description: "Greets a person",
    inputSchema: {
      type: "object",
      properties: {
        name: { type: "string", description: "Person's name" }
      },
      required: ["name"]
    }
  }]
}));

// Implement the tool
server.setRequestHandler("tools/call", async (request) => {
  if (request.params.name === "greet") {
    const name = request.params.arguments.name;
    return { content: [{ type: "text", text: `Hello, ${name}!` }] };
  }
});

// Start server
const transport = new StdioServerTransport();
await server.connect(transport);

Basic Python Server

from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent

server = Server("my-server")

@server.list_tools()
async def list_tools():
    return [
        Tool(
            name="greet",
            description="Greets a person",
            inputSchema={
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "Name"}
                },
                "required": ["name"]
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "greet":
        return [TextContent(type="text", text=f"Hello, {arguments['name']}!")]

async def main():
    async with stdio_server() as (read, write):
        await server.run(read, write)

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

Configure Claude Desktop

Edit the Claude Desktop configuration file:

macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["/path/to/my-server/build/index.js"]
    },
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/user/projects"
      ]
    }
  }
}

Popular MCP Servers with Examples

1. Filesystem Server

Allows Claude to read and write files on your system.

Installation:

npx -y @modelcontextprotocol/server-filesystem /allowed/path

Configuration:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/user/Documents",
        "/Users/user/projects"
      ]
    }
  }
}

Usage in Claude: "Read the package.json file from my project"

2. GitHub Server

Manage repositories, issues, and pull requests.

Installation:

npm install -g @modelcontextprotocol/server-github

Configuration:

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_your_token_here"
      }
    }
  }
}

Available Tools:

ToolDescription
create_repositoryCreate new repository
search_repositoriesSearch repositories
create_issueCreate issue
create_pull_requestCreate PR
push_filesUpload files
list_commitsList commits

Usage in Claude: "Create an issue in my repo describing this bug"

3. PostgreSQL Server

Execute SQL queries on your database.

Installation:

npm install -g @modelcontextprotocol/server-postgres

Configuration:

{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-postgres",
        "postgresql://user:password@localhost:5432/my_database"
      ]
    }
  }
}

Usage in Claude: "Show me the last 10 registered users"

4. Playwright Server

Web browser automation.

Installation:

npm install -g @playwright/mcp

Configuration:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["-y", "@playwright/mcp"]
    }
  }
}

Available Tools:

ToolDescription
browser_navigateNavigate to URL
browser_clickClick element
browser_typeType text
browser_snapshotCapture page state
browser_screenshotTake screenshot

Usage in Claude: "Go to github.com and search for MCP repositories"

5. Supabase Server

Interact with your Supabase backend.

Installation:

npx supabase mcp

Configuration:

{
  "mcpServers": {
    "supabase": {
      "command": "npx",
      "args": ["-y", "supabase", "mcp"],
      "env": {
        "SUPABASE_URL": "https://your-project.supabase.co",
        "SUPABASE_SERVICE_ROLE_KEY": "your_service_role_key"
      }
    }
  }
}

Create an Advanced MCP Server

Example server exposing resources and tools:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListResourcesRequestSchema,
  ListToolsRequestSchema,
  ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
  { name: "api-server", version: "1.0.0" },
  { capabilities: { tools: {}, resources: {} } }
);

// Simulated database
const users = [
  { id: 1, name: "Ana", email: "ana@example.com" },
  { id: 2, name: "Carlos", email: "carlos@example.com" },
];

// List available resources
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: [{
    uri: "api://users",
    name: "User list",
    mimeType: "application/json"
  }]
}));

// Read a resource
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  if (request.params.uri === "api://users") {
    return {
      contents: [{
        uri: "api://users",
        mimeType: "application/json",
        text: JSON.stringify(users, null, 2)
      }]
    };
  }
});

// List tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "create_user",
      description: "Creates a new user",
      inputSchema: {
        type: "object",
        properties: {
          name: { type: "string" },
          email: { type: "string", format: "email" }
        },
        required: ["name", "email"]
      }
    },
    {
      name: "search_user",
      description: "Search user by name",
      inputSchema: {
        type: "object",
        properties: {
          query: { type: "string" }
        },
        required: ["query"]
      }
    }
  ]
}));

// Execute tools
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  switch (name) {
    case "create_user":
      const newId = users.length + 1;
      const newUser = { id: newId, ...args };
      users.push(newUser);
      return {
        content: [{
          type: "text",
          text: `User created with ID: ${newId}`
        }]
      };

    case "search_user":
      const results = users.filter(u =>
        u.name.toLowerCase().includes(args.query.toLowerCase())
      );
      return {
        content: [{
          type: "text",
          text: JSON.stringify(results, null, 2)
        }]
      };

    default:
      throw new Error(`Unknown tool: ${name}`);
  }
});

const transport = new StdioServerTransport();
await server.connect(transport);

Debugging MCP Servers

Using MCP Inspector

npx @modelcontextprotocol/inspector node build/index.js

This opens a web interface where you can:

  • View available tools and resources
  • Test tool calls
  • Inspect responses

Claude Desktop Logs

macOS:

tail -f ~/Library/Logs/Claude/mcp*.log

Windows (PowerShell):

Get-Content -Path "$env:APPDATA\Claude\Logs\mcp*.log" -Wait

Best Practices

  1. Input validation: Use JSON Schema to validate arguments
  2. Error handling: Return clear error messages
  3. Minimal permissions: Only expose what's necessary
  4. Documentation: Clear descriptions for tools and resources
  5. Testing: Use the Inspector before deploying

Additional Resources

MCP is transforming how developers build AI applications. With these examples, you can start creating your own integrations.

Other posts