Now I understand your confusion. Let me explain how MCP actually works, which is quite different from your initial mental model.
The Key Misconception
Your intuition that "the LLM only understands natural language" led you to expect special tokens or text markers. But MCP doesn't communicate directly with the LLM at all. Instead:
┌─────────────┐ JSON-RPC/stdio ┌─────────────┐
│ MCP Server │ ◄─────────────────► │ Host │ ◄───► LLM
│ (this code)│ │(Claude Code)│
└─────────────┘ └─────────────┘
The Host (like Claude Code, Claude Desktop, or any MCP-compatible client) acts as the intermediary: 1. It connects to MCP servers via JSON-RPC (over stdio, HTTP, etc.) 2. It queries MCP servers for their available tools, prompts, and resources 3. It presents those tools to the LLM in the LLM's native tool format 4. When the LLM calls a tool, the Host translates it to an MCP protocol call
Where the "Instructions to the LLM" Actually Live
The "instruction" to the LLM comes from Python docstrings and type hints. Look at this tool definition in src/dirsrv_mcp/tools/users.py:21-45:
@mcp.tool()
def list_all_users(limit: int = 50, server_name: Optional[str] = None) -> Dict[str, Any]:
"""List users in the directory with computed status.
Note: In privacy mode (default), returns count only.
Set LDAP_MCP_EXPOSE_SENSITIVE_DATA=true for full user details.
"""
The @mcp.tool() decorator (from FastMCP) does several things:
1. Function name → becomes the tool name (list_all_users)
2. Docstring → becomes the tool description shown to the LLM
3. Type hints (limit: int, server_name: Optional[str]) → become the JSON schema for parameters
4. Return type → informs the expected response structure
Another example from src/dirsrv_mcp/tools/health.py:307-329:
@mcp.tool()
def first_look() -> Dict[str, Any]:
"""Comprehensive health overview - the go-to tool for "what's wrong with my directory?"
Performs a complete health assessment across all configured servers including:
- Server connectivity and basic health
- Connection and thread utilization
- Replication status and errors
- Cache efficiency (entry cache hit ratios)
- Disk space usage (local servers only)
- SSL certificate expiration (local servers only)
...
"""
The Registration Pattern
In src/dirsrv_mcp/server.py:82-84, tools get registered:
self._register_prompts()
self._register_tools()
self._register_resources()
And _register_tools() calls functions like register_user_tools(mcp) which then use the @mcp.tool() decorator on each function.
The Protocol Layer (The "Missing" Code)
The protocol handling you're looking for is not in this project - it's in the fastmcp library. This project extends FastMCP:
from fastmcp import FastMCP
class LDAPAssistantMCP(FastMCP):
...
FastMCP handles:
- JSON-RPC message serialization/deserialization
- The tools/list, tools/call protocol methods
- Schema generation from Python type hints
- Transport (stdio, SSE, etc.)
The Actual Wire Protocol
When the Host asks "what tools do you have?", FastMCP responds with JSON like:
{
"tools": [
{
"name": "list_all_users",
"description": "List users in the directory with computed status...",
"inputSchema": {
"type": "object",
"properties": {
"limit": {"type": "integer", "default": 50},
"server_name": {"type": "string"}
}
}
}
]
}
When the LLM (via the Host) calls a tool:
{"method": "tools/call", "params": {"name": "list_all_users", "arguments": {"limit": 10}}}
FastMCP routes this to the decorated Python function and returns the result.
Summary
| What you expected | What actually happens |
|---|---|
| Special tokens in context | JSON-RPC protocol over stdio |
| Text injected at session start | Host queries server for tool schemas |
| LLM parses special markers | Host translates LLM tool calls to MCP protocol |
| Schema definitions in code | Auto-generated from Python type hints + docstrings |
The "meat" you were looking for is:
1. Tool definitions: The @mcp.tool() decorated functions in src/dirsrv_mcp/tools/*.py
2. Protocol handling: The fastmcp library (not in this repo)
3. LLM instructions: Python docstrings that FastMCP extracts and serves via JSON-RPC