Skip to main content
Glama
mattjjahn-spec

resume-mcp

resume-mcp

A resume that answers questions instead of sitting in a PDF.

The problem

Recruiters and hiring managers skim resumes for 20 to 30 seconds. AI agents (Claude Desktop, Cursor, and similar tools) can do much better than skimming, but only if they have something structured to query. A PDF or a wall of markdown gives a model a pile of text to summarize. It does not give the model a way to ask a follow-up question, look up one specific claim, or take an action.

This repo turns a resume into a small local server that speaks the Model Context Protocol (MCP). Point an MCP-aware agent at it, and the agent can search work history by keyword, search it by meaning, pull a deep dive on a specific company, or open an email pre-filled with a scheduling request. It is the same resume data, exposed as tools instead of prose.

Related MCP server: resume-mcp

Approach and why

MCP over a custom API. MCP is the emerging standard for connecting agents (Claude Desktop, Cursor, and others) to external tools and data. Building on it means any MCP-compliant client can use this server without custom integration work. The tradeoff is that MCP is still young and the SDK's surface has moved between versions, so this repo pins a known-working SDK version rather than always tracking latest.

Structured JSON as the source of truth, not the resume file itself. resume.json holds the actual content: summary, skills, and per-job highlights. Both tools (keyword and semantic search) read from the same structure, so there is one place to update when the resume changes, and no risk of the two search modes drifting out of sync with each other.

Two search tools, not one. Keyword search (query_resume_history) is exact, fast, and has no dependencies beyond fs.readFileSync. It is the right tool when the caller already knows the word they are looking for ("LEAP", "Acquisition"). Semantic search (semantic_search_history) is for the more common case: a recruiter asking "has he managed hardware rollouts?" when the resume says "field teams" and "Starbucks locations" instead of "hardware." Keeping both means the fast, dependency-light path never regresses just because the semantic path exists. See "RAG design notes" below for how the semantic tool is built.

Local-first, no API key. The semantic search tool embeds text on the machine running the server, using a small quantized model (see below). There is no network call at query time and no vendor key to configure. That is a deliberate constraint: a portfolio artifact that a stranger can clone and run should not require them to sign up for anything.

Semantic search is opt-in, so the base install stays clean. The embedding library (@xenova/transformers) is not a default dependency. npm install pulls only the MCP SDK, so the base server has no native or ONNX toolchain and npm audit is clean. Keyword search, company deep dives, and scheduling all work out of the box. You enable semantic search explicitly with npm run add-semantic, and the server only advertises the semantic_search_history tool once it is installed.

One honest tradeoff worth knowing before you opt in: @xenova/transformers pulls in an ONNX runtime whose transitive protobufjs dependency carries a critical npm audit advisory. It only affects parsing untrusted protobuf input, and this server only uses the runtime to run inference on one pinned, trusted local model file, never to parse input from a network caller. The suggested fix downgrades transformers.js to a broken pre-release, a worse trade. Isolating this to an opt-in install keeps that advisory out of anyone who does not need the feature.

Stdio transport, not HTTP. MCP supports both. Stdio keeps the server a single local process with no port to manage or secure, which fits a personal, single-user tool. An HTTP transport would matter if this needed to serve multiple concurrent clients or run as a hosted service; it does not.

Tools exposed

Tool

Arguments

What it does

query_resume_history

query: string

Exact, case-insensitive substring search over the summary and work history.

semantic_search_history

query: string, topK?: number

Local embedding-based search over the same work history, for paraphrased or conceptual queries. Opt-in: only advertised after npm run add-semantic.

get_company_deep_dive

company: "nomad-go" | "tune" | "wide-orbit"

Returns expanded detail on a specific role that does not fit in a resume bullet.

send_scheduling_email

none

Opens the local default mail client with a pre-filled scheduling message.

Quickstart

Requires Node.js 18 or higher.

git clone <this-repo-url>
cd resume-mcp
npm install
npm test

npm test runs the unit tests for the keyword search and the semantic search's chunking, cosine similarity, and ranking logic (no model download required for the tests).

