portfolio-mcp-server 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- portfolio_mcp_server-1.0.0/.github/workflows/ci.yml +42 -0
- portfolio_mcp_server-1.0.0/.gitignore +10 -0
- portfolio_mcp_server-1.0.0/LICENSE +21 -0
- portfolio_mcp_server-1.0.0/PKG-INFO +187 -0
- portfolio_mcp_server-1.0.0/README.md +166 -0
- portfolio_mcp_server-1.0.0/assets/MCP.gif +0 -0
- portfolio_mcp_server-1.0.0/assets/MCP_Chat_Demo.png +0 -0
- portfolio_mcp_server-1.0.0/assets/MCP_Inspector_Demo.png +0 -0
- portfolio_mcp_server-1.0.0/claude_desktop_config_snippet.json +8 -0
- portfolio_mcp_server-1.0.0/portfolio_mcp_server/__init__.py +3 -0
- portfolio_mcp_server-1.0.0/portfolio_mcp_server/server.py +250 -0
- portfolio_mcp_server-1.0.0/pyproject.toml +37 -0
- portfolio_mcp_server-1.0.0/requirements-dev.txt +3 -0
- portfolio_mcp_server-1.0.0/requirements.txt +1 -0
- portfolio_mcp_server-1.0.0/server.py +18 -0
- portfolio_mcp_server-1.0.0/tests/__init__.py +0 -0
- portfolio_mcp_server-1.0.0/tests/smoke_test.py +69 -0
- portfolio_mcp_server-1.0.0/tests/test_tools.py +73 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint-and-test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Check out repository
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python-version }}
|
|
24
|
+
cache: "pip"
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --upgrade pip
|
|
29
|
+
pip install -r requirements.txt
|
|
30
|
+
pip install -r requirements-dev.txt
|
|
31
|
+
|
|
32
|
+
- name: Lint (ruff)
|
|
33
|
+
run: ruff check .
|
|
34
|
+
|
|
35
|
+
- name: Type check (mypy)
|
|
36
|
+
run: mypy server.py
|
|
37
|
+
|
|
38
|
+
- name: Run tests
|
|
39
|
+
run: pytest -v
|
|
40
|
+
|
|
41
|
+
- name: Smoke test MCP tools over stdio
|
|
42
|
+
run: python -m tests.smoke_test
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ayush Tomar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: portfolio-mcp-server
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MCP server exposing Ayush Tomar's AI project portfolio as queryable tools for Claude Desktop and other MCP clients
|
|
5
|
+
Project-URL: Homepage, https://github.com/ayush-s-tomar/portfolio-mcp-server
|
|
6
|
+
Project-URL: Repository, https://github.com/ayush-s-tomar/portfolio-mcp-server
|
|
7
|
+
Project-URL: Issues, https://github.com/ayush-s-tomar/portfolio-mcp-server/issues
|
|
8
|
+
Author: Ayush Tomar
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai-agents,claude,mcp,model-context-protocol,portfolio
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: mcp[cli]>=1.0.0
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
<div align="center">
|
|
23
|
+
|
|
24
|
+
# Portfolio MCP Server
|
|
25
|
+
|
|
26
|
+
**An MCP server that turns my AI project portfolio into something you can *query*, not just read.**
|
|
27
|
+
|
|
28
|
+
Point any MCP client (Claude Desktop, Cursor, custom agents) at it and ask
|
|
29
|
+
*"What has Ayush built with LangGraph?"* or *"What's his flagship project?"*
|
|
30
|
+
— it answers from live structured data, not a static PDF.
|
|
31
|
+
|
|
32
|
+
[](LICENSE)
|
|
33
|
+
[](https://www.python.org/downloads/)
|
|
34
|
+
[](https://modelcontextprotocol.io)
|
|
35
|
+
[](https://github.com/ayush-s-tomar/portfolio-mcp-server/actions/workflows/ci.yml)
|
|
36
|
+
[](https://github.com/astral-sh/ruff)
|
|
37
|
+
[](CONTRIBUTING.md)
|
|
38
|
+
|
|
39
|
+
<img src="assets/MCP.gif" alt="Portfolio MCP Server demo" width="720">
|
|
40
|
+
|
|
41
|
+
[Watch the full walkthrough ▶](https://github.com/user-attachments/assets/4c1b844a-087f-48a6-b156-bdef27282acc)
|
|
42
|
+
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Table of contents
|
|
48
|
+
|
|
49
|
+
- [Why this exists](#why-this-exists)
|
|
50
|
+
- [Demo](#demo)
|
|
51
|
+
- [Tools exposed](#tools-exposed)
|
|
52
|
+
- [Quickstart](#quickstart)
|
|
53
|
+
- [Connect to Claude Desktop](#connect-to-claude-desktop)
|
|
54
|
+
- [Stack](#stack)
|
|
55
|
+
- [Testing & CI](#testing--ci)
|
|
56
|
+
- [Project structure](#project-structure)
|
|
57
|
+
- [Roadmap](#roadmap)
|
|
58
|
+
- [License](#license)
|
|
59
|
+
- [Author](#author)
|
|
60
|
+
|
|
61
|
+
## Why this exists
|
|
62
|
+
|
|
63
|
+
Most AI-developer portfolios are a list of links. This is a working MCP
|
|
64
|
+
server — the same protocol agentic products use to connect to tools — built
|
|
65
|
+
around my own portfolio. It's both a real implementation of the spec and an
|
|
66
|
+
answer to *"show me you've actually built with MCP,"* not just talked about it.
|
|
67
|
+
|
|
68
|
+
## Demo
|
|
69
|
+
|
|
70
|
+
| MCP Inspector — tool discovery | Live chat demo |
|
|
71
|
+
|---|---|
|
|
72
|
+
|  |  |
|
|
73
|
+
|
|
74
|
+
Full video walkthrough (setup → tool calls → live answers): **[watch here ▶](https://github.com/user-attachments/assets/4c1b844a-087f-48a6-b156-bdef27282acc)**
|
|
75
|
+
|
|
76
|
+
## Tools exposed
|
|
77
|
+
|
|
78
|
+
| Tool | Description |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `list_projects` | Short summary of all 9 projects |
|
|
81
|
+
| `get_project_details(project_name)` | Full details for one project |
|
|
82
|
+
| `search_projects_by_stack(technology)` | Find projects using a given technology |
|
|
83
|
+
| `get_flagship_project` | The single best project to look at first |
|
|
84
|
+
| `get_resume_summary` | Background, target role, and core stack |
|
|
85
|
+
|
|
86
|
+
## Quickstart
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
git clone https://github.com/ayush-s-tomar/portfolio-mcp-server.git
|
|
90
|
+
cd portfolio-mcp-server
|
|
91
|
+
|
|
92
|
+
python -m venv venv
|
|
93
|
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
94
|
+
|
|
95
|
+
pip install -r requirements.txt
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Test it interactively with the MCP Inspector before wiring it into a client:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
mcp dev server.py
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This opens a browser UI where you can call each tool manually and inspect
|
|
105
|
+
raw request/response payloads.
|
|
106
|
+
|
|
107
|
+
## Connect to Claude Desktop
|
|
108
|
+
|
|
109
|
+
Open your Claude Desktop config file:
|
|
110
|
+
|
|
111
|
+
| OS | Path |
|
|
112
|
+
|---|---|
|
|
113
|
+
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
|
|
114
|
+
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
|
|
115
|
+
|
|
116
|
+
If the file already has an `mcpServers` key with other servers in it, add
|
|
117
|
+
the `"portfolio"` entry inside the existing object rather than overwriting
|
|
118
|
+
the file. Use the **absolute path** to `server.py` on your machine:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"mcpServers": {
|
|
123
|
+
"portfolio": {
|
|
124
|
+
"command": "python",
|
|
125
|
+
"args": ["/absolute/path/to/portfolio-mcp-server/server.py"]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Restart Claude Desktop, then ask it something like:
|
|
132
|
+
|
|
133
|
+
> "What projects has Ayush built with FastAPI?"
|
|
134
|
+
|
|
135
|
+
Claude will call `search_projects_by_stack` and answer from the live data.
|
|
136
|
+
|
|
137
|
+
## Stack
|
|
138
|
+
|
|
139
|
+
- **Python 3.10+**
|
|
140
|
+
- **[MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)** (`FastMCP`)
|
|
141
|
+
- **stdio transport**
|
|
142
|
+
|
|
143
|
+
## Testing & CI
|
|
144
|
+
|
|
145
|
+
Every push and pull request runs through GitHub Actions:
|
|
146
|
+
|
|
147
|
+
- **Lint** — `ruff check .`
|
|
148
|
+
- **Type check** — `mypy server.py`
|
|
149
|
+
- **Smoke test** — spins up the server and calls each of the 5 tools over
|
|
150
|
+
stdio to confirm they return valid, schema-matching JSON
|
|
151
|
+
|
|
152
|
+
See [`.github/workflows/ci.yml`](.github/workflows/ci.yml). Run the same
|
|
153
|
+
checks locally before opening a PR:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
pip install -r requirements-dev.txt
|
|
157
|
+
ruff check .
|
|
158
|
+
mypy server.py
|
|
159
|
+
pytest
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Project structure
|
|
163
|
+
portfolio-mcp-server/
|
|
164
|
+
├── server.py # FastMCP server + tool definitions
|
|
165
|
+
├── data/
|
|
166
|
+
│ └── projects.json # Project data the tools read from
|
|
167
|
+
├── tests/
|
|
168
|
+
│ └── test_tools.py # Smoke tests for each tool
|
|
169
|
+
├── requirements.txt
|
|
170
|
+
├── requirements-dev.txt
|
|
171
|
+
└── .github/workflows/ci.yml
|
|
172
|
+
## Roadmap
|
|
173
|
+
|
|
174
|
+
- [ ] `search_projects_by_stack` — support matching on multiple technologies at once
|
|
175
|
+
- [ ] Add an HTTP/SSE transport option alongside stdio for remote clients
|
|
176
|
+
- [ ] Publish to the MCP server registry
|
|
177
|
+
- [ ] Cache resume/project data with a lightweight refresh endpoint instead of static JSON
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
Released under the [MIT License](LICENSE).
|
|
182
|
+
|
|
183
|
+
## Author
|
|
184
|
+
|
|
185
|
+
**Ayush Tomar** — [GitHub](https://github.com/ayush-s-tomar)
|
|
186
|
+
|
|
187
|
+
If this was useful as a reference for building your own MCP server, a ⭐ on the repo is appreciated.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# Portfolio MCP Server
|
|
4
|
+
|
|
5
|
+
**An MCP server that turns my AI project portfolio into something you can *query*, not just read.**
|
|
6
|
+
|
|
7
|
+
Point any MCP client (Claude Desktop, Cursor, custom agents) at it and ask
|
|
8
|
+
*"What has Ayush built with LangGraph?"* or *"What's his flagship project?"*
|
|
9
|
+
— it answers from live structured data, not a static PDF.
|
|
10
|
+
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
[](https://www.python.org/downloads/)
|
|
13
|
+
[](https://modelcontextprotocol.io)
|
|
14
|
+
[](https://github.com/ayush-s-tomar/portfolio-mcp-server/actions/workflows/ci.yml)
|
|
15
|
+
[](https://github.com/astral-sh/ruff)
|
|
16
|
+
[](CONTRIBUTING.md)
|
|
17
|
+
|
|
18
|
+
<img src="assets/MCP.gif" alt="Portfolio MCP Server demo" width="720">
|
|
19
|
+
|
|
20
|
+
[Watch the full walkthrough ▶](https://github.com/user-attachments/assets/4c1b844a-087f-48a6-b156-bdef27282acc)
|
|
21
|
+
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Table of contents
|
|
27
|
+
|
|
28
|
+
- [Why this exists](#why-this-exists)
|
|
29
|
+
- [Demo](#demo)
|
|
30
|
+
- [Tools exposed](#tools-exposed)
|
|
31
|
+
- [Quickstart](#quickstart)
|
|
32
|
+
- [Connect to Claude Desktop](#connect-to-claude-desktop)
|
|
33
|
+
- [Stack](#stack)
|
|
34
|
+
- [Testing & CI](#testing--ci)
|
|
35
|
+
- [Project structure](#project-structure)
|
|
36
|
+
- [Roadmap](#roadmap)
|
|
37
|
+
- [License](#license)
|
|
38
|
+
- [Author](#author)
|
|
39
|
+
|
|
40
|
+
## Why this exists
|
|
41
|
+
|
|
42
|
+
Most AI-developer portfolios are a list of links. This is a working MCP
|
|
43
|
+
server — the same protocol agentic products use to connect to tools — built
|
|
44
|
+
around my own portfolio. It's both a real implementation of the spec and an
|
|
45
|
+
answer to *"show me you've actually built with MCP,"* not just talked about it.
|
|
46
|
+
|
|
47
|
+
## Demo
|
|
48
|
+
|
|
49
|
+
| MCP Inspector — tool discovery | Live chat demo |
|
|
50
|
+
|---|---|
|
|
51
|
+
|  |  |
|
|
52
|
+
|
|
53
|
+
Full video walkthrough (setup → tool calls → live answers): **[watch here ▶](https://github.com/user-attachments/assets/4c1b844a-087f-48a6-b156-bdef27282acc)**
|
|
54
|
+
|
|
55
|
+
## Tools exposed
|
|
56
|
+
|
|
57
|
+
| Tool | Description |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `list_projects` | Short summary of all 9 projects |
|
|
60
|
+
| `get_project_details(project_name)` | Full details for one project |
|
|
61
|
+
| `search_projects_by_stack(technology)` | Find projects using a given technology |
|
|
62
|
+
| `get_flagship_project` | The single best project to look at first |
|
|
63
|
+
| `get_resume_summary` | Background, target role, and core stack |
|
|
64
|
+
|
|
65
|
+
## Quickstart
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/ayush-s-tomar/portfolio-mcp-server.git
|
|
69
|
+
cd portfolio-mcp-server
|
|
70
|
+
|
|
71
|
+
python -m venv venv
|
|
72
|
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
73
|
+
|
|
74
|
+
pip install -r requirements.txt
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Test it interactively with the MCP Inspector before wiring it into a client:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
mcp dev server.py
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This opens a browser UI where you can call each tool manually and inspect
|
|
84
|
+
raw request/response payloads.
|
|
85
|
+
|
|
86
|
+
## Connect to Claude Desktop
|
|
87
|
+
|
|
88
|
+
Open your Claude Desktop config file:
|
|
89
|
+
|
|
90
|
+
| OS | Path |
|
|
91
|
+
|---|---|
|
|
92
|
+
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
|
|
93
|
+
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
|
|
94
|
+
|
|
95
|
+
If the file already has an `mcpServers` key with other servers in it, add
|
|
96
|
+
the `"portfolio"` entry inside the existing object rather than overwriting
|
|
97
|
+
the file. Use the **absolute path** to `server.py` on your machine:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"mcpServers": {
|
|
102
|
+
"portfolio": {
|
|
103
|
+
"command": "python",
|
|
104
|
+
"args": ["/absolute/path/to/portfolio-mcp-server/server.py"]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Restart Claude Desktop, then ask it something like:
|
|
111
|
+
|
|
112
|
+
> "What projects has Ayush built with FastAPI?"
|
|
113
|
+
|
|
114
|
+
Claude will call `search_projects_by_stack` and answer from the live data.
|
|
115
|
+
|
|
116
|
+
## Stack
|
|
117
|
+
|
|
118
|
+
- **Python 3.10+**
|
|
119
|
+
- **[MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)** (`FastMCP`)
|
|
120
|
+
- **stdio transport**
|
|
121
|
+
|
|
122
|
+
## Testing & CI
|
|
123
|
+
|
|
124
|
+
Every push and pull request runs through GitHub Actions:
|
|
125
|
+
|
|
126
|
+
- **Lint** — `ruff check .`
|
|
127
|
+
- **Type check** — `mypy server.py`
|
|
128
|
+
- **Smoke test** — spins up the server and calls each of the 5 tools over
|
|
129
|
+
stdio to confirm they return valid, schema-matching JSON
|
|
130
|
+
|
|
131
|
+
See [`.github/workflows/ci.yml`](.github/workflows/ci.yml). Run the same
|
|
132
|
+
checks locally before opening a PR:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pip install -r requirements-dev.txt
|
|
136
|
+
ruff check .
|
|
137
|
+
mypy server.py
|
|
138
|
+
pytest
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Project structure
|
|
142
|
+
portfolio-mcp-server/
|
|
143
|
+
├── server.py # FastMCP server + tool definitions
|
|
144
|
+
├── data/
|
|
145
|
+
│ └── projects.json # Project data the tools read from
|
|
146
|
+
├── tests/
|
|
147
|
+
│ └── test_tools.py # Smoke tests for each tool
|
|
148
|
+
├── requirements.txt
|
|
149
|
+
├── requirements-dev.txt
|
|
150
|
+
└── .github/workflows/ci.yml
|
|
151
|
+
## Roadmap
|
|
152
|
+
|
|
153
|
+
- [ ] `search_projects_by_stack` — support matching on multiple technologies at once
|
|
154
|
+
- [ ] Add an HTTP/SSE transport option alongside stdio for remote clients
|
|
155
|
+
- [ ] Publish to the MCP server registry
|
|
156
|
+
- [ ] Cache resume/project data with a lightweight refresh endpoint instead of static JSON
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
Released under the [MIT License](LICENSE).
|
|
161
|
+
|
|
162
|
+
## Author
|
|
163
|
+
|
|
164
|
+
**Ayush Tomar** — [GitHub](https://github.com/ayush-s-tomar)
|
|
165
|
+
|
|
166
|
+
If this was useful as a reference for building your own MCP server, a ⭐ on the repo is appreciated.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Portfolio MCP Server
|
|
3
|
+
---------------------
|
|
4
|
+
Exposes Ayush Tomar's project portfolio as MCP tools so any MCP-compatible
|
|
5
|
+
client (Claude Desktop, etc.) can query it interactively.
|
|
6
|
+
|
|
7
|
+
Run locally with the inspector:
|
|
8
|
+
mcp dev server.py
|
|
9
|
+
|
|
10
|
+
Run standalone (stdio transport, used by Claude Desktop):
|
|
11
|
+
python server.py
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from mcp.server.fastmcp import FastMCP
|
|
15
|
+
|
|
16
|
+
mcp = FastMCP("portfolio-server")
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Data — edit this to keep it current. Later you can swap this for a JSON
|
|
20
|
+
# file or a small DB without changing any tool signatures below.
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
PROJECTS: list[dict] = [
|
|
24
|
+
{
|
|
25
|
+
"name": "SalesAgent",
|
|
26
|
+
"description": (
|
|
27
|
+
"Autonomous B2B sales agent. Paste a LinkedIn URL, it researches "
|
|
28
|
+
"the lead, scores them with ML (84/100), and drafts a hyper-"
|
|
29
|
+
"personalized cold email referencing real company events, in "
|
|
30
|
+
"under 45 seconds."
|
|
31
|
+
),
|
|
32
|
+
"stack": ["LangGraph", "FastAPI", "React", "scikit-learn", "Groq", "Tavily"],
|
|
33
|
+
"github": "https://github.com/ayush-s-tomar/salesagent",
|
|
34
|
+
"demo": "https://salesagent-ai.streamlit.app/",
|
|
35
|
+
"writeup": "https://dev.to/ayushsinghtomar/i-got-tired-of-writing-cold-emails-so-i-built-an-ai-agent-to-do-it-for-me-2m4h",
|
|
36
|
+
"flagship": True,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "AgentLoop",
|
|
40
|
+
"description": (
|
|
41
|
+
"A multi-step research agent that breaks a question into "
|
|
42
|
+
"sub-questions, searches the live web, reflects on gaps, loops "
|
|
43
|
+
"back, and delivers a fully cited report."
|
|
44
|
+
),
|
|
45
|
+
"stack": ["FastAPI", "LangGraph", "Groq", "Tavily"],
|
|
46
|
+
"github": "https://github.com/ayush-s-tomar/agentloop",
|
|
47
|
+
"demo": "https://agentloop.onrender.com/",
|
|
48
|
+
"writeup": None,
|
|
49
|
+
"flagship": False,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "AskMyDocs",
|
|
53
|
+
"description": (
|
|
54
|
+
"RAG pipeline that answers questions over 50-page PDFs in under "
|
|
55
|
+
"3 seconds, with source citations and cosine similarity scores. "
|
|
56
|
+
"No SQL, no code."
|
|
57
|
+
),
|
|
58
|
+
"stack": ["Next.js", "Supabase", "pgvector", "Cohere"],
|
|
59
|
+
"github": "https://github.com/ayush-s-tomar/intellect-docs-ai",
|
|
60
|
+
"demo": "https://intellect-docs-ai.vercel.app/",
|
|
61
|
+
"writeup": None,
|
|
62
|
+
"flagship": False,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "AI Data Analyst Agent",
|
|
66
|
+
"description": (
|
|
67
|
+
"Upload any CSV, ask questions in plain English, get instant "
|
|
68
|
+
"charts and insights. No SQL. No code."
|
|
69
|
+
),
|
|
70
|
+
"stack": ["FastAPI", "React", "Groq", "pandas", "matplotlib"],
|
|
71
|
+
"github": "https://github.com/ayush-s-tomar/ai-data-analyst",
|
|
72
|
+
"demo": "https://ai-data-analyst-six-sooty.vercel.app/",
|
|
73
|
+
"writeup": None,
|
|
74
|
+
"flagship": False,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "JobHunt",
|
|
78
|
+
"description": (
|
|
79
|
+
"Multi-user Telegram job aggregator. AI scores every post and "
|
|
80
|
+
"auto-applies via email or form-fill. Watches job channels 24/7."
|
|
81
|
+
),
|
|
82
|
+
"stack": ["FastAPI", "PostgreSQL", "Groq", "Playwright"],
|
|
83
|
+
"github": "https://github.com/ayush-s-tomar/jobhunt",
|
|
84
|
+
"demo": "https://jobhunt-demo.vercel.app/",
|
|
85
|
+
"writeup": None,
|
|
86
|
+
"flagship": False,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "Email Agent",
|
|
90
|
+
"description": (
|
|
91
|
+
"AI agent that reads Gmail, classifies emails, drafts context-"
|
|
92
|
+
"aware replies, and lets you approve or edit before sending."
|
|
93
|
+
),
|
|
94
|
+
"stack": ["FastAPI", "React", "LLaMA 3.3"],
|
|
95
|
+
"github": "https://github.com/ayush-s-tomar/Email-agent",
|
|
96
|
+
"demo": "https://ai-inbox-agent.streamlit.app/",
|
|
97
|
+
"writeup": None,
|
|
98
|
+
"flagship": False,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "ARIA - Voice AI Assistant",
|
|
102
|
+
"description": (
|
|
103
|
+
"Speech-to-speech AI assistant with 99-language support and "
|
|
104
|
+
"conversation memory. Speak in any language, ARIA transcribes, "
|
|
105
|
+
"thinks, and talks back."
|
|
106
|
+
),
|
|
107
|
+
"stack": ["FastAPI", "Faster-Whisper", "Groq", "LLaMA", "gTTS"],
|
|
108
|
+
"github": "https://github.com/ayush-s-tomar/aria-voice-assistant",
|
|
109
|
+
"demo": "https://aria-bot.streamlit.app/",
|
|
110
|
+
"writeup": None,
|
|
111
|
+
"flagship": False,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "StartupScope",
|
|
115
|
+
"description": (
|
|
116
|
+
"Multi-agent CrewAI crew: Researcher, Analyst, and Writer agents "
|
|
117
|
+
"collaborate to search the web and generate structured startup "
|
|
118
|
+
"intelligence reports."
|
|
119
|
+
),
|
|
120
|
+
"stack": ["CrewAI", "Groq", "SerperDev"],
|
|
121
|
+
"github": "https://github.com/ayush-s-tomar/startupscope",
|
|
122
|
+
"demo": "https://startupscope-ephq.onrender.com/",
|
|
123
|
+
"writeup": None,
|
|
124
|
+
"flagship": False,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "n8n Email to Slack",
|
|
128
|
+
"description": (
|
|
129
|
+
"No-code AI automation pipeline: fetches unread Gmail, "
|
|
130
|
+
"summarizes with Groq LLaMA, detects priority, pushes digest to "
|
|
131
|
+
"Slack."
|
|
132
|
+
),
|
|
133
|
+
"stack": ["n8n", "Groq", "Gmail", "Slack"],
|
|
134
|
+
"github": "https://github.com/ayush-s-tomar/n8n-email-slack",
|
|
135
|
+
"demo": None,
|
|
136
|
+
"writeup": None,
|
|
137
|
+
"flagship": False,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "ResumeIQ",
|
|
141
|
+
"description": (
|
|
142
|
+
"AI resume screener that scores ATS compatibility, identifies "
|
|
143
|
+
"gaps, and exports detailed PDF reports."
|
|
144
|
+
),
|
|
145
|
+
"stack": ["Python", "Flask", "Groq"],
|
|
146
|
+
"github": "https://github.com/ayush-s-tomar/ResumeIQ",
|
|
147
|
+
"demo": "https://resumeiq-55h8.onrender.com/",
|
|
148
|
+
"writeup": None,
|
|
149
|
+
"flagship": False,
|
|
150
|
+
},
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
PROFILE = {
|
|
154
|
+
"name": "Ayush Tomar",
|
|
155
|
+
"role_target": "AI Developer (early-stage startups)",
|
|
156
|
+
"education": "B.Tech IT, MITS Gwalior (2023-2027, final year)",
|
|
157
|
+
"core_stack": [
|
|
158
|
+
"Python", "LangChain", "LangGraph", "CrewAI", "FastAPI", "Groq",
|
|
159
|
+
"Supabase pgvector", "Tavily", "React", "Render", "Vercel",
|
|
160
|
+
],
|
|
161
|
+
"github": "https://github.com/ayush-s-tomar",
|
|
162
|
+
"highlights": [
|
|
163
|
+
"9 independently built and deployed AI projects, no prior internship",
|
|
164
|
+
"Built and shipped an MCP server (this one) to expose the portfolio itself",
|
|
165
|
+
"Active LinkedIn cold-outreach campaign targeting Indian AI startups",
|
|
166
|
+
],
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ---------------------------------------------------------------------------
|
|
171
|
+
# Tools
|
|
172
|
+
# ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
@mcp.tool()
|
|
175
|
+
def list_projects() -> list[dict]:
|
|
176
|
+
"""List all projects in Ayush's portfolio with a short summary of each.
|
|
177
|
+
|
|
178
|
+
Returns a compact list (name, one-line description, stack) — use
|
|
179
|
+
get_project_details for the full description, GitHub link, and demo URL.
|
|
180
|
+
"""
|
|
181
|
+
return [
|
|
182
|
+
{
|
|
183
|
+
"name": p["name"],
|
|
184
|
+
"description": p["description"][:120] + "...",
|
|
185
|
+
"stack": p["stack"],
|
|
186
|
+
}
|
|
187
|
+
for p in PROJECTS
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@mcp.tool()
|
|
192
|
+
def get_project_details(project_name: str) -> dict:
|
|
193
|
+
"""Get full details for a single project by name (case-insensitive,
|
|
194
|
+
partial match allowed, e.g. 'sales' matches 'SalesAgent').
|
|
195
|
+
|
|
196
|
+
Returns description, full stack, GitHub repo, live demo link, and
|
|
197
|
+
writeup link if one exists.
|
|
198
|
+
"""
|
|
199
|
+
query = project_name.lower().strip()
|
|
200
|
+
for p in PROJECTS:
|
|
201
|
+
if query in p["name"].lower():
|
|
202
|
+
return p
|
|
203
|
+
return {
|
|
204
|
+
"error": f"No project found matching '{project_name}'",
|
|
205
|
+
"available_projects": [p["name"] for p in PROJECTS],
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@mcp.tool()
|
|
210
|
+
def search_projects_by_stack(technology: str) -> list[dict]:
|
|
211
|
+
"""Find all projects that use a given technology or tool, e.g.
|
|
212
|
+
'LangGraph', 'Groq', 'FastAPI', 'React'. Case-insensitive, partial match.
|
|
213
|
+
|
|
214
|
+
Useful for answering "does Ayush have experience with X?"
|
|
215
|
+
"""
|
|
216
|
+
query = technology.lower().strip()
|
|
217
|
+
matches = [
|
|
218
|
+
{"name": p["name"], "stack": p["stack"], "github": p["github"]}
|
|
219
|
+
for p in PROJECTS
|
|
220
|
+
if any(query in tech.lower() for tech in p["stack"])
|
|
221
|
+
]
|
|
222
|
+
if not matches:
|
|
223
|
+
return [{"message": f"No projects found using '{technology}'"}]
|
|
224
|
+
return matches
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@mcp.tool()
|
|
228
|
+
def get_flagship_project() -> dict:
|
|
229
|
+
"""Get Ayush's flagship/best project — the one to look at first for a
|
|
230
|
+
quick sense of his skill level."""
|
|
231
|
+
for p in PROJECTS:
|
|
232
|
+
if p.get("flagship"):
|
|
233
|
+
return p
|
|
234
|
+
return PROJECTS[0]
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@mcp.tool()
|
|
238
|
+
def get_resume_summary() -> dict:
|
|
239
|
+
"""Get a summary of Ayush's background: education, target role, core
|
|
240
|
+
tech stack, and career highlights."""
|
|
241
|
+
return PROFILE
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def main() -> None:
|
|
245
|
+
"""Entry point used by the `portfolio-mcp-server` console script."""
|
|
246
|
+
mcp.run(transport="stdio")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
if __name__ == "__main__":
|
|
250
|
+
main()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "portfolio-mcp-server"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "MCP server exposing Ayush Tomar's AI project portfolio as queryable tools for Claude Desktop and other MCP clients"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Ayush Tomar" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["mcp", "model-context-protocol", "portfolio", "ai-agents", "claude"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"mcp[cli]>=1.0.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/ayush-s-tomar/portfolio-mcp-server"
|
|
30
|
+
Repository = "https://github.com/ayush-s-tomar/portfolio-mcp-server"
|
|
31
|
+
Issues = "https://github.com/ayush-s-tomar/portfolio-mcp-server/issues"
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
portfolio-mcp-server = "portfolio_mcp_server.server:main"
|
|
35
|
+
|
|
36
|
+
[tool.hatch.build.targets.wheel]
|
|
37
|
+
packages = ["portfolio_mcp_server"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mcp[cli]>=1.0.0
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Portfolio MCP Server -- root-level entry point.
|
|
3
|
+
|
|
4
|
+
The actual implementation lives in portfolio_mcp_server/server.py so the
|
|
5
|
+
project can be packaged and published to PyPI / the MCP Registry. This file
|
|
6
|
+
is kept so existing instructions keep working unchanged:
|
|
7
|
+
|
|
8
|
+
mcp dev server.py
|
|
9
|
+
python server.py
|
|
10
|
+
|
|
11
|
+
and so the Claude Desktop config snippet (which points at this file's
|
|
12
|
+
absolute path) continues to work without edits.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from portfolio_mcp_server.server import main, mcp # noqa: F401
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
End-to-end smoke test: spins up the portfolio MCP server as a subprocess
|
|
3
|
+
and calls each of the 5 tools over real stdio transport, confirming each
|
|
4
|
+
returns valid, non-empty JSON-serializable output.
|
|
5
|
+
|
|
6
|
+
Run with:
|
|
7
|
+
python -m tests.smoke_test
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from mcp import ClientSession, StdioServerParameters
|
|
15
|
+
from mcp.client.stdio import stdio_client
|
|
16
|
+
|
|
17
|
+
SERVER_PATH = str(Path(__file__).resolve().parent.parent / "server.py")
|
|
18
|
+
|
|
19
|
+
EXPECTED_TOOLS = {
|
|
20
|
+
"list_projects",
|
|
21
|
+
"get_project_details",
|
|
22
|
+
"search_projects_by_stack",
|
|
23
|
+
"get_flagship_project",
|
|
24
|
+
"get_resume_summary",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def run_smoke_test() -> None:
|
|
29
|
+
params = StdioServerParameters(command=sys.executable, args=[SERVER_PATH])
|
|
30
|
+
|
|
31
|
+
async with stdio_client(params) as (read, write):
|
|
32
|
+
async with ClientSession(read, write) as session:
|
|
33
|
+
await session.initialize()
|
|
34
|
+
|
|
35
|
+
tools_result = await session.list_tools()
|
|
36
|
+
tool_names = {t.name for t in tools_result.tools}
|
|
37
|
+
missing = EXPECTED_TOOLS - tool_names
|
|
38
|
+
assert not missing, f"Missing expected tools: {missing}"
|
|
39
|
+
print(f"Discovered all {len(EXPECTED_TOOLS)} expected tools")
|
|
40
|
+
|
|
41
|
+
result = await session.call_tool("list_projects", {})
|
|
42
|
+
assert result.content, "list_projects returned empty content"
|
|
43
|
+
print("list_projects returned content")
|
|
44
|
+
|
|
45
|
+
result = await session.call_tool(
|
|
46
|
+
"get_project_details", {"project_name": "SalesAgent"}
|
|
47
|
+
)
|
|
48
|
+
assert result.content, "get_project_details returned empty content"
|
|
49
|
+
print("get_project_details returned content")
|
|
50
|
+
|
|
51
|
+
result = await session.call_tool(
|
|
52
|
+
"search_projects_by_stack", {"technology": "Groq"}
|
|
53
|
+
)
|
|
54
|
+
assert result.content, "search_projects_by_stack returned empty content"
|
|
55
|
+
print("search_projects_by_stack returned content")
|
|
56
|
+
|
|
57
|
+
result = await session.call_tool("get_flagship_project", {})
|
|
58
|
+
assert result.content, "get_flagship_project returned empty content"
|
|
59
|
+
print("get_flagship_project returned content")
|
|
60
|
+
|
|
61
|
+
result = await session.call_tool("get_resume_summary", {})
|
|
62
|
+
assert result.content, "get_resume_summary returned empty content"
|
|
63
|
+
print("get_resume_summary returned content")
|
|
64
|
+
|
|
65
|
+
print("\nAll 5 tools responded correctly over stdio. Smoke test passed.")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
asyncio.run(run_smoke_test())
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Smoke/unit tests for the portfolio MCP server's tools.
|
|
3
|
+
|
|
4
|
+
These call the underlying tool functions directly (FastMCP's @mcp.tool()
|
|
5
|
+
decorator doesn't wrap them, so they're plain callables) rather than going
|
|
6
|
+
through the MCP protocol layer -- fast, no transport needed.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from portfolio_mcp_server.server import (
|
|
10
|
+
PROJECTS,
|
|
11
|
+
get_flagship_project,
|
|
12
|
+
get_project_details,
|
|
13
|
+
get_resume_summary,
|
|
14
|
+
list_projects,
|
|
15
|
+
search_projects_by_stack,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_list_projects_returns_all_projects():
|
|
20
|
+
result = list_projects()
|
|
21
|
+
assert isinstance(result, list)
|
|
22
|
+
assert len(result) == len(PROJECTS)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_list_projects_has_expected_keys():
|
|
26
|
+
result = list_projects()
|
|
27
|
+
for item in result:
|
|
28
|
+
assert "name" in item
|
|
29
|
+
assert "description" in item
|
|
30
|
+
assert "stack" in item
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_get_project_details_found():
|
|
34
|
+
result = get_project_details("SalesAgent")
|
|
35
|
+
assert result["name"] == "SalesAgent"
|
|
36
|
+
assert "stack" in result
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_get_project_details_case_insensitive_partial_match():
|
|
40
|
+
result = get_project_details("sales")
|
|
41
|
+
assert result["name"] == "SalesAgent"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_get_project_details_not_found():
|
|
45
|
+
result = get_project_details("NonexistentProjectXYZ")
|
|
46
|
+
assert "error" in result
|
|
47
|
+
assert "available_projects" in result
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_search_projects_by_stack_found():
|
|
51
|
+
result = search_projects_by_stack("Groq")
|
|
52
|
+
assert isinstance(result, list)
|
|
53
|
+
assert len(result) > 0
|
|
54
|
+
assert all("stack" in item for item in result)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_search_projects_by_stack_not_found():
|
|
58
|
+
result = search_projects_by_stack("NonexistentTechXYZ")
|
|
59
|
+
assert isinstance(result, list)
|
|
60
|
+
assert "message" in result[0]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_get_flagship_project_returns_a_project():
|
|
64
|
+
result = get_flagship_project()
|
|
65
|
+
assert "name" in result
|
|
66
|
+
assert result in PROJECTS
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_get_resume_summary_has_expected_keys():
|
|
70
|
+
result = get_resume_summary()
|
|
71
|
+
assert isinstance(result, dict)
|
|
72
|
+
assert "github" in result
|
|
73
|
+
assert "highlights" in result
|