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
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。