The base install gives you keyword search, company deep dives, and scheduling. To enable the semantic search tool:

npm run add-semantic

This installs @xenova/transformers and, on the next server start, the semantic_search_history tool is advertised. Without it, the server runs fine and simply does not list that tool.

To run the server directly and inspect it interactively:

npx @modelcontextprotocol/inspector node index.js

This opens a browser GUI where you can call each tool and see the raw request and response. The server itself talks JSON-RPC over stdio, so running node index.js in a plain terminal will appear to hang. That is expected: it is waiting for an MCP client to connect, not for keyboard input.

The first call to semantic_search_history downloads a small (about 23MB) quantized embedding model from Hugging Face and caches it locally. Every call after that, including in future sessions, uses the cached copy.

Connecting to an agent

Claude Desktop

Edit your Claude Desktop config file:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

  • Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "resume": {
      "command": "node",
      "args": ["/absolute/path/to/resume-mcp/index.js"]
    }
  }
}

Restart Claude Desktop after saving. The four tools above will show up as available tools in a new conversation.

Cursor

  1. Open Cursor Settings > Features > MCP.

  2. Click + Add New MCP Server.

  3. Set:

    • Name: resume

    • Type: stdio

    • Command: node /absolute/path/to/resume-mcp/index.js

  4. Save.

Both clients launch the server as a subprocess and talk to it over stdio, so there is no port or URL to configure.

Example output

Captured from a real run against index.js, using the MCP SDK's own client library to call each tool over stdio (not hand-written):

=== tools/list ===
query_resume_history, semantic_search_history, get_company_deep_dive, send_scheduling_email

=== call: query_resume_history { query: 'Marketplace' } ===
Found 1 matching items for "Marketplace":

[2021 - 2023] Technical Product Manager at Wide Orbit (Greater Seattle Area):
 - Designed, built, and shipped the Wide Orbit Marketplace, empowering direct ad spend management.

=== call: semantic_search_history { query: 'experience scaling releases across hardware and retail locations' } ===
Top 3 semantic matches for "experience scaling releases across hardware and retail locations":

1. [score 0.506] Sr. Technical Program/Product Manager at Nomad Go (2025 - 2026)
   Product Manager leading core application code development and directing cross-organizational coordination to manage releases scaling to all North American Starbucks locations (US & Canada).

2. [score 0.488] Technical Product Manager at Wide Orbit (2021 - 2023)
   Managed regular software release cycles, coordinating timing, release notes, and deployments.

3. [score 0.486] Summary
   10+ years of technical product leadership, directing cross-functional teams, preparing SaaS platforms for acquisition, and scaling enterprise releases across retail and ad-tech spaces.

=== call: get_company_deep_dive { company: 'tune' } ===
TUNE / HasOffers Acquisition Details:
- Role: Product Manager II.
- Greenfield Product: Payout Structures rule builder.
- Business Value: Allowed direct advertisers to configure complex tiered, funnel-stage, geographic, and retroactive payout rate-cards.
- Valuation Impact: Transformed TUNE from serving solely affiliate networks to direct enterprise advertisers, massively expanding TAM/ARR and setting up the Constellation Software acquisition in 2020.

Notice the semantic query never uses the words "Starbucks," "field teams," or "release cadence" that appear in the top match. It asks about "hardware" and "retail locations," and the embedding model connects that to the bullet about North American retail rollouts anyway. A keyword search for the same phrase would have returned nothing.

Example agent conversation

This is the shape of what an agent sees and does when a person asks a question in plain language:

User: Has Matt ever managed a rollout involving physical hardware, not just software?

Agent (tool call):

{
  "name": "semantic_search_history",
  "arguments": { "query": "rollout involving physical hardware", "topK": 2 }
}

Tool response:

