diff --git a/projects/unit3/build-mcp-server/solution/server.py b/projects/unit3/build-mcp-server/solution/server.py index ceb8049..a87211e 100644 --- a/projects/unit3/build-mcp-server/solution/server.py +++ b/projects/unit3/build-mcp-server/solution/server.py @@ -7,7 +7,7 @@ import json import os import subprocess -from typing import Dict, List, Any, Optional +from typing import Optional from pathlib import Path from mcp.server.fastmcp import FastMCP @@ -18,6 +18,34 @@ # PR template directory (shared between starter and solution) TEMPLATES_DIR = Path(__file__).parent.parent.parent / "templates" +# Default PR templates +DEFAULT_TEMPLATES = { + "bug.md": "Bug Fix", + "feature.md": "Feature", + "docs.md": "Documentation", + "refactor.md": "Refactor", + "test.md": "Test", + "performance.md": "Performance", + "security.md": "Security" +} + +# Type mapping for PR templates +TYPE_MAPPING = { + "bug": "bug.md", + "fix": "bug.md", + "feature": "feature.md", + "enhancement": "feature.md", + "docs": "docs.md", + "documentation": "docs.md", + "refactor": "refactor.md", + "cleanup": "refactor.md", + "test": "test.md", + "testing": "test.md", + "performance": "performance.md", + "optimization": "performance.md", + "security": "security.md" +} + @mcp.tool() async def analyze_file_changes( @@ -44,7 +72,7 @@ async def analyze_file_changes( root = roots_result.roots[0] # FileUrl object has a .path property that gives us the path directly working_directory = root.uri.path - except Exception as e: + except Exception: # If we can't get roots, fall back to current directory pass @@ -143,30 +171,14 @@ async def analyze_file_changes( @mcp.tool() async def get_pr_templates() -> str: """List available PR templates with their content.""" - templates = [] - - # Define default templates - default_templates = { - "bug.md": "Bug Fix", - "feature.md": "Feature", - "docs.md": "Documentation", - "refactor.md": "Refactor", - "test.md": "Test", - "performance.md": "Performance", - "security.md": "Security" - } - - for filename, template_type in default_templates.items(): - template_path = TEMPLATES_DIR / filename - - # Read template content - content = template_path.read_text() - - templates.append({ + templates = [ + { "filename": filename, "type": template_type, - "content": content - }) + "content": (TEMPLATES_DIR / filename).read_text() + } + for filename, template_type in DEFAULT_TEMPLATES.items() + ] return json.dumps(templates, indent=2) @@ -184,25 +196,8 @@ async def suggest_template(changes_summary: str, change_type: str) -> str: templates_response = await get_pr_templates() templates = json.loads(templates_response) - # Map change types to template files - type_mapping = { - "bug": "bug.md", - "fix": "bug.md", - "feature": "feature.md", - "enhancement": "feature.md", - "docs": "docs.md", - "documentation": "docs.md", - "refactor": "refactor.md", - "cleanup": "refactor.md", - "test": "test.md", - "testing": "test.md", - "performance": "performance.md", - "optimization": "performance.md", - "security": "security.md" - } - # Find matching template - template_file = type_mapping.get(change_type.lower(), "feature.md") + template_file = TYPE_MAPPING.get(change_type.lower(), "feature.md") selected_template = next( (t for t in templates if t["filename"] == template_file), templates[0] # Default to first template if no match diff --git a/projects/unit3/github-actions-integration/solution/server.py b/projects/unit3/github-actions-integration/solution/server.py index 4a11633..79454e6 100644 --- a/projects/unit3/github-actions-integration/solution/server.py +++ b/projects/unit3/github-actions-integration/solution/server.py @@ -7,7 +7,7 @@ import json import os import subprocess -from typing import Dict, Any, Optional +from typing import Optional from pathlib import Path from mcp.server.fastmcp import FastMCP @@ -18,9 +18,37 @@ # PR template directory (shared between starter and solution) TEMPLATES_DIR = Path(__file__).parent.parent.parent / "templates" +# Default PR templates +DEFAULT_TEMPLATES = { + "bug.md": "Bug Fix", + "feature.md": "Feature", + "docs.md": "Documentation", + "refactor.md": "Refactor", + "test.md": "Test", + "performance.md": "Performance", + "security.md": "Security" +} + # File where webhook server stores events EVENTS_FILE = Path(__file__).parent / "github_events.json" +# Type mapping for PR templates +TYPE_MAPPING = { + "bug": "bug.md", + "fix": "bug.md", + "feature": "feature.md", + "enhancement": "feature.md", + "docs": "docs.md", + "documentation": "docs.md", + "refactor": "refactor.md", + "cleanup": "refactor.md", + "test": "test.md", + "testing": "test.md", + "performance": "performance.md", + "optimization": "performance.md", + "security": "security.md" +} + # ===== Original Tools from Module 1 (with output limiting) ===== @@ -49,7 +77,7 @@ async def analyze_file_changes( root = roots_result.roots[0] # FileUrl object has a .path property that gives us the path directly working_directory = root.uri.path - except Exception as e: + except Exception: # If we can't get roots, fall back to current directory pass @@ -122,30 +150,14 @@ async def analyze_file_changes( @mcp.tool() async def get_pr_templates() -> str: """List available PR templates with their content.""" - templates = [] - - # Define default templates - default_templates = { - "bug.md": "Bug Fix", - "feature.md": "Feature", - "docs.md": "Documentation", - "refactor.md": "Refactor", - "test.md": "Test", - "performance.md": "Performance", - "security.md": "Security" - } - - for filename, template_type in default_templates.items(): - template_path = TEMPLATES_DIR / filename - - # Read template content - content = template_path.read_text() - - templates.append({ + templates = [ + { "filename": filename, "type": template_type, - "content": content - }) + "content": (TEMPLATES_DIR / filename).read_text() + } + for filename, template_type in DEFAULT_TEMPLATES.items() + ] return json.dumps(templates, indent=2) @@ -163,25 +175,8 @@ async def suggest_template(changes_summary: str, change_type: str) -> str: templates_response = await get_pr_templates() templates = json.loads(templates_response) - # Map change types to template files - type_mapping = { - "bug": "bug.md", - "fix": "bug.md", - "feature": "feature.md", - "enhancement": "feature.md", - "docs": "docs.md", - "documentation": "docs.md", - "refactor": "refactor.md", - "cleanup": "refactor.md", - "test": "test.md", - "testing": "test.md", - "performance": "performance.md", - "optimization": "performance.md", - "security": "security.md" - } - # Find matching template - template_file = type_mapping.get(change_type.lower(), "feature.md") + template_file = TYPE_MAPPING.get(change_type.lower(), "feature.md") selected_template = next( (t for t in templates if t["filename"] == template_file), templates[0] # Default to first template if no match diff --git a/projects/unit3/github-actions-integration/starter/server.py b/projects/unit3/github-actions-integration/starter/server.py index 0485b67..56610a1 100644 --- a/projects/unit3/github-actions-integration/starter/server.py +++ b/projects/unit3/github-actions-integration/starter/server.py @@ -7,7 +7,7 @@ import json import os import subprocess -from typing import Dict, List, Any, Optional +from typing import Optional from pathlib import Path from datetime import datetime @@ -19,9 +19,37 @@ # PR template directory (shared between starter and solution) TEMPLATES_DIR = Path(__file__).parent.parent.parent / "templates" +# Default PR templates +DEFAULT_TEMPLATES = { + "bug.md": "Bug Fix", + "feature.md": "Feature", + "docs.md": "Documentation", + "refactor.md": "Refactor", + "test.md": "Test", + "performance.md": "Performance", + "security.md": "Security" +} + # TODO: Add path to events file where webhook_server.py stores events # Hint: EVENTS_FILE = Path(__file__).parent / "github_events.json" +# Type mapping for PR templates +TYPE_MAPPING = { + "bug": "bug.md", + "fix": "bug.md", + "feature": "feature.md", + "enhancement": "feature.md", + "docs": "docs.md", + "documentation": "docs.md", + "refactor": "refactor.md", + "cleanup": "refactor.md", + "test": "test.md", + "testing": "test.md", + "performance": "performance.md", + "optimization": "performance.md", + "security": "security.md" +} + # ===== Module 1 Tools (Already includes output limiting fix from Module 1) ===== @@ -94,38 +122,22 @@ async def analyze_file_changes( return json.dumps(analysis, indent=2) except subprocess.CalledProcessError as e: - return f"Error analyzing changes: {e.stderr}" + return json.dumps({"error": f"Git error: {e.stderr}"}) except Exception as e: - return f"Error: {str(e)}" + return json.dumps({"error": str(e)}) @mcp.tool() async def get_pr_templates() -> str: """List available PR templates with their content.""" - templates = [] - - # Define default templates - default_templates = { - "bug.md": "Bug Fix", - "feature.md": "Feature", - "docs.md": "Documentation", - "refactor.md": "Refactor", - "test.md": "Test", - "performance.md": "Performance", - "security.md": "Security" - } - - for filename, template_type in default_templates.items(): - template_path = TEMPLATES_DIR / filename - - # Read template content - content = template_path.read_text() - - templates.append({ + templates = [ + { "filename": filename, "type": template_type, - "content": content - }) + "content": (TEMPLATES_DIR / filename).read_text() + } + for filename, template_type in DEFAULT_TEMPLATES.items() + ] return json.dumps(templates, indent=2) @@ -143,25 +155,8 @@ async def suggest_template(changes_summary: str, change_type: str) -> str: templates_response = await get_pr_templates() templates = json.loads(templates_response) - # Map change types to template files - type_mapping = { - "bug": "bug.md", - "fix": "bug.md", - "feature": "feature.md", - "enhancement": "feature.md", - "docs": "docs.md", - "documentation": "docs.md", - "refactor": "refactor.md", - "cleanup": "refactor.md", - "test": "test.md", - "testing": "test.md", - "performance": "performance.md", - "optimization": "performance.md", - "security": "security.md" - } - # Find matching template - template_file = type_mapping.get(change_type.lower(), "feature.md") + template_file = TYPE_MAPPING.get(change_type.lower(), "feature.md") selected_template = next( (t for t in templates if t["filename"] == template_file), templates[0] # Default to first template if no match diff --git a/projects/unit3/slack-notification/solution/server.py b/projects/unit3/slack-notification/solution/server.py index 0c24dbb..2af9939 100644 --- a/projects/unit3/slack-notification/solution/server.py +++ b/projects/unit3/slack-notification/solution/server.py @@ -8,7 +8,7 @@ import os import subprocess import requests -from typing import Dict, Any, Optional +from typing import Optional from pathlib import Path from mcp.server.fastmcp import FastMCP @@ -19,9 +19,37 @@ # PR template directory (shared between starter and solution) TEMPLATES_DIR = Path(__file__).parent.parent.parent / "templates" +# Default PR templates +DEFAULT_TEMPLATES = { + "bug.md": "Bug Fix", + "feature.md": "Feature", + "docs.md": "Documentation", + "refactor.md": "Refactor", + "test.md": "Test", + "performance.md": "Performance", + "security.md": "Security" +} + # File where webhook server stores events EVENTS_FILE = Path(__file__).parent / "github_events.json" +# Type mapping for PR templates +TYPE_MAPPING = { + "bug": "bug.md", + "fix": "bug.md", + "feature": "feature.md", + "enhancement": "feature.md", + "docs": "docs.md", + "documentation": "docs.md", + "refactor": "refactor.md", + "cleanup": "refactor.md", + "test": "test.md", + "testing": "test.md", + "performance": "performance.md", + "optimization": "performance.md", + "security": "security.md" +} + # ===== Tools from Modules 1 & 2 (Complete with output limiting) ===== @@ -50,7 +78,7 @@ async def analyze_file_changes( root = roots_result.roots[0] # FileUrl object has a .path property that gives us the path directly working_directory = root.uri.path - except Exception as e: + except Exception: # If we can't get roots, fall back to current directory pass @@ -123,30 +151,14 @@ async def analyze_file_changes( @mcp.tool() async def get_pr_templates() -> str: """List available PR templates with their content.""" - templates = [] - - # Define default templates - default_templates = { - "bug.md": "Bug Fix", - "feature.md": "Feature", - "docs.md": "Documentation", - "refactor.md": "Refactor", - "test.md": "Test", - "performance.md": "Performance", - "security.md": "Security" - } - - for filename, template_type in default_templates.items(): - template_path = TEMPLATES_DIR / filename - - # Read template content - content = template_path.read_text() - - templates.append({ + templates = [ + { "filename": filename, "type": template_type, - "content": content - }) + "content": (TEMPLATES_DIR / filename).read_text() + } + for filename, template_type in DEFAULT_TEMPLATES.items() + ] return json.dumps(templates, indent=2) @@ -164,25 +176,8 @@ async def suggest_template(changes_summary: str, change_type: str) -> str: templates_response = await get_pr_templates() templates = json.loads(templates_response) - # Map change types to template files - type_mapping = { - "bug": "bug.md", - "fix": "bug.md", - "feature": "feature.md", - "enhancement": "feature.md", - "docs": "docs.md", - "documentation": "docs.md", - "refactor": "refactor.md", - "cleanup": "refactor.md", - "test": "test.md", - "testing": "test.md", - "performance": "performance.md", - "optimization": "performance.md", - "security": "security.md" - } - # Find matching template - template_file = type_mapping.get(change_type.lower(), "feature.md") + template_file = TYPE_MAPPING.get(change_type.lower(), "feature.md") selected_template = next( (t for t in templates if t["filename"] == template_file), templates[0] # Default to first template if no match diff --git a/projects/unit3/slack-notification/starter/server.py b/projects/unit3/slack-notification/starter/server.py index fc5ca01..b755519 100644 --- a/projects/unit3/slack-notification/starter/server.py +++ b/projects/unit3/slack-notification/starter/server.py @@ -7,7 +7,7 @@ import json import os import subprocess -from typing import Dict, Any, Optional +from typing import Optional from pathlib import Path from mcp.server.fastmcp import FastMCP @@ -18,9 +18,37 @@ # PR template directory (shared between starter and solution) TEMPLATES_DIR = Path(__file__).parent.parent.parent / "templates" +# Default PR templates +DEFAULT_TEMPLATES = { + "bug.md": "Bug Fix", + "feature.md": "Feature", + "docs.md": "Documentation", + "refactor.md": "Refactor", + "test.md": "Test", + "performance.md": "Performance", + "security.md": "Security" +} + # File where webhook server stores events EVENTS_FILE = Path(__file__).parent / "github_events.json" +# Type mapping for PR templates +TYPE_MAPPING = { + "bug": "bug.md", + "fix": "bug.md", + "feature": "feature.md", + "enhancement": "feature.md", + "docs": "docs.md", + "documentation": "docs.md", + "refactor": "refactor.md", + "cleanup": "refactor.md", + "test": "test.md", + "testing": "test.md", + "performance": "performance.md", + "optimization": "performance.md", + "security": "security.md" +} + # ===== Tools from Modules 1 & 2 (Complete with output limiting) ===== @@ -101,30 +129,14 @@ async def analyze_file_changes( @mcp.tool() async def get_pr_templates() -> str: """List available PR templates with their content.""" - templates = [] - - # Define default templates - default_templates = { - "bug.md": "Bug Fix", - "feature.md": "Feature", - "docs.md": "Documentation", - "refactor.md": "Refactor", - "test.md": "Test", - "performance.md": "Performance", - "security.md": "Security" - } - - for filename, template_type in default_templates.items(): - template_path = TEMPLATES_DIR / filename - - # Read template content - content = template_path.read_text() - - templates.append({ + templates = [ + { "filename": filename, "type": template_type, - "content": content - }) + "content": (TEMPLATES_DIR / filename).read_text() + } + for filename, template_type in DEFAULT_TEMPLATES.items() + ] return json.dumps(templates, indent=2) @@ -142,25 +154,8 @@ async def suggest_template(changes_summary: str, change_type: str) -> str: templates_response = await get_pr_templates() templates = json.loads(templates_response) - # Map change types to template files - type_mapping = { - "bug": "bug.md", - "fix": "bug.md", - "feature": "feature.md", - "enhancement": "feature.md", - "docs": "docs.md", - "documentation": "docs.md", - "refactor": "refactor.md", - "cleanup": "refactor.md", - "test": "test.md", - "testing": "test.md", - "performance": "performance.md", - "optimization": "performance.md", - "security": "security.md" - } - # Find matching template - template_file = type_mapping.get(change_type.lower(), "feature.md") + template_file = TYPE_MAPPING.get(change_type.lower(), "feature.md") selected_template = next( (t for t in templates if t["filename"] == template_file), templates[0] # Default to first template if no match diff --git a/units/en/unit3/build-mcp-server.mdx b/units/en/unit3/build-mcp-server.mdx index 8936d58..5ba06c5 100644 --- a/units/en/unit3/build-mcp-server.mdx +++ b/units/en/unit3/build-mcp-server.mdx @@ -178,7 +178,9 @@ Then: 3. Watch Claude use your tools to provide intelligent suggestions + **Common first error**: If you get "MCP tool response exceeds maximum allowed tokens (25000)", this is expected! Large repositories can generate massive diffs. This is a valuable learning moment - see the "Handling Large Outputs" section for the solution. + ## Common Patterns @@ -212,13 +214,17 @@ except Exception as e: ``` + **Error Handling**: Always return valid JSON from your tools, even for errors. Claude needs structured data to understand what went wrong and provide helpful responses to users. + ### Handling Large Outputs (Critical Learning Moment!) + **Real-world constraint**: MCP tools have a token limit of 25,000 tokens per response. Large git diffs can easily exceed this limit 10x or more! This is a critical lesson for production MCP development. + When implementing `analyze_file_changes`, you'll likely encounter this error: diff --git a/units/en/unit3/github-actions-integration.mdx b/units/en/unit3/github-actions-integration.mdx index 62680e9..5747c6e 100644 --- a/units/en/unit3/github-actions-integration.mdx +++ b/units/en/unit3/github-actions-integration.mdx @@ -147,7 +147,9 @@ The webhook server handles all the HTTP details - you just need to read the JSON Just like in Module 1 where you created tools for file analysis, you'll now create tools for CI/CD analysis. These tools will work alongside your existing PR analysis tools, giving Claude a complete view of both code changes and build status. + **Note**: The starter code already includes the output limiting fix from Module 1, so you won't encounter token limit errors. Focus on the new concepts in this module! + Implement two new tools: diff --git a/units/en/unit3/introduction.mdx b/units/en/unit3/introduction.mdx index 7417970..67e3f9b 100644 --- a/units/en/unit3/introduction.mdx +++ b/units/en/unit3/introduction.mdx @@ -71,7 +71,9 @@ Follow the [official installation guide](https://docs.anthropic.com/en/docs/clau Once installed, you'll use Claude Code throughout this unit to test your MCP server and interact with the workflow automation you build. + **New to Claude Code?** If you encounter any setup issues, the [troubleshooting guide](https://docs.anthropic.com/en/docs/claude-code/troubleshooting) covers common installation and authentication problems. + By the end of this unit, you'll have built a complete MCP server that demonstrates how to transform Claude Code into a powerful team development assistant, with hands-on experience using all three MCP primitives. \ No newline at end of file diff --git a/units/en/unit3/slack-notification.mdx b/units/en/unit3/slack-notification.mdx index 3002626..92fbc6b 100644 --- a/units/en/unit3/slack-notification.mdx +++ b/units/en/unit3/slack-notification.mdx @@ -131,7 +131,9 @@ slack-notification/ - Treat it like a password - anyone with this URL can send messages to your channel + **Security Alert**: Webhook URLs are sensitive credentials! Anyone with your webhook URL can send messages to your Slack channel. Always store them as environment variables and never commit them to version control. + ### Step 2: Add Slack Tool (15 min) @@ -139,7 +141,9 @@ slack-notification/ Now that you have a working webhook, you'll add a new MCP tool to your existing server.py from Module 2. This tool will handle sending notifications to Slack by making HTTP requests to the webhook URL. + **Note**: The starter code includes all improvements from Modules 1 & 2 (output limiting, webhook handling). Focus on the new Slack integration! + Add this tool to your server.py: