AI Engineer

MCP Server

Five custom Claude tools running on an MCP server I built to extend my own workflow — pulling ideas from a Notion vault, suggesting palettes, voice-checking copy, scaffolding project pages. Hands-on MCP server engineering, tool design, and Claude API integration.

5 tools MCP server Zod schemas Notion API
The Brief

Most projects that use Claude treat it as a generator wrapped in code. You write a function, the function calls Claude, Claude returns text, the function parses the text back into something usable. Claude does the work; the code is plumbing.

This project flips that. Claude is the decision-maker; the tools are the constraints — rules about what fits my voice, my color library, my workflow. Three patterns, deliberately: retrieval (the tool returns options, Claude picks), critique (the tool flags problems, Claude rewrites), mutation (Claude decides, the tool writes the change).

No tool reaches for Claude. Claude reaches for the tools. The case study below is itself the proof: this page was drafted, voice-checked, and scaffolded by Claude — using the same five tools the page documents.

01 — The Tools 5 tools · 3 patterns · ~785 lines of source code

Five tools, three patterns.

Pattern, name, role. Each tool is one file. Inputs are validated on every call with Zod.

Retrieval
vault_search
Search my Inspiration Vault on Notion. Filter by section. Matches both the entry title and the first ~20 blocks of body content.
Retrieval
palette_suggest
Pulls color palettes from my Color Lab on Notion — each with hex codes, mood, category, and notes. Returns options, not a ranking. Picking the right one is Claude's job.
Critique
voice_check
Runs a piece of writing against my voice rules — no hedging, no filler transitions, no marketing warmth, no repetition, short sentences. Returns the specific violations and a verdict. Does not rewrite (that's a separate decision).
Mutation
vault_capture
Saves a new entry to my Vault on Notion, formatted to match the layout my command-line tool already uses. Section is set via a tag in the title.
Mutation
project_scaffold
Generates a new project card and writes it into the site's index.html. Auto-numbers the project. Supports a "preview-only" mode (dry_run).
Design rationale The pattern label matters more than the tool name. Claude picks better when it knows which of the three patterns it's reaching for. Naming the three — retrieval, critique, mutation — IS the structure. Every tool below those three is just a specific case.
02 — A real workflow captured 2026-05-10 · this page

The case study built itself.

The traces below are real. The card on the home page that linked you here was generated by the third tool call in this sequence.

step 1 of 3
tools/call · palette_suggest [ RETRIEVAL ]
request
{
  "brief": "AI engineering, restrained,
            design-engineer aesthetic,
            signal-orange-friendly",
  "limit": 4
}
response (4 of 16 palettes)
the candidate I picked
{
  "name": "Off-White x Air Jordan 1 'The Ten'",
  "hexes": [
    "#F4F0E8", "#C8102E",
    "#111111", "#E8601C",
    "#D4CAAD"
  ],
  "notes": "The benchmark. White sail
    carrier with Chicago Red signal.
    Zip-tie orange as the one
    controlled disruption..."
}
Reasoning The brief asked for restrained, signal-orange-friendly. The notes on this palette literally read "Zip-tie orange as the one controlled disruption." The server didn't rank — it handed me four options, each with its color codes, mood, and author notes. My pick was the one whose author's own note matched the brief word-for-word. That's the design point: when the options carry their own justifications, Claude's job is recognition, not invention.
step 2 of 3
tools/call · voice_check [ CRITIQUE ]
verdict ship violations 0 context project_card
draft
Five custom Claude tools I built to extend
my own creative workflow — find ideas in my
vault, suggest a color palette, check copy
in my voice, scaffold a new project page,
save a new idea. The tools hand Claude the
options; Claude does the picking and the
writing. Proves: AI tool design, server
engineering, and the idea of encoding
taste as rules rather than agents.
verdict + violations
{
  "verdict": "ship",
  "violation_count": 0,
  "violations": [],
  "note": "Heuristics passed.
    Consider whether the copy actually
    says something — heuristics
    don't catch empty."
}
Reasoning Verdict ship means the rule-based checks found nothing — no hedge words, no filler transitions, no marketing warmth, no repeated phrases, no oversize sentences. The note is the honest part: rules can't catch empty writing. That's why the tool refuses to rewrite. Rewriting is a tone judgment; rule-checking is just enforcement.
step 3 of 3
tools/call · project_scaffold (dry_run) [ MUTATION ]
file index.html auto-detected 008 written false (dry_run)
@@ around line 1688 @@
-     </div>
+
+       <!-- Project 008 — Six Percent MCP (ACTIVE) -->
+       <a href="008-six-percent-mcp.html" class="exhibit-card live reveal" aria-label="Open Project 008 — Six Percent MCP">
+         <div class="exhibit-art" aria-hidden="true">
+           <div class="exhibit-art-num">008</div>
+           <div class="exhibit-art-glow"></div>
+           <div class="exhibit-art-icon">⌬</div>
+         </div>
+         <div class="exhibit-plaque">
+           <div class="exhibit-plaque-num">Project 008 · MCP</div>
+           <div class="exhibit-plaque-title">Six Percent <span style="color:var(--signal);">MCP</span></div>
+           <div class="exhibit-plaque-medium">@modelcontextprotocol/sdk · Node ESM · Notion API · Zod</div>
+           <div class="exhibit-plaque-desc">
+             [the description that just passed voice_check]
+           </div>
+           <div class="exhibit-before-after">
+             <span class="ex-before">AI As Wrapper</span>
+             <span class="ex-arrow">→</span>
+             <span class="ex-after">Tools Returning Contracts</span>
+           </div>
+           <div class="exhibit-op-tags">
+             <span class="ex-op-tag">// MCP</span>
+             <span class="ex-op-tag">// Tool Design</span>
+             <span class="ex-op-tag">// Claude Engineering</span>
+           </div>
+         </div>
+         <div class="exhibit-floor"></div>
+       </a>
+
+     </div>
Reasoning The scaffold tool finds the right spot in index.html — the project-gallery container — and inserts the new card just before it closes. Indentation matches the existing cards. The new project number is auto-detected by scanning for the existing <!-- Project NNN --> markers. dry_run returns the change as a preview, not a write — so Claude can review it before approving. After approval, the same call (without dry_run) wrote the card you saw on the home page.
03 — Inside voice_check rule-based · predictable · no AI call

Constraints, encoded.

The voice rules from my project's instructions, encoded as plain-text patterns. Read aloud: hedge words, filler transitions, marketing warmth. The tool flags every match with the exact excerpt and a one-line suggestion.

no_hedging
perhapsmaybemightpossiblyprobablysomewhatsort ofkind ofI thinkI believearguablytends toseems to
no_filler
thereforethusthat saidbasicallyessentiallyto be honestat the end of the daywhen it comes toin terms ofin many ways
no_marketing_warmth
amazingincredibleawesomepassionatejourneydive deepleveragesynergyelevateempowerunleashtransformcutting-edgegame-changer
Why heuristic, not LLM-backed A Claude-powered voice_check would be more expressive but less honest. Claude already reads the draft — asking it to call a tool that just asks Claude again is a circular system. Rule-based detection is what the tool can offer that Claude can't — the rules are visible, the failure cases are knowable, the output is repeatable. The note that ships on every passing verdict — "heuristics don't catch empty" — is the tool admitting its own limit. That admission is the design.
In review
Craft decisions
  • Three patterns named first, five tools fit into them. Naming the three shapes IS the structure. Claude picks the right tool for free when it knows whether it's asking for retrieval versus mutation.
  • palette_suggest doesn't rank — it returns options and lets Claude reason. The options already carry their author's notes. Ranking would discard the notes; reasoning over them is the value.
  • voice_check refuses to rewrite. The split between "what's wrong" (tool) and "what to write instead" (Claude) is intentional. Rewriting is taste; flagging is rules. They don't belong in the same tool.
  • project_scaffold is mutation-with-preview. dry_run: true returns the proposed change first. Claude sees the change, decides, then re-calls without the flag. Two steps in one tool — preview, then commit.
  • Notion's data structure is flatter than its UI. Vault entries are stored as flat child_page blocks; section headings are decorative heading_2 blocks that don't actually contain the entries underneath. The tool encodes the truth, not the UI metaphor — sections live in a [section-key] tag inside the title.
What I'd do differently
  • vault_capture still saves entries directly to the Vault root and tags the section name in the title. A more honest version would create real section pages and save the entries under those — restructuring the Vault, not working around it.
  • No real tests. The smoke test in the README is the test. Day 2 of the build adds Vitest, with mocked Notion data for the test cases.
  • Suggestion text in voice_check violations is the same boilerplate each time. A v0.2 would route suggestions through Claude with the rule and excerpt as context — rules for flagging, Claude for the rewrite hint. Hybrid.
  • The palette_suggest output gets large when the lab grows. Beyond ~30 palettes, returning every option wastes context. The fix: pre-sort options by mood similarity (a vector-embedding pass on the server) and return the top few — still letting Claude make the final pick.
  • project_scaffold writes only into six-percent-studio/index.html. Single target. A repo-aware version would take the gallery file as an input, opening the door to other portfolio pages or even other projects entirely.
PALETTE