from mcp.server.fastmcp import FastMCP

mcp = FastMCP("MENTORR Guide")

# ---------------------------------------------------------------------------
# Content: MCP Architecture Explanations
# ---------------------------------------------------------------------------

MCP_ARCHITECTURE = {
    "overview": """
--- MCP Overview ---

The Model Context Protocol (MCP) is an open standard that defines how AI
applications (clients) connect to external tools and data sources (servers).
Think of it as a universal adapter — instead of every AI app writing custom
integrations for every tool, MCP provides one protocol that works everywhere.

Architecture:

  Host Application (Claude Desktop, IDE, etc.)
      |
      v
  MCP Client  <---- JSON-RPC 2.0 ---->  MCP Server
                                            |
                                            v
                                    Local files, APIs,
                                    databases, hardware

Key concepts:
  - Host: The application the user interacts with (e.g., Claude Desktop)
  - Client: Lives inside the host, manages the connection to one server
  - Server: Exposes tools, resources, and prompts over the protocol

The protocol uses JSON-RPC 2.0 as its message format. Every interaction is
either a request (expects a response), a response, or a notification (fire
and forget, no response expected).

Three primitives servers can expose:
  1. Tools   — Functions the AI can call (actions with side effects)
  2. Resources — Data the AI can read (read-only, URI-addressed)
  3. Prompts — Reusable prompt templates with arguments

The lifecycle:
  1. Client sends 'initialize' with its capabilities
  2. Server responds with its capabilities
  3. Client sends 'notifications/initialized'
  4. Normal operation (tool calls, resource reads, etc.)
  5. Either side can close the connection

Use 'explain_mcp_architecture' with topics like 'tools', 'resources',
'transports', 'lifecycle', 'prompts', 'sampling', or 'roots' for deep dives.
""",

    "transports": """
--- MCP Transports ---

A transport is the communication layer between client and server. MCP
supports three transport types:

1. STDIO (Standard I/O)
   - How it works: Client launches the server as a subprocess. Messages
     flow over stdin (client -> server) and stdout (server -> client).
     Each message is a JSON-RPC object followed by a newline.
   - When to use: Local tools, development, CLI integrations.
   - Pros: Simple, no network setup, process isolation.
   - Cons: Local only, one client per server process.
   - This is what all MENTORR servers use: mcp.run(transport='stdio')

2. SSE (Server-Sent Events)
   - How it works: Client connects via HTTP. Server pushes messages
     through an SSE stream. Client sends messages via HTTP POST.
   - When to use: Legacy setups. This transport is being phased out
     in favor of Streamable HTTP.
   - Pros: Works over networks, firewall-friendly (HTTP).
   - Cons: One-directional streaming, more complex than stdio.

3. Streamable HTTP
   - How it works: Client sends JSON-RPC requests as HTTP POST to a
     single endpoint. Server responds inline or upgrades to SSE for
     streaming. Supports session management via Mcp-Session-Id header.
   - When to use: Production deployments, remote servers, multi-client.
   - Pros: Standard HTTP infrastructure, load balancers, auth, caching.
     Supports both request-response and streaming patterns.
   - Cons: More infrastructure to manage.

Transport selection in FastMCP:

    mcp.run(transport='stdio')               # Local subprocess
    mcp.run(transport='sse', port=8080)       # SSE server
    mcp.run(transport='streamable-http')      # Streamable HTTP

For MENTORR, stdio is the right choice — all servers run locally on your
machine, launched as subprocesses by Claude Desktop or Claude Code.
""",

    "tools": """
--- MCP Tools ---

Tools are the most important MCP primitive. They let a server expose
callable functions that the AI can invoke to perform actions.

How tools work in the protocol:

  1. Client calls 'tools/list' → Server returns all tool definitions
  2. Each tool definition includes:
     - name: string (e.g., "calculate_fret_layout")
     - description: string (from the Python docstring)
     - inputSchema: JSON Schema object (auto-generated from type hints)
  3. Client calls 'tools/call' with { name, arguments }
  4. Server executes the function, returns { content: [{ type, text }] }

In FastMCP, defining a tool is one decorator:

    @mcp.tool()
    def calculate_fret_layout(scale_length_inches: float,
                              number_of_frets: int = 22) -> str:
        \"\"\"Generates fret slotting table.\"\"\"
        # ... implementation ...
        return formatted_result

What happens behind the scenes:
  - Function name → tool name ("calculate_fret_layout")
  - Docstring → tool description
  - Type hints → JSON Schema (float → {"type": "number"}, etc.)
  - Default values → marked as optional in the schema
  - Return value → wrapped in content blocks and sent back

Key characteristics of tools:
  - They perform ACTIONS (can have side effects)
  - They are MODEL-controlled (the AI decides when to call them)
  - They return results to the AI, which incorporates them into its response
  - The client (AI) may call multiple tools in sequence

Versus Resources:
  - Tools = verbs (do something). Resources = nouns (read something).
  - Tools can modify state. Resources are read-only.
  - Tools are invoked by the AI. Resources can be attached by the user.
""",

    "resources": """
--- MCP Resources ---

Resources expose data that the AI can read. They are URI-addressed,
read-only data sources — like files, database records, or live status.

How resources work in the protocol:

  1. Client calls 'resources/list' → Server returns available resources
  2. Each resource has:
     - uri: string (e.g., "file:///path/to/file" or "mcp://docs/spec")
     - name: string
     - description: string (optional)
     - mimeType: string (e.g., "text/plain", "application/json")
  3. Client calls 'resources/read' with { uri }
  4. Server returns { contents: [{ uri, mimeType, text }] }

In FastMCP, resources use the @mcp.resource() decorator:

    # Static resource (fixed URI, no parameters)
    @mcp.resource("mcp://docs/protocol",
                  name="protocol_spec",
                  description="Core MCP spec reference",
                  mime_type="text/plain")
    def protocol_spec() -> str:
        return spec_content

    # Template resource (dynamic URI with parameters)
    @mcp.resource("mcp://projects/{project_name}/status")
    def project_status(project_name: str) -> str:
        return f"Status for {project_name}: ..."

Resource types:
  - Static: Fixed URI, no parameters. Registered at server startup.
  - Template: URI with {placeholders} that map to function parameters.
    New instances created dynamically when accessed.

Return types:
  - str → returned as text content
  - bytes → returned as binary (base64-encoded in transport)
  - dict/list → serialized to JSON automatically

Resources vs Tools:
  - Resources are USER-controlled (user selects which to attach)
  - Resources are READ-ONLY (no side effects)
  - Resources are DATA (nouns, not verbs)
  - Tools are MODEL-controlled, can have side effects, perform actions
""",

    "prompts": """
--- MCP Prompts ---

Prompts are reusable prompt templates that a server can expose. They let
you package complex instructions with arguments into shareable templates.

How prompts work:

  1. Client calls 'prompts/list' → Server returns available prompts
  2. Each prompt has:
     - name: string
     - description: string
     - arguments: list of { name, description, required }
  3. Client calls 'prompts/get' with { name, arguments }
  4. Server returns { messages: [{ role, content }] }

In FastMCP:

    @mcp.prompt()
    def debug_guitar_issue(symptom: str, guitar_model: str = "unknown") -> str:
        return f\"\"\"You are a master luthier. A customer reports: {symptom}
        Guitar model: {guitar_model}
        Diagnose the issue step by step.\"\"\"

Prompts are USER-controlled — the user selects which prompt to use, not
the AI. They are like stored procedures for conversations.

Use cases:
  - Complex diagnostic workflows
  - Standardized analysis templates
  - Multi-step reasoning patterns
  - Domain-specific instruction sets

Note: The MENTORR system does not currently use prompts. All interaction
patterns are handled through tools. Prompts would be useful if you wanted
to package specific lutherie diagnostic workflows as reusable templates.
""",

    "lifecycle": """
--- MCP Lifecycle ---

The MCP connection lifecycle has three phases: initialization, operation,
and shutdown.

Phase 1: Initialization (Handshake)

  Client                              Server
    |                                    |
    |--- initialize ------------------>  |
    |    { protocolVersion,              |
    |      capabilities,                 |
    |      clientInfo }                  |
    |                                    |
    |  <--- response ------------------- |
    |       { protocolVersion,           |
    |         capabilities,              |
    |         serverInfo }               |
    |                                    |
    |--- notifications/initialized ---> |
    |    (no response expected)          |

  The handshake establishes:
  - Protocol version compatibility
  - What each side supports (tools, resources, prompts, sampling, roots)
  - Identity information (name, version)

Phase 2: Normal Operation

  Once initialized, the client can:
  - tools/list → get available tools
  - tools/call → invoke a tool
  - resources/list → get available resources
  - resources/read → read a resource
  - prompts/list → get available prompts
  - prompts/get → get a prompt with arguments

  The server can:
  - Send notifications (e.g., tools/list changed)
  - Request sampling from the client (if capable)

Phase 3: Shutdown

  Either side can close the connection:
  - Client closes the transport (e.g., terminates the subprocess)
  - Server can signal shutdown
  - For stdio: closing stdin/stdout ends the session
  - For HTTP: session expiration or explicit close

With FastMCP and stdio transport, you don't manage any of this manually.
The library handles the handshake, message routing, and shutdown. You just
define tools and resources.
""",

    "sampling": """
--- MCP Sampling ---

Sampling is an advanced feature where the SERVER asks the CLIENT to
generate text using the AI model. This inverts the normal flow.

Normal flow:  User → AI → Tool call → Server → Result → AI → User
Sampling flow: Server (during tool execution) → Client → AI generates → Server

How it works:

  1. Server declares sampling capability during init
  2. During a tool execution, server sends 'sampling/createMessage'
     with { messages, modelPreferences, systemPrompt, maxTokens }
  3. Client routes this to the AI model
  4. AI generates a response
  5. Client returns the generated text to the server
  6. Server continues its tool execution with the AI-generated content

Use cases:
  - Multi-step reasoning within a tool (server needs AI analysis mid-task)
  - Content generation as part of a workflow
  - Agentic patterns where the server orchestrates complex operations

Example scenario:
  A "smart diagnosis" tool could:
  1. Gather symptom data from sensors
  2. Use sampling to ask the AI to analyze the raw data
  3. Cross-reference the AI analysis with known issues
  4. Return a structured diagnosis

This is a HUMAN-IN-THE-LOOP feature — the client (host application)
controls whether and how sampling requests are fulfilled. The server
cannot force the AI to generate content.

FastMCP supports sampling through the Context object passed to tools.
""",

    "roots": """
--- MCP Roots ---

Roots let the CLIENT tell the SERVER about relevant filesystem locations.
They define the boundaries of what the server should operate on.

How roots work:

  1. Client declares roots capability during init
  2. Client sends 'roots/list' notification with URIs
  3. Server uses these roots to scope its operations
  4. Client can update roots during the session

Root format:
  { uri: "file:///Users/magus/MENTORR", name: "MENTORR Project" }

Use cases:
  - Telling a code analysis server which directories to scan
  - Scoping file operations to specific project folders
  - Multi-project setups where the server needs to know boundaries

In the MENTORR system, roots could be used to tell servers where
project data lives:
  - "file:///Users/magus/kimballDEMO/data/projects" for build data
  - "file:///Users/magus/kimballDEMO/knowledge" for the physics library

Roots are informational — servers SHOULD respect them but the protocol
does not enforce access control. They are hints about scope, not security
boundaries.

This feature is most useful for IDE integrations where the workspace
defines the roots automatically.
""",
}

# ---------------------------------------------------------------------------
# Content: MENTORR System Capabilities
# ---------------------------------------------------------------------------

MENTORR_TOOLS = {
    "Luthier Physics": {
        "server": "luthier_physics_mcp.py",
        "domain": "Build management, engineering calculations, knowledge retrieval",
        "tool_count": 14,
        "tools": {
            "register_project": "Add a project (luthier or music) to the Master Schedule with deadline and priority",
            "generate_work_day": "Create a prioritized daily schedule across all active projects given available hours",
            "forecast_materials": "Check upcoming build steps and list materials needed before you get blocked",
            "start_build": "Initialize a new guitar build project with a checklist from a template",
            "get_build_status": "View the current checklist and progress for a specific build",
            "update_build_step": "Mark a build step as Done, In Progress, or Pending with optional notes",
            "customize_build": "Add a custom step to an existing build checklist",
            "consult_library": "Semantic search across the luthier knowledge base using OpenAI embeddings",
            "save_project_specs": "Persist measurements and design specifications for a build",
            "get_project_specs": "Retrieve saved measurements and specs for a build",
            "get_wood_specs": "Look up physical properties (density, Janka, MOE) for a wood species",
            "calculate_fret_layout": "Generate a fret slotting table from scale length and fret count",
            "calculate_string_tension": "Compute per-string and total tension for a given gauge set and tuning",
            "calculate_saddle_placement": "Calculate saddle compensation offset for proper intonation",
        },
    },
    "Shelley Inventory": {
        "server": "shelley_inventory_mcp.py",
        "domain": "Tonewood inventory scraping, search, and analytics",
        "tool_count": 6,
        "tools": {
            "update_shelley_inventory": "Scrape Shelley Hardwoods catalog and sync to master inventory",
            "update_exotic_inventory": "Scrape Exotic Wood Zone catalog and sync to master inventory",
            "update_stewmac_inventory": "Scrape StewMac sitemap-based catalog and sync to master inventory",
            "process_lab_inbox": "Extract inventory data from receipt images using AI vision",
            "get_inventory_stats": "Summary statistics grouped by seller and wood species",
            "search_inventory": "Full-text search across the master inventory by query string",
        },
    },
    "Luthier Logic": {
        "server": "servers/luthier-logic-mcp/server.py",
        "domain": "Logical verification, diagnostics, design validation",
        "tool_count": 5,
        "tools": {
            "verify_truth_table": "Generate a rigorous truth table for a propositional logic formula",
            "diagnose": "Diagnose a guitar issue (buzz, intonation, dead spot) using logic trees",
            "recommend_wood": "Suggest tonewoods for a tonal goal and check inventory availability",
            "verify_design": "Validate a project design against common lutherie constraints",
            "explain_rule": "Explain the physics behind a lutherie rule of thumb",
        },
    },
}

