All CUJ Case Studies
Critical User Journey Case Study

In-Browser RAG & Model Context Protocol: Sub-10ms Semantic Retrieval at Zero Cloud Cost

πŸ“… 2026-09-11 πŸ‘€ Daniel Herrington
GitHub Repository
In-Browser RAG & Model Context Protocol: Sub-10ms Semantic Retrieval at Zero Cloud Cost Hero Graphic

How we built a serverless, zero-cloud-cost in-browser RAG retrieval engine and Model Context Protocol (MCP) server indexing 50+ essays, podcasts, and software architectures directly inside dh.sh.


The Floating Shell: What Is That Thing?

You may have noticed a shell floating on my website and wondered what that was. Even if you haven't clicked on it, I'd love to tell you a little more about the technology.

When visitors land on herrington.ai, they often see a subtle pill in the bottom corner reading dh.sh. Most personal sites that feature floating widgets are embedding off-the-shelf third-party chat widgetsβ€”an iframe that connects to an OpenAI API wrapper, sends every keystroke to a hosted server, takes three seconds to respond, and costs the site owner recurring cloud tokens on every visit.

The floating shell on this site is fundamentally different: it is a live, transparent client-side execution environment for Retrieval-Augmented Generation (RAG) and the Model Context Protocol (MCP).

When you open dh.sh and ask a question, no backend server is spun up. No third-party LLM API endpoint is billed. Instead, your browser downloads a precomputed 1MB inverted index, executes a multi-factor BM25 / TF-IDF ranking algorithm across 50 documents and 730 semantic passages in vanilla JavaScript, runs a deterministic MCP tool call (search_article_insights), and renders a verified answer with exact quotes and direct citations in under 8 milliseconds.

Furthermore, because the retrieval engine adheres strictly to the open Model Context Protocol (MCP) specification, this exact same engine is exposed as an MCP server that you can plug directly into Claude Desktop, Cursor, or autonomous agent swarms.

Here is why we built it, how the architecture functions, and the Critical User Journeys (CUJs) it unlocks for autonomous agents, developers, journalists, and human readers.


The Problem Space: Why Traditional RAG Fails Personal Sites

Over the past few years, this website has evolved into an extensive technical repository:

When someone visits the site wanting to know: "What is Daniel's stance on vibe coding vs. agent harnesses?", "How does he measure ML Goodput?", or "What did he say about actuarial models for autonomous driving?", standard web navigation falls apart. No one wants to manually click through five different archive carousels, open 20 tabs, and Ctrl+F through thousands of words.

The Standard Industry Playbook: Cloud Vector RAG

The standard recommendation for natural language search across custom content is a cloud RAG pipeline:

  1. Ingest markdown into an external vector database (Pinecone, Qdrant, Weaviate).
  2. Host an API server (FastAPI, Express) that receives incoming user queries.
  3. Call an embedding model (e.g., text-embedding-3-small) to convert the query into vectors.
  4. Perform approximate nearest neighbor (ANN) search across the cloud index.
  5. Pass the retrieved passages to a large language model to stream an answer.

While viable for enterprise document sets with millions of pages, this architecture introduces massive friction for a public portfolio:

  1. The Vector DB Subscription Tax: Cloud vector databases cost between $50 and $200 per month just to stay alive in RAM, even when nobody is querying the site at 3 AM.
  2. Open-Ended Token Vulnerability: A public web form calling OpenAI or Anthropic directly exposes your API keys and monthly credit card limits to scrape bots, script kiddies, and recursive prompt injection loops.
  3. Severe Cold-Start Latency: Between embedding generation, remote network round-trips to the database, vector similarity compute, and LLM first-token generation, typical cloud RAG pipelines clock 1,200ms to 3,500ms of latency.
  4. Black-Box Opacity: Standard chat widgets show an animated spinner and dump text into a bubble. You have no visibility into what documents were retrieved, what scoring metric was used, or what tools were executed.

We decided to invert the paradigm: precompute semantic weights at static site build time, ship the inverted index to the browser, execute retrieval on the edge in sub-10ms, and expose the tools through the Model Context Protocol.


Critical User Journeys (CUJs)

To guide the engineering of this system, we defined four distinct Critical User Journeys following our standardized CUJ framework:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚               CRITICAL USER JOURNEYS (CUJ FRAMEWORK)                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 1. Autonomous Research Agent  β”‚ Machine-to-machine zero-token retrievalβ”‚
β”‚ 2. Human Developer / MCP User β”‚ Native querying inside Claude / IDE    β”‚
β”‚ 3. Tech Journalist & Media    β”‚ High-precision sourcing & quotes       β”‚
β”‚ 4. In-Browser Visitor         β”‚ Sub-10ms interactive shell (dh.sh)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

CUJ 1: Autonomous Research Agent CUJ (Machine-to-Machine Discovery)

CUJ 2: Human Power-User via MCP CUJ (Claude Desktop & IDE Integration)

CUJ 3: Tech Journalist & Media Researcher CUJ (The Future of Sourcing)

CUJ 4: In-Browser Human Visitor CUJ (Instant Interactive Terminal)


Dual Architecture: Edge Inverted Index + Model Context Protocol

The engine operates across two decoupled planes: Build-Time Precomputation (Python SSG) and Dual-Mode Consumption (Client-Side Vanilla JS + External MCP Server).

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   BUILD-TIME PIPELINE (Python SSG)                     β”‚
β”‚                                                                        β”‚
β”‚   28 Native Essays   6 CUJ Studies   4 Media Transcripts   Project Docsβ”‚
β”‚          β”‚                 β”‚                 β”‚                  β”‚      β”‚
β”‚          β–Ό                 β–Ό                 β–Ό                  β–Ό      β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚   β”‚        Heading-Aware Semantic Chunker & Markdown Normalizer    β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                    β–Ό                                   β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚   β”‚     TF-IDF Weighting & Inverted Index Generator (6,583 Terms)  β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                    β–Ό                                   β”‚
β”‚             public/data/article_index.json (1.0MB / 180KB gzip)        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                     β”‚
           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β–Ό                                                   β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     CLIENT-SIDE ENGINE (Vanilla JS)   β”‚   β”‚     EXTERNAL MCP SERVER (TypeScript)  β”‚
β”‚                                       β”‚   β”‚                                       β”‚
β”‚   Runs inside dh.sh Terminal          β”‚   β”‚   Runs via Stdio / SSE for Claude     β”‚
β”‚   - Tokenizer & Stop-Word Filter      β”‚   β”‚   - @modelcontextprotocol/sdk         β”‚
β”‚   - Postings Accumulator (3ms)        β”‚   β”‚   - Tools: search_article_insights    β”‚
β”‚   - Exact-Phrase & Title Boosting     β”‚   β”‚   - get_career_timeline, get_writings β”‚
β”‚   - JSON-RPC 2.0 Observable Traces    β”‚   β”‚   - Plugs into claude_desktop_config  β”‚
β”‚                                       β”‚   β”‚                                       β”‚
β”‚   Output: Sub-10ms UI Answers         β”‚   β”‚   Output: Native Agent Tool Execution β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Technical Deep-Dive

1. Build-Time Semantic Chunking & Indexing

When python3 scripts/build_all.py compiles the static site, it runs scripts/build_rag_index.py. The indexer scans all markdown assets across skills/cujs/references/, public/insights/, and structured records in public/data/.

Rather than using crude fixed-length character splitters (which cut sentences and destroy semantic context), our chunker is heading-aware:

def chunk_markdown(slug, title, url, body, category="insight", tags=None):
    """Splits markdown into coherent semantic chunks bounded by headings."""
    chunks = []
    lines = body.split('\n')
    current_heading = title
    current_paras = []
    
    for line in lines:
        heading_match = re.match(r'^(#{1,3})\s+(.+)$', line)
        if heading_match:
            # Flush existing section before starting new heading
            if current_paras:
                content = "\n".join(current_paras).strip()
                if len(content) > 60:
                    chunks.append({
                        "slug": slug,
                        "title": title,
                        "heading": current_heading,
                        "url": url,
                        "content": content,
                        "category": category,
                        "tags": tags or []
                    })
                current_paras = []
            current_heading = heading_match.group(2).strip()
        else:
            if line.strip():
                current_paras.append(line.strip())

Each chunk retains its parent title, current section heading, canonical URL, and category badge (Native Essay, CUJ Case Study, Media & Interview, or Project Architecture).

2. Inverted Index with BM25 / TF-IDF Weighting

For each term $t$ in chunk $d$, we compute its Term Frequency ($TF$), and across the entire corpus of $N$ documents, its Inverse Document Frequency ($IDF$):

$$\text{IDF}(t) = \ln\left(1 + \frac{N - n_t + 0.5}{n_t + 0.5}\right)$$

Terms appearing frequently in a specific passage but rarely across the general corpus (e.g., "actuarial", "goodput", "unleashed", "vibe", "diarization") receive strong positive weights, while conversational stopwords are eliminated.

The resulting compiled artifact (public/data/article_index.json) contains:

3. Client-Side Query Execution & Heuristic Re-Ranking

When a user submits a query to dh.sh, searchArticleInsights(query) executes directly in the visitor's browser:

