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: