Build a Simple MCP Server in PythonΒΆ

This notebook walks you through building a minimal MCP (Model Context Protocol) server from scratch. By the end you will have a working server that exposes two tools an AI agent can call:

  1. word_count β€” count words in a text

  2. search_glossary β€” look up AI/ML terms in a small glossary

What you will learn:

  • The structure of an MCP server

  • How to define tools with typed parameters

  • How to register tools with the MCP SDK

  • How to configure VS Code to use your server

Prerequisites: Python 3.10+, mcp package

1 β€” Install the MCP SDKΒΆ

The mcp package is the official Python SDK for building MCP servers.

# Check if mcp is available (install if needed)
try:
    import mcp
    print(f"MCP SDK version: {mcp.__version__}")
except ImportError:
    print("MCP SDK not installed. Install with: pip install mcp")
    print("For this notebook, we'll demonstrate the server code structure")
    print("without requiring the mcp package to be installed.")

2 β€” Define Your ToolsΒΆ

An MCP tool is a Python function with:

  • A name (string, snake_case)

  • A description (what the tool does, read by the AI to decide when to use it)

  • Parameters with types and descriptions

  • A return value (string or structured data)

Let’s define two simple tools.

# ── Tool 1: Word Count ────────────────────────────────────────────

def word_count(text: str) -> dict:
    """Count words, sentences, and characters in the given text."""
    words = text.split()
    sentences = text.count(".") + text.count("!") + text.count("?")
    return {
        "words": len(words),
        "sentences": max(sentences, 1),
        "characters": len(text),
        "avg_word_length": round(sum(len(w) for w in words) / max(len(words), 1), 1),
    }


# ── Tool 2: Glossary Lookup ───────────────────────────────────────

GLOSSARY = {
    "rag": "Retrieval-Augmented Generation: combining retrieval from a knowledge base with LLM generation to produce grounded answers.",
    "mcp": "Model Context Protocol: an open standard for connecting AI clients to external tools and data sources.",
    "embedding": "A dense vector representation of text (or other data) that captures semantic meaning in a continuous space.",
    "fine-tuning": "Adapting a pre-trained model to a specific task or domain by training on a smaller, targeted dataset.",
    "lora": "Low-Rank Adaptation: a parameter-efficient fine-tuning method that trains small rank-decomposition matrices instead of all weights.",
    "vector database": "A database optimized for storing and querying high-dimensional vectors, used for similarity search in AI applications.",
    "tokenizer": "A component that splits text into tokens (subwords, characters, or words) for processing by a language model.",
    "transformer": "A neural network architecture based on self-attention, the foundation of modern LLMs like GPT and BERT.",
    "hyde": "Hypothetical Document Embeddings: generating a hypothetical answer to a query, then using its embedding for retrieval.",
    "crag": "Corrective RAG: a pattern that grades retrieval quality and retries or abstains when evidence is weak.",
}


def search_glossary(term: str) -> dict:
    """Look up an AI/ML term in the glossary. Returns the definition if found."""
    key = term.lower().strip()
    if key in GLOSSARY:
        return {"term": key, "definition": GLOSSARY[key], "found": True}
    # Fuzzy match: check if the search term appears in any key
    matches = [k for k in GLOSSARY if key in k or k in key]
    if matches:
        return {"term": matches[0], "definition": GLOSSARY[matches[0]], "found": True, "note": "fuzzy match"}
    return {"term": key, "found": False, "available_terms": sorted(GLOSSARY.keys())}


# Quick test
print("=== word_count ===")
print(word_count("The quick brown fox jumps over the lazy dog."))

print("\n=== search_glossary ===")
print(search_glossary("RAG"))
print(search_glossary("attention"))

3 β€” The MCP Server ScriptΒΆ

Below is the complete server script that registers both tools with the MCP framework. In production you would save this as a .py file and point your MCP config at it.

We display it here as a string so the notebook runs without requiring the mcp package. You can copy this to a file and run it directly.

