# 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)