{
  "content": [
    {
      "type": "text",
      "text": "Top 2 semantic matches for \"rollout involving physical hardware\":\n\n1. [score 0.53] Sr. Technical Program/Product Manager at Nomad Go (2025 - 2026)\n   Product Manager leading core application code development and directing cross-organizational coordination to manage releases scaling to all North American Starbucks locations (US & Canada).\n\n2. [score 0.41] Technical Program/Project Manager at Point Inside (2014 - 2017)\n   Coordinated company-wide releases for 3D mapping and in-store Bluetooth location beacons."
    }
  ]
}

Agent (to user): Yes. At Nomad Go he coordinated releases across all North American Starbucks locations, working with hardware field teams. Earlier, at Point Inside, he coordinated releases for in-store Bluetooth location beacons, which is also a physical hardware rollout.

RAG design notes

The semantic search tool is a small, deliberately minimal retrieval-augmented generation (RAG) setup. Every choice below was made to keep it local, fast to start, and honest about its own limits.

Chunking: one highlight per chunk. resume.json is chunked in lib/semantic_search.js at the level of individual bullet points (plus one chunk for the summary), not whole jobs and not individual sentences within a bullet. A whole-job chunk would bury a specific claim ("positioned TUNE for acquisition") inside three unrelated bullets, diluting its embedding. Splitting below the bullet level would not help here: resume highlights are already single, dense claims, so there is nothing smaller worth retrieving on its own. The right chunk size depends on the shape of the source document, and for a resume that shape is the bullet point.

Embedding model: all-MiniLM-L6-v2, quantized, run through transformers.js. This is a 384-dimension sentence embedding model, small enough (about 23MB quantized) to download on first use and cache locally, and fast enough to embed a resume's worth of chunks in a few milliseconds after the model is loaded. It is not the most accurate embedding model available. It is accurate enough to separate "hardware rollout" from "software release" in a document with a few dozen chunks, which is the actual job here.

Why local instead of an API embedding call. This server is meant to be cloned and run by a stranger with minimal setup (npm install, then one npm run add-semantic to opt into embeddings). An API-based embedding model would mean an API key, a paid account, and a network dependency for a feature that is answering questions about a single resume. None of that cost buys meaningfully better retrieval at this scale. Local-first also means the tool works offline and the resume content never leaves the machine it runs on.

No vector database. Chunk embeddings are computed once per process (on the first semantic search call, then cached in memory) and compared with plain cosine similarity over an array. A resume has dozens of chunks, not millions. A vector database (Pinecone, Weaviate, pgvector, and similar) earns its keep when the corpus is large enough that a linear scan becomes slow, when chunks need to be updated incrementally without re-embedding everything, or when multiple processes need to share one index. None of those conditions apply to one person's work history. Reaching for a vector database here would add an infrastructure dependency to solve a problem this dataset does not have.

What this does not do. There is no re-ranking step, no hybrid keyword-plus-vector fusion, and no generation step: the tool returns ranked source chunks, not a synthesized answer. That is intentional. The calling agent (Claude, or whatever model is driving the conversation) is the one doing the reasoning over the retrieved chunks. This tool's only job is retrieval.

How this was built

Built with Claude Code as the primary development tool, using the Model Context Protocol SDK (@modelcontextprotocol/sdk) as the runtime this server implements against. The original three-tool version of this server (query_resume_history, get_company_deep_dive, send_scheduling_email) was extracted from a private portfolio project. The semantic search tool, its unit tests, and this README were built on top of that base in an agentic coding session: writing the chunking and retrieval logic, verifying the embedding model actually downloads and runs before committing to it, and capturing real tool output (shown above) rather than writing example output by hand.

Project layout

resume-mcp/
  index.js                    MCP server: tool definitions and request handlers
  resume.json                 The actual resume content (source of truth for both search tools)
  lib/
    keyword_search.js         Exact substring search, pure function, unit tested
    semantic_search.js        Chunking, local embeddings, cosine ranking, unit tested
  test/
    keyword_search.test.js
    semantic_search.test.js

License

MIT. See LICENSE.

Abstracted from a larger private system I built; more at mjportfolio.dev.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mattjjahn-spec/resume-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server