// Heuristic Multi-Factor Re-Ranking in Vanilla JS
scores.forEach((score, chunkIdx) => {
    const chunk = this.articleIndex.chunks[chunkIdx];
    const titleLower = chunk.title.toLowerCase();
    const headingLower = chunk.heading.toLowerCase();
    
    rawTokens.forEach(token => {
        if (titleLower.includes(token)) {
            scores.set(chunkIdx, scores.get(chunkIdx) + 2.5); // Boost title matches
        }
        if (headingLower.includes(token)) {
            scores.set(chunkIdx, scores.get(chunkIdx) + 1.5); // Boost section heading
        }
    });

    // Exact contiguous phrase match bonus
    if (chunk.content.toLowerCase().includes(queryLower)) {
        scores.set(chunkIdx, scores.get(chunkIdx) + 4.0);
    }
});

To prevent a single long essay from dominating all top spots, the engine applies diversity deduplication, guaranteeing that the user receives the top-scoring excerpt from multiple distinct works.


The Model Context Protocol (MCP) Integration

The retrieval engine is not locked to a web browser. We implemented the official @modelcontextprotocol/sdk in TypeScript (mcp-server/src/index.ts), supporting both Stdio and SSE (Server-Sent Events) transports.

Exposed MCP Tools

The server exposes 7 distinct tools:

  1. search_article_insights: Semantic search and RAG retrieval across Daniel's essays, media appearances, and architecture case studies.
  2. get_career_timeline: Structured executive career history (The Zebra, Google, Vroom, Capital One, Priceline, MSU).
  3. get_latest_writings: Most recent articles, LinkedIn publications, and arXiv research papers.
  4. get_interviews_and_media: Podcast appearances, press interviews, and thought leadership profiles.
  5. get_projects: Open-source and architectural project portfolio details.
  6. get_personal_facts: Hobbies, setup preferences, and verified personal background.
  7. get_social_links: Verified social and code repository links.

How to Hook Daniel's MCP Server into Claude Desktop

Developers, researchers, and journalists can query this site directly inside Claude Desktop by adding the server to claude_desktop_config.json:

{
  "mcpServers": {
    "herrington-ai": {
      "command": "node",
      "args": [
        "/path/to/herrington.ai-site/mcp-server/build/index.js"
      ]
    }
  }
}

Once configured, Claude gains native tool-calling access to the entire body of work:

Human: "What does Daniel Herrington say about the economics of vibe coding?"
Claude: Calls search_article_insights(query="vibe coding economics")
Tool Output: Returns passages from 'The End of Vibe Coding' and 'Bricks, Bots, and Vibe Coding'.
Claude: "Daniel argues that while vibe coding accelerates initial prototyping, it incurs a massive maintenance debt once systems encounter production constraints..."


Interactive Try-It-Live Showcase

You can test the RAG engine right now on this page! Click any of the query buttons below to launch the floating dh.sh terminal and watch it retrieve verified insights in real time:


Architectural Benchmarks: Edge RAG vs. Cloud RAG

MetricTraditional Cloud RAG (Vector DB + LLM)dh.sh In-Browser RAG EngineLocal MCP Server (Claude Desktop)
Retrieval Latency1,200ms – 3,500ms4ms – 8ms (~250x faster)10ms – 25ms (Local stdio RPC)
Monthly Hosting Cost$50 – $200 / month$0.00 / month (Static CDN)$0.00 / month (Local execution)
Marginal Cost per Query~$0.01 – $0.05 / query$0.00 (Zero API tokens)Included in Claude subscription
Offline Resiliency❌ Fails without networkβœ” 100% offline capableβœ” Works with local LLMs
Vulnerability to Scrape BotsHigh (Exposes API credits)Zero risk (Client compute)Zero risk (Local stdio)
Protocol StandardizationProprietary REST endpointsJSON-RPC 2.0 (MCP)Official Model Context Protocol

The Future of Personal Portfolios & Autonomous Sourcing

The web is undergoing a rapid phase transition from human-browsed HTML documents to agent-synthesized intelligence.

In this emerging paradigm:

  1. Personal websites must become queryable APIs: Simply presenting a resume or blog list is no longer sufficient. If autonomous research agents cannot query your body of work through structured protocols, your ideas will be ignored by the synthesis engines that inform enterprise decision-makers.
  2. Journalists and researchers will expect verified MCP endpoints: Rather than emailing PR representatives or reading outdated boilerplate, reporters will use MCP-connected assistants to explore an executive's published record, verify past quotes, and analyze topic trajectories.
  3. Edge compute wins on cost and privacy: By shipping precomputed inverted indexes directly to the client, site creators can deliver conversational, agentic discovery to millions of visitors without spending a single dollar on cloud vector databases or token APIs.

The floating shell on herrington.ai is a live proof-of-concept for this future. Click dh.sh, type a question, and experience sub-10ms retrieval for yourself.

Have thoughts on this CUJ or framework?

Let's discuss product strategy, machine learning engineering, or system architecture.

Connect on LinkedIn Explore Other CUJs