← Back to list

I Built an AI That Answers Recruiter Questions About My Resume So I Don’t Have To

And yes, the irony of using AI to get an AI job is not lost on me.

Kanthetisrikanth · 2026-05-26 21:19 · 2 claps · 6.7 min read
#mcps #python #ai-engineering #fastapi #agent-design
Open on Medium ↗
Wiki topics: AGT · AI Agents AI · AI · General

I Built an AI That Answers Recruiter Questions About My Resume So I Don’t Have To

And yes, the irony of using AI to get an AI job is not lost on me.

Let me back up.

I’ve spent the last 7 years as an AEM Tech Lead building content platforms for hotel chains, banks, enterprise clients. Good work. Real scale. But about a year ago I got this itch that I couldn’t shake: I wanted to be on the other side. Not using AI tools, but building the infrastructure underneath them.

The problem is that career pivots are awkward to explain on a resume. You’ve got a list of past jobs that all say “AEM Developer” and then a skills section where you’ve quietly added “Python, LLMs, MCP protocol” and hope someone notices. Most don’t.

So I figured what if instead of telling people about the AI work I’m learning, I just showed them something live?

I built an AI agent that sits on my portfolio site and lets recruiters chat with it about my background. Ask it anything. It pulls from my actual career data, reasons about the question, and gives a real answer. Not a scripted FAQ. An actual agent with tool calls and a language model doing the synthesis.

This post is about how I built it and what I learned along the way.

The stack, before I get into the weeds

  • A Next.js chat widget on my portfolio site (srikanthkanteti.com)
  • A FastAPI backend deployed on Railway that runs the agent
  • A custom MCP server I wrote from scratch that exposes my career data as tools
  • Ollama running locally with a quantized Qwen 2.5 model, tunneled through Cloudflare

The last two are where it gets interesting.

Why I didn’t just read a YAML file

My first version did exactly this. Hardcoded my skills and experience into the prompt context, sent it to the LLM, got an answer. Done. Worked fine.

But it bugged me. The agent logic was tangled up with the data retrieval. Changing one broke the other. And more importantly — it wasn’t how real AI systems are built. Real systems have separation between the tools and the reasoning layer. The agent shouldn’t know how to fetch your data. It should just know that a tool exists and what it does.

That’s MCP in a nutshell. Model Context Protocol is Anthropic’s open protocol for giving AI agents access to tools and data sources. Instead of stuffing everything into a prompt, you define tools with typed schemas, and the agent calls them when it needs information.

I could have used one of the Python SDK wrappers. I chose not to, because I wanted to actually understand the protocol. Best decision I made on this project.

What MCP actually looks like under the hood

MCP runs over stdio. That’s it. No HTTP server, no sockets. Your agent spawns the MCP server as a subprocess and they talk to each other through stdin and stdout using JSON-RPC 2.0 messages.

When I first understood this I had to sit with it for a minute. It felt almost too simple. But it makes sense — you want the protocol to be lightweight and embeddable, not another service to run and manage.

The handshake goes like this:

Agent → Server:   {"jsonrpc": "2.0", "id": 1, "method": "initialize", ...}
Server → Agent:   {"jsonrpc": "2.0", "id": 1, "result": {"protocolVersion": "2024-11-05", ...}}
Agent → Server:   {"jsonrpc": "2.0", "method": "notifications/initialized"}
Agent → Server:   {"jsonrpc": "2.0", "id": 2, "method": "tools/list"}
Server → Agent:   {"jsonrpc": "2.0", "id": 2, "result": {"tools": [...]}}

After that, the agent knows what tools are available and can start calling them.

The main loop in my server is just this:

python

def main():
    load_data()  # reads career.yaml and projects.yaml
    while True:
        message = read_message()   # sys.stdin.readline() + json.loads()
        if message is None:
            break
        method = message.get("method")
        if method == "initialize":
            response = handle_initialize(message)
        elif method == "notifications/initialized":
            continue  # notification, no response needed
        elif method == "tools/list":
            response = handle_tools_list(message)
        elif method == "tools/call":
            response = handle_tools_call(message)
        send_message(response)  # json.dump() to stdout + flush

One thing that burned me early: you cannot write anything to stdout except valid JSON-RPC. I had a print statement in there for debugging, it corrupted the stream, and the client just silently stopped working. Took me an embarrassing amount of time to figure out. Log everything to stderr:

python

def log(message: str):
    print(f"[MCP] {message}", file=sys.stderr, flush=True)

Write that down. Save yourself the hour.

The tools I exposed

I have five tools in the server:

  • get_profile — returns my name, title, summary, contact info
  • list_skills — skills organized by category (AI/ML, systems architecture, leadership, etc.)
  • get_experience — work history, filterable by company name
  • get_projects — project portfolio, filterable by technology
  • match_to_role — takes a job description and returns a fit score with relevant skills and projects highlighted

The last one is the one people always ask about first. Here’s the tool definition:

python

{
    "name": "match_to_role",
    "description": "Match Srikanth's background to a job description. Returns fit score, relevant skills, and projects.",
    "inputSchema": {
        "type": "object",
        "properties": {
            "job_description": {
                "type": "string",
                "description": "Job description or role summary to match against"
            }
        },
        "required": ["job_description"]
    }
}

