# 3. Format results and send to Claude
mcp_docs = "\n\n".join([
    f"[Page {r['page']}]\n{r['text']}"
    for r in mcp_results["results"]
])

anthropic_client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
response = anthropic_client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=200,
    messages=[{
        "role": "user",
        "content": f"Based on this Django documentation:\n\n{mcp_docs}\n\nQuestion: {question}\n\nProvide a short, direct answer."
    }]
)

print("Answer:")
print(response.content[0].text)
print(f"\nTokens used: {response.usage.input_tokens + response.usage.output_tokens}")
```

The three chunks cover the Django 5.2 release notes, new features, and improvements. They're semantically related to "new features", even if they don't use the exact words used in the prompt. RAG understands that questions about releases relate to feature updates and documentation changes.

![Using RAG](/assets/blog/rag-vs-mcp/rag-implementation-result.png)

The RAG semantic search uses:

- **Tokens:** 9,619
- **Cost:** $0.036
- **Response time:** 8.89s

### MCP: Keyword search

Building an MCP server for Django documentation requires a different architectural approach. Instead of pre-indexing prompts with embeddings, we expose the Django PDF through **Tools** that Claude can call on demand. MCP servers communicate via JSON-RPC over stdio, making them easy to integrate with Claude Desktop or Claude Code.

#### MCP data

An MCP server mainly provides two types of primitives:

- **Resources** are read-only data endpoints (like `django://docs/models`).
- **Tools** are callable functions that perform searches, lookups, or actions.

#### MCP implementation

The server loads the Django PDF once at startup, then provides a `search_django_docs` tool that performs keyword matching across all pages. When Claude needs information about Django 5.2, it calls this tool with a search query and receives all matching pages as JSON.

```python
#!/usr/bin/env python3
"""MCP Server for Django Documentation"""

from pathlib import Path
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
from PyPDF2 import PdfReader
import json

class DjangoMCPServer:
    def __init__(self):
        self.server = Server("django-docs")
        self.documentation_pages = []
        self._load_documentation()
        self._setup_handlers()

    def _load_documentation(self):
        """Load Django PDF once at startup"""
        pdf_path = Path("django.pdf")
        if not pdf_path.exists():
            return

        reader = PdfReader(str(pdf_path))
        self.documentation_pages = [
            page.extract_text() for page in reader.pages
        ]

    def _search_documentation(self, query: str, max_pages: int = 50):
        """Keyword search across all pages"""
        query_words = query.lower().split()
        matching_pages = []

        for i, page_text in enumerate(self.documentation_pages):
            page_lower = page_text.lower()

            # Check if any query word appears in this page
            if any(word in page_lower for word in query_words):
                matching_pages.append({
                    "text": page_text,
                    "page_num": i + 1
                })

            if len(matching_pages) >= max_pages:
                break

        return matching_pages

    def _setup_handlers(self):
        """Register MCP tool handlers"""

        @self.server.list_tools()
        async def list_tools():
            return [
                Tool(
                    name="search_django_docs",
                    description="Search Django documentation and return all matching pages",
                    inputSchema={
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "string",
                                "description": "Search query (e.g., 'models', 'authentication')"
                            }
                        },
                        "required": ["query"]
                    }
                )
            ]

        @self.server.call_tool()
        async def call_tool(name: str, arguments: dict):
            if name == "search_django_docs":
                query = arguments.get("query", "")
                results = self._search_documentation(query)

                formatted_results = {
                    "query": query,
                    "results_count": len(results),
                    "results": [
                        {
                            "page": r["page_num"],
                            "text": r["text"]
                        }
                        for r in results
                    ]
                }

                return [TextContent(
                    type="text",
                    text=json.dumps(formatted_results)
                )]

            return [TextContent(
                type="text",
                text=json.dumps({"error": f"Unknown tool: {name}"})
            )]

    async def run(self):
        """Start the MCP server"""
        async with stdio_server() as (read_stream, write_stream):
            await self.server.run(
                read_stream,
                write_stream,
                self.server.create_initialization_options()
            )

def main():
    import asyncio
    server = DjangoMCPServer()
    asyncio.run(server.run())

if __name__ == "__main__":
    main()
```

The server performs simple keyword matching. When Claude asks about new Django 5.2 features, the server finds all documentation pages containing words like "Django", "5.2", "new", or "features".

### MCP outcome

The MCP server splits the query into keywords, searches for any pages containing those words, and returns up to 50 matching pages:

```python
from mcp import ClientSession, StdioServerParameters, stdio_client
import asyncio
import json

async def query_mcp(question: str):
    # 1. Connect to MCP server
    server_params = StdioServerParameters(
        command="python3",
        args=["mcp_server.py"],
        env=None
    )

    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # 2. Call search tool
            result = await session.call_tool(
                "search_django_docs",
                arguments={"query": question}
            )

            return json.loads(result.content[0].text)