portfolio-mcp

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.

Category
访问服务器

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)

  1. Authenticate wrangler once locally: npx wrangler login.
  2. Set secrets (never in wrangler.jsonc):
    npx wrangler secret put GEMINI_API_KEY
    npx wrangler secret put GITHUB_TOKEN   # optional
    
  3. 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 permission
  • CLOUDFLARE_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

  1. Deploy the Worker (see above) so you have a public URL, e.g. https://portfolio-mcp.<you>.workers.dev.
  2. 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
    
  3. 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

  1. Add (or extend) a module under src/tools/, exporting a register*Tools(server, env) function.
  2. Call server.registerTool(name, { title, description, inputSchema }, safeTool(name, handler))inputSchema is a Zod raw shape (object of Zod validators, not z.object(...)), and safeTool (from src/utils/errors.ts) converts thrown errors/Zod failures into proper MCP error results automatically.
  3. Register the module in src/server.ts's createMcpServer.
  4. Add a test under tests/ (unit test the underlying logic, or extend tests/integration/server.test.ts for 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) or wrangler secret put GEMINI_API_KEY (deployed).
  • get_project's liveMetadata is always null: GitHub REST is unauthenticated by default and rate-limited; set GITHUB_TOKEN to raise the limit. Pinned repos / contribution summary specifically require GITHUB_TOKEN (GraphQL) — expected to be null without it.
  • CORS errors from a browser-based MCP client: src/index.ts already sends permissive CORS headers on every response including OPTIONS; check the client is hitting /mcp with POST, not GET.
  • /metrics counters 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 dev can't find secrets: local secrets go in a git-ignored .dev.vars file (KEY=value per line), not .env.env is only for the stdio dev entry (npm run dev:stdio).

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选