One thing I didn’t expect: the description field on a tool is basically a prompt. When an LLM decides which tool to call, it reads those descriptions. If you write vague ones, you get bad tool selection. I spent real time tuning these descriptions, which felt weird — writing copy for a JSON schema — but it genuinely matters.

The matching logic itself is currently keyword-based, and I’m being upfront about that limitation. If a recruiter types “what’s your strongest technical area” the agent might not connect that to list_skills. It's on my list to replace with embedding-based intent matching. But I'd rather ship the working version and improve it than never ship.

How the agent loop works

When a message comes in through the chat widget, the FastAPI backend does roughly this:

  1. Look at the message and figure out which tools are relevant
  2. Call those tools through the MCP subprocess
  3. Take the structured data back, build a prompt with it as context
  4. Send that prompt to Ollama for synthesis
  5. Return the response

In code, the MCP call looks like this:

python

async def call_tool(self, tool_name: str, arguments: dict) -> dict:
    request = {
        "jsonrpc": "2.0",
        "id": self._next_id(),
        "method": "tools/call",
        "params": {
            "name": tool_name,
            "arguments": arguments
        }
    }
    await self._send(request)
    response = await self._receive()
    return response["result"]["content"][0]["text"]

One thing I learned the hard way: keep the MCP subprocess alive across requests. My first implementation spawned a new subprocess per request. That added 300–500ms per call and eventually caused race conditions when two requests hit simultaneously. Now it starts once at API startup and stays running.

The production setup (and its honest tradeoffs)

The backend is deployed on Railway. Ollama runs on my local machine, tunneled through Cloudflare to ai.bullminder.com. The Next.js site on srikanthkanteti.com hits that tunnel endpoint.

Is this scalable? No. If I ever got a flood of traffic, my laptop would melt.

Is that a problem right now? Also no. This is a portfolio demo. The realistic traffic is a handful of recruiters and some curious people who found it online. For that use case, running inference on local hardware and eating the cost of the Cloudflare tunnel is fine.

The honest latency numbers:

  • First request after server start: 55–70 seconds (model loading into memory)
  • After that: 10–20 seconds

The first-request delay is rough. I added a loading state to the widget and it helps psychologically, but it’s still a wait. If I needed this to feel fast, I’d swap Ollama for the Claude API — probably under 2 seconds per response, around $0.001 per query at Haiku pricing. Worth it if this were serving real volume.

What building this actually taught me

I’ve been in tech long enough to be skeptical of “what I learned” sections in blog posts. So I’ll be specific.

CORS is not a set-it-and-forget-it thing. I spent a full evening on a preflight failure that turned out to be one missing origin in the allowlist. The error message in the browser developer tools was not helpful. Now I log every blocked CORS request on the server side.

Tool descriptions are UX, not documentation. I kept thinking of them as comments in a JSON file. They’re not. They’re the interface your agent uses to decide what to do. Write them like you’re writing copy that a confused intern will read at 3am.

The MCP protocol is genuinely well-designed. Stdio is simple, JSON-RPC is widely understood, and the tool schema spec is clear enough that I implemented a working server in an afternoon without reading anything except the spec. That’s a sign of good protocol design.

The overlap between distributed systems and agent systems is real. Everything I learned about decoupling at Wyndham — keeping the content layer separate from the delivery layer, designing for graceful degradation, building observable systems — applies directly here. I’m not starting over. I’m applying a different domain to the same principles.

What’s next

A few things I want to add when I have time:

Semantic tool selection with embeddings, so intent matching is more reliable than keyword scanning. A small embedding model running through Ollama would handle this without adding latency.

Multi-turn conversation memory. Right now each message is stateless — the agent doesn’t remember what you said earlier in the chat. Adding session IDs and conversation history would make it feel like a real dialogue.

Streaming responses. Sending tokens as they’re generated rather than waiting for the full response would make the latency feel much shorter, even if the total time is the same.

I don’t know if this project will get me the job I want. But I know that building it taught me more about agent systems, production deployment, and protocol design than any course I could have taken. And now when someone asks “have you actually shipped an AI system?” the answer is yes, here’s the URL, try it.

That feels worth something.

Code: github.com/srikanth010/openagent-workspace

Live demo: srikanthkanteti.com

If you’re building AI infrastructure and want to talk: LinkedIn

Tags: MCP Python AI Engineering FastAPI Career Change Agent Design Software Engineering


메타데이터
post_id
4e39c6bd2bb8
slug
i-built-an-ai-that-answers-recruiter-questions-about-my-resume-so-i-dont-have-to-4e39c6bd2bb8
url
https://medium.com/@kanthetisrikanth0/i-built-an-ai-that-answers-recruiter-questions-about-my-resume-so-i-dont-have-to-4e39c6bd2bb8
canonical_url
https://medium.com/@kanthetisrikanth0/i-built-an-ai-that-answers-recruiter-questions-about-my-resume-so-i-dont-have-to-4e39c6bd2bb8
author_url
https://medium.com/@kanthetisrikanth0
status
ok
fetched_at
2026-06-09 15:37:30