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
Visit Server

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

Recommended Servers

playwright-mcp

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.

Official
Featured
TypeScript
Magic Component Platform (MCP)

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.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

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.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

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.

Official
Featured
TypeScript
Kagi MCP Server

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.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

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.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured