Live on this site

Model Context
Protocol.

MCP is an open standard that lets AI models talk to external tools through a structured interface — purpose-built for agents. This site uses it today: the Job Hunt page has Codex CLI connected to Brave Search via MCP, so it can search the live web for roles rather than working from a static prompt.

Live Brave Search integration
Codex CLI → MCP → Brave API
Open standard by Anthropic

What is MCP?

The Model Context Protocol is an open standard published by Anthropic in November 2024. It defines how AI models request and receive external context — think of it as a USB-C port for AI tools: instead of every model needing custom integration code for every service, MCP is one universal connector.

A MCP server exposes a set of tools — callable functions like brave_web_search or read_file. An MCP client (the agent framework) discovers those tools at startup, then calls them during a session using JSON-RPC messages over stdio or HTTP.

The key insight: the model doesn't need to know how Brave Search works. It just knows "there's a tool called brave_web_search that takes a query and returns results." The MCP server handles the HTTP call, authentication, and response parsing.

HTTP GET vs JSON-RPC — what's actually different?

Both are ways to fetch data over a network. The difference is in structure, semantics, and who controls the call. MCP uses JSON-RPC 2.0 — here's exactly what that means compared to a plain HTTP GET.

HTTP GET
Request
GET /search?q=salesforce+jobs+sydney&count=10 HTTP/1.1
Host: api.search.brave.com
Accept: application/json
X-Subscription-Token: ••••••••••••••
Response — vendor-specific shape
{
  "web": {
    "results": [
      { "title": "Salesforce Jobs...",
        "url": "https://...",
        "description": "..." }
    ]
  }
}
JSON-RPC (MCP)
Request — named method, typed args
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "brave_web_search",
    "arguments": {
      "query": "salesforce jobs sydney",
      "count": 10
    }
  }
}
Response — standardised content array
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      { "type": "text",
        "text": "Salesforce Jobs..." }
    ]
  }
}
HTTP GET

Parameters in the URL. Response shape is whatever the API vendor chose. Your app code must parse it and inject the result into the model prompt manually.

JSON-RPC

Named method + structured args. Response is always a typed content array. The model can call any tool the same way — no per-tool parsing logic needed.

Who makes the call

Your code decides when and how to call the API, then feeds the result into the prompt. The model is passive.

Who makes the call

The model autonomously decides when to call a tool, which tool, and with what arguments — during its own reasoning, not from app code.

Adding a new API

Write new fetch code, new response parser, update the prompt with the new data. Multiple places to change.

Adding a new server

Register the server in config. The model discovers its tools at startup. No code changes needed — it just works.

REST API vs MCP — who owns the glue work?

Both approaches can use Brave Search. The difference is who writes the integration logic.

REST API
Your App
↓ HTTP GET /search?q=…
External API
Brave / Stripe / GitHub
↓ Vendor JSON response
Your App
parses + injects into prompt
  • You write code to call the API
  • You parse and validate the response
  • You inject context into the model prompt
  • You decide when and how to call it
  • Works great when you control the logic
MCP
AI Model / Agent
↓ JSON-RPC tools/call
MCP Server
brave-search / filesystem
↓ Calls external API internally
External API
Brave / filesystem / DB
↓ Structured result back to model
AI Model
uses result in reasoning
  • The model decides when to call the tool
  • MCP server handles all API plumbing
  • Results flow directly into model context
  • New tools available without prompt changes
  • Works great when the agent controls the logic

How MCP works under the hood

MCP uses JSON-RPC 2.0 messages over stdio (subprocess pipes) or HTTP/SSE. The sequence for every session looks like this:

Codex CLI
MCP client
→ initialize request
Handshake: exchange capabilities and tool list
← initialize response + tools/list
Server declares: brave_web_search(query, count)
→ tools/call {"name": "brave_web_search", "arguments": {"query": "…"}}
Model autonomously decides to search
← tools/call result {"content": [{"type": "text", "text": "…"}]}
Results injected directly into model context
Brave MCP Server
@modelcontextprotocol/server-brave-search
~/.codex/config.toml — registering the MCP server
[mcp_servers.brave-search]
command = "npx"
args    = ["-y", "@modelcontextprotocol/server-brave-search"]

[mcp_servers.brave-search.env]
BRAVE_API_KEY = "••••••••••••••"

When Codex CLI starts, it spawns npx @modelcontextprotocol/server-brave-search as a subprocess. The two processes communicate over stdin/stdout using JSON-RPC. The model sees brave_web_search as a native tool — identical to its built-in file-reading tools.

Live integration: Job Hunt + Brave Search

The Job Hunt page uses this exact pattern. Here's the full flow from "generate today's shortlist" to results:

🖱️
User clicks "Generate"
Job Hunt page POSTs to /api/jobhunt/generate
🐍
Flask API builds context
Loads resume, seed jobs, location prefs, live Greenhouse/Lever/Ashby job pools
Codex CLI invoked as subprocess
Prompt instructs the model to call brave_web_search for 3–5 targeted searches before ranking
🔌
Codex calls Brave Search via MCP
JSON-RPC → brave_web_search("Salesforce architect jobs Sydney 2025") → live web results
🎯
Model ranks all candidates + returns JSON
Merges web results with board scraper results, applies resume context, returns 5 prioritised suggestions
📊
Usage tracked + results saved
Brave query count incremented · suggestions saved to opportunity tracker · shown in UI
Brave Search limits 2,000 queries / month 50 req/s max Free tier
View live usage on Job Hunt →

When MCP makes sense

  • The AI model needs to decide dynamically which tools to call
  • Multiple models or frameworks need the same tool surface
  • You want a clean boundary between "model reasoning" and "external data"
  • The tool ecosystem is growing — new servers slot in without code changes
  • You're building agentic workflows with multi-step tool use
On this site: Codex CLI decides autonomously how many Brave searches to run and what queries to use — MCP gives it real agency over the search process.

When plain functions are better

  • The app code controls when and how to call external services
  • You're building server-side features that inject context into prompts
  • The AI and the data source live in the same codebase / process
  • Latency matters and you can't afford the protocol overhead
  • One-off integrations that don't need to be composable
This site's chatbot was reverted to plain Python functions for exactly this reason — data sources and Flask live in the same process. No subprocess boundary needed.

Retrospective: the chatbot MCP experiment

In early 2025 this site ran a local stdio MCP server for the chatbot. It was removed. The reason wasn't that MCP is bad — it's that the use case was wrong:

❌ Wrong fit

The chatbot and all its data sources run in the same Python process. MCP added a protocol boundary with no real separation of concerns.

❌ Wrong fit

Debugging, permission handling, and observability all got more awkward once data had to cross a subprocess pipe as pseudo tool calls.

✅ Right fit

The Job Hunt Codex integration genuinely benefits: Codex is a separate process that needs real-time web access it couldn't otherwise have.

✅ Right fit

MCP turns Codex from a text model with a static prompt into an agent that can plan searches, execute them, and synthesise results autonomously.

MCP isn't overkill — it's just a scalpel. The chatbot needed a spatula. The job-hunt agent actually needed the scalpel.

Used on this site

  • Job Hunt — Codex CLI + Brave Search MCP for live job discovery
  • Codex config~/.codex/config.toml registers the server
  • Package@modelcontextprotocol/server-brave-search
  • Previously: local stdio MCP for chatbot (removed — wrong tool for same-process data)
The Vibecode page has the full build story, including how Claude Code and Codex are used together across this site.