I didn’t just want a portfolio that looks good - I wanted one that thinks. We live in an era where AI agents are becoming the primary consumers of our data. Yet, most portfolios are stuck in Web 2.0: static text, markdown files, and manual navigation.

Here’s a live demo of Claude Desktop querying my site via MCP.

I wanted to change that. I wanted an AI Agent (specifically Claude Desktop) to be able to "plug in" to my site and query my professional data directly — projects, blogs, resume, everything — without me writing a 5-page prompt every time. The technology that enables this is Model Context Protocol (MCP).

🎯 The Goal

I wanted my portfolio (aniketppatil.com) to have a brain. Instead of just serving static pages to humans, I wanted an API that AI agents could talk to. I wanted them to ask structured questions like:

  • "List his open source projects"

  • "Tell me the technical architecture of RagBot"

  • "Why should I hire him?"

And I wanted my server to answer.

🚧 The Constraints

This sounded simple on paper. But my infrastructure made it tricky:

  1. Framework: Next.js (App Router)

  2. Hosting: Vercel (Serverless / Free Tier)

  3. Budget: $0 (I didn’t want a separate VPS just to run a socket server)

I thought I could just install a library and be done. I was wrong.

Failure 1: The "Official" Way vs. Serverless Reality

I started by following the official MCP documentation. I installed the standard SDK (@modelcontextprotocol/sdk) and tried to spin up a server.

The Problem

The official SDK is designed for stateful environments. It assumes your server stays alive forever, maintaining a persistent connection (via stdio or WebSockets). Vercel is stateless. Functions spin up, handle a request, and die milliseconds later.

The Symptom

  • 📉 Connection timeouts.

  • 🚫 "Hanging" requests.

  • Claude would try to connect, but Vercel would kill the process before the handshake could finish.

I realized: Official MCP support for serverless was basically non-existent. I needed a way to "fake" persistence.

🔄 The Pivot: Enter mcp-handler

I found a community library called mcp-handler (v1.0.5) that changed everything.

npm install mcp-handler @modelcontextprotocol/sdk zod

Why This Worked

  • Designed for Next.js: Built specifically for App Router.

  • HTTP-First: It uses standard POST requests for tool calls.

  • Server-Sent Events (SSE): Uses SSE for the "push" messages (like logs).

  • Redis Backed: It uses Redis to store the connection state across those stateless function calls.

This matched Vercel’s execution model perfectly.

🐛 The Debugging Sprint

Even with the right library, getting it to actually work took a weekend of debugging.

Issue 1: The 404 / Slug Nightmare

Claude Desktop kept failing with: Connection failed: 404 Not Found.

The Investigation: I looked at the logs.

  • Client Request: GET https://aniketppatil.com/api/mcp/sse

  • My Route: Handles /api/mcp ONLY.

The MCP client automatically appends /sse and /message to your URL. My route wasn't catching those.

The Fix: Rename the route to a "Catch-All":

app/api/mcp/[[...slug]]/route.ts

Now, one file handles everything: /api/mcp, /api/mcp/sse, and /api/mcp/message.

🏗️ The Modular Refactor

By Sunday morning, my route.tsfile was500 lines of spaghetti code. It had Zod schemas, file reading logic, HTML parsing regex, and server config all mixed together. I hated it. So I refactored. I split the "brain" into a clean lib/mcp/ architecture:

  • 📄 types.ts: Pure TypeScript interfaces.

  • 💾 data.ts: Read-only JSON loaders.

  • 🧠 utils.ts: The semantic search logic.

  • 📦 index.ts: Clean exports.

Now, my route handler is just a beautiful registry:

server.registerTool("list_projects", { ... }, async () => { const projects = await loadProjects(); return formatProjects(projects); });

(Bonus: I also migrated to the new server.registerTool() API to future-proof the code!)

🛡️ Security: The "NASA Hack" Test

I am exposing my file system (fs) to the internet. That is dangerous. I needed to prove that no one could use this to read my private .env files.

The Strategy

  1. No Writes: There is literally no fs.writeFile code in the project.

  2. Allow-Lists: Strict Zod Enums for every input.

The Test

I asked Claude: "Tell me about the project secret-nasa-hack". The API received: { "slug": "secret-nasa-hack" } The Zod Schema: z.enum(["ragbot", "coursebot", ...])

The Result: 🛑 Blocked. Input validation error. It didn't even try to touch the disk. The security works.

🚀 The Result: A Personal AI API

This wasn't just "installing a library." It was adapting a stateful protocol to a stateless platform, debugging routing manualy, and designing a secure architecture. Now, anyone (or any AI) can connect to my portfolio and query it like an API. Try It Yourself: If you use Claude Desktop, add this to your config:

"aniket-portfolio": { "command": "npx", "args": ["-y", "mcp-remote", "https://aniketppatil.com/api/mcp"] }

It works. And it’s built to last.

👋 Thanks for reading! If you're building with MCP on Vercel, hit reply — I'd love to hear what you're making.

Recommended for you

No posts found