# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
> **Note**: This guide is for developers working ON the Canvas MCP codebase. If you're an AI agent USING the MCP server, see [AGENTS.md](../AGENTS.md) instead.
# Canvas MCP Development Guide
## Environment Setup
- Install uv package manager: `pip install uv`
- Install dependencies: `uv pip install -e .`
- Create `.env` file with `CANVAS_API_TOKEN` and `CANVAS_API_URL`
- Server installed as CLI command: `canvas-mcp-server`
## Commands
- **Start server**: `canvas-mcp-server` (or `./start_canvas_server.sh` for legacy setup)
- **Test server**: `canvas-mcp-server --test`
- **View config**: `canvas-mcp-server --config`
- **MCP client config**: Update your MCP client's configuration file (e.g., `~/Library/Application Support/Claude/claude_desktop_config.json` for Claude Desktop)
## Repository Structure
```
canvas-mcp/
├── src/canvas_mcp/ # Main application code
│ ├── core/ # Core utilities (client, config, validation)
│ ├── tools/ # MCP tool implementations
│ ├── resources/ # MCP resources and prompts
│ └── server.py # FastMCP server entry point
├── docs/ # Essential documentation
├── archive/ # Legacy code and development specs (git-ignored)
├── .env # Configuration
└── start_canvas_server.sh # Server startup script
```
## Architecture Overview
### Core Design Patterns
- **FastMCP framework**: Built on FastMCP for robust MCP server implementation with proper tool registration
- **Type-driven validation**: All MCP tools use `@validate_params` decorator with sophisticated Union/Optional type handling
- **Dual-layer caching**: Bidirectional course code ↔ ID mapping via `course_code_to_id_cache` and `id_to_course_code_cache`
- **Flexible identifiers**: Support for Canvas IDs, course codes, and SIS IDs through `get_course_id()` abstraction
- **ISO 8601 standardization**: All dates converted via `format_date()` and `parse_date()` functions
### MCP Tool Organization
- **Progressive disclosure**: List → Details → Content → Analytics pattern
- **Functional grouping**: Tools organized by Canvas entity (courses, assignments, discussions, messaging, etc.)
- **Consistent naming**: `{action}_{entity}[_{specifier}]` pattern
- **Educational analytics focus**: Student performance, completion rates, missing work identification
- **Discussion workflow**: Browse → View → Read → Reply pattern for student interaction
- **Messaging workflow**: Analytics → Target → Template → Send pattern for automated communications
### API Layer Architecture
- **Centralized requests**: All Canvas API calls go through `make_canvas_request()`
- **Form data support**: Messaging endpoints use `use_form_data=True` for Canvas compatibility
- **Automatic pagination**: `fetch_all_paginated_results()` handles Canvas pagination transparently
- **Async throughout**: All I/O operations use async/await
- **Graceful error handling**: Returns JSON error responses rather than raising exceptions
- **Privacy protection**: Student data anonymization via configurable `anonymize_response_data()`
## Key Components
### Parameter Validation System
- `validate_parameter()`: Runtime type coercion supporting complex types
- `@validate_params`: Automatic validation decorator for all MCP tools
- Handles Union types, Optional types, string→JSON conversion, comma-separated lists
### Course Identifier Handling
- `get_course_id()`: Converts any identifier type to Canvas ID
- `get_course_code()`: Reverse lookup from ID to human-readable code
- `refresh_course_cache()`: Rebuilds identifier mapping from Canvas API
### Analytics Engine
- `get_student_analytics()`: Multi-dimensional educational data analysis
- `get_assignment_analytics()`: Statistical performance analysis with grade distribution
- `get_peer_review_completion_analytics()`: Peer review tracking and completion analysis
- `get_peer_review_comments()`: Extract actual peer review comment text and analysis
- `analyze_peer_review_quality()`: Comprehensive comment quality analysis with metrics
- `identify_problematic_peer_reviews()`: Automated flagging of low-quality reviews
- Temporal filtering (current vs. all assignments)
- Risk identification and performance categorization
### Messaging System
- `send_conversation()`: Core Canvas messaging with form data support
- `send_peer_review_reminders()`: Automated peer review reminder workflow
- `send_peer_review_followup_campaign()`: Complete analytics → messaging pipeline
- `MessageTemplates`: Flexible template system for various communication types
- Privacy-aware: Works with anonymization while preserving functional user IDs
## Git Workflow - ASK FIRST
**Before starting any new feature or significant change, ASK:**
> "Should I create a feature branch for this, or work directly on main?"
| Change Type | Default Branch | Notes |
|-------------|----------------|-------|
| New tool/feature | `feature/tool-name` | PR with CI checks |
| Bug fix | `fix/issue-description` | PR recommended |
| Documentation only | `main` okay | Direct push acceptable |
| Quick fix (typo, etc.) | `main` okay | Direct push acceptable |
**Branch naming:** `feature/`, `fix/`, `docs/`, `refactor/`
This repo has branch protection on `main` (PR + status checks required), but admin can bypass. Always ask the user which workflow they prefer for the current task.
---
## Release Checklist
When bumping the version in `pyproject.toml`, also update:
- [ ] `src/canvas_mcp/__init__.py` - Update `__version__`
- [ ] `server.json` - Update both `version` fields (top-level and packages[0]) for MCP Registry
- [ ] `README.md` - Update "Latest Release" section with new version, date, and changelog
- [ ] `docs/index.html` - Update version badge, tool count, and meta descriptions (GitHub Pages site)
- [ ] Create git tag: `git tag vX.Y.Z && git push origin vX.Y.Z`
---
## Coding Standards
- **Type hints**: Mandatory for all functions, use Union/Optional appropriately
- **MCP tools**: Use `@mcp.tool()` decorator with `@validate_params`
- **Async functions**: All API interactions must be async
- **Course identifiers**: Use `Union[str, int]` and `get_course_id()` for flexibility
- **Date handling**: Use `format_date()` for all date outputs
- **Error responses**: Return JSON strings with "error" key for failures
- **Form data**: Use `use_form_data=True` for Canvas POST/PUT endpoints
- **Privacy**: Student IDs preserved, names anonymized in `_should_anonymize_endpoint()`
- **Optional params**: Use `Optional[T]` type hints for parameters that can be `None`
## Test-Driven Development (TDD) - ENFORCED
**All new MCP tools MUST have tests before the feature is considered complete.**
### TDD Workflow
1. **Write tests first** (or alongside) for new tools
2. **Minimum 3 tests per tool**: success path, error handling, edge case
3. **Run tests** before committing: `pytest tests/tools/`
4. **No merging** without passing tests
### Test Structure
```
tests/
├── tools/ # Unit tests for MCP tools
│ ├── test_modules.py # Reference implementation
│ ├── test_pages.py # Page tools tests
│ └── ...
└── security/ # Security-focused tests
```
### Test Patterns (from test_modules.py)
```python
@pytest.fixture
def mock_canvas_request():
with patch('src.canvas_mcp.tools.modules.make_canvas_request') as mock:
yield mock
@pytest.mark.asyncio
async def test_tool_success(mock_canvas_request, mock_course_id):
mock_canvas_request.return_value = {"id": 123, "name": "Test"}
result = await tool_function(course_identifier="test", ...)
assert "success" in result.lower() or "123" in result
```
### What to Test
- ✅ Successful API responses
- ✅ API error handling (404, 401, 500)
- ✅ Parameter validation (missing required params, invalid types)
- ✅ Edge cases (empty lists, None values, special characters)
- ✅ Canvas API quirks (form data requirements, pagination)
See: [Issue #56](https://github.com/vishalsachdev/canvas-mcp/issues/56) for comprehensive test coverage plan.
## Discussion Forum Interaction Workflow
- **Browse discussions**: `list_discussion_topics(course_id)` - Find available discussion forums
- **View student posts**: `list_discussion_entries(course_id, topic_id)` - See all posts in a discussion
- **Read full content**: `get_discussion_entry_details(course_id, topic_id, entry_id)` - Get complete student comment
- **Reply to students**: `reply_to_discussion_entry(course_id, topic_id, entry_id, "Your response")` - Respond to student comments
- **Create discussions**: `create_discussion_topic(course_id, title, message)` - Start new discussion forums
- **Post new entries**: `post_discussion_entry(course_id, topic_id, message)` - Add top-level posts
## Canvas Messaging Workflow
- **Analyze completion**: `get_peer_review_completion_analytics(course_id, assignment_id)` - Get students needing reminders
- **Target recipients**: Extract user IDs from analytics results for messaging
- **Choose template**: Use `MessageTemplates.get_template()` or custom message content
- **Send reminders**: `send_peer_review_reminders()` for targeted messaging
- **Bulk campaigns**: `send_peer_review_followup_campaign()` for complete automated workflow
- **Monitor delivery**: Check Canvas inbox for message delivery confirmation
## Peer Review Comment Analysis Workflow
- **Extract comments**: `get_peer_review_comments(course_id, assignment_id)` - Get all review text and metadata
- **Analyze quality**: `analyze_peer_review_quality(course_id, assignment_id)` - Generate comprehensive quality metrics
- **Flag problems**: `identify_problematic_peer_reviews(course_id, assignment_id)` - Find reviews needing attention
- **Export data**: `extract_peer_review_dataset(course_id, assignment_id, format="csv")` - Export for further analysis
- **Generate reports**: `generate_peer_review_feedback_report(course_id, assignment_id)` - Create instructor-ready reports
- **Take action**: Use problematic review lists to provide targeted feedback or follow-up
## Canvas API Specifics
- Base URL from `CANVAS_API_URL` environment variable
- Authentication via Bearer token in `CANVAS_API_TOKEN`
- Always use pagination for list endpoints
- Course codes preferred over IDs in user-facing output
- Handle both published and unpublished content states
- **Messaging requires form data**: Use `use_form_data=True` for `/conversations` endpoints
- **Privacy protection**: Real user IDs preserved for functionality, names anonymized for privacy
## Documentation Maintenance
### Source of Truth Hierarchy
This repository has multiple documentation files for different audiences. To prevent redundancy:
| File | Audience | Contains | Updates When |
|------|----------|----------|--------------|
| `AGENTS.md` | AI agents/MCP clients | Tool tables, workflows, constraints, examples | Tools added/changed |
| `tools/README.md` | Human users | Comprehensive tool docs with all parameters | Tools added/changed |
| `tools/TOOL_MANIFEST.json` | Programmatic access | Machine-readable tool catalog | Tools added/changed |
| `README.md` | Everyone (entry point) | Installation, overview, links to other docs | Major releases only |
| `examples/*.md` | Human users | Workflow tutorials, not tool reference | New workflows added |
| `CLAUDE.md` | Developers | Codebase architecture, NOT tool usage | Architecture changes |
### Rules to Prevent Redundancy
1. **Tool documentation**:
- Source of truth: `tools/README.md` (humans) and `AGENTS.md` (agents)
- `README.md` inline section exists ONLY for fetch-constrained agents
- Do NOT add tool details to examples/*.md - link to tools/README.md instead
2. **Example prompts**:
- Source of truth: `AGENTS.md` (has example prompts per tool)
- `tools/TOOL_MANIFEST.json` mirrors these for machine access
- Quickstart guides use DIFFERENT examples (workflow-focused, not tool-focused)
3. **Rate limits/constraints**:
- Source of truth: `AGENTS.md` (agent-facing constraints)
- Do NOT duplicate in README.md or tools/README.md
4. **Workflows**:
- Source of truth: `AGENTS.md` (common workflows) + `examples/*.md` (detailed tutorials)
- `TOOL_MANIFEST.json` has simplified workflow references
5. **When adding a new tool**:
- Update `tools/README.md` with full documentation
- Update `AGENTS.md` tool table (keep it concise)
- Update `tools/TOOL_MANIFEST.json` with parameters and examples
- Do NOT update README.md unless it's a major feature
6. **When updating tool behavior**:
- Update the source of truth files above
- Check for stale references in examples/*.md
### What NOT to Do
- Do NOT copy tool tables between files (they drift)
- Do NOT add installation instructions outside README.md
- Do NOT add architecture details to AGENTS.md (that's for CLAUDE.md)
- Do NOT add example prompts to tools/README.md (that's for AGENTS.md)
## Current Focus
- [ ] CI/CD cleanup and maintenance
- [ ] Backlog triage (module templates, bulk creation, page versioning)
## Roadmap
- [x] Module management tools (7 tools, 36 tests)
- [x] Page settings tools (2 tools, 15 tests)
- [x] TDD enforcement in development workflow
- [x] Release v1.0.6
- [x] `update_assignment` tool (9 tests)
- [x] Security hardening — PII sanitization, token validation, audit logging, sandbox defaults
- [x] CodeQL alert remediation (31 alerts → 0)
- [x] Ruff linting enforcement + pre-commit hook
- [x] Release v1.0.8 — all CI/CD pipelines passing (PyPI, MCP Registry, GitHub Release)
## Backlog
- [ ] Module templates (pre-configured module structures)
- [ ] Bulk module creation from JSON/YAML specs
- [ ] Module duplication across courses
- [ ] Page templates
- [ ] Bulk page creation from markdown files
- [ ] Page content versioning/history tools
## Session Log
> Full history: [session-history.md](./session-history.md)
### 2026-02-23
- **PR #75 Review & Merge**: Reviewed Samuel Parks' file download/listing tools PR
- Fixed path traversal vulnerability (sanitize_filename on API-provided filenames)
- Switched to streaming downloads (aiter_bytes) for large files
- Added sort/order parameter validation in list_course_files
- Replaced hardcoded `/tmp` with `tempfile.gettempdir()`
- Added 17 new tests (50 total file tests), Codex review passed
- Cherry-picked fix commits onto main after fork-based merge gap
- **Article**: "The Moment Your Side Project Stops Being Yours" — OSS contributor stories
- Published drafts to Substack, LinkedIn (1,101 subscribers), and X/Twitter
- Generated 3 cover images (LinkedIn 1200x628, Substack 1100x220, Twitter 1200x675)
- **Skill Updates**: Fixed `/publish-to-substack` and `/publish-to-linkedin` skills
- Substack: title/subtitle changed from contenteditable divs to `<textarea>` elements
- Substack: body editor selector changed to `.tiptap.ProseMirror`
- LinkedIn: title also changed to `<textarea>` — native value setter pattern needed
- Both skills: updated CSS selectors reference tables and known bugs
### 2026-02-20
- **CI cleanup**: Removed auto-update README step from `create-release.yml` (~160 lines deleted)
- The step created orphaned branches (e.g., `auto-update-readme-v1.0.8`) when branch protection blocked direct pushes
- README is already updated manually during release prep — automation was redundant
- Also removed `pull-requests: write` permission (no longer needed)
- **Branch cleanup**: Deleted orphaned remote branch `auto-update-readme-v1.0.8`
### 2026-02-16
- **Security Hardening (v1.0.8)**:
- Implemented 4 security features via PR #74 (`feature/security-hardening`):
- PII sanitization in logs (`LOG_REDACT_PII=true` default)
- Token validation on startup (warns but doesn't block)
- Structured JSON audit logging (`LOG_ACCESS_EVENTS`, `LOG_EXECUTION_EVENTS`)
- Sandbox hardening — secure-by-default (sandbox ON, network blocked, CPU/memory limits)
- Codex CLI review caught 3 issues: raw error payloads in audit logs, stderr in code execution audit, missing Docker env vars — all fixed
- 235+ tests (up from 167)
- **CodeQL Alert Remediation**:
- Resolved all 31 open alerts: 9 dismissed (archive), 4 false positives, 3 intentional patterns, 15 fixed in source/tests
- Codex CLI handled 12 test file cleanups automatically
- **Ruff Linting Enforcement**:
- Fixed 464 lint issues across codebase (443 auto, 21 manual)
- Added `.git/hooks/pre-commit` running ruff on staged files
- Updated `~/.claude/AGENTS.md` with linting setup template for all Python repos
- **Release v1.0.8**:
- Bumped version across `pyproject.toml`, `__init__.py`, `docs/index.html`, `server.json`
- Fixed server.json version (was stuck at 1.0.6 — caused MCP Registry "duplicate version" error)
- Added `workflow_dispatch` to `publish-mcp.yml` for manual re-triggers
- Made README auto-update non-blocking in `create-release.yml` with summary step
- All workflows passing: PyPI, MCP Registry, GitHub Release, GitHub Pages
- Added `server.json` and `__init__.py` to release checklist in CLAUDE.md
- **Cleanup**: Removed `Build AI Product Sense/` and `smithery-wrapper/` from repo
- **Tooling**: Created `/codex-review` skill for cross-checking changes with OpenAI Codex CLI
- **Decision**: Smithery publishing dropped from backlog (wrapper removed, marketplace access blocked)
### 2026-02-01
- **Smithery Publishing Attempt** (blocked):
- Goal: Publish canvas-mcp to Smithery marketplace for additional distribution
- **Findings**:
- Smithery has 3 publishing options: URL (HTTP), Hosted, Local (stdio)
- **URL option**: Requires Streamable HTTP transport (canvas-mcp uses stdio)
- **Hosted option**: "Private Early Access" - not publicly available
- **Local option**: CLI expects server entry to exist first; can't create new servers via CLI
- Web UI only exposes URL option; no way to create Hosted/Local servers
- **What we built**: TypeScript wrapper at `smithery-wrapper/` with 10 core tools
- Native TS Canvas MCP using `@modelcontextprotocol/sdk`
- Builds successfully with `smithery build`
- Ready for future deployment if Smithery opens up access
- **Decision**: Skip Smithery → focus on MCP Registry + PyPI (already published)
- `smithery-wrapper/` removed in 2026-02-16 session (unused prototype)