# ---------------------------------------------------------------------------
# Content: Tutorial Lessons
# ---------------------------------------------------------------------------

TUTORIALS = {
    1: {
        "title": "MCP Fundamentals",
        "content": """
--- Lesson 1 of 6: MCP Fundamentals ---

What you'll learn:
  - What MCP is and why it exists
  - The client-server architecture
  - How messages flow between components

The Problem MCP Solves:

  Without MCP, every AI application needs custom code to connect to every
  tool. If you have 5 AI apps and 10 tools, that's 50 custom integrations.
  MCP reduces this to 5 + 10 = 15 implementations (one per app, one per
  tool), because everyone speaks the same protocol.

The Architecture:

  Your machine runs a Host (like Claude Desktop). Inside it, an MCP Client
  connects to an MCP Server. The server is just a Python script that
  exposes tools via the protocol.

      Claude Desktop (Host)
          |
          MCP Client
          |  (JSON-RPC over stdio)
          |
          MCP Server (your Python script)
              |
              Your code (files, APIs, calculations)

Minimal Server:

    from mcp.server.fastmcp import FastMCP

    mcp = FastMCP("My First Server")

    @mcp.tool()
    def hello(name: str) -> str:
        \"\"\"Say hello to someone.\"\"\"
        return f"Hello, {name}!"

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

  That's it. Save this as my_server.py, and you have a working MCP server
  with one tool. Claude can call hello("Magus") and get back "Hello, Magus!"

Exercise:
  Look at the code above. What happens if you remove the docstring from
  the hello function? (Answer: The tool will have no description, making
  it harder for the AI to know when and how to use it. Docstrings are the
  primary way tools communicate their purpose.)

Next: Lesson 2 - Writing Your First Tool
""",
    },
    2: {
        "title": "Writing Your First Tool",
        "content": """
--- Lesson 2 of 6: Writing Your First Tool ---

What you'll learn:
  - How @mcp.tool() works under the hood
  - Type hints and JSON Schema generation
  - Parameter defaults and validation

The @mcp.tool() Decorator:

  When you write:

    @mcp.tool()
    def calculate_area(length: float, width: float = 1.0) -> str:
        \"\"\"Calculate the area of a rectangle.\"\"\"
        area = length * width
        return f"Area: {area:.2f} sq units"

  FastMCP automatically generates this tool definition for the client:

    {
      "name": "calculate_area",
      "description": "Calculate the area of a rectangle.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "length": { "type": "number" },
          "width": { "type": "number", "default": 1.0 }
        },
        "required": ["length"]
      }
    }

  Notice:
  - Function name → tool name
  - Docstring → description
  - float → "number" in JSON Schema
  - Default value → field is optional, default included
  - No default → field is required

Type mapping:
  str   → { "type": "string" }
  int   → { "type": "integer" }
  float → { "type": "number" }
  bool  → { "type": "boolean" }
  list  → { "type": "array" }
  dict  → { "type": "object" }

Return values:
  Always return str. The protocol wraps your return value in a content
  block: { "type": "text", "text": your_return_value }

  If you return a non-string, FastMCP will convert it, but explicit str
  return is the convention used across all MENTORR servers.

Real example from the MENTORR system:

    @mcp.tool()
    def get_wood_specs(species: str) -> str:
        \"\"\"Look up physical properties of a wood species.\"\"\"
        db = load_physics_db()
        wood = db.get(species.lower())
        if not wood:
            return f"Species '{species}' not found in database."
        return (
            f"--- {species} ---\\n"
            f"Density: {wood['density']} kg/m3\\n"
            f"Janka Hardness: {wood['janka']} lbf\\n"
            f"MOE: {wood['moe']} GPa"
        )

Exercise:
  Write a tool called convert_temperature that takes a value (float) and
  a from_unit (str, either "C" or "F") and returns the converted
  temperature as a formatted string. Include a descriptive docstring.

Next: Lesson 3 - Working with Resources
""",
    },
    3: {
        "title": "Working with Resources",
        "content": """
--- Lesson 3 of 6: Working with Resources ---

What you'll learn:
  - The difference between tools and resources
  - Static vs template resources
  - URI design and MIME types

Tools vs Resources:

  Tools = ACTIONS (verbs). The AI calls them to DO something.
    "Calculate fret layout" / "Search inventory" / "Diagnose issue"

  Resources = DATA (nouns). The AI reads them to GET information.
    "Protocol specification" / "Project configuration" / "System status"

  Tools are model-controlled (AI decides when to call).
  Resources are user-controlled (user selects which to attach).

Static Resource:

  A resource with a fixed URI and no parameters:

    @mcp.resource("mcp://docs/protocol",
                  name="protocol_spec",
                  description="Core MCP specification reference",
                  mime_type="text/plain")
    def protocol_spec() -> str:
        return "The MCP protocol uses JSON-RPC 2.0..."

  The function is called when a client reads the resource at that URI.

Template Resource:

  A resource with parameters in the URI (curly braces):

    @mcp.resource("mcp://builds/{project_name}/status")
    def build_status(project_name: str) -> str:
        # project_name extracted from the URI
        return f"Build {project_name}: 7 of 15 steps complete"

  Accessing "mcp://builds/Dragon-1/status" calls build_status("Dragon-1").
  The {placeholder} names must match the function parameter names exactly.

URI Design:

  URIs identify resources. You can use any scheme:
    file:///path/to/file         — filesystem resources
    mcp://domain/path            — custom MCP resources
    resource://category/item     — generic resource scheme

  Best practice: Use a consistent scheme for your server. The MENTORR
  guide uses "mcp://docs/..." for documentation resources.

Return Types:
  - str: Returned as text content (most common)
  - bytes: Returned as binary (base64-encoded in transport)
  - dict/list: Auto-serialized to JSON

Exercise:
  Design a URI scheme for a recipe management server. You need:
  - A list of all recipes
  - A specific recipe by name
  - The ingredients for a specific recipe
  What URIs would you use? (Hint: use template resources for the last two.)

Next: Lesson 4 - Advanced Patterns
""",
    },
    4: {
        "title": "Advanced Patterns",
        "content": """
--- Lesson 4 of 6: Advanced Patterns ---

What you'll learn:
  - Error handling strategies
  - The Context object
  - Long-running operations

Error Handling:

  MCP tools should return error information as strings, not raise
  exceptions. This keeps the AI informed about what went wrong:

    @mcp.tool()
    def get_wood_specs(species: str) -> str:
        \"\"\"Look up physical properties of a wood species.\"\"\"
        db = load_physics_db()
        wood = db.get(species.lower())
        if not wood:
            return f"Species '{species}' not found. Available: {', '.join(db.keys())}"
        return format_wood_data(wood)

  If you raise an unhandled exception, FastMCP catches it and returns
  an error to the client, but the error message may not be as helpful.
  Prefer explicit error strings that guide the AI toward a fix.

The Context Object:

  FastMCP provides a Context object you can inject into any tool for
  logging and progress reporting:

    from mcp.server.fastmcp import Context

    @mcp.tool()
    async def long_search(query: str, ctx: Context) -> str:
        \"\"\"Search with progress reporting.\"\"\"
        await ctx.info(f"Starting search for: {query}")
        results = []
        for i, source in enumerate(sources):
            await ctx.report_progress(i, len(sources))
            results.extend(search_source(source, query))
        return format_results(results)

  Context methods:
    ctx.info(msg)    — Log an informational message
    ctx.debug(msg)   — Log a debug message
    ctx.warning(msg) — Log a warning
    ctx.error(msg)   — Log an error
    ctx.report_progress(current, total) — Report progress to client

Async vs Sync:

  FastMCP supports both. Use async when doing I/O-bound work:

    # Sync — fine for CPU-bound or simple operations
    @mcp.tool()
    def calculate(x: float) -> str: ...

    # Async — better for network calls, file I/O, etc.
    @mcp.tool()
    async def fetch_data(url: str) -> str: ...

Structuring Large Servers:

  When a server grows beyond 10-15 tools, consider:
  - Grouping related tools with clear naming prefixes
  - Extracting helper functions (not decorated) for shared logic
  - Keeping all tools in one file (MCP servers are typically single-file)
  - Using module-level constants and data structures for content

  The MENTORR physics server (14 tools) uses this pattern: helper
  functions at the top (load_physics_db, load_schedule, etc.) and
  decorated tools below.

Exercise:
  Take this naive tool and improve its error handling:

    @mcp.tool()
    def divide(a: float, b: float) -> str:
        return str(a / b)

  What happens when b is 0? How would you handle it gracefully?

Next: Lesson 5 - Testing and Debugging
""",
    },
    5: {
        "title": "Testing and Debugging",
        "content": """
--- Lesson 5 of 6: Testing and Debugging ---

What you'll learn:
  - How to test MCP servers
  - Configuration for Claude Desktop / Claude Code
  - Common pitfalls and debugging strategies

Running Your Server Directly:

  python mentorr_guide_mcp.py

  This starts the server in stdio mode, waiting for JSON-RPC messages on
  stdin. If it starts without errors, your server is syntactically correct.
  Press Ctrl+C to stop.

MCP Inspector:

  The MCP Inspector is a browser-based tool for testing servers:

    npx @modelcontextprotocol/inspector python mentorr_guide_mcp.py

  This launches a web UI where you can:
  - See all registered tools, resources, and prompts
  - Call tools with custom arguments
  - Read resources
  - Inspect the JSON-RPC messages

Claude Desktop Configuration:

  Add your server to ~/Library/Application Support/Claude/claude_desktop_config.json:

    {
      "mcpServers": {
        "mentorr-guide": {
          "command": "/path/to/.venv/bin/python",
          "args": ["/path/to/mentorr_guide_mcp.py"]
        }
      }
    }

  Important: Use the full path to the Python binary in your virtual
  environment, not just "python". This ensures the correct packages
  are available.

Claude Code Configuration:

  Add to your .claude.json or project settings:

    {
      "mcpServers": {
        "mentorr-guide": {
          "command": "/path/to/.venv/bin/python",
          "args": ["/path/to/mentorr_guide_mcp.py"]
        }
      }
    }

Common Pitfalls:

  1. Wrong Python path
     Symptom: "ModuleNotFoundError: No module named 'mcp'"
     Fix: Use the venv Python, not system Python.

  2. Print statements in server code
     Symptom: Server crashes or sends malformed messages.
     Fix: Never use print() in an MCP server! stdout is the transport.
     Use ctx.info() or stderr logging instead.

  3. Missing docstrings
     Symptom: Tool works but AI doesn't know how to use it well.
     Fix: Always include descriptive docstrings.

  4. Non-string returns
     Symptom: Unexpected JSON output.
     Fix: Return str from all tools (convention in this codebase).

  5. Blocking I/O in async tools
     Symptom: Server hangs during tool execution.
     Fix: Use async I/O or run blocking code in a thread executor.

Exercise:
  You have a server with this tool that keeps failing:

    @mcp.tool()
    def read_config() -> str:
        with open("config.json") as f:
            return f.read()

  List three things that could go wrong and how you'd fix each one.

Next: Lesson 6 - Integration and Deployment
""",
    },
    6: {
        "title": "Integration and Deployment",
        "content": """
--- Lesson 6 of 6: Integration and Deployment ---

What you'll learn:
  - Multi-server architectures
  - How the MENTORR system is organized
  - Production deployment patterns

Multi-Server Architecture:

  MCP is designed for multiple servers running simultaneously. Each server
  is a separate process with its own responsibilities. The client (Claude)
  manages connections to all of them.

      Claude
        |
        +--- Luthier Physics (builds, calculations, knowledge)
        |
        +--- Shelley Inventory (wood sourcing, stock tracking)
        |
        +--- Luthier Logic (diagnostics, verification, logic)
        |
        +--- MENTORR Guide (education, documentation)

  Each server is independent:
  - Separate process (crash isolation)
  - Can use different dependencies
  - Can be developed and deployed independently
  - Communicates only through the MCP protocol

  Servers do NOT talk to each other directly. Claude orchestrates all
  cross-server workflows. For example, to verify a design:
  1. Claude calls get_project_specs (Physics) to get measurements
  2. Claude calls verify_design (Logic) to check constraints
  3. Claude calls search_inventory (Inventory) to check wood availability
  4. Claude synthesizes all results into a coherent response

Design Principles:

  1. Domain separation: Each server owns one domain of functionality
  2. Self-contained: Each server manages its own data and dependencies
  3. Stateless tools: Tools should not depend on call order
  4. Descriptive APIs: Docstrings and naming should be self-documenting
  5. String returns: Keep output format consistent (formatted text)

When to Split Into Multiple Servers:

  - Different dependency trees (one needs numpy, another needs sympy)
  - Different execution environments (one local, one remote)
  - Different update cadences (inventory scraping vs calculations)
  - Clear domain boundaries (physics vs logic vs inventory)

When to Keep as One Server:

  - Tightly coupled tools that share data structures
  - Small tool count (under 8-10)
  - Same dependencies throughout

Production Patterns:

  For remote/shared deployment:
  - Use Streamable HTTP transport instead of stdio
  - Add authentication middleware
  - Deploy behind a reverse proxy
  - Monitor health with readiness probes

  For local use (current MENTORR setup):
  - stdio transport, launched as subprocess
  - Virtual environment with pinned dependencies
  - Configuration in Claude Desktop/Code config file

You have completed all 6 lessons.

Review:
  Lesson 1: MCP Fundamentals (architecture, message flow)
  Lesson 2: Writing Tools (decorators, type hints, JSON Schema)
  Lesson 3: Working with Resources (URIs, static vs template)
  Lesson 4: Advanced Patterns (error handling, Context, async)
  Lesson 5: Testing and Debugging (Inspector, config, pitfalls)
  Lesson 6: Integration and Deployment (multi-server, production)

Next steps: Build something. Start with a single tool that solves a real
problem, then expand from there.
""",
    },
}

# ---------------------------------------------------------------------------
# Content: Protocol Specification Reference
# ---------------------------------------------------------------------------

PROTOCOL_SPEC_CONTENT = """\
=======================================
 MCP Protocol Quick Reference
=======================================

1. MESSAGE FORMAT (JSON-RPC 2.0)
   Request:      { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {...} }
   Response:     { "jsonrpc": "2.0", "id": 1, "result": {...} }
   Error:        { "jsonrpc": "2.0", "id": 1, "error": { "code": -1, "message": "..." } }
   Notification: { "jsonrpc": "2.0", "method": "notifications/initialized" }
   (Notifications have no "id" and expect no response.)

2. INITIALIZATION HANDSHAKE
   Client -> Server:  initialize { protocolVersion, capabilities, clientInfo }
   Server -> Client:  response   { protocolVersion, capabilities, serverInfo }
   Client -> Server:  notifications/initialized
   (Normal operation begins after this exchange.)

3. SERVER CAPABILITIES
   tools:     { listChanged: bool }  — Server exposes callable tools
   resources: { subscribe: bool, listChanged: bool }  — Server exposes readable resources
   prompts:   { listChanged: bool }  — Server exposes prompt templates
   sampling:  {}  — Server may request LLM sampling from client

4. TOOL SCHEMA
   List:   tools/list -> [{ name, description, inputSchema }]
   Call:   tools/call  { name: "...", arguments: {...} }
   Result: { content: [{ type: "text", text: "..." }], isError: false }

5. RESOURCE SCHEMA
   List:   resources/list -> [{ uri, name, description, mimeType }]
   Read:   resources/read { uri: "..." }
   Result: { contents: [{ uri, mimeType, text: "..." }] }

6. PROMPT SCHEMA
   List:   prompts/list -> [{ name, description, arguments }]
   Get:    prompts/get  { name: "...", arguments: {...} }
   Result: { messages: [{ role: "user"|"assistant", content: {...} }] }

7. TRANSPORT TYPES
   stdio:            stdin/stdout, newline-delimited JSON-RPC (local only)
   SSE:              HTTP + Server-Sent Events (legacy, being deprecated)
   Streamable HTTP:  POST-based, optional SSE upgrade, session headers

8. STANDARD ERROR CODES
   -32700  Parse error (invalid JSON)
   -32600  Invalid request (malformed JSON-RPC)
   -32601  Method not found
   -32602  Invalid params
   -32603  Internal error

9. FASTMCP PYTHON PATTERNS
   from mcp.server.fastmcp import FastMCP
   mcp = FastMCP("Server Name")

   @mcp.tool()
   def my_tool(param: str) -> str:
       \"\"\"Description becomes tool description.\"\"\"
       return "result"

   @mcp.resource("scheme://path", name="...", mime_type="text/plain")
   def my_resource() -> str:
       return "content"

   @mcp.prompt()
   def my_prompt(arg: str) -> str:
       return f"Prompt with {arg}"

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


# ---------------------------------------------------------------------------
# Tools
# ---------------------------------------------------------------------------

@mcp.tool()
def explain_mcp_architecture(topic: str = "overview") -> str:
    """Deep-dive explanation of MCP (Model Context Protocol) architecture.

    topic: One of 'overview', 'transports', 'tools', 'resources', 'prompts',
           'lifecycle', 'sampling', 'roots'. Defaults to 'overview'.
    Returns formatted educational content about the specified MCP concept.
    """
    explanation = MCP_ARCHITECTURE.get(topic.lower().strip())
    if explanation:
        return explanation.strip()

    available = ", ".join(sorted(MCP_ARCHITECTURE.keys()))
    return (
        f"Unknown topic: '{topic}'\n\n"
        f"Available topics: {available}\n\n"
        "Example: explain_mcp_architecture(topic='tools')"
    )


@mcp.tool()
def get_mentorr_capabilities(format: str = "detailed") -> str:
    """Returns a structured reference of all tools in the MENTORR system.

    Lists every tool across all three MCP servers: Luthier Physics (14 tools),
    Shelley Inventory (6 tools), and Luthier Logic (5 tools).

    format: 'detailed' for full descriptions, 'compact' for names only.
    """
    lines = ["--- MENTORR System Capabilities ---", ""]
    total = sum(s["tool_count"] for s in MENTORR_TOOLS.values())
    lines.append(f"Total: {total} tools across {len(MENTORR_TOOLS)} servers")
    lines.append("")

    for server_name, info in MENTORR_TOOLS.items():
        lines.append(f"=== {server_name} ({info['tool_count']} tools) ===")
        lines.append(f"Server: {info['server']}")
        lines.append(f"Domain: {info['domain']}")
        lines.append("")

        for tool_name, description in info["tools"].items():
            if format.lower().strip() == "compact":
                lines.append(f"  - {tool_name}")
            else:
                lines.append(f"  {tool_name}")
                lines.append(f"    {description}")
                lines.append("")

        lines.append("")

    return "\n".join(lines).strip()


@mcp.tool()
def start_tutorial(lesson_number: int = 1) -> str:
    """Interactive walkthrough lessons on building MCP servers and tools.

    lesson_number: 1-6. Lessons cover fundamentals, tools, resources,
    advanced patterns, testing, and integration.

    Each lesson includes key concepts, code examples, and an exercise.
    """
    lesson = TUTORIALS.get(lesson_number)
    if lesson:
        return lesson["content"].strip()

    index_lines = ["--- MENTORR Tutorial Index ---", ""]
    index_lines.append(f"Available lessons (1-{len(TUTORIALS)}):")
    index_lines.append("")
    for num, info in sorted(TUTORIALS.items()):
        index_lines.append(f"  Lesson {num}: {info['title']}")
    index_lines.append("")
    index_lines.append("Usage: start_tutorial(lesson_number=1)")

    return "\n".join(index_lines)


# ---------------------------------------------------------------------------
# Resources
# ---------------------------------------------------------------------------

@mcp.resource(
    "mcp://docs/protocol",
    name="protocol_spec",
    description="Core MCP technical specification quick reference. Covers message format, initialization, capabilities, tool/resource/prompt schemas, transports, and error codes.",
    mime_type="text/plain",
)
def protocol_spec() -> str:
    """Return the MCP protocol specification reference document."""
    return PROTOCOL_SPEC_CONTENT


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

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