github-talent-mcp 0.1.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.
Files changed (24) hide show
  1. github_talent_mcp-0.1.0/LICENSE +21 -0
  2. github_talent_mcp-0.1.0/PKG-INFO +218 -0
  3. github_talent_mcp-0.1.0/README.md +189 -0
  4. github_talent_mcp-0.1.0/pyproject.toml +51 -0
  5. github_talent_mcp-0.1.0/setup.cfg +4 -0
  6. github_talent_mcp-0.1.0/src/github_talent_mcp/__init__.py +0 -0
  7. github_talent_mcp-0.1.0/src/github_talent_mcp/__main__.py +3 -0
  8. github_talent_mcp-0.1.0/src/github_talent_mcp/github_client.py +191 -0
  9. github_talent_mcp-0.1.0/src/github_talent_mcp/models.py +96 -0
  10. github_talent_mcp-0.1.0/src/github_talent_mcp/scoring.py +169 -0
  11. github_talent_mcp-0.1.0/src/github_talent_mcp/server.py +127 -0
  12. github_talent_mcp-0.1.0/src/github_talent_mcp/tools/__init__.py +0 -0
  13. github_talent_mcp-0.1.0/src/github_talent_mcp/tools/contributors.py +61 -0
  14. github_talent_mcp-0.1.0/src/github_talent_mcp/tools/profile.py +219 -0
  15. github_talent_mcp-0.1.0/src/github_talent_mcp/tools/rank.py +91 -0
  16. github_talent_mcp-0.1.0/src/github_talent_mcp/tools/search.py +43 -0
  17. github_talent_mcp-0.1.0/src/github_talent_mcp.egg-info/PKG-INFO +218 -0
  18. github_talent_mcp-0.1.0/src/github_talent_mcp.egg-info/SOURCES.txt +22 -0
  19. github_talent_mcp-0.1.0/src/github_talent_mcp.egg-info/dependency_links.txt +1 -0
  20. github_talent_mcp-0.1.0/src/github_talent_mcp.egg-info/entry_points.txt +2 -0
  21. github_talent_mcp-0.1.0/src/github_talent_mcp.egg-info/requires.txt +4 -0
  22. github_talent_mcp-0.1.0/src/github_talent_mcp.egg-info/top_level.txt +1 -0
  23. github_talent_mcp-0.1.0/tests/test_scoring.py +237 -0
  24. github_talent_mcp-0.1.0/tests/test_search.py +67 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Carolina Cherry
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,218 @@
1
+ Metadata-Version: 2.4
2
+ Name: github-talent-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for searching, scoring, and ranking GitHub developers for technical recruiting
5
+ Author: Carolina Cherry
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/carolinacherry/github-talent-mcp
8
+ Project-URL: Repository, https://github.com/carolinacherry/github-talent-mcp
9
+ Project-URL: Issues, https://github.com/carolinacherry/github-talent-mcp/issues
10
+ Keywords: mcp,github,recruiting,developer-tools,claude
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
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
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: mcp>=1.0.0
25
+ Requires-Dist: httpx>=0.27.0
26
+ Requires-Dist: pydantic>=2.0.0
27
+ Requires-Dist: python-dotenv>=1.0.0
28
+ Dynamic: license-file
29
+
30
+ # github-talent-mcp
31
+
32
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
33
+ [![Python 3.14+](https://img.shields.io/badge/Python-3.14+-blue.svg)](https://www.python.org)
34
+ [![MCP](https://img.shields.io/badge/MCP-Model_Context_Protocol-8A2BE2)](https://modelcontextprotocol.io)
35
+ [![Claude](https://img.shields.io/badge/Built_for-Claude_by_Anthropic-d4a373)](https://claude.ai)
36
+ [![GitHub API](https://img.shields.io/badge/GitHub-REST_API_v3-181717?logo=github)](https://docs.github.com/en/rest)
37
+
38
+ MCP server that searches, scores, and ranks GitHub developers for technical recruiting.
39
+
40
+ ## Demo
41
+
42
+ https://github.com/user-attachments/assets/2dfd82b4-3eb5-4f2b-bc0a-2580b95043e4
43
+
44
+ ### Profile deep dive
45
+
46
+ > Get the full developer profile and activity score for torvalds on GitHub
47
+
48
+ Claude calls `get_developer_profile("torvalds")` and returns:
49
+
50
+ | Field | Value |
51
+ |---|---|
52
+ | **Activity Score** | **150** (reputation floor applied) |
53
+ | Location | Portland, OR |
54
+ | Followers | 293,321 |
55
+ | Stars Received | 235,068 |
56
+ | Primary Language | C (98.1%) |
57
+ | Commits (90d) | 0 |
58
+ | PRs (90d) | 0 |
59
+ | Notable Repos | linux (183K stars), libdc-for-dirk, subsurface-for-dirk, uemacs, pesern-resolve |
60
+ | Profile README | No |
61
+ | Hireable | No |
62
+
63
+ Torvalds has zero recent GitHub activity because kernel development flows through mailing lists, not GitHub PRs. The **reputation floor** (293K followers) overrides the behavioral score and sets it to 150.
64
+
65
+ ### Repo contributor ranking
66
+
67
+ > Get the top contributors to huggingface/transformers and rank them for a founding ML engineer role at an AI startup
68
+
69
+ Claude calls `get_repo_contributors("huggingface/transformers")` → `rank_candidates` on the top 24 contributors:
70
+
71
+ | Rank | Developer | Combined Score | Activity | Relevance | Strengths |
72
+ |---|---|---|---|---|---|
73
+ | 1 | stas00 | 83.4 | 150 | 72 | 4,553 stars, contributes to major OSS, MIT-licensed repos |
74
+ | 2 | cyyever | 80.8 | 120 | 64 | 1,217 followers, active contributor, profile README |
75
+ | 3 | Cyrilvallez | 77.2 | 120 | 56 | Active: 13 commits + 57 PRs in 90 days, strong OSS presence |
76
+ | 4 | ArthurZucker | 74.4 | 120 | 48 | 37 PRs in 90 days, contributes to huggingface/transformers |
77
+ | 5 | ydshieh | 72.0 | 120 | 40 | Active: 9 commits + 40 PRs in 90 days |
78
+
79
+ Combined score = activity × 0.4 + relevance × 0.6. Relevance is keyword overlap with the job description (ML, AI, startup, engineer, etc.).
80
+
81
+ ## Installation
82
+
83
+ ### 1. Clone and install
84
+
85
+ ```bash
86
+ git clone https://github.com/carolinacherry/github-talent-mcp.git
87
+ cd github-talent-mcp
88
+ python3 -m venv .venv && source .venv/bin/activate
89
+ pip install -e .
90
+ ```
91
+
92
+ ### 2. Create a GitHub personal access token
93
+
94
+ Go to [github.com/settings/tokens](https://github.com/settings/tokens) and create a **fine-grained** or **classic** token with these scopes:
95
+
96
+ | Scope | Why |
97
+ |---|---|
98
+ | `read:user` | Read user profiles and search users |
99
+ | `public_repo` | Read public repo data, languages, contributors |
100
+
101
+ Create a `.env` file in the project root:
102
+
103
+ ```
104
+ GITHUB_TOKEN=ghp_xxxxxxxxxxxx
105
+ ```
106
+
107
+ ### 3. Connect to Claude
108
+
109
+ #### Claude Code (CLI)
110
+
111
+ One command:
112
+
113
+ ```bash
114
+ claude mcp add github-talent -- /path/to/github-talent-mcp/.venv/bin/python3 -m github_talent_mcp
115
+ ```
116
+
117
+ Then set the token as an environment variable. Either:
118
+ - Export it in your shell: `export GITHUB_TOKEN=ghp_xxxxxxxxxxxx`
119
+ - Or keep it in the `.env` file — the server reads it via `python-dotenv` on startup
120
+
121
+ Restart Claude Code to pick up the new server. Verify with `/mcp` — you should see 4 tools under `github-talent`.
122
+
123
+ #### Claude Desktop
124
+
125
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
126
+
127
+ ```json
128
+ {
129
+ "mcpServers": {
130
+ "github-talent": {
131
+ "command": "/path/to/github-talent-mcp/.venv/bin/python3",
132
+ "args": ["-m", "github_talent_mcp"],
133
+ "cwd": "/path/to/github-talent-mcp",
134
+ "env": {
135
+ "GITHUB_TOKEN": "ghp_xxxxxxxxxxxx"
136
+ }
137
+ }
138
+ }
139
+ }
140
+ ```
141
+
142
+ Restart Claude Desktop. The tools will appear in the toolbox icon.
143
+
144
+ ## Try It
145
+
146
+ Once installed, paste these prompts to verify everything works:
147
+
148
+ **Basic search:**
149
+ > Find Python developers in Raleigh active in the last 60 days
150
+
151
+ **Profile deep dive:**
152
+ > Get the full developer profile and activity score for torvalds on GitHub
153
+
154
+ **Full workflow:**
155
+ > Find 10 ML engineers in San Francisco active in the last 30 days, then rank them for a senior LLM inference engineer role
156
+
157
+ **Repo contributors:**
158
+ > Get the top contributors to huggingface/transformers and rank them for a founding ML engineer role at an AI startup
159
+
160
+ ## Tools
161
+
162
+ | Tool | Description |
163
+ |---|---|
164
+ | `search_developers` | Search GitHub users by language, location, activity, followers. For topic-based sourcing, use `get_repo_contributors` on relevant repos instead. |
165
+ | `get_developer_profile` | Deep profile enrichment: languages, stars, commits + PRs, OSS contributions, license breakdown, profile README, and activity score with breakdown. |
166
+ | `rank_candidates` | Rank usernames against a job description. Returns sorted candidates with combined score, strengths, gaps, and reasoning. |
167
+ | `get_repo_contributors` | Top contributors for any repo. Accepts `owner/repo` or full URL. The fastest way to source for a specific domain. |
168
+
169
+ ## Scoring
170
+
171
+ The activity score combines two layers: **behavioral signals** (what you did recently) and a **reputation floor** (what you've built over time).
172
+
173
+ ### Behavioral Score (0-205)
174
+
175
+ | Signal | Max Points | How |
176
+ |---|---|---|
177
+ | Commits + PRs (last 90 days) | 60 | Push commits + PR opens (PRs weighted x3). Captures both push-based and PR-based workflows. |
178
+ | Stars on repos | 40 | Personal repo stars + stars on repos you contribute to. Org repo maintainers get credit. |
179
+ | Profile README | 20 | Presence of a profile README (github.com/username/username). |
180
+ | Followers | 20 | Capped at 20. |
181
+ | Repos with descriptions | 20 | Ratio of repos that have descriptions. Signal of care and polish. |
182
+ | Permissive license repos | 15 | Has at least one repo with MIT, Apache-2.0, BSD, ISC, or Unlicense. |
183
+ | Major OSS contributions | 30 | PRs, pushes, or issues on repos you don't own. Capped at 3 repos (10 pts each). |
184
+
185
+ ### Reputation Floor
186
+
187
+ The behavioral score alone penalizes developers whose work doesn't produce GitHub events — Torvalds works through mailing lists, senior maintainers merge via org bots, and many engineers work in private repos.
188
+
189
+ The reputation floor ensures cumulative impact isn't erased by a quiet quarter:
190
+
191
+ | Threshold | Floor |
192
+ |---|---|
193
+ | 10K+ followers **or** 50K+ stars | 150 |
194
+ | 1K+ followers **or** 5K+ stars | 120 |
195
+ | 500+ followers **or** 1K+ stars | 100 |
196
+ | 100+ followers **or** 200+ stars | 80 |
197
+
198
+ The final score is `max(behavioral_score, reputation_floor)`. If the floor is applied, the breakdown includes a `reputation_floor` field so you know.
199
+
200
+ ### Score Tiers
201
+
202
+ - **150+** — exceptional (top OSS maintainers, well-known engineers)
203
+ - **120-149** — strong signal, worth reaching out
204
+ - **80-119** — solid developer with meaningful public work
205
+ - **40-79** — active but limited public signal
206
+ - **<40** — low signal (likely private work or junior)
207
+
208
+ ### Ranking
209
+
210
+ `rank_candidates` combines the activity score with a **relevance score** (0-100) based on keyword overlap between the job description and the candidate's profile (bio, languages, repo topics, README). The combined score weights relevance at 60% and activity at 40% — a high-activity developer with no overlap to the job shouldn't outrank a relevant one.
211
+
212
+ ## Rate Limits
213
+
214
+ GitHub REST API: 5,000 requests/hour with token. A typical workflow (search + enrich 5 candidates + rank) uses ~60-100 API calls. Profile results are cached within a session to avoid redundant calls during ranking.
215
+
216
+ ## License
217
+
218
+ MIT
@@ -0,0 +1,189 @@
1
+ # github-talent-mcp
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
4
+ [![Python 3.14+](https://img.shields.io/badge/Python-3.14+-blue.svg)](https://www.python.org)
5
+ [![MCP](https://img.shields.io/badge/MCP-Model_Context_Protocol-8A2BE2)](https://modelcontextprotocol.io)
6
+ [![Claude](https://img.shields.io/badge/Built_for-Claude_by_Anthropic-d4a373)](https://claude.ai)
7
+ [![GitHub API](https://img.shields.io/badge/GitHub-REST_API_v3-181717?logo=github)](https://docs.github.com/en/rest)
8
+
9
+ MCP server that searches, scores, and ranks GitHub developers for technical recruiting.
10
+
11
+ ## Demo
12
+
13
+ https://github.com/user-attachments/assets/2dfd82b4-3eb5-4f2b-bc0a-2580b95043e4
14
+
15
+ ### Profile deep dive
16
+
17
+ > Get the full developer profile and activity score for torvalds on GitHub
18
+
19
+ Claude calls `get_developer_profile("torvalds")` and returns:
20
+
21
+ | Field | Value |
22
+ |---|---|
23
+ | **Activity Score** | **150** (reputation floor applied) |
24
+ | Location | Portland, OR |
25
+ | Followers | 293,321 |
26
+ | Stars Received | 235,068 |
27
+ | Primary Language | C (98.1%) |
28
+ | Commits (90d) | 0 |
29
+ | PRs (90d) | 0 |
30
+ | Notable Repos | linux (183K stars), libdc-for-dirk, subsurface-for-dirk, uemacs, pesern-resolve |
31
+ | Profile README | No |
32
+ | Hireable | No |
33
+
34
+ Torvalds has zero recent GitHub activity because kernel development flows through mailing lists, not GitHub PRs. The **reputation floor** (293K followers) overrides the behavioral score and sets it to 150.
35
+
36
+ ### Repo contributor ranking
37
+
38
+ > Get the top contributors to huggingface/transformers and rank them for a founding ML engineer role at an AI startup
39
+
40
+ Claude calls `get_repo_contributors("huggingface/transformers")` → `rank_candidates` on the top 24 contributors:
41
+
42
+ | Rank | Developer | Combined Score | Activity | Relevance | Strengths |
43
+ |---|---|---|---|---|---|
44
+ | 1 | stas00 | 83.4 | 150 | 72 | 4,553 stars, contributes to major OSS, MIT-licensed repos |
45
+ | 2 | cyyever | 80.8 | 120 | 64 | 1,217 followers, active contributor, profile README |
46
+ | 3 | Cyrilvallez | 77.2 | 120 | 56 | Active: 13 commits + 57 PRs in 90 days, strong OSS presence |
47
+ | 4 | ArthurZucker | 74.4 | 120 | 48 | 37 PRs in 90 days, contributes to huggingface/transformers |
48
+ | 5 | ydshieh | 72.0 | 120 | 40 | Active: 9 commits + 40 PRs in 90 days |
49
+
50
+ Combined score = activity × 0.4 + relevance × 0.6. Relevance is keyword overlap with the job description (ML, AI, startup, engineer, etc.).
51
+
52
+ ## Installation
53
+
54
+ ### 1. Clone and install
55
+
56
+ ```bash
57
+ git clone https://github.com/carolinacherry/github-talent-mcp.git
58
+ cd github-talent-mcp
59
+ python3 -m venv .venv && source .venv/bin/activate
60
+ pip install -e .
61
+ ```
62
+
63
+ ### 2. Create a GitHub personal access token
64
+
65
+ Go to [github.com/settings/tokens](https://github.com/settings/tokens) and create a **fine-grained** or **classic** token with these scopes:
66
+
67
+ | Scope | Why |
68
+ |---|---|
69
+ | `read:user` | Read user profiles and search users |
70
+ | `public_repo` | Read public repo data, languages, contributors |
71
+
72
+ Create a `.env` file in the project root:
73
+
74
+ ```
75
+ GITHUB_TOKEN=ghp_xxxxxxxxxxxx
76
+ ```
77
+
78
+ ### 3. Connect to Claude
79
+
80
+ #### Claude Code (CLI)
81
+
82
+ One command:
83
+
84
+ ```bash
85
+ claude mcp add github-talent -- /path/to/github-talent-mcp/.venv/bin/python3 -m github_talent_mcp
86
+ ```
87
+
88
+ Then set the token as an environment variable. Either:
89
+ - Export it in your shell: `export GITHUB_TOKEN=ghp_xxxxxxxxxxxx`
90
+ - Or keep it in the `.env` file — the server reads it via `python-dotenv` on startup
91
+
92
+ Restart Claude Code to pick up the new server. Verify with `/mcp` — you should see 4 tools under `github-talent`.
93
+
94
+ #### Claude Desktop
95
+
96
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
97
+
98
+ ```json
99
+ {
100
+ "mcpServers": {
101
+ "github-talent": {
102
+ "command": "/path/to/github-talent-mcp/.venv/bin/python3",
103
+ "args": ["-m", "github_talent_mcp"],
104
+ "cwd": "/path/to/github-talent-mcp",
105
+ "env": {
106
+ "GITHUB_TOKEN": "ghp_xxxxxxxxxxxx"
107
+ }
108
+ }
109
+ }
110
+ }
111
+ ```
112
+
113
+ Restart Claude Desktop. The tools will appear in the toolbox icon.
114
+
115
+ ## Try It
116
+
117
+ Once installed, paste these prompts to verify everything works:
118
+
119
+ **Basic search:**
120
+ > Find Python developers in Raleigh active in the last 60 days
121
+
122
+ **Profile deep dive:**
123
+ > Get the full developer profile and activity score for torvalds on GitHub
124
+
125
+ **Full workflow:**
126
+ > Find 10 ML engineers in San Francisco active in the last 30 days, then rank them for a senior LLM inference engineer role
127
+
128
+ **Repo contributors:**
129
+ > Get the top contributors to huggingface/transformers and rank them for a founding ML engineer role at an AI startup
130
+
131
+ ## Tools
132
+
133
+ | Tool | Description |
134
+ |---|---|
135
+ | `search_developers` | Search GitHub users by language, location, activity, followers. For topic-based sourcing, use `get_repo_contributors` on relevant repos instead. |
136
+ | `get_developer_profile` | Deep profile enrichment: languages, stars, commits + PRs, OSS contributions, license breakdown, profile README, and activity score with breakdown. |
137
+ | `rank_candidates` | Rank usernames against a job description. Returns sorted candidates with combined score, strengths, gaps, and reasoning. |
138
+ | `get_repo_contributors` | Top contributors for any repo. Accepts `owner/repo` or full URL. The fastest way to source for a specific domain. |
139
+
140
+ ## Scoring
141
+
142
+ The activity score combines two layers: **behavioral signals** (what you did recently) and a **reputation floor** (what you've built over time).
143
+
144
+ ### Behavioral Score (0-205)
145
+
146
+ | Signal | Max Points | How |
147
+ |---|---|---|
148
+ | Commits + PRs (last 90 days) | 60 | Push commits + PR opens (PRs weighted x3). Captures both push-based and PR-based workflows. |
149
+ | Stars on repos | 40 | Personal repo stars + stars on repos you contribute to. Org repo maintainers get credit. |
150
+ | Profile README | 20 | Presence of a profile README (github.com/username/username). |
151
+ | Followers | 20 | Capped at 20. |
152
+ | Repos with descriptions | 20 | Ratio of repos that have descriptions. Signal of care and polish. |
153
+ | Permissive license repos | 15 | Has at least one repo with MIT, Apache-2.0, BSD, ISC, or Unlicense. |
154
+ | Major OSS contributions | 30 | PRs, pushes, or issues on repos you don't own. Capped at 3 repos (10 pts each). |
155
+
156
+ ### Reputation Floor
157
+
158
+ The behavioral score alone penalizes developers whose work doesn't produce GitHub events — Torvalds works through mailing lists, senior maintainers merge via org bots, and many engineers work in private repos.
159
+
160
+ The reputation floor ensures cumulative impact isn't erased by a quiet quarter:
161
+
162
+ | Threshold | Floor |
163
+ |---|---|
164
+ | 10K+ followers **or** 50K+ stars | 150 |
165
+ | 1K+ followers **or** 5K+ stars | 120 |
166
+ | 500+ followers **or** 1K+ stars | 100 |
167
+ | 100+ followers **or** 200+ stars | 80 |
168
+
169
+ The final score is `max(behavioral_score, reputation_floor)`. If the floor is applied, the breakdown includes a `reputation_floor` field so you know.
170
+
171
+ ### Score Tiers
172
+
173
+ - **150+** — exceptional (top OSS maintainers, well-known engineers)
174
+ - **120-149** — strong signal, worth reaching out
175
+ - **80-119** — solid developer with meaningful public work
176
+ - **40-79** — active but limited public signal
177
+ - **<40** — low signal (likely private work or junior)
178
+
179
+ ### Ranking
180
+
181
+ `rank_candidates` combines the activity score with a **relevance score** (0-100) based on keyword overlap between the job description and the candidate's profile (bio, languages, repo topics, README). The combined score weights relevance at 60% and activity at 40% — a high-activity developer with no overlap to the job shouldn't outrank a relevant one.
182
+
183
+ ## Rate Limits
184
+
185
+ GitHub REST API: 5,000 requests/hour with token. A typical workflow (search + enrich 5 candidates + rank) uses ~60-100 API calls. Profile results are cached within a session to avoid redundant calls during ranking.
186
+
187
+ ## License
188
+
189
+ MIT
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "github-talent-mcp"
7
+ version = "0.1.0"
8
+ description = "MCP server for searching, scoring, and ranking GitHub developers for technical recruiting"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ {name = "Carolina Cherry"},
14
+ ]
15
+ keywords = ["mcp", "github", "recruiting", "developer-tools", "claude"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
26
+ "Topic :: Software Development :: Libraries",
27
+ ]
28
+ dependencies = [
29
+ "mcp>=1.0.0",
30
+ "httpx>=0.27.0",
31
+ "pydantic>=2.0.0",
32
+ "python-dotenv>=1.0.0",
33
+ ]
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/carolinacherry/github-talent-mcp"
37
+ Repository = "https://github.com/carolinacherry/github-talent-mcp"
38
+ Issues = "https://github.com/carolinacherry/github-talent-mcp/issues"
39
+
40
+ [project.scripts]
41
+ github-talent-mcp = "github_talent_mcp.server:main"
42
+
43
+ [tool.setuptools.packages.find]
44
+ where = ["src"]
45
+
46
+ [tool.ruff]
47
+ target-version = "py310"
48
+ line-length = 100
49
+
50
+ [tool.pytest.ini_options]
51
+ asyncio_mode = "auto"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from github_talent_mcp.server import main
2
+
3
+ main()
@@ -0,0 +1,191 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import os
5
+ import time
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ GITHUB_API = "https://api.github.com"
11
+ PERMISSIVE_LICENSES = frozenset({
12
+ "mit", "apache-2.0", "bsd-2-clause", "bsd-3-clause", "isc", "unlicense",
13
+ })
14
+
15
+ log = logging.getLogger("github-talent-mcp")
16
+
17
+
18
+ class GitHubClient:
19
+ def __init__(self, token: str | None = None):
20
+ self._token = token or os.environ.get("GITHUB_TOKEN", "")
21
+ self._cache: dict[str, tuple[float, Any]] = {}
22
+ self._cache_ttl = 300 # 5 minutes
23
+ headers: dict[str, str] = {
24
+ "Accept": "application/vnd.github+json",
25
+ "X-GitHub-Api-Version": "2022-11-28",
26
+ }
27
+ if self._token:
28
+ headers["Authorization"] = f"Bearer {self._token}"
29
+ self._client = httpx.AsyncClient(
30
+ base_url=GITHUB_API,
31
+ headers=headers,
32
+ timeout=30.0,
33
+ )
34
+
35
+ async def close(self) -> None:
36
+ await self._client.aclose()
37
+
38
+ # -- Cache helpers --
39
+
40
+ def _cache_get(self, key: str) -> Any | None:
41
+ if key in self._cache:
42
+ ts, data = self._cache[key]
43
+ if time.monotonic() - ts < self._cache_ttl:
44
+ return data
45
+ del self._cache[key]
46
+ return None
47
+
48
+ def _cache_set(self, key: str, data: Any) -> None:
49
+ self._cache[key] = (time.monotonic(), data)
50
+
51
+ def _check_rate_limit(self, resp: httpx.Response) -> None:
52
+ remaining = resp.headers.get("X-RateLimit-Remaining")
53
+ if remaining and int(remaining) < 100:
54
+ reset = resp.headers.get("X-RateLimit-Reset", "unknown")
55
+ log.warning(f"Rate limit low: {remaining} remaining, resets at {reset}")
56
+
57
+ # -- API methods --
58
+
59
+ async def search_users(
60
+ self,
61
+ *,
62
+ languages: list[str] | None = None,
63
+ location: str | None = None,
64
+ min_followers: int | None = None,
65
+ min_repos: int | None = None,
66
+ per_page: int = 30,
67
+ page: int = 1,
68
+ ) -> dict:
69
+ parts: list[str] = ["type:user"]
70
+ if languages:
71
+ for lang in languages:
72
+ parts.append(f"language:{lang}")
73
+ if location:
74
+ parts.append(f"location:{location}")
75
+ if min_followers is not None:
76
+ parts.append(f"followers:>={min_followers}")
77
+ if min_repos is not None:
78
+ parts.append(f"repos:>={min_repos}")
79
+ # Note: pushed:> is NOT a valid qualifier for /search/users — it silently
80
+ # returns 0 results. Use created:> for account age filtering instead.
81
+ # Recent activity should be verified via get_developer_profile.
82
+
83
+ q = " ".join(parts)
84
+ resp = await self._client.get(
85
+ "/search/users",
86
+ params={"q": q, "per_page": per_page, "page": page, "sort": "followers", "order": "desc"},
87
+ )
88
+ self._check_rate_limit(resp)
89
+ resp.raise_for_status()
90
+ return resp.json()
91
+
92
+ async def get_repo_info(self, owner: str, repo: str) -> dict:
93
+ cache_key = f"repo:{owner}/{repo}"
94
+ cached = self._cache_get(cache_key)
95
+ if cached is not None:
96
+ return cached
97
+ resp = await self._client.get(f"/repos/{owner}/{repo}")
98
+ self._check_rate_limit(resp)
99
+ resp.raise_for_status()
100
+ data = resp.json()
101
+ self._cache_set(cache_key, data)
102
+ return data
103
+
104
+ async def get_user(self, username: str) -> dict:
105
+ cache_key = f"user:{username}"
106
+ cached = self._cache_get(cache_key)
107
+ if cached is not None:
108
+ return cached
109
+ resp = await self._client.get(f"/users/{username}")
110
+ self._check_rate_limit(resp)
111
+ resp.raise_for_status()
112
+ data = resp.json()
113
+ self._cache_set(cache_key, data)
114
+ return data
115
+
116
+ async def get_user_repos(self, username: str, per_page: int = 100) -> list[dict]:
117
+ cache_key = f"repos:{username}"
118
+ cached = self._cache_get(cache_key)
119
+ if cached is not None:
120
+ return cached
121
+ resp = await self._client.get(
122
+ f"/users/{username}/repos",
123
+ params={"per_page": per_page, "sort": "pushed", "direction": "desc", "type": "owner"},
124
+ )
125
+ self._check_rate_limit(resp)
126
+ resp.raise_for_status()
127
+ data = resp.json()
128
+ self._cache_set(cache_key, data)
129
+ return data
130
+
131
+ async def get_repo_languages(self, owner: str, repo: str) -> dict[str, int]:
132
+ cache_key = f"langs:{owner}/{repo}"
133
+ cached = self._cache_get(cache_key)
134
+ if cached is not None:
135
+ return cached
136
+ resp = await self._client.get(f"/repos/{owner}/{repo}/languages")
137
+ self._check_rate_limit(resp)
138
+ resp.raise_for_status()
139
+ data = resp.json()
140
+ self._cache_set(cache_key, data)
141
+ return data
142
+
143
+ async def get_user_events(self, username: str, max_pages: int = 3) -> list[dict]:
144
+ cache_key = f"events:{username}"
145
+ cached = self._cache_get(cache_key)
146
+ if cached is not None:
147
+ return cached
148
+ all_events: list[dict] = []
149
+ for page in range(1, max_pages + 1):
150
+ resp = await self._client.get(
151
+ f"/users/{username}/events/public",
152
+ params={"per_page": 100, "page": page},
153
+ )
154
+ self._check_rate_limit(resp)
155
+ resp.raise_for_status()
156
+ events = resp.json()
157
+ if not events:
158
+ break
159
+ all_events.extend(events)
160
+ self._cache_set(cache_key, all_events)
161
+ return all_events
162
+
163
+ async def get_profile_readme(self, username: str) -> str | None:
164
+ cache_key = f"readme:{username}"
165
+ cached = self._cache_get(cache_key)
166
+ if cached is not None:
167
+ return cached
168
+ try:
169
+ resp = await self._client.get(
170
+ f"/repos/{username}/{username}/readme",
171
+ headers={"Accept": "application/vnd.github.raw+json"},
172
+ )
173
+ if resp.status_code == 200:
174
+ content = resp.text[:3000]
175
+ self._cache_set(cache_key, content)
176
+ return content
177
+ except httpx.HTTPError:
178
+ pass
179
+ self._cache_set(cache_key, None)
180
+ return None
181
+
182
+ async def get_repo_contributors(
183
+ self, owner: str, repo: str, per_page: int = 30,
184
+ ) -> list[dict]:
185
+ resp = await self._client.get(
186
+ f"/repos/{owner}/{repo}/contributors",
187
+ params={"per_page": per_page},
188
+ )
189
+ self._check_rate_limit(resp)
190
+ resp.raise_for_status()
191
+ return resp.json()