WebMCP Portfolio Integration
This portfolio registers structured tools through the WebMCP browser API, so an AI agent can query it directly, search projects, read the resume, even change the page, instead of scraping the DOM.
- ROLE
- Builder
- PERIOD
- 2026
- DOMAIN
- Web standards
- STATUS
- Published
- CODE
- GitHub ↗
OVERVIEW
This portfolio registers structured tools through the WebMCP browser API (navigator.modelContext), so an AI agent can query it directly instead of scraping the DOM. Eight tools let an agent search every project, read the full resume, list skills and lab experiments, and even change the site's theme or presentation mode, each with a JSON Schema for inputs and typed JSON results. The site works normally for everyone; the tools register only when the browser exposes the API (Chrome 146+ with the WebMCP flag) and unregister on cleanup. Site data is gathered server-side and handed to a client component that does the registration in the browser.
Tools this site registers.
The 8 tools this site registers with the WebMCP browser API. An agent in Chrome 146+ calls them directly; here is the catalog and one sample call.
search_projectsreadSearch projects by query, tech, tag, domain, or featured-only.
get_projectreadFull details for one project by ID: challenge, solution, impact, stack.
get_resumereadResume data by section: experience, education, skills, competencies, contact.
search_skillsreadTechnical skills by category or keyword.
get_contactreadContact info and social links.
list_experimentsreadWhat Jay is currently building, exploring, or watching in the lab.
toggle_themewriteSwitch the site between light and dark theme.
switch_modewriteToggle reader mode: a calm, motion-free reading view, or back to the full site.
AGENT CALLS search_projects
{
"tool": "search_projects",
"arguments": { "query": "protein", "featured_only": true }
}SITE RETURNS
{
"count": 2,
"projects": [
{
"id": "nobel-dataintelligence",
"title": "Nobel Data Intelligence",
"tech": ["Python", "PyTorch", "ProDy", "Transformers"],
"domain": "Computational Biology",
"url": "https://jayhemnani.in/projects/nobel-dataintelligence"
},
{
"id": "biotech-accelerator",
"title": "Biotech Accelerator",
"tech": ["Python", "LangGraph", "ProDy", "httpx"],
"domain": "AI/ML",
"url": "https://jayhemnani.in/projects/biotech-accelerator"
}
]
}ARRIVED AS
AI agents that use a website have to scrape its DOM and click buttons, which is brittle, slow, and breaks the moment a layout changes. The emerging WebMCP browser API takes a different route: a site registers structured tools that an agent calls directly, with JSON Schema inputs and typed JSON results. This project works out what it takes to expose a real, content-heavy site through that API.
The WebMCP proposal lets a website expose tools to an AI agent the same way an MCP server exposes them to a model: a name, a description, a JSON Schema for inputs, and a handler that returns structured data. Instead of screenshotting or scraping this site, an agent can call search_projects or get_resume and get clean JSON back. The API is new, navigator.modelContext behind a flag in Chrome 146, so part of the work was simply learning how the registration lifecycle behaves on a real Next.js site rather than inventing a new data layer.
WHAT I BUILT
- 01Registers 8 structured tools on this site through the WebMCP browser API (navigator.modelContext): project search, single-project lookup, resume, skills, contact, lab experiments, and two that change the page (theme and presentation mode).
- 02Each tool ships a JSON Schema for its inputs and returns typed JSON with canonical jayhemnani.in URLs, so an agent gets structured data instead of parsing rendered HTML.
- 03Progressive enhancement: the site works normally everywhere, and the tools register only when the browser exposes the API (Chrome 146+ with the WebMCP flag), then unregister on cleanup.
- 04Site data is gathered server-side (projects, resume, socials, lab) into one typed object and handed to a client component that registers the tools in the browser on mount.
WHAT CHANGED
- Turns the portfolio into a queryable surface: an agent can search every project, pull the full resume, list skills, and read what Jay is currently building, all as structured JSON.
- Two of the tools carry real side effects (theme toggle and presentation-mode switch), so WebMCP acts as a control surface, not just a read API.
- Doubles as a working reference for an early-stage browser standard, with the whole registration layer in one typed module.
Data flow
click a stage
WebMCPLoader reads projects (MDX), the resume, social links, and lab experiments and builds one typed SiteData object.
COMPONENT
WebMCPLoader (server)Reads projects, resume, socials, and lab data at the server and assembles the typed SiteData passed to the client.
Decisions, with the cost of each.
A decision without its trade-off is marketing. Each row says what was chosen, why, and what it gave up.
Expose tools through WebMCP rather than an llms.txt file or a separate API
WebMCP gives an agent typed, callable tools inside the page it is already on, with JSON Schema inputs and structured returns, which removes the scraping and clicking layer entirely. A static text file or a standalone REST API would not be discoverable from the browsing session.
llms.txt (static, not interactive); a public REST API (extra surface to host, not tied to the page the agent is viewing).
Progressive enhancement behind an availability guard
The API is behind a flag in one browser channel, so the site has to work for everyone and add tools only when navigator.modelContext is present. The provider checks first, registers nothing otherwise, and unregisters on unmount.
Assume the API exists (breaks the site for normal visitors); feature-detect once globally (misses the React mount and unmount lifecycle).
Load data server-side, register client-side
Tool handlers need the full project and resume data, but registration must run in the browser where navigator lives. A server component assembles the data once and a client component registers the tools over it, so handlers operate on in-memory data with no per-call fetch.
Fetch inside each handler (a network round-trip per call); hand-inline the data in the client bundle (duplicates the content loader).
The part that mattered.
The numbers behind the work, and the code that produced them.
- registered
- 8 tools
- 6 read · 2 write, via navigator.modelContext
- typed inputs
- JSON Schema
- structured JSON returns, no DOM scraping
- behind a flag
- Chrome 146+
- progressive enhancement everywhere else
- registration layer
- 1 module
- server loads data, client registers
mc.registerTool({
name: "search_projects",
description: "Search Jay's projects by query, technology, tags, or domain.",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Free-text across title, summary, tech" },
tech: { type: "string", description: "Filter by technology" },
featured_only: { type: "boolean", description: "Only featured projects" },
},
},
handler: async (args) => {
let results = [...data.projects];
// filter by query / tech / tag / domain / featured ...
return {
count: results.length,
projects: results.map((p) => ({
id: p.id, title: p.title, tech: p.tech,
url: `https://jayhemnani.in/projects/${p.id}`,
})),
};
},
});
Every tool follows the same contract: a name, a description, a JSON Schema for inputs, and an async handler that transforms the in-memory site data. The agent gets back structured JSON with canonical URLs, never rendered HTML.
useEffect(() => {
if (!isWebMCPAvailable()) return; // API is behind a flag; do nothing otherwise
registerWebMCPTools(data);
return () => {
unregisterWebMCPTools(); // clean up on unmount
};
}, [data]);
The site renders normally for every visitor. The tools register only when navigator.modelContext is present, and the effect's cleanup unregisters them, so the lifecycle follows React's mount and unmount.
// toggle_theme handler
const current = document.documentElement.getAttribute("data-theme") || "dark";
const newTheme =
args.theme === "light" || args.theme === "dark"
? args.theme
: current === "dark" ? "light" : "dark";
document.documentElement.setAttribute("data-theme", newTheme);
localStorage.setItem("theme", newTheme);
// storage event so the site's React ThemeContext picks it up
window.dispatchEvent(new StorageEvent("storage", { key: "theme", newValue: newTheme }));
return { previous: current, current: newTheme };
Six tools read; two write. An agent can flip the theme or switch presentation mode, which dispatches a storage event the site's React contexts already listen for, so WebMCP becomes a control surface rather than only a query layer.
✓ LEARNED
WebMCP reframes a site as a set of typed tools, not a page to scrape. The hard part is shaping existing content into clean tool inputs and returns, not the registration call itself.
Progressive enhancement is non-negotiable for a flagged API: guard on availability, register on mount, unregister on cleanup, and the site stays normal for everyone else.
A tool that changes the page is as interesting as one that reads it. Theme and mode tools turn the site into something an agent can operate, not only query.