SERVER_CODE = '''
#!/usr/bin/env python3
"""Minimal MCP server exposing word_count and search_glossary tools."""

from mcp.server.fastmcp import FastMCP

# Create server instance
mcp = FastMCP("glossary-server")


# ── Tool 1: Word Count ────────────────────────────────────────────

@mcp.tool()
def word_count(text: str) -> str:
    """Count words, sentences, and characters in the given text."""
    words = text.split()
    sentences = text.count(".") + text.count("!") + text.count("?")
    w = len(words)
    s = max(sentences, 1)
    c = len(text)
    avg = round(sum(len(w_) for w_ in words) / max(w, 1), 1)
    return f"Words: {w}, Sentences: {s}, Characters: {c}, Avg word length: {avg}"


# ── Tool 2: Glossary Lookup ───────────────────────────────────────

GLOSSARY = {
    "rag": "Retrieval-Augmented Generation: combining retrieval from a knowledge base with LLM generation.",
    "mcp": "Model Context Protocol: an open standard for connecting AI clients to external tools.",
    "embedding": "A dense vector representation of text that captures semantic meaning.",
    "fine-tuning": "Adapting a pre-trained model to a specific task by training on targeted data.",
    "lora": "Low-Rank Adaptation: parameter-efficient fine-tuning via rank-decomposition matrices.",
    "vector database": "A database optimized for storing and querying high-dimensional vectors.",
    "tokenizer": "Splits text into tokens (subwords, characters, or words) for a language model.",
    "transformer": "Neural network architecture based on self-attention, foundation of modern LLMs.",
    "hyde": "Hypothetical Document Embeddings: generate a hypothetical answer, embed it for retrieval.",
    "crag": "Corrective RAG: grade retrieval quality and retry or abstain when evidence is weak.",
}


@mcp.tool()
def search_glossary(term: str) -> str:
    """Look up an AI/ML term in the glossary. Returns the definition if found."""
    key = term.lower().strip()
    if key in GLOSSARY:
        return f"{key}: {GLOSSARY[key]}"
    matches = [k for k in GLOSSARY if key in k or k in key]
    if matches:
        return f"{matches[0]} (fuzzy match): {GLOSSARY[matches[0]]}"
    return f"Term \'{key}\' not found. Available: {\', \'.join(sorted(GLOSSARY.keys()))}"


if __name__ == "__main__":
    mcp.run(transport="stdio")
'''

print(SERVER_CODE)

4 β€” Save the Server ScriptΒΆ

Run the cell below to write the server to a file you can actually use.

from pathlib import Path

server_path = Path("glossary_mcp_server.py")
server_path.write_text(SERVER_CODE.strip() + "\n")
print(f"Server script saved to: {server_path.resolve()}")
print(f"\nTo test manually:")
print(f"  python {server_path}")
print(f"\nTo use in VS Code, add to .vscode/mcp.json:")
print(f'''  {{"servers": {{"glossary": {{"command": "python", "args": ["{server_path.resolve()}"]}}}}}}''')

5 β€” Configure Your IDEΒΆ

VS Code (.vscode/mcp.json)ΒΆ

{
  "servers": {
    "glossary": {
      "command": "python",
      "args": ["31-ai-powered-dev-tools/glossary_mcp_server.py"]
    }
  }
}

Project Root (.mcp.json)ΒΆ

{
  "mcpServers": {
    "glossary": {
      "command": "python",
      "args": ["31-ai-powered-dev-tools/glossary_mcp_server.py"]
    }
  }
}

Once configured, open Copilot Agent Mode and try:

Look up what RAG means in the glossary, then count the words in its definition.

The agent should call search_glossary("RAG"), get the definition, then call word_count() on the result.

6 β€” Extending the ServerΒΆ

Add a new toolΒΆ

To add a tool that lists all glossary terms:

@mcp.tool()
def list_glossary_terms() -> str:
    """List all available terms in the AI/ML glossary."""
    return ", ".join(sorted(GLOSSARY.keys()))

Add a resource (read-only data)ΒΆ

Resources let the AI read data without calling a tool:

@mcp.resource("glossary://terms")
def glossary_resource() -> str:
    """The complete AI/ML glossary."""
    lines = [f"- **{k}**: {v}" for k, v in sorted(GLOSSARY.items())]
    return "\n".join(lines)

Add a prompt templateΒΆ

Prompts are pre-written templates the AI can use:

@mcp.prompt()
def explain_term(term: str) -> str:
    """Generate a prompt to explain an AI/ML term to a beginner."""
    return f"Explain the AI/ML concept '{term}' in simple terms. Use an analogy."

Production patternsΒΆ

For real-world servers, consider:

  • Database access: Replace the dict with a SQLite or PostgreSQL query

  • API wrapper: Wrap an internal REST API as MCP tools

  • W&B integration: Expose experiment metrics as tools (get_run_metrics, compare_runs)

  • Vector store: Expose your RAG pipeline (search_docs, get_chunk)

  • Error handling: Catch exceptions and return user-friendly error messages

7 β€” Key TakeawaysΒΆ

Concept

Details

MCP server

A Python (or Node.js) process that exposes tools, resources, and prompts

Tool

A function the AI agent can call with typed parameters

Resource

Read-only data the AI can access

Prompt

A pre-written template the AI can use

Transport

stdio (local) or HTTP/SSE (remote)

Configuration

.vscode/mcp.json or .mcp.json

The key insight: MCP turns domain-specific functions into tools any AI agent can use.
If you can write a Python function, you can build an MCP server.

Next stepsΒΆ