PortfolioMCP
README.md
# PortfolioMCP
A publicly deployable, MCP-powered portfolio: a recruiter or visitor can chat
with a candidate's portfolio and get grounded, factual answers about skills,
projects, experience and role fit — backed by a real Model Context Protocol
(MCP) server, not a hardcoded FAQ.
## Documentation
This README covers setup and a high-level overview. For depth:
- **[docs/MCP.md](docs/MCP.md)** — the MCP tools/resources/prompts this
project exposes, why each exists, the exact request flow from a chat
question to a grounded answer, and how to extend it.
- **[docs/AGENTIC_WORKFLOW.md](docs/AGENTIC_WORKFLOW.md)** — the research +
content agent workflow: architecture, agent tool-selection, prompts,
state/retention, and the human-in-the-loop approval flow (`/admin`).
- **[docs/WORKFLOW_VS_AGENTIC_AI.md](docs/WORKFLOW_VS_AGENTIC_AI.md)** —
conceptual explainer: what actually makes this a "workflow" versus a
pile of buttons, what "agentic" means here versus a scripted pipeline,
with exact code citations for each.
- **[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)** — how to deploy the frontend
and backend on free hosting tiers, and why they need different kinds of
hosts.
## Status
- [x] Repository scaffold (server / client / data / api / frontend)
- [x] Portfolio JSON (`data/portfolio.json`) filled with real candidate data
- [x] MCP server with tools, resources and prompts (`server/portfolio_server.py`)
- [x] MCP client that discovers and exercises the server (`client/portfolio_client.py`)
- [x] Gemini-backed chat loop over the MCP tools (`api/chat_server.py`), verified against a real key
- [x] Next.js portfolio + chat UI (`frontend/`)
- [x] Initial failure-case testing (unknown project, hallucination fix — see [docs/MCP.md](docs/MCP.md))
- [x] Research agent: RSS news tools + PortfolioMCP tools, 3 structured recommendations/day, daily cron — see [docs/AGENTIC_WORKFLOW.md](docs/AGENTIC_WORKFLOW.md)
- [x] Content agent + deterministic validation + `/admin` human-review UI (approve/reject/revise, manual publish + auto-delete)
- [ ] Public deployment (see [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) for the plan)
- [ ] Real LinkedIn/Medium auto-publish API integration (deferred — see [docs/AGENTIC_WORKFLOW.md](docs/AGENTIC_WORKFLOW.md#human-review--publishing))
## Architecture
```
Next.js Portfolio + Chat UI --> FastAPI Chat Service --> Python MCP Client --> Python MCP Server --> data/portfolio.json
| ^
`---------------------- Gemini (tool-calling) ------'
```
The MCP server (`server/portfolio_server.py`) exposes:
- **Tools** (actions/queries): `get_skills`, `get_projects`, `get_experience`, `search_profile`
- **Resources** (stable read-only context): `portfolio://profile`, `portfolio://skills`,
`portfolio://experience`, `portfolio://projects`
- **Prompts** (reusable interaction patterns): `recruiter_summary`, `technical_profile`,
`project_summary` — discoverable and demonstrated by the standalone client;
see [docs/MCP.md](docs/MCP.md) for their current status relative to the live chat
`api/chat_server.py` is a FastAPI service that keeps one persistent MCP client
session open and:
- serves `GET /api/portfolio` for the Next.js landing page by reading the
`portfolio://` resources (no duplicated data file for the frontend),
- serves `POST /api/chat`, which runs a bounded loop letting Gemini decide
which MCP tool(s) to call, executes them against the live MCP session, and
returns a grounded answer plus a log of which tools were used.
The LLM provider is isolated behind `server/model_adapter.py` (a small
`ModelAdapter` interface) so swapping Gemini for another provider later means
writing one new adapter class, not touching the chat loop.
## Repository layout
```
PortfolioMCP/
├── data/
│ ├── portfolio.json # single source of truth for candidate data
│ └── runs/ # ephemeral research/draft state, git-ignored (see docs/AGENTIC_WORKFLOW.md)
├── server/
│ ├── portfolio_data.py # loads the JSON data
│ ├── portfolio_server.py # FastMCP server: tools, resources, prompts
│ ├── mcp_bridge.py # shared MCP client bridge (chat API + agents)
│ ├── model_adapter.py # provider-agnostic LLM interface
│ ├── gemini_adapter.py # Gemini function-calling implementation
│ ├── schemas.py # Recommendation/Draft pydantic models
│ ├── state.py # data/runs/ JSON state store + retention
│ ├── tools/
│ │ └── news_tools.py # RSS-based TechCrunch/Verge search + get_article
│ ├── config/
│ │ ├── platforms.py # LinkedIn/blog content requirements
│ │ └── styles.py # the 4 content style arcs
│ └── agents/
│ ├── agent_runtime.py # shared bounded tool-calling loop
│ ├── tool_registry.py # merges local + MCP tools into one ToolSpec list
│ ├── research_agent.py # daily research agent (cron entrypoint)
│ ├── content_agent.py # drafts content for a chosen recommendation
│ └── validators.py # deterministic draft quality/grounding checks
├── client/
│ └── portfolio_client.py # standalone MCP client for local testing
├── api/
│ └── chat_server.py # FastAPI service: chat, contact, portfolio, and the research/content admin API
├── tests/
│ └── test_research_agent.py # offline failure-case tests (no live API calls)
└── frontend/
└── src/app/
├── page.tsx # public portfolio + chat page
└── admin/page.tsx # private research/content review UI
```
## Local setup
### Backend (MCP server + chat API)
1. Create and activate a virtual environment, then install dependencies:
```bash
python -m venv .venv
.venv\Scripts\activate # Windows
pip install -r requirements.txt
```
2. Copy `.env.example` to `.env` and add a free
[Google AI Studio](https://aistudio.google.com/apikey) API key:
```bash
copy .env.example .env
```
3. (Optional) Verify the MCP core on its own — spawns the server over stdio,
discovers its tools/resources/prompts, and exercises a few of each:
```bash
python client/portfolio_client.py
```
4. Start the chat API (keeps one MCP session alive for the process lifetime):
```bash
uvicorn api.chat_server:app --reload --port 8000
```
`GET http://localhost:8000/api/health` should list the four tools.
### Frontend (Next.js)
1. Copy `frontend/.env.local.example` to `frontend/.env.local` (defaults to
`http://localhost:8000` for the backend).
2. Install and run:
```bash
cd frontend
npm install
npm run dev
```
3. Open `http://localhost:3000` — the portfolio page and chat widget should
both load from the running backend. The private research/content review
UI is at `http://localhost:3000/admin` (not linked from the public nav).
### Research/content agents (optional, uses live Gemini quota)
```bash
# Offline tests — no API calls, safe to run anytime
python tests/test_research_agent.py
# One real research run (also runs daily via the scheduled-tasks cron,
# or on-demand from the "Run Research Now" button in /admin)
python server/agents/research_agent.py
```
See [docs/AGENTIC_WORKFLOW.md](docs/AGENTIC_WORKFLOW.md) for the full
architecture, prompts, and retention rules.
## What this project deliberately does not include
No NestJS backend, no database or vector store, no RAG/embeddings, no
multi-agent system, no automatic social posting, no complex auth, and no
sprawling tool surface — the goal is a small, explainable MCP demonstration,
not production infrastructure.
## Sample questions the chat should be able to answer
- "What frontend frameworks does this candidate know?"
- "Tell me about the Merchant Portal project."
- "Why would this candidate be a good fit for a React role?"
- "Has this candidate worked with Kubernetes in production?" (should say "not
found in the data" rather than guessing, since Kubernetes only appears as a
certification topic, not production experience)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues