<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daniel Ainoko</title>
    <description>The latest articles on DEV Community by Daniel Ainoko (@thecodedaniel).</description>
    <link>https://dev.to/thecodedaniel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3766907%2F8c99f7d1-c627-45b4-ac4f-b3ae4a3301a9.jpg</url>
      <title>DEV Community: Daniel Ainoko</title>
      <link>https://dev.to/thecodedaniel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thecodedaniel"/>
    <language>en</language>
    <item>
      <title>I Taught a Security Camera to Say "I Don't Know" — and It Got More Useful, Not Less</title>
      <dc:creator>Daniel Ainoko</dc:creator>
      <pubDate>Mon, 20 Jul 2026 20:05:37 +0000</pubDate>
      <link>https://dev.to/thecodedaniel/i-taught-a-security-camera-to-say-i-dont-know-and-it-got-more-useful-not-less-1d87</link>
      <guid>https://dev.to/thecodedaniel/i-taught-a-security-camera-to-say-i-dont-know-and-it-got-more-useful-not-less-1d87</guid>
      <description>&lt;p&gt;Somewhere around day four of building a doorbell-camera AI for a hackathon,&lt;br&gt;
I caught my own system lying to me. Not maliciously — it was just guessing,&lt;br&gt;
the way all of us do when we don't want to say "I don't know" out loud. The&lt;br&gt;
fix for that turned out to be more interesting than anything else in the&lt;br&gt;
build, so this post is mostly about that, with some genuinely funny/painful&lt;br&gt;
bug stories along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem nobody built a camera to solve
&lt;/h2&gt;

&lt;p&gt;Every doorbell camera on the market answers one question: &lt;em&gt;was there&lt;br&gt;
motion?&lt;/em&gt; That's the wrong question. The right question — the one a human&lt;br&gt;
actually wants answered at 11pm when their phone buzzes — is &lt;em&gt;should I&lt;br&gt;
care about this?&lt;/em&gt; Cameras don't answer that, so people train themselves to&lt;br&gt;
swipe the notification away without looking. The alert becomes noise. The&lt;br&gt;
one time it isn't noise, you've already stopped paying attention.&lt;/p&gt;

&lt;p&gt;So the project — I called it &lt;strong&gt;Kenbunshoku&lt;/strong&gt; (見聞色, roughly "the art of&lt;br&gt;
perceiving," a term from certain martial traditions for reading a&lt;br&gt;
situation before it becomes obvious) — tries to add one missing layer:&lt;br&gt;
&lt;em&gt;context&lt;/em&gt;. Not "something moved." Something closer to "a person in a&lt;br&gt;
delivery uniform is standing at your door with a package," or "someone is&lt;br&gt;
lingering by the entrance at an unusual hour without a clear reason to be&lt;br&gt;
there."&lt;/p&gt;

&lt;p&gt;The architecture, in one breath: a local YOLOv8n detector watches a camera&lt;br&gt;
stream and only forwards frames that actually contain a person. Those&lt;br&gt;
frames go to a cloud backend that calls Qwen-VL (Alibaba Cloud's&lt;br&gt;
vision-language model) to reason about what's actually happening in the&lt;br&gt;
image. A lightweight memory layer checks whether this is a recurring&lt;br&gt;
pattern. A push notification goes to the homeowner's phone with the&lt;br&gt;
reasoning attached, in plain language. That's the whole system. No part of&lt;br&gt;
it ever acts on your behalf — no locks, no alarms, no calls to anyone. It&lt;br&gt;
tells you things. You decide.&lt;/p&gt;

&lt;p&gt;That last sentence is doing more work than it looks like, and I'll come&lt;br&gt;
back to it.&lt;/p&gt;

&lt;p&gt;Here it is actually running, filmed on my phone with zero production&lt;br&gt;
values whatsoever — a real detection, a real Qwen-VL classification, a&lt;br&gt;
real push notification landing on a real device:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/6j4ZZhUeahY" rel="noopener noreferrer"&gt;https://youtu.be/6j4ZZhUeahY&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Judge the system, not the cinematography.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The lie I caught it telling
&lt;/h2&gt;

&lt;p&gt;Early on, the classification prompt just asked Qwen-VL to look at a frame&lt;br&gt;
and decide: is this person "familiar," "delivery-like," or "anomalous"? I&lt;br&gt;
tested it against a deliberately ugly, blurry, low-detail image — barely a&lt;br&gt;
silhouette — expecting either a refusal or an honest "unclear." Instead I&lt;br&gt;
got:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"familiar" — &lt;em&gt;"...suggesting they are a known individual."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read that again. The model had no reference photo of anyone who lives at&lt;br&gt;
this house. It had never seen this "known individual" before in its&lt;br&gt;
existence. It could not possibly know that. And yet it produced a&lt;br&gt;
confident, specific, entirely fabricated claim of recognition — because I&lt;br&gt;
had asked it a question shaped like an identity question, and it answered&lt;br&gt;
in kind, filling the gap with plausible-sounding language instead of&lt;br&gt;
admitting the gap existed.&lt;/p&gt;

&lt;p&gt;This is, if you squint, the exact same failure mode as a person who&lt;br&gt;
doesn't want to look uninformed in a meeting. The information isn't&lt;br&gt;
there, but the &lt;em&gt;shape of a confident answer&lt;/em&gt; is easy to produce, and&lt;br&gt;
confident-shaped answers get rewarded more often than "I don't know"&lt;br&gt;
ones — right up until the moment they're wrong in a way that matters.&lt;/p&gt;

&lt;p&gt;The fix wasn't a bigger model or a longer prompt lecture. It was&lt;br&gt;
redefining what "familiar" was even allowed to mean:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"familiar" means the visit &lt;em&gt;presents as&lt;/em&gt; expected and low-concern —&lt;br&gt;
calm demeanor, ordinary daytime approach, no unusual items — not that&lt;br&gt;
you recognize the person's identity. You have no reference photos.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And one more line that did almost all of the actual work:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the visual evidence doesn't clearly support "familiar" or&lt;br&gt;
"delivery-like," choose "anomalous" rather than guessing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same blurry test image, same model, new prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"anomalous" — &lt;em&gt;"The image is too blurry to determine any clear&lt;br&gt;
behavior, carried items, or context, making it impossible to classify&lt;br&gt;
as familiar or delivery-like."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's not a worse answer. It's a &lt;em&gt;categorically more honest&lt;/em&gt; one. The&lt;br&gt;
model went from confabulating a memory it didn't have to correctly&lt;br&gt;
reporting the limits of what it could see. For a home security system,&lt;br&gt;
"I'm not sure, so I'm erring cautious" is strictly better than a&lt;br&gt;
confident wrong guess in either direction — and it only took giving the&lt;br&gt;
model permission to say so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refusing to build the "creepier, more expensive" version
&lt;/h2&gt;

&lt;p&gt;The natural next question, if you're chasing "familiar" as identity&lt;br&gt;
recognition, is: why not just do real face matching? Store embeddings,&lt;br&gt;
compare against known visitors, get an actual identity-confidence score&lt;br&gt;
back.&lt;/p&gt;

&lt;p&gt;I sat with that question for a while and landed on: no, deliberately,&lt;br&gt;
permanently, not out of laziness but out of principle.&lt;/p&gt;

&lt;p&gt;Here's the reasoning. A system that stores biometric-adjacent data about&lt;br&gt;
everyone who walks up to your door is a fundamentally different kind of&lt;br&gt;
object than one that doesn't — different risk if it leaks, different&lt;br&gt;
ethical weight, different relationship to the people it observes (some of&lt;br&gt;
whom never consented to being in &lt;em&gt;anyone's&lt;/em&gt; facial recognition database,&lt;br&gt;
let alone a hackathon project's). And critically: it wasn't necessary. The&lt;br&gt;
actual product need — "don't wake me up for the same delivery guy every&lt;br&gt;
Tuesday" — is fully satisfied by a much cheaper, much more honest&lt;br&gt;
mechanism: pattern-matching on &lt;em&gt;visits&lt;/em&gt;, not identity.&lt;/p&gt;

&lt;p&gt;The memory layer just asks: has this camera seen a similar classification,&lt;br&gt;
on the same day of week, at a similar time, repeatedly, in the last two&lt;br&gt;
weeks? If yes, that's a "recognized pattern" — still logged, but the push&lt;br&gt;
notification gets quietly suppressed instead of buzzing your phone for the&lt;br&gt;
fifth Tuesday in a row. If a genuinely unrecognized, anomalous visit&lt;br&gt;
happens even in a pattern-matched slot, it still gets through — the system&lt;br&gt;
never goes quiet on "anomalous," patterned or not.&lt;/p&gt;

&lt;p&gt;This costs nothing extra. No embeddings, no vector database, no additional&lt;br&gt;
model calls, no image storage beyond what's needed for the one live&lt;br&gt;
classification. It's just a SQLite table and a modest SQL query. And it&lt;br&gt;
never claims to know something it doesn't. "This time slot has a routine"&lt;br&gt;
is a true, verifiable, honest claim. "I recognize this specific human&lt;br&gt;
being" is not a claim this system is entitled to make, so I made sure it&lt;br&gt;
never could.&lt;/p&gt;

&lt;p&gt;Cheap and honest turned out to be the same design decision twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restraint as the actual feature
&lt;/h2&gt;

&lt;p&gt;The system never takes autonomous action. Not "rarely" — never, as a hard&lt;br&gt;
rule I refused to relax even when it would have been a more impressive&lt;br&gt;
demo. No auto-locking doors on "anomalous," no auto-calls to anyone, no&lt;br&gt;
control signal of any kind, ever. Just a notification with reasoning&lt;br&gt;
attached, to a human, who decides.&lt;/p&gt;

&lt;p&gt;It would be easy to read that as a limitation — "the AI could do more but&lt;br&gt;
we held it back." I'd argue the opposite: the restraint &lt;em&gt;is&lt;/em&gt; the design.&lt;br&gt;
A system that watches your front door and occasionally gets it wrong (all&lt;br&gt;
of them do) is tolerable precisely because the cost of being wrong is "a&lt;br&gt;
slightly annoying notification," not "the system did something to a real&lt;br&gt;
door because a vision model was 73% confident." Keeping a human in the&lt;br&gt;
loop isn't a feature you bolt on later. It's the thing that makes&lt;br&gt;
everything upstream of it — including the confident-sounding mistakes —&lt;br&gt;
survivable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bugs that only existed once I stopped trusting the diff
&lt;/h2&gt;

&lt;p&gt;Every single one of these passed a code review. All of them looked fine on&lt;br&gt;
the page. None of them were fine.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An em dash crashed a push notification.&lt;/strong&gt; The alert title used a
plain em dash for style. HTTP header values have to be ASCII/latin-1.
The request didn't fail loudly in a way that pointed at the cause — it
just threw a &lt;code&gt;UnicodeEncodeError&lt;/code&gt; deep inside a networking library, the
first time a real alert actually tried to fire. Nothing about the code
&lt;em&gt;looked&lt;/em&gt; wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A mobile OS silently ate all outbound network traffic.&lt;/strong&gt; The
notification client connected to nothing, showed no error, gave no
signal that anything was wrong — because macOS's App Sandbox was
quietly blocking every request at the entitlement level, before it ever
reached a socket. Android had the mirror-image problem: no &lt;code&gt;INTERNET&lt;/code&gt;
permission declared, same silent nothing. Static analysis had nothing
to say about either one. Only running the actual app on actual hardware
surfaced them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The camera loop got stuck reprocessing the past.&lt;/strong&gt; Point the camera
at your hand, then a pen, then your face — and the system kept
reporting the pen. Not broken, exactly: &lt;code&gt;cv2.VideoCapture&lt;/code&gt; buffers
incoming frames faster than a multi-second classification round-trip
can drain them, so the loop was dutifully working through a growing
backlog of &lt;em&gt;old&lt;/em&gt; frames while the live scene moved on without it. The
fix was a background thread that keeps only the newest frame and lets
everything older get silently, deliberately discarded — the standard
answer to this exact class of problem, but only obvious once you've
watched it happen with your own hand in front of your own camera.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The system got slower the more careful it became.&lt;/strong&gt; A single request
was quietly doing two sequential network calls before responding — one
to the vision model, one to the push service — with no timeout set on
either. Most of the time it was fine. Occasionally, both hops had a
slow moment at once, blew past the timeout budget, and the system fell
back to a generic "cloud unreachable" alert instead of the real
classification it had actually computed a few seconds too late.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are exotic. Every one of them is a well-known category of&lt;br&gt;
bug with a well-known fix. What they have in common is that &lt;em&gt;reading the&lt;br&gt;
code gave no signal anything was wrong.&lt;/em&gt; The only way to find any of them&lt;br&gt;
was to run the actual thing, on actual hardware, against an actual&lt;br&gt;
network, and watch it fail in front of me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The recursive part
&lt;/h2&gt;

&lt;p&gt;I built this with an AI pair programmer doing most of the typing. There's&lt;br&gt;
an irony there I didn't fully appreciate until I was three days in:&lt;br&gt;
I spent this entire project teaching one AI system to stop confidently&lt;br&gt;
guessing and start admitting uncertainty — while collaborating, in real&lt;br&gt;
time, with a &lt;em&gt;different&lt;/em&gt; AI system whose suggestions also needed&lt;br&gt;
constant grounding against reality rather than being trusted on the&lt;br&gt;
strength of how plausible they sounded. Code that compiles is not code&lt;br&gt;
that works. A plan that reads well is not a plan that survives contact&lt;br&gt;
with a real phone, a real Alibaba Cloud console, a real curl command&lt;br&gt;
against a real server.&lt;/p&gt;

&lt;p&gt;The throughline for the whole build, human-authored parts and&lt;br&gt;
AI-authored parts alike, ended up being the same single sentence: &lt;em&gt;verify&lt;br&gt;
against reality, not against your own confidence.&lt;/em&gt; That sentence is the&lt;br&gt;
entire fix for the "familiar" hallucination. It's the entire fix for&lt;br&gt;
every bug in the list above. It's also, probably, decent advice for&lt;br&gt;
anyone shipping anything, with or without a vision model attached.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Kenbunshoku was built for the Global AI Hackathon Series with Qwen&lt;br&gt;
Cloud, running on Alibaba Cloud ECS with Qwen-VL for visitor reasoning.&lt;br&gt;
Camera-agnostic by design, human-in-the-loop by principle, and — as of&lt;br&gt;
this week — a little more honest about what it doesn't know.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>computervision</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Gemma-San — A Teacher in Every Pocket.</title>
      <dc:creator>Daniel Ainoko</dc:creator>
      <pubDate>Tue, 19 May 2026 19:33:57 +0000</pubDate>
      <link>https://dev.to/thecodedaniel/gemma-san-a-teacher-in-every-pocket-16g6</link>
      <guid>https://dev.to/thecodedaniel/gemma-san-a-teacher-in-every-pocket-16g6</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06"&gt;Gemma 4 Challenge: Build with Gemma 4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gemma-San&lt;/strong&gt; is an offline AI tutor for children aged 5–12, built for Nigerian kids first but speaking whatever language the child speaks. It runs Google's &lt;a href="https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm" rel="noopener noreferrer"&gt;Gemma 4 E2B&lt;/a&gt; model fully on-device on a sub-$200 Android phone — no cloud, no data plan after the one-time setup.&lt;/p&gt;

&lt;p&gt;A child taps the mic, talks (English, Pidgin, Yoruba, Hausa, Igbo — even Japanese), and Gemma-San responds with voice, illustrations, and patience. It knows when to ask a Socratic question and when to just explain. It remembers the child across sessions. It quizzes them on past lessons. It draws simple SVG diagrams when the topic isn't already in its illustration library.&lt;/p&gt;

&lt;p&gt;Built with &lt;a href="https://flutter.dev/" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt; + &lt;a href="https://pub.dev/packages/flutter_gemma" rel="noopener noreferrer"&gt;&lt;code&gt;flutter_gemma 0.15.1&lt;/code&gt;&lt;/a&gt; + &lt;a href="https://github.com/ggerganov/whisper.cpp" rel="noopener noreferrer"&gt;Whisper.cpp&lt;/a&gt; + &lt;a href="https://pub.dev/packages/sqflite" rel="noopener noreferrer"&gt;sqflite&lt;/a&gt;. Targets 4–6 GB RAM Android phones like the Tecno Spark 10 and Infinix Hot 30 — the phones African kids actually share with their families.&lt;/p&gt;

&lt;p&gt;The problem it solves: millions of kids in low-bandwidth regions have brilliant questions and no one to ask. Cloud tutors fail when the network does. Gemma-San lives inside the phone.&lt;/p&gt;

&lt;p&gt;Core features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Voice in (Whisper tiny on-device) → Gemma 4 → voice out (Android TTS)&lt;/li&gt;
&lt;li&gt;Six native function-calling tools: &lt;code&gt;socratic_teach&lt;/code&gt;, &lt;code&gt;direct_teach&lt;/code&gt;, &lt;code&gt;encourage&lt;/code&gt;, &lt;code&gt;remember&lt;/code&gt;, &lt;code&gt;try_drawing&lt;/code&gt;, &lt;code&gt;show_illustration&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;22 hand-built SVG illustrations + an on-the-fly SVG drawing fallback&lt;/li&gt;
&lt;li&gt;Five-question quizzes and phonics practice with a spaced-repetition scheduler&lt;/li&gt;
&lt;li&gt;A three-tier memory system (working window + cross-session compaction + long-term facts) so the tutor knows the child&lt;/li&gt;
&lt;li&gt;Multilingual mirroring — the model replies in whatever language the child used, never imposes one&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/FD7dJ33XGXc"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/TheCodeDaniel" rel="noopener noreferrer"&gt;
        TheCodeDaniel
      &lt;/a&gt; / &lt;a href="https://github.com/TheCodeDaniel/gemma_san" rel="noopener noreferrer"&gt;
        gemma_san
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Gemma-San&lt;/h1&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;Offline AI tutor for Nigerian children, powered by Gemma 4 on-device.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hackathon submission — deadline May 18, 2026.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What It Is&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Gemma-San is a native Android app that acts as a patient, voice-first tutor for Nigerian children aged 5–12. It runs Google's Gemma 4 E2B model entirely on-device using flutter_gemma, so it works in classrooms with no internet after the initial model download. The tutor speaks in Nigerian Pidgin or English, uses the phone camera to annotate physical objects, and adapts to each child via a local memory system.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Millions of Nigerian children lack access to quality, personalized tutoring. Gemma-San brings Socratic and direct-teach pedagogy to a $100 Android phone, offline, in the child's own language.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Architecture&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;┌─────────────────────────────────────────────┐
│                Flutter UI                   │
│  (Riverpod state, feature-first widgets)    │
└────────────────────┬────────────────────────┘
                     │
        ┌────────────▼────────────┐
        │     Domain / Use Cases  │
        └────────────┬────────────┘
                     │
   ┌─────────────────┼──────────────────┐
   │                 │                  │
┌──▼──┐         ┌────▼────┐       ┌────▼────┐
│sqflite│       │flutter_&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/TheCodeDaniel/gemma_san" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Used Gemma 4
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Model: Gemma 4 E2B&lt;/strong&gt; (&lt;a href="https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm" rel="noopener noreferrer"&gt;&lt;code&gt;litert-community/gemma-4-E2B-it.litertlm&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why E2B and not E4B or 31B Dense:&lt;/strong&gt; E4B added roughly 1.5 GB to disk and tripped the OOM killer on my 8 GB target device once Whisper.cpp loaded alongside it. 31B Dense was never an option — these are phones, not workstations. E2B's 2-billion effective parameters fits in about 2.4 GB on disk, runs at ~25–35 tok/s on the GPU backend via &lt;a href="https://github.com/google-ai-edge/LiteRT-LM" rel="noopener noreferrer"&gt;LiteRT-LM&lt;/a&gt;, and leaves headroom for STT/TTS/UI. The quality-vs-fit tradeoff was the entire bet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Native function calling — six tools, no plain text
&lt;/h3&gt;

&lt;p&gt;Every model reply is a function call. There is no free-text path. The system prompt has a &lt;a href="https://www.khanmigo.ai/" rel="noopener noreferrer"&gt;Khanmigo&lt;/a&gt;-style decision tree the model walks top-down on every turn (visual request → illustration if exact match else SVG → direct fact → Socratic ladder → escalate after 2 IDKs). Side effect: the UI gets a structured &lt;code&gt;TutorResponse&lt;/code&gt; with a &lt;code&gt;mode&lt;/code&gt; field, so I render a different colored mode pill for each teaching style.&lt;/p&gt;

&lt;h3&gt;
  
  
  Selective thinking mode (the biggest discovery)
&lt;/h3&gt;

&lt;p&gt;Gemma 4 supports thinking traces via &lt;code&gt;enableThinking: true&lt;/code&gt;. I assumed "more thinking = better answers" and switched it on everywhere. That backfired.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where thinking helps:&lt;/strong&gt; routing across 6 tools. Without it (and with default &lt;code&gt;topK=1&lt;/code&gt;), once Gemma's first JSON token commits, it can't recover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where thinking hurts:&lt;/strong&gt; structured-output composition. My lesson-summary path enabled thinking and the model started spending its whole token budget on &lt;code&gt;&amp;lt;|channel&amp;gt;thought|&amp;gt;&lt;/code&gt; traces, never reaching the actual &lt;code&gt;lesson_summary&lt;/code&gt; function call. Raw thinking JSON leaked into the cached summary and rendered to kids as &lt;code&gt;{"role":"assistant","channels":{"thought":"Thinking"}}...&lt;/code&gt;. Beautiful.&lt;/p&gt;

&lt;p&gt;I now apply thinking per-path:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Thinking&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;generate()&lt;/code&gt; chat&lt;/td&gt;
&lt;td&gt;ON&lt;/td&gt;
&lt;td&gt;6-tool routing benefits from reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;generateWithImage()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ON&lt;/td&gt;
&lt;td&gt;Vision + routing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;generateLessonSummary()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;td&gt;Single tool, pure composition&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And I always pass explicit sampling — the defaults are a trap for small models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;tools:&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;systemInstruction:&lt;/span&gt; &lt;span class="n"&gt;sysPrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;enableThinking:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;temperature:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// not 0.8 default — too noisy for tool selection&lt;/span&gt;
  &lt;span class="nl"&gt;topK:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// not 1 default — pure greedy locks in wrong tools&lt;/span&gt;
  &lt;span class="nl"&gt;topP:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;randomSeed:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Compressing the prompt for a 2B model
&lt;/h3&gt;

&lt;p&gt;My first system prompt was 1,450 tokens — a beautifully written teaching ladder. Gemma E2B's attention horizon couldn't hold it together with 6 tool schemas and conversation history. It misrouted, hallucinated tool names, and sometimes emitted plain text.&lt;/p&gt;

&lt;p&gt;Rewriting it as a flat ~440-token decision tree with two worked examples (one Socratic, one direct-teach with the child speaking Pidgin and the model mirroring it back) took tool-call accuracy from "frustrating" to "actually usable."&lt;/p&gt;

&lt;h3&gt;
  
  
  Multilingual mirroring
&lt;/h3&gt;

&lt;p&gt;Hard rule in the prompt: &lt;strong&gt;whatever language the child uses, the model uses back.&lt;/strong&gt; English in, English out. Pidgin in, Pidgin out. Yoruba, Hausa, Igbo, Japanese — match it. The &lt;code&gt;language_code&lt;/code&gt; &lt;a href="https://www.rfc-editor.org/info/bcp47" rel="noopener noreferrer"&gt;BCP-47&lt;/a&gt; field on every tool call propagates to Android TTS so the voice matches too (Google TTS preferred when installed; en-NG &amp;gt; en-GB &amp;gt; en-US fallback).&lt;/p&gt;

&lt;h3&gt;
  
  
  Recovery loop, never an error
&lt;/h3&gt;

&lt;p&gt;When parsing fails (rare, but real on a 2B model), Gemma-San retries once with a nudge appended — &lt;code&gt;"Respond by calling exactly ONE function. No plain text."&lt;/code&gt; — and falls back to a graceful &lt;code&gt;direct_teach&lt;/code&gt; if the retry also fails. The child never sees an error message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Thinking mode is a knife, not a hammer.&lt;/strong&gt; ON for routing. OFF for composition. Mixing them in the same session causes JSON leaks and budget exhaustion.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;topK=1&lt;/code&gt; + thinking is the worst sampling combo on a small model.&lt;/strong&gt; Pure greedy decoding can't recover from a bad first token even when the thinking trace has already considered alternatives. Bump &lt;code&gt;topK&lt;/code&gt; to ~40 and &lt;code&gt;temperature&lt;/code&gt; to ~0.4 for tool calling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compress your prompt brutally.&lt;/strong&gt; A 2B model with 6 tool schemas + working memory + a 1,400-token system prompt has nothing left for attention. A flat Khanmigo-style decision tree with one or two worked examples beats verbose pedagogy every time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validate, don't reject.&lt;/strong&gt; My SVG validator auto-fixes the model's malformed &lt;code&gt;viewBox&lt;/code&gt;, injects missing &lt;code&gt;fill&lt;/code&gt; attributes, and only rejects what truly can't render. Treat the model as a fragile partner, not a black box.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mirror, don't impose.&lt;/strong&gt; Telling the model "respond in Pidgin" forced bad output when kids spoke English. Telling it "mirror the child's language" produced fluent code-switching across five Nigerian languages out of the box.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06"&gt;Gemma 4 Challenge&lt;/a&gt;. Feedback welcome — especially from anyone shipping on-device LLMs for low-resource contexts.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
    </item>
    <item>
      <title>From Notion Page to Figma Screens in Seconds</title>
      <dc:creator>Daniel Ainoko</dc:creator>
      <pubDate>Sat, 28 Mar 2026 19:29:38 +0000</pubDate>
      <link>https://dev.to/thecodedaniel/from-notion-page-to-figma-screens-in-seconds-1g59</link>
      <guid>https://dev.to/thecodedaniel/from-notion-page-to-figma-screens-in-seconds-1g59</guid>
      <description>&lt;h1&gt;
  
  
  I Turned Notion Into a UI Design Tool — Claude Reads Your Page and Draws the Screens in Figma
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/notion-2026-03-04"&gt;Notion MCP Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;NotionCanvas MCP&lt;/strong&gt; — a Model Context Protocol server that creates a live pipeline from your Notion workspace directly into Figma.&lt;/p&gt;

&lt;p&gt;You write your app idea, feature spec, or product requirements in Notion — the way you normally would. Then you tell Claude:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Read my Notion page and generate a mobile UI design in Figma"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And it does. Fully. Screens appear in Figma.&lt;/p&gt;

&lt;p&gt;No Figma experience required. No prompt engineering. No copy-pasting. No manually describing your idea to an AI. Your Notion page &lt;strong&gt;is&lt;/strong&gt; the brief — and NotionCanvas turns it into a real, editable UI in seconds.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Problem It Solves
&lt;/h3&gt;

&lt;p&gt;Every product builder knows this moment: you have a clear idea written up in Notion — user flows, feature descriptions, screen names, content — and then you spend the next hour either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trying to describe that same idea to an AI image generator with mixed results&lt;/li&gt;
&lt;li&gt;Opening Figma and building frames manually from scratch&lt;/li&gt;
&lt;li&gt;Hiring a designer and waiting days for a first draft&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The idea is already written. Why describe it again?&lt;/p&gt;

&lt;p&gt;NotionCanvas closes that gap entirely. Your Notion page is the single source of truth — and Claude bridges it to Figma automatically.&lt;/p&gt;




&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;The system has three parts working together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Notion Page
      │
      ▼
  notioncanvas-mcp  (Node.js MCP Server)
      │
      ├── Notion API  →  reads your page content
      ├── Claude API  →  interprets it, generates a Design IR (structured JSON)
      └── WebSocket   →  sends commands to the Figma Bridge Plugin
                                  │
                           Figma Desktop
                      (NotionCanvas Bridge Plugin)
                          draws frames on canvas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1 — Notion Reader&lt;/strong&gt;: Reads your page recursively — headings, paragraphs, lists, callouts, properties — and builds a clean structured content object.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Claude Interpreter&lt;/strong&gt;: Claude receives the Notion content + your design preferences (mobile/web, theme, color, design system) and outputs a &lt;strong&gt;Design Intermediate Representation (DIR)&lt;/strong&gt; — a validated JSON schema describing every screen, every component, every coordinate, every color.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — DIR Validator&lt;/strong&gt;: Zod validates every component before anything touches Figma. Malformed AI output never reaches the canvas. Claude gets up to 2 automatic retries with targeted error feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Figma Writer&lt;/strong&gt;: The MCP server sends each screen to the Figma Bridge Plugin over a local WebSocket. The plugin executes Figma Plugin API calls — &lt;code&gt;createFrame()&lt;/code&gt;, &lt;code&gt;createText()&lt;/code&gt;, &lt;code&gt;createRectangle()&lt;/code&gt; — and your screens appear live on the canvas.&lt;/p&gt;




&lt;h3&gt;
  
  
  What It Can Generate
&lt;/h3&gt;

&lt;p&gt;From a single Notion page, NotionCanvas produces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full mobile screens (390×844) or web layouts (1440×900)&lt;/li&gt;
&lt;li&gt;Text components with font weight, size, alignment&lt;/li&gt;
&lt;li&gt;Input fields with placeholder text&lt;/li&gt;
&lt;li&gt;Buttons with variants (primary, secondary, ghost, destructive)&lt;/li&gt;
&lt;li&gt;Frames with Auto Layout support (vertical/horizontal, padding, spacing)&lt;/li&gt;
&lt;li&gt;Rectangles with stroke, fill, corner radius&lt;/li&gt;
&lt;li&gt;Every component positioned absolutely with proper coordinates&lt;/li&gt;
&lt;li&gt;Background colors derived from your brand description&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All components land in Figma as &lt;strong&gt;native, editable nodes&lt;/strong&gt; — not images, not embeds. You can grab any element and keep designing from there.&lt;/p&gt;




&lt;h3&gt;
  
  
  The 6 MCP Tools
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;generate_ui_design&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full pipeline: reads your Notion page → Claude → Figma screens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;generate_screen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single screen from a plain-text description (no Notion needed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;notion_read_page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read and return structured content of any Notion page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;notion_read_db&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Query a Notion database and return all entries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list_figma_frames&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List all existing frames in a Figma file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_design_status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check if the Figma Bridge Plugin is connected and ready&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Real Example
&lt;/h3&gt;

&lt;p&gt;I wrote this in Notion:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I want a simple ride hailing application. Main color theme is red. Use Inter font."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it. Two sentences.&lt;/p&gt;

&lt;p&gt;NotionCanvas read that page, sent it to Claude, generated a full DesignIR with an onboarding screen, a home screen with a map layout, a booking screen with input fields and a confirm button — and sent all of it to Figma as native frames.&lt;/p&gt;

&lt;p&gt;The whole pipeline ran in under 90 seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/6NajsdxjQXo"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/6NajsdxjQXo" rel="noopener noreferrer"&gt;Watch on YouTube&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Show us the code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/TheCodeDaniel/notion_canvas" rel="noopener noreferrer"&gt;github.com/TheCodeDaniel/notion_canvas&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Project structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;notioncanvas-mcp/
├── src/
│   ├── index.ts                    ← MCP server entry point (stdio transport)
│   ├── clients/
│   │   ├── notion.ts               ← Notion API + p-queue rate limiter (2.8 req/s)
│   │   ├── claude.ts               ← Claude API + JSON retry logic (3 attempts)
│   │   ├── figma-ws.ts             ← WebSocket bridge server (localhost:9223)
│   │   └── figma-rest.ts           ← Figma REST API (read-only frame listing)
│   ├── interpreter/
│   │   ├── schema.ts               ← Zod DesignIR schema (5 component types)
│   │   └── prompt.ts               ← Claude system/user prompt builders
│   ├── tools/                      ← 6 MCP tools
│   ├── types/                      ← TypeScript interfaces
│   └── utils/                      ← logger, retry, PAT expiry checker
└── plugin/
    ├── manifest.json               ← Figma plugin manifest
    ├── plugin.js                   ← Figma Plugin API (main thread)
    └── ui.html                     ← WebSocket iframe (relay bridge)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setup is one command after filling your &lt;code&gt;.env&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run build
npm run setup   &lt;span class="c"&gt;# auto-detects Claude Desktop config path, merges entry, resolves absolute paths&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;setup&lt;/code&gt; command handles everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads credentials from your &lt;code&gt;.env&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Detects &lt;code&gt;claude_desktop_config.json&lt;/code&gt; path for macOS or Windows&lt;/li&gt;
&lt;li&gt;Creates the file if it doesn't exist&lt;/li&gt;
&lt;li&gt;Merges the &lt;code&gt;notioncanvas&lt;/code&gt; entry without touching your other MCP servers&lt;/li&gt;
&lt;li&gt;Auto-resolves the absolute path to &lt;code&gt;dist/index.js&lt;/code&gt; — nothing to edit manually&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript / Node.js&lt;/strong&gt; — ESM, strict mode&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt;&lt;/strong&gt; — MCP server with stdio transport&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@notionhq/client&lt;/code&gt;&lt;/strong&gt; — Notion API with p-queue rate limiting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@anthropic-ai/sdk&lt;/code&gt;&lt;/strong&gt; — Claude API (claude-sonnet-4-6)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ws&lt;/code&gt;&lt;/strong&gt; — WebSocket server for the Figma bridge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;zod&lt;/code&gt;&lt;/strong&gt; — DesignIR schema validation before any Figma writes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Figma Plugin API&lt;/strong&gt; — native canvas writes via the bridge plugin&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How I Used Notion MCP
&lt;/h2&gt;

&lt;p&gt;Notion MCP is the foundation that makes this whole idea possible — and not in a trivial way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Notion as the input source is actually powerful
&lt;/h3&gt;

&lt;p&gt;Most AI design tools start with a blank text box. You describe your idea to the AI. The AI designs something. But that description lives nowhere — it's not connected to your roadmap, your feature specs, your user stories. It's a one-off prompt.&lt;/p&gt;

&lt;p&gt;Notion changes that. Your product thinking already lives there. Feature specs, screen flows, content requirements, user personas — structured, searchable, shareable with your team. With Notion MCP as the reader, that existing knowledge becomes the direct input to the design pipeline.&lt;/p&gt;

&lt;p&gt;You're not describing your idea &lt;em&gt;to&lt;/em&gt; an AI. You're pointing an AI &lt;em&gt;at&lt;/em&gt; your idea. That's a fundamentally different workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  The specific integration
&lt;/h3&gt;

&lt;p&gt;I used the &lt;strong&gt;Notion internal integration token&lt;/strong&gt; (via &lt;code&gt;@notionhq/client&lt;/code&gt;) to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Read pages&lt;/strong&gt; recursively — the client traverses the full block tree, handling pagination (100 blocks per request), nested toggles, callouts, lists, and all block types. Pages with hundreds of blocks are handled cleanly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Query databases&lt;/strong&gt; — the &lt;code&gt;notion_read_db&lt;/code&gt; tool lets you pull structured data from Notion databases and feed it into screen generation. Imagine a database of app screens with properties like "screen type", "primary action", "user role" — and generating each one automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handle the page-sharing security model correctly&lt;/strong&gt; — Notion's integration tokens only work on pages explicitly shared with the integration. The server returns a helpful, specific error message when a 404 is returned, rather than a generic failure.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What Notion MCP unlocks that nothing else does
&lt;/h3&gt;

&lt;p&gt;The pipeline means your &lt;strong&gt;Notion page becomes a living design brief&lt;/strong&gt;. Update the page — run the tool again — get updated screens. It's not a one-time export. It's a repeatable process driven by the content you're already maintaining.&lt;/p&gt;

&lt;p&gt;For solo founders, indie developers, and small product teams who live in Notion: this means going from written idea to visual screen without leaving your workflow or learning Figma from scratch.&lt;/p&gt;

&lt;p&gt;That's the unlock. Notion stops being a notes app and starts being a design input system.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Claude Code, &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt;, &lt;code&gt;@notionhq/client&lt;/code&gt;, and the Figma Plugin API.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Stack: TypeScript · Node.js · WebSockets · Zod · claude-sonnet-4-6&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>I Built an MCP That Publishes Your Blog to Every Platform With One Sentence to Claude</title>
      <dc:creator>Daniel Ainoko</dc:creator>
      <pubDate>Sat, 28 Mar 2026 00:27:55 +0000</pubDate>
      <link>https://dev.to/thecodedaniel/i-built-an-mcp-that-publishes-your-blog-to-every-platform-with-one-sentence-to-claude-26li</link>
      <guid>https://dev.to/thecodedaniel/i-built-an-mcp-that-publishes-your-blog-to-every-platform-with-one-sentence-to-claude-26li</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/notion-2026-03-04"&gt;Notion MCP Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Every developer I know has the same problem. They write a solid post, spend 20 minutes formatting it for Dev.to, copy-paste to Hashnode, re-upload the images that broke because Notion's CDN URLs expire, then give up on Medium entirely. The writing was the easy part. The publishing killed the momentum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BlogCast MCP&lt;/strong&gt; fixes that.&lt;/p&gt;

&lt;p&gt;It's an open-source MCP server + dashboard that turns Notion into a publishing hub. Write once — in Notion or in the built-in rich text editor — then tell Claude to publish it everywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Publish my TypeScript post to Dev.to and Hashnode"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. One sentence. Done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it actually does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publishes to Dev.to, Hashnode, and Medium simultaneously&lt;/li&gt;
&lt;li&gt;Handles Notion's expiring image URLs automatically (downloads → caches → re-uploads to each platform's CDN)&lt;/li&gt;
&lt;li&gt;Adapts content per platform — Medium gets clean HTML, Dev.to/Hashnode get optimized Markdown&lt;/li&gt;
&lt;li&gt;Syncs analytics back into Notion (views, reactions, comments — all in one place)&lt;/li&gt;
&lt;li&gt;Schedules posts via Claude: &lt;em&gt;"Schedule my React post for tomorrow at 9am UTC"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;AI pre-publish checks — catches missing excerpts, vague titles, broken formatting before you hit send&lt;/li&gt;
&lt;li&gt;Auto-configures Claude Desktop — no JSON editing required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing runs locally. No third-party servers, no SaaS fees, no data leaving your machine. Your API keys are stored encrypted on your device.&lt;/p&gt;




&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/NUdPBGZWTg0"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  Show us the code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/TheCodeDaniel/blogcast_mcp" rel="noopener noreferrer"&gt;github.com/TheCodeDaniel/blogcast_mcp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built as a proper monorepo — three packages that work together:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mcp/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MCP server — the Claude integration layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;server/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Express backend — publishing, image caching, AI, scheduling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;client/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;React dashboard — write, publish, manage everything visually&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Stack: TypeScript, Node.js, Express, React, Vite, TipTap editor, Anthropic SDK, Notion API.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Used Notion MCP
&lt;/h2&gt;

&lt;p&gt;Notion sits at the center of this whole thing — not as a passive database, but as the living CMS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The MCP server&lt;/strong&gt; talks to Notion directly via &lt;code&gt;@notionhq/client&lt;/code&gt;. When Claude calls &lt;code&gt;publish_post&lt;/code&gt;, the server reads the Notion page blocks, converts them to Markdown using &lt;code&gt;notion-to-md&lt;/code&gt;, handles all the images, pushes to each platform, then writes the results &lt;em&gt;back&lt;/em&gt; into Notion — a linked Analytics database that tracks views, reactions, and comments per platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Notion integration does five specific things:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Posts database&lt;/strong&gt; — every draft, scheduled post, and published article lives here. Status, tags, platforms, canonical URL — all tracked as Notion properties.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Analytics database&lt;/strong&gt; — a relational table linked to Posts. Every publish result is logged: URL, status (Success/Failed), and synced stats from each platform's API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Auto-migration&lt;/strong&gt; — on first run, BlogCast patches both databases automatically. New user? You don't set up a single column manually. Just share your databases with the integration and BlogCast does the rest.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Two-way sync&lt;/strong&gt; — &lt;code&gt;sync_analytics&lt;/code&gt; pulls the latest numbers from Dev.to and Hashnode back into Notion. Your post performance dashboard lives where you already work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scheduling&lt;/strong&gt; — set &lt;code&gt;Scheduled At&lt;/code&gt; in Notion (or via Claude: &lt;em&gt;"schedule this for Friday 9am"&lt;/em&gt;), and the backend polls every N minutes to auto-publish when the time comes.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The MCP layer is what makes Claude feel like a natural publishing assistant rather than a chatbot bolted onto a dashboard. You're not filling out forms — you're just talking.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Show me what's in Review"&lt;/em&gt; → &lt;code&gt;list_drafts&lt;/code&gt;&lt;br&gt;
&lt;em&gt;"Preview my latest draft"&lt;/em&gt; → &lt;code&gt;preview_post&lt;/code&gt;&lt;br&gt;
&lt;em&gt;"Publish the TypeScript one to all platforms"&lt;/em&gt; → &lt;code&gt;publish_post&lt;/code&gt;&lt;br&gt;
&lt;em&gt;"What's the view count on my last post?"&lt;/em&gt; → &lt;code&gt;sync_analytics&lt;/code&gt; + &lt;code&gt;get_publish_status&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Everything that used to take 30 minutes of copy-paste and re-uploading now takes one message.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>I Got Tired of Cold Outreach. So I Made Claude Do It.</title>
      <dc:creator>Daniel Ainoko</dc:creator>
      <pubDate>Thu, 26 Mar 2026 13:49:37 +0000</pubDate>
      <link>https://dev.to/thecodedaniel/i-got-tired-of-cold-outreach-so-i-made-claude-do-it-5flp</link>
      <guid>https://dev.to/thecodedaniel/i-got-tired-of-cold-outreach-so-i-made-claude-do-it-5flp</guid>
      <description>&lt;h1&gt;
  
  
  I Built an MCP That Turns Claude Into a Cold Outreach Machine — and It Actually Works
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Submitted for the Notion MCP Challenge 2026&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Let me be honest with you.&lt;/p&gt;

&lt;p&gt;Cold outreach is broken.&lt;/p&gt;

&lt;p&gt;Not because the idea is bad — cold emails work. People get hired, land clients, and build relationships through cold outreach every single day. It's broken because of &lt;strong&gt;how long it actually takes to do it right&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Think about it. To send one good cold email, you have to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find a company that's actually a fit for your skills&lt;/li&gt;
&lt;li&gt;Go to their website and actually read it&lt;/li&gt;
&lt;li&gt;Figure out what they're working on, what problems they might have&lt;/li&gt;
&lt;li&gt;Find a contact email somewhere on the site&lt;/li&gt;
&lt;li&gt;Write something that doesn't sound like everyone else's template&lt;/li&gt;
&lt;li&gt;Send it&lt;/li&gt;
&lt;li&gt;Log it somewhere so you don't forget who you've reached out to&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's 20–40 minutes. Per company.&lt;/p&gt;

&lt;p&gt;If you do 10 companies — which is barely enough to move the needle — you've just burned a full workday on outreach alone.&lt;/p&gt;

&lt;p&gt;So most people don't do it. Or they send lazy, generic emails that get deleted in two seconds. Both outcomes are bad.&lt;/p&gt;

&lt;p&gt;I got tired of it. So I built something.&lt;/p&gt;




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/x7UvlIxKoPI"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built: Cold Outreach MCP
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cold Outreach MCP&lt;/strong&gt; is a custom MCP server that turns Claude into a fully automated cold email agent. You paste your CV once. Claude does everything else.&lt;/p&gt;

&lt;p&gt;Here's the exact flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: "Here is my CV. Find 10 companies that match my background,
      research each one, and show me your recommendations."

Claude: [researches 10 companies]

      1. stripe.com → opportunity_pitch
         "Their checkout onboarding has friction — matches your UX background"

      2. linear.app → role_inquiry
         "Fast-growing team, strong eng culture, no obvious gap spotted"

      ... 8 more

You: "Looks good. Send as recommended."

Claude: [generates 10 tailored emails, sends them, logs everything to Notion]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. What used to take a full day now takes 3 minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Part That Actually Makes It Different
&lt;/h2&gt;

&lt;p&gt;Most "automation" tools skip the part that matters most — the email itself.&lt;/p&gt;

&lt;p&gt;They send templated garbage like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Hi [First Name], I came across [Company] and I was really impressed by your work in [Industry]. I think my background in [Skill] could add value to your team..."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nobody replies to that. Everybody knows it's a template. It goes straight to the bin.&lt;/p&gt;

&lt;p&gt;Cold Outreach MCP does something different. Before generating the email, Claude actually &lt;strong&gt;reads the company's website&lt;/strong&gt;, identifies specific things about their product or team, and writes something that references what it found:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Saw you're building a design system at scale — I've done exactly this at [Company] and ran into the same versioning problems you're probably hitting. Worth a quick chat?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's an email someone opens. That's an email someone replies to.&lt;/p&gt;

&lt;p&gt;And the tone rules are hard-coded into the prompts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Under 120 words&lt;/li&gt;
&lt;li&gt;No buzzwords ("synergy", "leverage", "circle back" — all banned)&lt;/li&gt;
&lt;li&gt;Short sentences. Casual but not sloppy.&lt;/li&gt;
&lt;li&gt;One specific observation from their site&lt;/li&gt;
&lt;li&gt;One clear ask at the end&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It sounds like a person wrote it. Because an extremely good AI was told to write like a person.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Notion Fits In
&lt;/h2&gt;

&lt;p&gt;Every outreach — sent, failed, or pending — gets logged to a Notion database automatically.&lt;/p&gt;

&lt;p&gt;The schema looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Website&lt;/th&gt;
&lt;th&gt;Email&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Date Sent&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;stripe.com&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:hello@stripe.com"&gt;hello@stripe.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;opportunity_pitch&lt;/td&gt;
&lt;td&gt;sent&lt;/td&gt;
&lt;td&gt;2026-03-26&lt;/td&gt;
&lt;td&gt;&lt;em&gt;full email body&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linear&lt;/td&gt;
&lt;td&gt;linear.app&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:team@linear.app"&gt;team@linear.app&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;role_inquiry&lt;/td&gt;
&lt;td&gt;sent&lt;/td&gt;
&lt;td&gt;2026-03-26&lt;/td&gt;
&lt;td&gt;&lt;em&gt;full email body&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No manual logging. No "I think I emailed them last week?". No spreadsheet.&lt;/p&gt;

&lt;p&gt;And the Notion integration is set up like a proper database migration — you create an empty database on Notion, paste its ID in your config, run &lt;code&gt;setup_notion_db&lt;/code&gt; once, and all the columns are provisioned automatically. It's exactly how backend migrations work.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 10 Tools Under the Hood
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;setup_notion_db&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Migrates your Notion database — adds all required columns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;parse_profile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extracts structured profile from your CV text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;discover_companies&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Finds real companies that match your skills using Claude&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;find_company_email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scrapes a company site to find a contact email&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;research_company&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Analyzes a company — summary, pain points, recommended email type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;generate_email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Writes a human-sounding outreach email&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;send_email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sends via SMTP (Gmail, Outlook, anything)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;log_to_notion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Logs a record to your Notion tracker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;retry_failed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Re-attempts any failed sends automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bulk_outreach&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full pipeline for up to 15 companies at once&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  It's Human-in-the-Loop by Design
&lt;/h2&gt;

&lt;p&gt;This is important. The tool doesn't just fire off emails without asking you.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bulk_outreach&lt;/code&gt; runs in two phases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1&lt;/strong&gt; — Claude researches all companies and returns its recommendations. You see everything before a single email is sent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2&lt;/strong&gt; — You confirm (or override) each recommendation. Then and only then does Claude generate and send.&lt;/p&gt;

&lt;p&gt;You're always in control. The AI is doing the tedious work, not making the decisions for you.&lt;/p&gt;

&lt;p&gt;There's also a hard limit of 15 companies per run — enough to be productive, not enough to accidentally spam half the internet if something goes wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP SDK&lt;/strong&gt; — &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt; (official Anthropic SDK)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt; — Claude via &lt;code&gt;@anthropic-ai/sdk&lt;/code&gt; with adaptive thinking for research and email generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scraping&lt;/strong&gt; — &lt;code&gt;axios&lt;/code&gt; + &lt;code&gt;cheerio&lt;/code&gt; for website analysis and email extraction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email&lt;/strong&gt; — &lt;code&gt;nodemailer&lt;/code&gt; with lazy SMTP validation (server doesn't crash if SMTP isn't configured yet)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notion&lt;/strong&gt; — raw Notion REST API (&lt;code&gt;fetch&lt;/code&gt; directly to &lt;code&gt;https://api.notion.com/v1/...&lt;/code&gt;) for reliability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language&lt;/strong&gt; — TypeScript, Node.js 18+, ES modules&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What the Setup Looks Like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/thecodedaniel/cold-outreach-mcp
&lt;span class="nb"&gt;cd &lt;/span&gt;cold-outreach-mcp
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fill in your &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ANTHROPIC_API_KEY=sk-ant-...
NOTION_API_KEY=ntn_...
NOTION_DATABASE_ID=your_database_id
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=you@gmail.com
SMTP_PASS=your_app_password
SENDER_EMAIL=you@gmail.com
SENDER_NAME=Your Name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add to Claude Desktop config. Restart. Done.&lt;/p&gt;

&lt;p&gt;The full setup takes about 10 minutes if you already have the API keys.&lt;/p&gt;




&lt;h2&gt;
  
  
  The One Thing I'm Most Proud Of
&lt;/h2&gt;

&lt;p&gt;It's not the scraping. It's not the Notion integration. It's not even the email generation.&lt;/p&gt;

&lt;p&gt;It's the fact that &lt;strong&gt;you don't have to know which companies to target&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every other tool in this space assumes you already know who you want to reach out to. You still have to do the research, compile the list, find the websites.&lt;/p&gt;

&lt;p&gt;This one starts from your CV and figures out the rest. You describe what you do. Claude figures out who needs someone like you.&lt;/p&gt;

&lt;p&gt;That's the part that changes things.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Web search integration for real-time company discovery (Brave Search API)&lt;/li&gt;
&lt;li&gt;Follow-up email scheduling ("no response in 7 days → send a nudge")&lt;/li&gt;
&lt;li&gt;Notion status tracking — mark a reply directly in Notion, trigger a follow-up draft&lt;/li&gt;
&lt;li&gt;LinkedIn integration for finding decision-maker contacts&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/TheCodeDaniel/reachout_mcp" rel="noopener noreferrer"&gt;github.com/TheCodeDaniel/reachout_mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo&lt;/strong&gt;: &lt;a href="https://youtu.be/x7UvlIxKoPI" rel="noopener noreferrer"&gt;youtu.be/x7UvlIxKoPI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built for the Notion MCP Challenge 2026 by Daniel Ainoko (THECODEDANIEL)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you're a developer actively job hunting or trying to land clients — this is for you. The grind is real. Automate the boring parts.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
