portfolio-mcp
Exposes Karthikeyan K's portfolio (profile, skills, experience, etc.) as MCP tools, resources, and prompts, with optional Gemini-powered AI tools for job matching and portfolio analysis.
README
portfolio-mcp
A production-ready Model Context Protocol server exposing Karthikeyan K's portfolio — profile, skills, experience, education, projects, certifications, and resume — as MCP tools, resources, and prompts, so ChatGPT, Claude, and any other MCP-compatible client can query it directly instead of browsing the website.
It also ships Gemini-powered AI tools for job matching: compare a job description against the profile, estimate an ATS score, generate interview questions, get a learning plan, and draft tailored summaries.
Built with the official @modelcontextprotocol/sdk,
TypeScript, and Zod, deployed on Cloudflare Workers.
Architecture
┌─────────────────────────┐
│ MCP Client │
│ (ChatGPT / Claude / │
│ MCP Inspector) │
└───────────┬─────────────┘
│ JSON-RPC over
│ Streamable HTTP (POST /mcp)
▼
┌───────────────────────────────────────────────────────┐
│ Cloudflare Worker (src/index.ts) │
│ routes: POST /mcp · GET /health · /version · /metrics │
└───────────────────────┬─────────────────────────────────┘
│ per-request
▼
┌───────────────────────────────────────────────────────┐
│ StatelessHttpTransport (src/transport/) │
│ one JSON-RPC message in → one response out │
└───────────────────────┬─────────────────────────────────┘
▼
┌───────────────────────────────────────────────────────┐
│ McpServer (src/server.ts) │
│ ┌───────────┐ ┌────────────┐ ┌──────────────────┐ │
│ │ tools/ │ │ resources/ │ │ prompts/ │ │
│ │ 23 tools │ │ 8 resources│ │ 7 prompts │ │
│ └─────┬─────┘ └─────┬──────┘ └─────────┬──────────┘ │
│ └─────────────┴──────────────────┘ │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ services/ │ │
│ │ data.ts (JSON → Zod) · gemini.ts · github.ts · │ │
│ │ search.ts (keyword + optional embeddings) · │ │
│ │ cache.ts (in-memory TTL) · metrics.ts │ │
│ └───────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────┘
▲ ▲
│ │
Gemini API (generateContent, GitHub REST + GraphQL
embedContent) — optional, (repo metadata always;
GEMINI_API_KEY pinned repos / contributions
need GITHUB_TOKEN)
For local development, src/local.ts connects the same McpServer (from src/server.ts) over stdio
instead of HTTP — no Worker or network required.
Folder structure
portfolio-mcp/
src/
index.ts # Cloudflare Worker fetch entry (HTTP routes)
local.ts # stdio entry point for local MCP clients
server.ts # createMcpServer(env) — registers everything
transport/
statelessHttpTransport.ts # MCP Transport adapter for one-shot HTTP
tools/ # one module per domain, one per tool group
portfolio.ts skills.ts experience.ts education.ts
projects.ts certifications.ts resume.ts ai.ts
resources/index.ts # 8 resources reading from data/*.json
prompts/index.ts # 7 reusable prompt templates
services/
data.ts # loads + Zod-validates data/*.json, builds resume markdown
gemini.ts # Gemini REST client (generate, embed)
github.ts # GitHub REST + GraphQL client (repo/pinned/contributions)
search.ts # keyword scoring + optional embedding blend
cache.ts # in-memory TTL cache
metrics.ts # per-isolate tool call counters
utils/
logger.ts errors.ts sanitize.ts pagination.ts config.ts
types/
env.ts portfolio.ts # Zod schemas + inferred types
data/ # the actual portfolio content (edit these to update)
profile.json contact.json skills.json experience.json
education.json projects.json certifications.json resume.md
tests/
services/ utils/ integration/
.github/workflows/deploy.yml
wrangler.jsonc package.json tsconfig.json eslint.config.js .prettierrc
Installation
Requires Node.js 22+.
npm install
cp .env.example .env # for reference; wrangler dev uses .dev.vars instead (see below)
Development
Local stdio (Claude Desktop, MCP Inspector)
npm run dev:stdio
This runs src/local.ts directly with tsx, connecting the server over stdio. Point the
MCP Inspector at it:
npx @modelcontextprotocol/inspector npm run dev:stdio
To use it from Claude Desktop, add to claude_desktop_config.json:
{
"mcpServers": {
"portfolio": {
"command": "npx",
"args": ["tsx", "/absolute/path/to/portfolio-mcp/src/local.ts"],
"env": {
"GEMINI_API_KEY": "your-key-here",
"GITHUB_TOKEN": "optional-token-here"
}
}
}
}
Local Worker (Cloudflare Workers runtime)
# put local secrets in .dev.vars (git-ignored), one KEY=value per line:
echo "GEMINI_API_KEY=your-key-here" >> .dev.vars
npm run dev
Then test the HTTP endpoint directly:
curl http://localhost:8787/health
curl -X POST http://localhost:8787/mcp \
-H "content-type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
curl -X POST http://localhost:8787/mcp \
-H "content-type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_profile","arguments":{}}}'
Other commands
npm run typecheck # tsc --noEmit
npm run lint # eslint .
npm run lint:fix
npm run format # prettier --write .
npm test # vitest run
npm run test:watch
Deployment (Cloudflare Workers)
- Authenticate wrangler once locally:
npx wrangler login. - Set secrets (never in
wrangler.jsonc):npx wrangler secret put GEMINI_API_KEY npx wrangler secret put GITHUB_TOKEN # optional - Deploy:
npm run deploy:dry-run # sanity check the build first npm run deploy
GitHub Actions (CI/CD)
.github/workflows/deploy.yml runs lint/typecheck/test on every push and PR to main, and deploys to
Cloudflare Workers on push to main. Add these repository secrets first:
CLOUDFLARE_API_TOKEN— a token with Workers Scripts:Edit permissionCLOUDFLARE_ACCOUNT_ID— your Cloudflare account ID
Environment variables
| Variable | Required | Purpose |
|---|---|---|
GEMINI_API_KEY |
No | Enables all AI tools (compare_job, ats_match, interview_questions, recommend_learning, portfolio_summary, experience_summary, generate_resume_summary) and semantic search. Without it, AI tools return a clear MCP error and search falls back to keyword-only. |
GITHUB_TOKEN |
No | Enables pinned repos + contribution calendar (GraphQL), and raises the REST rate limit for live repo metadata on get_project. No scopes beyond public read access needed. |
SITE_URL |
No | Defaults to https://karthikeyan.vercel.app/. Used in /version and a couple of generated links. |
Tools
| Tool | Description |
|---|---|
get_profile |
Full profile: name, role, company, location, tagline, bio, focus areas |
get_about |
About-me narrative |
get_contact |
Email, phone, location |
get_social_links |
GitHub, LinkedIn, Instagram, X, Hugging Face, Linktree |
get_skills |
Skills grouped by category, optional category filter |
search_skills |
Keyword/semantic search across all skills |
get_experience |
Full work experience timeline |
experience_summary |
Natural-language summary of experience (AI or templated) |
get_education |
Education timeline |
get_projects |
Projects with filter (cluster/status/featured), sort, pagination |
get_project |
One project by id, optionally enriched with live GitHub metadata |
search_projects |
Keyword/semantic search across projects |
recommend_projects |
Best-matching projects for a query or skill list |
latest_projects |
Most recent projects |
get_certifications |
45 certifications/courses/badges/publications/achievements, filter + pagination |
search_certifications |
Keyword/semantic search across certifications |
get_resume |
Resume as markdown + PDF link |
generate_resume_summary |
AI-tailored resume summary paragraph |
compare_job |
Fit score, strengths, gaps, best-matching projects vs. a job description |
ats_match |
ATS keyword-match score, matched/missing keywords, suggestions |
interview_questions |
Likely interview questions grounded in real projects/experience |
recommend_learning |
Skills to learn + certifications to pursue |
portfolio_summary |
Natural-language portfolio overview tailored to an audience |
All AI tools (compare_job, ats_match, interview_questions, recommend_learning, portfolio_summary,
plus the AI paths of experience_summary/generate_resume_summary) require GEMINI_API_KEY. Every tool
validates input with Zod and returns a proper MCP error result (never an uncaught exception) on bad input,
missing config, or unexpected failures.
Resources
portfolio://resume.md · portfolio://profile.json · portfolio://skills.json ·
portfolio://experience.json · portfolio://education.json · portfolio://projects.json ·
portfolio://certifications.json · portfolio://contact.json
Prompts
professional_bio · linkedin_summary · resume_summary · interview_introduction ·
project_explanation · cover_letter · portfolio_overview
Connecting from ChatGPT
- Deploy the Worker (see above) so you have a public URL, e.g.
https://portfolio-mcp.<you>.workers.dev. - In ChatGPT, open Settings → Connectors → Advanced → Developer mode (requires a ChatGPT plan that
supports custom connectors) and add a new connector pointing at:
https://portfolio-mcp.<you>.workers.dev/mcp - Enable the connector in a chat and ask things like "What are Karthikeyan's featured projects?" or "Compare this job description against Karthikeyan's profile: ...".
Example tool responses
get_profile:
{
"name": "Karthikeyan K",
"headline": "AI Engineer",
"currentRole": "Associate Data Analyst",
"currentCompany": "Zinnov",
"tagline": "Building intelligent systems that act — not just answer."
}
ats_match (with GEMINI_API_KEY set):
{
"atsScore": 78,
"matchedKeywords": ["LangChain", "RAG", "Python", "Vector Databases"],
"missingKeywords": ["Kubernetes", "Terraform"],
"suggestions": ["Add measurable infra/deployment experience if applicable."]
}
Adding a new tool
- Add (or extend) a module under
src/tools/, exporting aregister*Tools(server, env)function. - Call
server.registerTool(name, { title, description, inputSchema }, safeTool(name, handler))—inputSchemais a Zod raw shape (object of Zod validators, notz.object(...)), andsafeTool(fromsrc/utils/errors.ts) converts thrown errors/Zod failures into proper MCP error results automatically. - Register the module in
src/server.ts'screateMcpServer. - Add a test under
tests/(unit test the underlying logic, or extendtests/integration/server.test.tsfor a full round-trip check).
Adding a new resource
Add an entry to the RESOURCES array in src/resources/index.ts with a unique name/uri, a
getContent() function, and register it — registerResources handles the rest.
Troubleshooting
- AI tools return "requires GEMINI_API_KEY": expected without a key configured — set it via
.dev.vars(local) orwrangler secret put GEMINI_API_KEY(deployed). get_project'sliveMetadatais alwaysnull: GitHub REST is unauthenticated by default and rate-limited; setGITHUB_TOKENto raise the limit. Pinned repos / contribution summary specifically requireGITHUB_TOKEN(GraphQL) — expected to benullwithout it.- CORS errors from a browser-based MCP client:
src/index.tsalready sends permissive CORS headers on every response includingOPTIONS; check the client is hitting/mcpwithPOST, notGET. /metricscounters reset unexpectedly: they're per-isolate, in-memory only — a Cloudflare Workers cold start resets them. This is a documented limitation, not a bug.wrangler devcan't find secrets: local secrets go in a git-ignored.dev.varsfile (KEY=valueper line), not.env—.envis only for the stdio dev entry (npm run dev:stdio).
License
MIT
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.