sai-roadmap-mcp
README.md
<div align="center">
<!-- Customized High-Tech AI Logo -->
<img src="public/logo.png" alt="SAI Roadmap MCP Logo" width="220" height="220" style="border-radius: 20%; filter: drop-shadow(0 0 25px rgba(0, 255, 255, 0.6));" />
<br />
# š SAI Roadmap MCP
**An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server exposing my certifications, projects, and AI engineering roadmap as callable tools ā including a semantic search engine built entirely from scratch.**
</div>
---
## š» Core Technologies & Animated Architecture
<div align="center">
<img src="https://img.shields.io/badge/Node.js-43853D?style=for-the-badge&logo=node.js&logoColor=white" />
<img src="https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white" />
<img src="https://img.shields.io/badge/NumPy-013243?style=for-the-badge&logo=numpy&logoColor=white" />
<img src="https://img.shields.io/badge/Model_Context_Protocol-6B21A8?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik00IDEwaDEyIi8+PHBhdGggZD0iTTQgMTRoOSIvPjxwb2x5Z29uIHBvaW50cz0iMyAzIDEwIDEwIDMgMTcgMyAzIi8+PC9zdmc+&logoColor=white" />
<img src="https://img.shields.io/badge/Claude_Desktop-D97757?style=for-the-badge&logo=anthropic&logoColor=white" />
</div>
<br/>
### š 1. High-Level Ecosystem (MCP Server Topology)
```mermaid
graph TB
%% Core Styling with High-Contrast Cyber Theme
classDef client fill:#0f172a,stroke:#38bdf8,stroke-width:3px,color:#fff,stroke-dasharray: 5 5
classDef server fill:#1e1b4b,stroke:#a855f7,stroke-width:3px,color:#fff
classDef python fill:#064e3b,stroke:#34d399,stroke-width:3px,color:#fff
classDef data fill:#451a03,stroke:#fbbf24,stroke-width:3px,color:#fff
%% Clients
subgraph Clients [MCP Clients]
ClaudeDesk[Claude Desktop App]:::client
ClaudeCode[Claude Code CLI]:::client
end
%% Node Server
subgraph NodeServer [Node.js MCP Server]
StdioTransport((stdio / JSON-RPC 2.0)):::server
ToolRouter{Tool Router}:::server
ProfileTool[get_profile]:::server
CertTool[get_certifications]:::server
ProjTool[get_projects]:::server
RoadmapTool[get_roadmap]:::server
SearchTool[semantic_search]:::server
end
%% Python Backend
subgraph PythonEngine [Python ML Subprocess]
QueryParser[LSA Query Parser]:::python
LSAWeights[(lsa_model.npz)]:::data
MergeSet[(Bigram Merge Set)]:::data
PortfolioData[(data.json)]:::data
end
%% Flow Mapping
Clients == "stdio (JSON-RPC)" ==> StdioTransport
StdioTransport --> ToolRouter
ToolRouter --> ProfileTool
ToolRouter --> CertTool
ToolRouter --> ProjTool
ToolRouter --> RoadmapTool
ToolRouter ==> SearchTool
SearchTool -.->|Spawns Child Process| PythonEngine
QueryParser <--> LSAWeights
QueryParser <--> MergeSet
QueryParser <--> PortfolioData
```
### ā” 2. Animated Interaction Sequence (Semantic Search ML Pipeline)
```mermaid
sequenceDiagram
autonumber
participant U as User Query (MCP Client)
participant N as Node.js Server
participant P as Python LSA Engine (query.py)
participant C as Corpus Builder
participant M as Truncated SVD Math
Note over C, M: Build Time (npm run train)
C->>C: Extract 51 Sentences from Portfolio
C->>C: Calculate PMI & Merge Bigrams (min_count=3)
C->>M: Apply Explicit TF-IDF Weighting
M->>M: Compute numpy.linalg.svd (Truncated)
M-->>C: Save lsa_model.npz & Bigrams
Note over U, P: Runtime Execution
U->>N: Call `semantic_search`
activate N
N->>P: Spawn process with query args
activate P
P->>P: Apply identical Bigram merges to query
P->>P: Project Query into Latent Space
P->>P: Compute Cosine Similarity vs Corpus
P-->>N: Return JSON matching documents
deactivate P
N-->>U: Return Formatted Result to Claude
deactivate N
```
---
Built on the official [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk) (v1.29.0), stdio transport, with a Python/NumPy subprocess powering the ML tool.
## Tools
| Tool | Description | Optional Input |
|---|---|---|
| `get_profile` | Basic profile info | ā |
| `get_certifications` | Certifications, filterable by skill | `skill: string` |
| `get_projects` | Portfolio projects, filterable by tech stack | `tech: string` |
| `get_roadmap` | 2026 learning roadmap, filterable by quarter | `quarter: "Q1"ā"Q4"` |
| `semantic_search` | Real semantic retrieval, not keyword matching | `query: string`, `top_n: number` |
## The semantic search engine
**Pipeline** (full implementation in [`src/ml/lsa.py`](src/ml/lsa.py)):
1. **Corpus** ([`src/ml/corpus.py`](src/ml/corpus.py)) ā 51 natural-language sentences generated from certifications, detailed project descriptions, and roadmap entries.
2. **Bigram phrase detection** ā pointwise mutual information (PMI), the same idea behind word2vec's original `word2phrase` tool. Merges tightly-bound word pairs (`machine_learning`, `medireach_ai`, `artificial_intelligence`) into single tokens before training.
3. **TF-IDF weighting** ā term frequency Ć smoothed inverse document frequency, computed explicitly.
4. **Truncated SVD** ā `numpy.linalg.svd`, manually truncated to the top-*k* singular vectors (the actual Latent Semantic Analysis step ā not `sklearn.fit_transform()`).
5. **Cosine similarity** in the resulting latent space ranks documents against a query.
### Two things I got wrong on the first pass, and fixed
**Bigram threshold, take one:** my first PMI threshold (`min_count=2, threshold=3.0`) merged **147 bigrams** ā almost all of them grammatical glue like `of_the`, `by_google`, `is_a`, not real phrases. The bug: with only 51 sentences, raw PMI is noisy, and stopwords weren't excluded from forming pairs. Fixed by excluding stopwords from bigram formation and raising the bar to `min_count=3, threshold=15.0` ā now produces 19 bigrams, and every single one is a genuine phrase (`machine_learning`, `google_cloud`, `vibe_coding`, `iit_bombay`).
**Query/training vocabulary mismatch:** after adding bigram merging, queries were still being tokenized with plain word-splitting ā so a query like "machine learning" stayed as two tokens while the trained vocabulary only had the merged `machine_learning`. Silent mismatch, no error thrown, just quietly worse retrieval. Fixed by persisting the learned merge set alongside the model and applying it identically at query time (`apply_bigram_merges()` in `corpus.py`).
### Quantitative evaluation ā precision@k
Most small ML side-projects show a few queries that "look like they work." `src/ml/evaluate.py` instead defines 10 hand-labeled queries with explicit relevance judgments and reports precision@k:
```
Query P@1 P@3 P@5
----------------------------------------------------------------------
python certifications 1.00 1.00 0.80
cloud computing certifications 1.00 0.67 0.40
multi agent healthcare assistant 1.00 1.00 1.00
edtech platform for students 1.00 1.00 1.00
frontend animation and design 0.00 0.00 0.00
hackathon and competition experience 0.00 0.00 0.00
SQL and database skills 0.00 0.00 0.00
generative AI and large language models 1.00 0.67 0.40
deep learning quarter in the roadmap 0.00 0.67 0.40
agentic IDE development tools 1.00 1.00 0.60
----------------------------------------------------------------------
MEAN 0.60 0.60 0.46
```
**Three queries scored zero ā here's exactly why**, diagnosed rather than hand-waved:
- `"frontend animation and design"` ā query contains "animation" (singular); the corpus only ever says "animations" (plural). Out-of-vocabulary, no signal. This is the classic bag-of-words weakness ā no stemming, no lemmatization.
- `"hackathon and competition experience"` ā same issue: "competition" and "experience" never appear in the corpus at all (it says "Hackathon," not "competition").
- `"SQL and database skills"` ā no OOV words here, but a genuine ranking failure: `idf("skills") == idf("database")` (both 3.833) because both happen to appear in exactly one document ā IDF can't tell "rare and topically specific" apart from "rare by coincidence" at this corpus size. The word "skills" then drags the ranking toward the wrong (but skills-heavy) document.
These are real, well-understood limitations of small-corpus bag-of-words retrieval, not implementation bugs ā and documenting them precisely is more useful than a misleadingly clean demo.
### Revisiting word2vec with the bigger corpus
I expanded the corpus 3Ć (17 ā 51 sentences) partly to test whether word-level skip-gram (still in [`src/ml/word2vec.py`](src/ml/word2vec.py), Mikolov et al. 2013, negative sampling, full from-scratch NumPy training) would become viable. Honest result: **no.**
```
'cloud' -> [('computing', 0.746), ('oac', 0.728), ('analytics', 0.698), ('infrastructure', 0.645)] ā coherent
'python' -> [('generative_ai', 0.684), ('workflow', 0.599), ('sql', 0.595)] ā noise
'agents' -> [('good', 0.687), ('intensive', 0.681), ('5', 0.636)] ā noise
```
"cloud" produces a genuinely sensible neighborhood; "python" and "agents" still don't. 51 sentences is closer to viable than 17 was, but word2vec realistically needs thousands of sentences minimum. LSA remains the correct choice for this corpus size ā confirmed by actually re-running the experiment, not just assumed.
## Setup
```bash
git clone https://github.com/saichintamani/sai-roadmap-mcp.git
cd sai-roadmap-mcp
npm install
pip3 install -r requirements.txt --break-system-packages
npm run train # builds corpus, detects bigrams, trains LSA model
npm run evaluate # runs the precision@k evaluation above
```
## Running standalone
```bash
npm start
```
Communicates over stdio via JSON-RPC 2.0. Ready message goes to stderr; stdout is reserved for protocol messages.
## Connecting to Claude Desktop
```json
{
"mcpServers": {
"sai-roadmap": {
"command": "node",
"args": ["/absolute/path/to/sai-roadmap-mcp/src/index.js"]
}
}
}
```
## Connecting to Claude Code
```bash
claude mcp add sai-roadmap -- node /absolute/path/to/sai-roadmap-mcp/src/index.js
```
## Repo structure
```
sai-roadmap-mcp/
āāā src/
ā āāā index.js # MCP server: 5 tools, stdio transport
ā āāā data.json # Structured portfolio data
ā āāā ml/
ā āāā corpus.py # Sentence generation + PMI bigram detection
ā āāā word2vec.py # Skip-gram + negative sampling (kept, documented as non-viable here)
ā āāā lsa.py # TF-IDF + truncated SVD -- the model actually used
ā āāā train.py # Trains and saves the model + merge set
ā āāā query.py # CLI query interface, called by index.js
ā āāā evaluate.py # Precision@k evaluation against hand-labeled queries
ā āāā lsa_model.npz # Pre-trained weights
āāā requirements.txt
āāā package.json
āāā README.md
```
## Why this exists
Most student AI portfolios show *using* an LLM API. This shows three different things: understanding of the **MCP protocol layer** production AI tools run on; a working **classical NLP/ML retrieval system** built from first principles; and ā maybe more importantly ā the engineering discipline to **measure it, find the failure modes, and document them precisely** instead of cherry-picking examples that look good.
## License
MIT
TDQS
A4.1/5.0
Scored across 4 tools
Disambiguation5/5
Each tool has a clear, distinct purpose: certifications, profile, projects, and roadmap. No overlap or confusion.
Naming Consistency5/5
All tool names follow a consistent 'get_' + noun pattern, making them predictable and easy to understand.
Tool Count5/5
With 4 tools, the server is well-scoped for a personal portfolio/roadmap. Each tool provides meaningful information without being excessive or insufficient.
Completeness4/5
The tools cover the main areas (certifications, profile, projects, roadmap). Minor gaps like skills or experience are acceptable for a focused server.
Maintenance
ActivityStale
ResponsivenessNo issues