I gave Claude a website-design tool
If you ask any current AI to "design me a website," it produces the same five-section template every single time. Hero, services grid, gallery, CTA band, contact form. Roboto font. Center-aligned everything. A blue-and-white palette. Maybe a teal accent if it's feeling adventurous. Every site looks like the others because the model has nothing pushing it away from the average.
I noticed this when I started building demo sites for local businesses as part of a side project. The first few I generated by giving Claude a long prompt and asking it to produce a single-file HTML demo. They were fine. They were also indistinguishable. A barber shop's site looked like a dentist's, which looked like a roofing contractor's, which looked like a manufacturer's. The category was implicit only in the words, never in the design.
So I wrote an opinionated prompt that pushed hard against the template default. Not "design a website" — "you are an elite web designer. The hero/services/gallery/CTA/contact pattern is overused and makes every site look the same. Design something specific to this business and industry. Use asymmetric layouts. Include at least one section with a non-standard layout. Make strong choices."
The prompt worked. The sites stopped looking the same. And after I'd run that prompt by hand maybe twenty times — copy, edit, paste, send — I realized the actual product wasn't the demo sites I was generating. It was the prompt itself. So I packaged it as a tool.
This week I open-sourced that tool as architect-mcp. It started as one tool — generate_blueprint — and grew into six as the workflow around it matured. The core is still the same: take a few facts about a business, return a long markdown spec for what the website should look like.
#What "blueprint" means
The output isn't HTML. It's a brief that another tool — Claude in a code-generation step, an Agent in an automated pipeline, a human web designer — can build from. A typical blueprint is around 5,000-8,000 characters and covers:
- Page structure — every section in scroll order, with purpose, layout type, background treatment, exact heading text
- Typography system — Google Font picks for heading + body, sizes, why this pairing fits the industry
- Color system — primary/secondary/accent/text/background hex values with rationale
- Unique design elements — at least three specific CSS techniques that make the site not look like a template
- Section-by-section CSS specs — padding, grid template, transforms, clip-paths, hover states, mobile adaptation
- What NOT to do — industry-specific anti-patterns to avoid
That's the full output. Hand it to a coding agent and you get a working single-file HTML demo. Hand it to a human designer and they have a starting point that took twenty seconds to produce instead of an hour.
#A real example
The smoke test I ran during development was for a fictional Bill's Barber Shop. Inputs: name, category (barber), services (haircuts, beard trims, hot towel shaves), vibe (old-school masculine, classic chair vibe), audience (men 25-65 in a small town).
The blueprint that came back opened with this:
"This isn't a website; it's a digital extension of the shop's chair. For a small-town barber shop, the goal isn't 'modern and sleek' — it's 'established and heavy.' We are moving away from the 'Software as a Service' look and toward an Editorial/Newspaper aesthetic. We want the user to feel the weight of the leather chair and the sharpness of the razor through the screen."
It then specified Deep Charcoal #1A1A1A as the dominant color, an asymmetric 65/35 split-screen hero, and warned explicitly against pure white backgrounds: "Pure #FFFFFF is too clinical. Always use a slightly tinted, 'aged' off-white."
Which is exactly the kind of opinionated, industry-specific reasoning you don't get from "design me a website."
#How it works
Three Go files of substance. Most of the repo is plumbing.
The first file is the prompt itself — about 70 lines of markdown wrapped in a Go string constant. That's the real product. Everything else exists to deliver it.
The second file is a thin Ollama HTTP client. Single function: take a request, POST to /api/generate, return the response. Fifteen-minute timeout because long blueprints from gemma4:26b can take 60-90 seconds.
The third file is the MCP scaffolding — a single tool registration named generate_blueprint with input fields for business name (required), category (required), services, vibe, colors, audience, competitor URL (all optional), and overrides for the model and Ollama URL. The MCP SDK generates the JSON Schema from the typed Go struct, so Claude sees a fully-described tool surface without me having to write any schema by hand.
That's it. Ten files in the whole repo if you count the README, LICENSE, Makefile, and config loader.
#Why it works as an MCP and not a CLI
Architect existed as a CLI for several months before it became an MCP. The CLI was useful — I generated more than 200 real blueprints with it, scripted in batches, building demo sites for prospects. The CLI was also what I had to remember.
Each blueprint took a command like:
architect \
-business "Bill's Barber Shop" \
-category "Barber Shop" \
-services "haircuts, beard trims, hot towel shaves" \
-vibe "old-school masculine, classic chair vibe" \
-audience "men 25-65 in a small town" \
-colors "#1F2937,#DC2626"
Workable, but not something I'd remember the flags for in the middle of a conversation. The MCP changes that. In a conversation, I just say what I want:
Architect a blueprint for Joe's Auto Repair — honest small-town mechanic, services are oil changes, brakes, and diagnostics, vibe is fair-priced and no-upselling.
Claude calls generate_blueprint with the right arguments. The tool runs against my local Ollama. The blueprint comes back as part of the conversation. I read it, decide I like it (or don't), and ask for adjustments. The CLI gave me primitives. The MCP gave me a vocabulary.
#The opinionated prompt is the actual product
This is the lesson I want to land. Anyone can wire up an MCP server. Anyone can call Ollama from Go. The reason architect-mcp is interesting isn't the plumbing — it's the prompt.
That prompt has had real mileage on it. It's generated more than 200 production blueprints for businesses I've actually pitched. Every iteration was tuned against real use: a section that kept producing weak output got rewritten, a category of failure mode I noticed got addressed with explicit "what NOT to do" instructions, the typography section grew more specific because vague typography guidance produced vague typography choices.
The package isn't valuable because of the Go around it. The package is valuable because the prompt has been beaten into shape over months of real use. The Go is there so other people can run the same prompt without my workflow.
This is, broadly, the case for shipping prompts as software. They're easy to undervalue because they look like just text. But a prompt that's been refined against hundreds of real outputs is a substantially different artifact from one written in a single sitting — and the refinement is the work. Wrapping a refined prompt in an MCP gives it a stable interface and an obvious distribution model. The README is the marketing copy. The schema is the API. The LICENSE makes it clear how it can be used.
#The tool surface grew
generate_blueprint is still the original. The other five emerged from real workflow needs:
generate_blueprint_from_url— pass a competitor's URL. The server uses headless Chrome to capture a desktop screenshot, base64-encodes it, and feeds the image to a vision-capable model alongside the prompt. The "differentiate from competitor X" guidance becomes visual instead of just textual.critique_blueprint— score a blueprint 1-10 across five dimensions (industry-appropriateness, differentiation, visual specificity, CSS feasibility, audience fit). Ends with three concrete weaknesses to fix and one thing the blueprint nails.compare_blueprints— head-to-head between two blueprints for the same business. Per-dimension winners, an overall winner, a hybrid suggestion.improve_blueprint— take an existing blueprint plus a critique, return a revised blueprint. Single model call so it fits inside MCP-client timeout budgets.multi_blueprint— generate N variants of the same blueprint at different sampling temperatures (0.7, 0.95, 1.2) for diversity. Capped at 3 to keep total runtime under five minutes.
Per-category presets shipped too — preset: dentist (or plumber, hvac, lawn-care, auto-repair, salon, restaurant, manufacturer, attorney, chiropractor) auto-fills services/vibe/audience/colors from a curated default. Explicit fields always win over preset values.
What didn't ship: streaming. Blueprints arrive after a 60-90s pause rather than incrementally. The MCP-SSE path is more complexity than the value-add justifies right now.
#The chromedp SSRF gate
The most security-relevant feature in architect-mcp is also the one I almost shipped without thinking about. generate_blueprint_from_url takes a user-supplied URL and runs it through headless Chrome. The first version validated only that the URL was http(s) with a non-empty host — which is what you'd call "validation theater" the moment you say it out loud.
The issue: Chrome resolves hostnames against the host's resolver. So a URL like http://vault.internal/, http://10.66.0.20:8201/ (where my Vault actually lives), or http://169.254.169.254/latest/meta-data/ (AWS metadata, the SSRF endpoint of choice) would resolve, get fetched, and have its embedded JS executed against the LAN. The screenshot then ships to a vision model. The whole flow is "give me a URL and I'll pivot off your machine."
The fix is a resolved-IP gate. Before chromedp ever sees the URL, the server does net.LookupIP(host) and rejects any result that's loopback, RFC 1918, link-local, IPv6 ULA, multicast, unspecified, or the AWS metadata IP. Plus a foot-gun-resistant override: ARCHITECT_ALLOW_PRIVATE_IPS=true opts back in for legitimate intra-LAN use (staging-vs-prod comparison), but only the literal "true" — not "1", not "yes", not "enabled" — turns the gate off.
There's a known limitation I didn't fix: DNS rebinding. Between the time the server validates the IP and the time chromedp dials, a hostile DNS could return a different IP. The robust fix needs either a Chrome-level proxy with its own allowlist or --host-rules to pin the resolved IP — both are significant complexity for a homelab tool. For now it's documented as best-effort.
The other v0.7 hardening worth mentioning, since the patterns generalize: JS is disabled in chromedp by default (a malicious public page with embedded <script>fetch('http://10.0.0.1/admin')</script> would otherwise probe the LAN even on a public-IP top-level URL); blueprint inputs are size-capped at 1 MB; the four duplicated Ollama HTTP boilerplates collapsed into a single CallOllama helper with body cap, redirect blocking, and per-request deadlines applied once instead of four times.
I wrote up the full set of patterns and the test-the-contract discipline that came with them in a separate post on the audit methodology — this architect-mcp work was the third in the series, and most of the patterns landed across all three repos in the same week.
#What's left
Streaming responses, mentioned above. Plus a few new ideas surfaced during use:
render_blueprint— pipe the blueprint through a code-generating step and produce a working static-HTML preview. Closes the "now what" loop after generate.refine_section— re-generate just one section of an existing blueprint (e.g., "rewrite the hero, keep everything else"). Currently you regenerate the whole thing.pick_palette— given a vibe + competitor screenshot, return three palette options. Useful as a step in the blueprint flow and also standalone.
The repo is at github.com/jasondillingham/architect-mcp. MIT-licensed. Public as of this week. The README points to where the prompt lives — internal/architect/prompt.go — because if you want to fork this, the prompt is what you're forking. The Go is just delivery.
#Three MCPs, one week, then an audit week
This is the third in a small set of MCP servers I built over the same week. The other two are ollama-mcp (manage local Ollama hosts) and homelab-status-mcp (read-only homelab observability with strict guardrails). All three follow the same skeleton: small Go binary, official MCP SDK, stdio transport, YAML config, MIT license. After the building week came an auditing week — same patterns showed up in all three repos, and the consolidated list became its own post.
The pattern is repeating because the pattern is correct. MCP turns out to be the right wrapper for "thing the LLM should be able to call mid-conversation, not thing I want to remember CLI flags for." Pretty much everything in my homelab is becoming an MCP candidate by that test.
The CLI was for me. The MCP is for the LLM that's helping me. Different audiences, different ergonomics, same underlying logic. That's the realization that's going to shape a lot of the tooling I build next.