portfolio-mcp
by ks342
README.md
# portfolio-mcp
A template **[MCP](https://modelcontextprotocol.io) server** that puts your
portfolio — projects, experience, skills, and a search tool — inside any AI
assistant. A recruiter using Claude, Cursor, or ChatGPT adds one line of config
and can then ask questions about your work and get answers from *your* content,
with links back to the real repos and live sites.
No AI runs in this server. It just exposes structured facts; the assistant does
the reasoning.
<p>
<a href="https://github.com/ks342/desktop-portfolio-mcp/generate">
<img alt="Use this template" src="https://img.shields.io/badge/Use%20this%20template-2ea44f?style=for-the-badge&logo=github">
</a>
<a href="https://render.com/deploy?repo=https://github.com/ks342/desktop-portfolio-mcp">
<img alt="Deploy to Render" src="https://img.shields.io/badge/Deploy%20to%20Render-46E3B7?style=for-the-badge&logo=render&logoColor=white">
</a>
</p>
---
## Quick start
```bash
npm install
npm run smoke # 17 in-memory checks against a real MCP client
npm run dev # stdio server (what an AI app launches)
npm run inspect # MCP Inspector — connect and poke it by hand
npm run http # HTTP version at http://localhost:3000/mcp
```
The repo ships with placeholder content so everything runs green immediately.
Replace it with your own (see [Making it yours](#making-it-yours)).
---
## Connect it to an AI client
**Local (stdio)** — the client launches the server as a subprocess:
```json
{
"mcpServers": {
"portfolio": { "command": "npx", "args": ["-y", "tsx", "src/server.ts"] }
}
}
```
**Deployed (HTTP)** — nothing to install, just a URL:
```json
{
"mcpServers": {
"portfolio": { "url": "https://your-server.onrender.com/mcp" }
}
}
```
### Claude Code
```bash
claude mcp add portfolio -- npx -y tsx src/server.ts
# or, once deployed:
claude mcp add portfolio --transport http https://your-server.onrender.com/mcp
```
Then `/mcp` in a chat to confirm, and ask it something about your work.
---
## What it exposes
### Tools — the assistant calls these
| Tool | Args | Returns |
| --- | --- | --- |
| `get_profile` | — | headline facts, links, availability, timezone |
| `get_bio` | — | prose bio |
| `list_projects` | — | every project: slug, stack, impact, links |
| `get_project` | `slug` | full case study (overview / challenge / solution / results) |
| `get_experience` | — | work history, education, achievements |
| `get_skills` | — | skills grouped by area |
| `search_portfolio` | `query`, `limit?` | best-matching snippets across everything |
| `contact` | `note?` | how to reach you |
Every data tool returns typed JSON (`structuredContent`) alongside a
human-readable text fallback.
### Resources — documents the client can attach
`portfolio://bio` · `portfolio://experience` · `portfolio://skills` ·
`portfolio://projects` (index) · `portfolio://projects/{slug}` (one case study)
### Prompts — the user invokes
`evaluate_candidate(role?)` — loads a brief that asks the assistant to assess you
against a role using the tools above, gaps included.
---
## Making it yours
Everything lives in [`content/`](./content) — the single source of truth. No
code changes needed.
```
content/
meta.json name, headline, links, availability, timezone
bio.md the prose bio
experience.json work / education / achievements
skills.json skills by group
projects/*.md one file per project (frontmatter + markdown body)
```
1. Edit each file. Keep the frontmatter keys in the project files; the headings
(`## Overview`, `## Challenge`, …) become individually searchable chunks.
2. Update `name`, `description`, and `keywords` in `package.json`.
3. `npm run smoke` — the checks read your content, so they stay green.
4. Add a project any time: drop a new `content/projects/<slug>.md`, rebuild.
Every tool, resource, and the search index pick it up automatically.
---
## Deploy the HTTP server
`src/http.ts` runs stateless, so it works on any Node host and on serverless.
```bash
npm run build
node dist/http.js # listens on $PORT (default 3000), endpoint /mcp
```
- **Render** — click the button above, or: New → Blueprint → pick your fork
(`render.yaml` is included). Set `SOURCE_URL` to your repo for the landing
page. Free tier sleeps after ~15 min idle; add an uptime pinger if that
matters ([`.github/workflows/keep-warm.yml`](./.github/workflows/keep-warm.yml)
is a starting point).
- **Railway / Fly / a VPS** — start command `node dist/http.js`.
- **Vercel** — wrap the Express app as a serverless function.
The HTTP server has open CORS (it's public, read-only data), a 60 req/min rate
limit, a `/health` endpoint, and a landing page.
---
## Publish to npm (optional)
```bash
npm login # free account, 2FA required
npm publish --access public # `prepublishOnly` builds first
```
`files` in `package.json` ships only `dist/`, `content/`, `README`, and
`LICENSE`. Then clients can use `npx <your-package-name>` directly.
---
## How it's built
- `@modelcontextprotocol/sdk` v1, for the widest client compatibility.
- `src/mcp.ts` defines the server once; `src/server.ts` (stdio) and
`src/http.ts` (HTTP) both reuse it.
- `src/lib/search.ts` is plain lexical search — no embeddings, no API keys, no
cost. It expands shorthand (`k8s`, `ts`, `postgres`) and tolerates small
typos. Swap the internals for embeddings later; the `search(query)` signature
stays.
[`NOTES.md`](./NOTES.md) is a from-scratch explanation of MCP, JSON-RPC, the
handshake, transports, tools vs resources vs prompts, and a file-by-file tour.
## License
MIT — see [`LICENSE`](./LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues