Inspiration

My Software Engineering and OOP classes ran entirely in Slack, but every other course lived in Canvas. I was constantly switching tabs just to check a deadline or find a rubric mid-conversation. The context switching was genuinely frustrating, so I built the thing I wished existed: a Canvas assistant that lives right inside Slack.

What it does

@mention the bot in any Slack thread and ask it anything about your Canvas — what's due this week, what a rubric says, what announcements were posted. Because I also work as a TA and grader, I knew read-only wouldn't cut it, so I added confirmation-gated write actions too: post an announcement, start a discussion, or add a private to-do, all without leaving Slack. The bot always restates what it's about to do and waits for explicit approval before touching anything on Canvas.

How I built it

I planned before I wrote a single line of code. I created a planning.md spec that mapped out the file structure, the purpose of every module, and the architecture before any prompting started. I used Claude Code to build the project, but I reviewed every piece of AI-generated code rather than accepting it blindly — the spec gave me a clear target to check against.

The bot is Python + Slack Bolt, with a ReAct loop connecting to Canvas through the MCP protocol. I originally planned to use Claude for inference, but pivoted to Groq (Llama 3.3) when budget became a constraint. Since Groq doesn't speak MCP natively, I wrote a custom bridge that translates MCP tool definitions into Groq's tool-calling schema on the fly — that translation layer ended up being the most technically interesting part of the project.

I also learned to use Git properly: committing frequently, one logical change per commit, and never merging into main while it was broken. Small habit, but it saved me multiple times.

Challenges

The MCP-to-Groq bridge was the first real wall — there was no existing solution, so I had to understand both protocols well enough to write the translation myself. After that, the ReAct loop kept running indefinitely on hard questions, so I added a MAX_STEPS cap and a hard timeout that tells the user to retry rather than hanging forever. I hit Groq rate limits constantly while testing and worked around them with a second account. Long model responses were breaking the Slack message formatter, so I added a character cap on tool results before they hit the context window. And when merge conflicts hit during a late-night push, I fixed them manually rather than force-pushing — maintaining a clean main branch mattered more than moving fast.

The biggest challenge wasn't technical though. It was staying consistent: finishing the project instead of abandoning it when things got messy. I think that's the part that actually transfers to real engineering work.

Built With

Share this project:

Updates

Submission history