mcp-github-crunchtools 1.0.1__py3-none-any.whl

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.
@@ -0,0 +1,287 @@
1
+ """Pull request tools.
2
+
3
+ Tools for listing, fetching, diffing, and checking GitHub pull requests.
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from ..client import get_client
9
+ from ..errors import ValidationError
10
+ from ..models import (
11
+ PR_STATES,
12
+ clamp_per_page,
13
+ resolve_owner,
14
+ validate_name,
15
+ validate_positive_int,
16
+ )
17
+
18
+ DIFF_ACCEPT = "application/vnd.github.diff"
19
+
20
+
21
+ async def list_pull_requests(
22
+ owner: str | None,
23
+ repo: str,
24
+ state: str = "open",
25
+ per_page: int = 30,
26
+ page: int = 1,
27
+ ) -> dict[str, Any]:
28
+ """List pull requests for a repository.
29
+
30
+ Args:
31
+ owner: Repository owner (defaults to GITHUB_DEFAULT_ORG if unset)
32
+ repo: Repository name
33
+ state: Filter by state (open, closed, all)
34
+ per_page: Results per page (max 100)
35
+ page: Page number
36
+
37
+ Returns:
38
+ List of pull requests with pagination info
39
+ """
40
+ owner = resolve_owner(owner)
41
+ repo = validate_name(repo, "repo")
42
+ if state not in PR_STATES:
43
+ allowed = ", ".join(sorted(PR_STATES))
44
+ raise ValidationError(f"Invalid state. Allowed: {allowed}")
45
+
46
+ client = get_client()
47
+
48
+ params: dict[str, Any] = {
49
+ "state": state,
50
+ "per_page": clamp_per_page(per_page),
51
+ "page": validate_positive_int(page, "page"),
52
+ }
53
+
54
+ return await client.get(f"/repos/{owner}/{repo}/pulls", params=params)
55
+
56
+
57
+ async def get_pull_request(
58
+ owner: str | None,
59
+ repo: str,
60
+ pull_number: int,
61
+ ) -> dict[str, Any]:
62
+ """Get a single pull request by number.
63
+
64
+ Args:
65
+ owner: Repository owner (defaults to GITHUB_DEFAULT_ORG if unset)
66
+ repo: Repository name
67
+ pull_number: Pull request number
68
+
69
+ Returns:
70
+ Pull request details including head/base refs and merge status
71
+ """
72
+ owner = resolve_owner(owner)
73
+ repo = validate_name(repo, "repo")
74
+ pull_number = validate_positive_int(pull_number, "pull_number")
75
+
76
+ client = get_client()
77
+ return await client.get(f"/repos/{owner}/{repo}/pulls/{pull_number}")
78
+
79
+
80
+ async def get_pull_request_diff(
81
+ owner: str | None,
82
+ repo: str,
83
+ pull_number: int,
84
+ ) -> dict[str, Any]:
85
+ """Get the unified diff for a pull request.
86
+
87
+ Args:
88
+ owner: Repository owner (defaults to GITHUB_DEFAULT_ORG if unset)
89
+ repo: Repository name
90
+ pull_number: Pull request number
91
+
92
+ Returns:
93
+ Dictionary with the diff text under the "content" key
94
+ """
95
+ owner = resolve_owner(owner)
96
+ repo = validate_name(repo, "repo")
97
+ pull_number = validate_positive_int(pull_number, "pull_number")
98
+
99
+ client = get_client()
100
+ return await client.get(
101
+ f"/repos/{owner}/{repo}/pulls/{pull_number}",
102
+ accept=DIFF_ACCEPT,
103
+ )
104
+
105
+
106
+ def _classify_checks(
107
+ check_runs: dict[str, Any], combined_status: dict[str, Any]
108
+ ) -> dict[str, list[dict[str, Any]]]:
109
+ """Sort check-runs and commit-status contexts into verdict buckets.
110
+
111
+ Check-run conclusions failure/cancelled/timed_out/action_required are
112
+ failures; skipped/neutral are skips (NOT failures); success passes. An
113
+ incomplete run, or any other completed conclusion, is pending. Commit
114
+ statuses map failure/error -> failing, pending -> pending, success ->
115
+ passed.
116
+ """
117
+ buckets: dict[str, list[dict[str, Any]]] = {
118
+ "failing": [],
119
+ "pending": [],
120
+ "skipped": [],
121
+ "passed": [],
122
+ }
123
+
124
+ for run in check_runs.get("check_runs", []):
125
+ name = run.get("name")
126
+ status = run.get("status")
127
+ if status != "completed":
128
+ buckets["pending"].append({"name": name, "status": status})
129
+ continue
130
+ match run.get("conclusion"):
131
+ case (
132
+ "failure" | "cancelled" | "timed_out" | "action_required"
133
+ ) as conclusion:
134
+ url = run.get("html_url") or run.get("details_url") or ""
135
+ buckets["failing"].append(
136
+ {"name": name, "conclusion": conclusion, "url": url}
137
+ )
138
+ case ("skipped" | "neutral") as conclusion:
139
+ buckets["skipped"].append({"name": name, "conclusion": conclusion})
140
+ case "success":
141
+ buckets["passed"].append({"name": name})
142
+ case _:
143
+ buckets["pending"].append({"name": name, "status": status})
144
+
145
+ for ctx in combined_status.get("statuses", []):
146
+ name = ctx.get("context")
147
+ match ctx.get("state"):
148
+ case ("failure" | "error") as state:
149
+ url = ctx.get("target_url") or ""
150
+ buckets["failing"].append(
151
+ {"name": name, "conclusion": state, "url": url}
152
+ )
153
+ case "pending":
154
+ buckets["pending"].append({"name": name, "status": "pending"})
155
+ case "success":
156
+ buckets["passed"].append({"name": name})
157
+
158
+ return buckets
159
+
160
+
161
+ async def get_pull_request_checks(
162
+ owner: str | None,
163
+ repo: str,
164
+ pull_number: int,
165
+ ) -> dict[str, Any]:
166
+ """Get a CI verdict for a pull request that distinguishes skip from fail.
167
+
168
+ Resolves the PR head SHA, then classifies every check-run (GitHub Checks
169
+ API) and legacy commit-status context into exactly one bucket: passed,
170
+ failing, pending, or skipped.
171
+
172
+ IMPORTANT: SKIPPED checks (conclusion "skipped" or "neutral") are NOT
173
+ failures and do not block merging. The legacy combined commit status can
174
+ report an overall state of "failure" when checks are merely skipped, which
175
+ is why this tool classifies each check explicitly instead of trusting that
176
+ aggregate. Use the ``ready_to_merge`` boolean as the signal for whether the
177
+ PR is clear to merge: it is True only when there are no failing and no
178
+ pending checks and the PR is not known-unmergeable.
179
+
180
+ Args:
181
+ owner: Repository owner (defaults to GITHUB_DEFAULT_ORG if unset)
182
+ repo: Repository name
183
+ pull_number: Pull request number
184
+
185
+ Returns:
186
+ A verdict with the head SHA, mergeability, per-bucket check lists, a
187
+ summary count, and the ``ready_to_merge`` signal
188
+ """
189
+ owner = resolve_owner(owner)
190
+ repo = validate_name(repo, "repo")
191
+ pull_number = validate_positive_int(pull_number, "pull_number")
192
+
193
+ client = get_client()
194
+
195
+ pr = await client.get(f"/repos/{owner}/{repo}/pulls/{pull_number}")
196
+ head = pr.get("head") or {}
197
+ sha = head.get("sha")
198
+ if not sha:
199
+ raise ValidationError("Could not resolve pull request head SHA")
200
+
201
+ check_runs = await client.get(
202
+ f"/repos/{owner}/{repo}/commits/{sha}/check-runs"
203
+ )
204
+ combined_status = await client.get(
205
+ f"/repos/{owner}/{repo}/commits/{sha}/status"
206
+ )
207
+
208
+ buckets = _classify_checks(check_runs, combined_status)
209
+ failing = buckets["failing"]
210
+ pending = buckets["pending"]
211
+ skipped = buckets["skipped"]
212
+ passed = buckets["passed"]
213
+
214
+ mergeable = pr.get("mergeable")
215
+ ready_to_merge = (
216
+ not failing and not pending and mergeable is not False
217
+ )
218
+
219
+ return {
220
+ "pull_number": pull_number,
221
+ "head_sha": sha,
222
+ "mergeable": mergeable,
223
+ "mergeable_state": pr.get("mergeable_state", ""),
224
+ "ready_to_merge": ready_to_merge,
225
+ "summary": {
226
+ "passed": len(passed),
227
+ "failing": len(failing),
228
+ "pending": len(pending),
229
+ "skipped": len(skipped),
230
+ },
231
+ "failing": failing,
232
+ "pending": pending,
233
+ "skipped": skipped,
234
+ "passed": passed,
235
+ }
236
+
237
+
238
+ async def update_pull_request(
239
+ owner: str | None,
240
+ repo: str,
241
+ pull_number: int,
242
+ state: str | None = None,
243
+ title: str | None = None,
244
+ body: str | None = None,
245
+ ) -> dict[str, Any]:
246
+ """Update a pull request — including closing or reopening it.
247
+
248
+ Note: this does NOT merge. Set state="closed" to close a PR without merging.
249
+
250
+ Args:
251
+ owner: Repository owner (defaults to GITHUB_DEFAULT_ORG if unset)
252
+ repo: Repository name
253
+ pull_number: Pull request number
254
+ state: "open" or "closed" (set "closed" to close the PR)
255
+ title: New title (optional)
256
+ body: New body (optional)
257
+
258
+ Returns:
259
+ Updated PR details (number, state, html_url, title)
260
+ """
261
+ owner = resolve_owner(owner)
262
+ repo = validate_name(repo, "repo")
263
+ pull_number = validate_positive_int(pull_number, "pull_number")
264
+
265
+ json_data: dict[str, Any] = {}
266
+ if state is not None:
267
+ if state not in ("open", "closed"):
268
+ raise ValidationError("state must be 'open' or 'closed'")
269
+ json_data["state"] = state
270
+ if title is not None:
271
+ json_data["title"] = title
272
+ if body is not None:
273
+ json_data["body"] = body
274
+ if not json_data:
275
+ raise ValidationError("no fields to update")
276
+
277
+ client = get_client()
278
+ result = await client.patch(
279
+ f"/repos/{owner}/{repo}/pulls/{pull_number}",
280
+ json_data=json_data,
281
+ )
282
+ return {
283
+ "number": result.get("number"),
284
+ "state": result.get("state"),
285
+ "html_url": result.get("html_url"),
286
+ "title": result.get("title"),
287
+ }
@@ -0,0 +1,81 @@
1
+ """Search tools.
2
+
3
+ Tools for searching code and issues/PRs across GitHub.
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from ..client import get_client
9
+ from ..errors import ValidationError
10
+ from ..models import (
11
+ MAX_QUERY_LENGTH,
12
+ clamp_per_page,
13
+ validate_positive_int,
14
+ )
15
+
16
+
17
+ def _validate_query(query: str) -> str:
18
+ if not query or not query.strip():
19
+ raise ValidationError("query must not be empty")
20
+ query = query.strip()
21
+ if len(query) > MAX_QUERY_LENGTH:
22
+ raise ValidationError("query is too long")
23
+ return query
24
+
25
+
26
+ async def search_code(
27
+ query: str,
28
+ per_page: int = 30,
29
+ page: int = 1,
30
+ ) -> dict[str, Any]:
31
+ """Search for code across GitHub.
32
+
33
+ Uses GitHub's code search syntax (e.g., "addClass repo:jquery/jquery").
34
+
35
+ Args:
36
+ query: Search query string
37
+ per_page: Results per page (max 100)
38
+ page: Page number
39
+
40
+ Returns:
41
+ Search results with total count and matched code items
42
+ """
43
+ query = _validate_query(query)
44
+
45
+ client = get_client()
46
+ params: dict[str, Any] = {
47
+ "q": query,
48
+ "per_page": clamp_per_page(per_page),
49
+ "page": validate_positive_int(page, "page"),
50
+ }
51
+
52
+ return await client.get("/search/code", params=params)
53
+
54
+
55
+ async def search_issues(
56
+ query: str,
57
+ per_page: int = 30,
58
+ page: int = 1,
59
+ ) -> dict[str, Any]:
60
+ """Search for issues and pull requests across GitHub.
61
+
62
+ Uses GitHub's issue search syntax (e.g., "is:open is:pr author:octocat").
63
+
64
+ Args:
65
+ query: Search query string
66
+ per_page: Results per page (max 100)
67
+ page: Page number
68
+
69
+ Returns:
70
+ Search results with total count and matched issues/PRs
71
+ """
72
+ query = _validate_query(query)
73
+
74
+ client = get_client()
75
+ params: dict[str, Any] = {
76
+ "q": query,
77
+ "per_page": clamp_per_page(per_page),
78
+ "page": validate_positive_int(page, "page"),
79
+ }
80
+
81
+ return await client.get("/search/issues", params=params)
@@ -0,0 +1,263 @@
1
+ Metadata-Version: 2.5
2
+ Name: mcp-github-crunchtools
3
+ Version: 1.0.1
4
+ Summary: Secure MCP server for GitHub issues, pull requests, files, and search
5
+ Author: crunchtools.com
6
+ License-Expression: AGPL-3.0-or-later
7
+ License-File: LICENSE
8
+ Keywords: devops,fastmcp,github,mcp
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: fastmcp>=2.0
18
+ Requires-Dist: httpx>=0.28
19
+ Requires-Dist: pydantic>=2.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: mypy>=1.13; extra == 'dev'
22
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
23
+ Requires-Dist: pytest>=8.0; extra == 'dev'
24
+ Requires-Dist: ruff>=0.8; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # MCP GitHub CrunchTools
28
+
29
+ A secure MCP (Model Context Protocol) server for GitHub issues, pull requests, repository files, and search. Works with github.com and GitHub Enterprise Server.
30
+
31
+ ## Overview
32
+
33
+ This MCP server is designed to be:
34
+
35
+ - **Secure by default** - STRIDE threat model (see [SECURITY.md](SECURITY.md)), Pydantic input validation, and the API token held as a `SecretStr` to prevent accidental logging
36
+ - **No third-party services** - Runs locally via stdio, your API token never leaves your machine
37
+ - **Multi-instance** - Works with github.com or GitHub Enterprise Server via configurable API URL
38
+ - **Cross-platform** - Works on Linux, macOS, and Windows
39
+ - **Automatically updated** - GitHub Actions monitor for CVEs and update dependencies
40
+ - **Containerized** - Available at `quay.io/crunchtools/mcp-github` built on [Hummingbird Python](https://quay.io/repository/hummingbird/python) base image
41
+
42
+ ## Naming Convention
43
+
44
+ | Component | Name |
45
+ |-----------|------|
46
+ | GitHub repo | [crunchtools/mcp-github](https://github.com/crunchtools/mcp-github) |
47
+ | Container | `quay.io/crunchtools/mcp-github` |
48
+ | Python package (PyPI) | `mcp-github-crunchtools` |
49
+ | CLI command | `mcp-github-crunchtools` |
50
+ | Module import | `mcp_github_crunchtools` |
51
+
52
+ ## Why Hummingbird?
53
+
54
+ The container image is built on the [Hummingbird Python base image](https://quay.io/repository/hummingbird/python) from [Project Hummingbird](https://github.com/hummingbird-project), which provides:
55
+
56
+ - **Minimal CVE exposure** - Built with a minimal package set, dramatically reducing the attack surface
57
+ - **Regular updates** - Security patches are applied promptly
58
+ - **Optimized for Python** - Pre-configured Python environment
59
+ - **Production-ready** - Proper signal handling and non-root user defaults
60
+
61
+ ## Features
62
+
63
+ ### Issues (3 tools)
64
+ - `list_issues_tool` - List issues for a repository (pull requests excluded)
65
+ - `get_issue_tool` - Get a single issue by number
66
+ - `create_issue_comment_tool` - Comment on an issue or pull request (write)
67
+
68
+ ### Pull Requests (4 tools)
69
+ - `list_pull_requests_tool` - List pull requests for a repository
70
+ - `get_pull_request_tool` - Get a single pull request by number
71
+ - `get_pull_request_diff_tool` - Get the unified diff for a pull request
72
+ - `get_pull_request_checks_tool` - Combined CI status (check-runs + commit status)
73
+
74
+ ### Files (2 tools)
75
+ - `get_file_content_tool` - Read decoded file content from a repository
76
+ - `list_repo_tree_tool` - List the git tree (files and directories)
77
+
78
+ ### Search (2 tools)
79
+ - `search_code_tool` - Search code across GitHub
80
+ - `search_issues_tool` - Search issues and pull requests across GitHub
81
+
82
+ ## Installation
83
+
84
+ ### With uvx (Recommended)
85
+
86
+ ```bash
87
+ uvx mcp-github-crunchtools
88
+ ```
89
+
90
+ ### With pip
91
+
92
+ ```bash
93
+ pip install mcp-github-crunchtools
94
+ ```
95
+
96
+ ### With Container
97
+
98
+ ```bash
99
+ podman run -e GITHUB_TOKEN=your_token \
100
+ quay.io/crunchtools/mcp-github
101
+ ```
102
+
103
+ ## Configuration
104
+
105
+ ### Environment Variables
106
+
107
+ | Variable | Required | Default | Description |
108
+ |----------|----------|---------|-------------|
109
+ | `GITHUB_TOKEN` | Yes | — | GitHub Personal Access Token |
110
+ | `GITHUB_API_URL` | No | `https://api.github.com` | API base URL (set for GHES) |
111
+ | `GITHUB_DEFAULT_ORG` | No | — | Default owner when a tool omits `owner` |
112
+ | `SSL_CERT_FILE` | No | — | Custom CA bundle path, for self-hosted GHES with an internal CA |
113
+ | `GITHUB_SSL_VERIFY` | No | `true` | Set `false` to disable TLS verification (not recommended) |
114
+
115
+ ### Creating a GitHub Personal Access Token
116
+
117
+ 1. **Navigate to token settings**
118
+ - Go to https://github.com/settings/tokens
119
+
120
+ 2. **Create a token**
121
+ - **Name**: `mcp-github-crunchtools`
122
+ - **Expiration**: Set an appropriate date (90 days recommended)
123
+ - **Scopes**: Grant read access to contents, issues, and pull requests.
124
+ Add write to issues/PRs only if you need `create_issue_comment_tool`.
125
+
126
+ 3. **Copy and Store Token**
127
+ - Copy the token immediately (shown only once)
128
+ - Store securely in a password manager
129
+
130
+ ### Add to Claude Code
131
+
132
+ ```bash
133
+ claude mcp add mcp-github-crunchtools \
134
+ --env GITHUB_TOKEN=your_token_here \
135
+ -- uvx mcp-github-crunchtools
136
+ ```
137
+
138
+ For GitHub Enterprise Server:
139
+
140
+ ```bash
141
+ claude mcp add mcp-github-crunchtools \
142
+ --env GITHUB_TOKEN=your_token_here \
143
+ --env GITHUB_API_URL=https://ghe.example.com/api/v3 \
144
+ -- uvx mcp-github-crunchtools
145
+ ```
146
+
147
+ For the container version:
148
+
149
+ ```bash
150
+ claude mcp add mcp-github-crunchtools \
151
+ --env GITHUB_TOKEN=your_token_here \
152
+ -- podman run -i --rm -e GITHUB_TOKEN quay.io/crunchtools/mcp-github
153
+ ```
154
+
155
+ ## Usage Examples
156
+
157
+ ### List Issues
158
+
159
+ ```
160
+ User: List open issues for crunchtools/mcp-github
161
+ Assistant: [calls list_issues_tool with owner="crunchtools", repo="mcp-github"]
162
+ ```
163
+
164
+ ### Review a Pull Request
165
+
166
+ ```
167
+ User: Show me the diff for PR #5 in crunchtools/mcp-github
168
+ Assistant: [calls get_pull_request_diff_tool with pull_number=5]
169
+ ```
170
+
171
+ ### Check CI Status
172
+
173
+ ```
174
+ User: Did the checks pass on pull request 5?
175
+ Assistant: [calls get_pull_request_checks_tool with pull_number=5]
176
+ ```
177
+
178
+ ### Read a File
179
+
180
+ ```
181
+ User: Show me src/server.py from crunchtools/mcp-github
182
+ Assistant: [calls get_file_content_tool with path="src/server.py"]
183
+ ```
184
+
185
+ ### Search
186
+
187
+ ```
188
+ User: Find code using FastMCP in crunchtools repos
189
+ Assistant: [calls search_code_tool with query="FastMCP org:crunchtools"]
190
+ ```
191
+
192
+ ## Security
193
+
194
+ This server was designed with security as a primary concern. See [SECURITY.md](SECURITY.md) for details.
195
+
196
+ ### Key Security Features
197
+
198
+ 1. **Token Protection**
199
+ - Stored as SecretStr (never accidentally logged)
200
+ - Environment variable only (never in files or args)
201
+ - Sanitized from all error messages
202
+
203
+ 2. **Input Validation**
204
+ - Pydantic models for write inputs
205
+ - Allowlist character validation for owner/repo names
206
+ - Path traversal prevention for file reads
207
+
208
+ 3. **API Hardening**
209
+ - Bearer-token auth and pinned GitHub API version
210
+ - HTTPS enforcement (except localhost)
211
+ - TLS certificate validation
212
+ - Request timeouts (30s)
213
+ - Response size limits (10MB)
214
+
215
+ 4. **Automated CVE Scanning**
216
+ - GitHub Actions scan dependencies
217
+ - Container security scanning with Trivy
218
+
219
+ ## Development
220
+
221
+ ### Setup
222
+
223
+ ```bash
224
+ git clone https://github.com/crunchtools/mcp-github.git
225
+ cd mcp-github
226
+ uv sync --all-extras
227
+ ```
228
+
229
+ ### Run Tests
230
+
231
+ ```bash
232
+ uv run pytest
233
+ ```
234
+
235
+ ### Lint and Type Check
236
+
237
+ ```bash
238
+ uv run ruff check src tests
239
+ uv run mypy src
240
+ ```
241
+
242
+ ### Build Container
243
+
244
+ ```bash
245
+ podman build -t mcp-github .
246
+ ```
247
+
248
+ ## License
249
+
250
+ AGPL-3.0-or-later
251
+
252
+ ## Contributing
253
+
254
+ Contributions welcome! Please read SECURITY.md before submitting security-related changes.
255
+
256
+ ## Links
257
+
258
+ - [GitHub REST API Documentation](https://docs.github.com/en/rest)
259
+ - [FastMCP Documentation](https://gofastmcp.com/)
260
+ - [MCP Specification](https://modelcontextprotocol.io/)
261
+ - [crunchtools.com](https://crunchtools.com)
262
+
263
+ <!-- mcp-name: io.github.crunchtools/github -->
@@ -0,0 +1,18 @@
1
+ mcp_github_crunchtools/__init__.py,sha256=TA95qz8FfAAQot2W1bJEylmCEBzv6yEN8np_T188lGU,1630
2
+ mcp_github_crunchtools/__main__.py,sha256=BYo13fF7OXaJiVbx-g9OxLXxXFX_FtH8SjvIiBDw3WM,115
3
+ mcp_github_crunchtools/client.py,sha256=Tg2_4s86Fj6iXhU7FRQgO0nTBJm35kMYCvIVG-IpscY,9112
4
+ mcp_github_crunchtools/config.py,sha256=n9NpxhxJdigCj_5T7C5qbBqJJMkyikxHooxPXJteOVw,4093
5
+ mcp_github_crunchtools/errors.py,sha256=z99ivMY95PrVu0a1VV9S88_d51F8NWOmoAk23K5p20g,1919
6
+ mcp_github_crunchtools/models.py,sha256=KgxcbSk0Zxngr0oOF_d9xDzt7L3_n5Wo4lOSyVqwoFE,5871
7
+ mcp_github_crunchtools/server.py,sha256=GYOBOSFTDKCdUpdlvQGdxOFItSBMU18MXXp0TddehZc,13726
8
+ mcp_github_crunchtools/tools/__init__.py,sha256=poTwR5xIH15MmUPSYlrGQI2HcAD107yz6UlsUzkkRyQ,1041
9
+ mcp_github_crunchtools/tools/actions.py,sha256=rli_XZ_wwO_gx1sTcY9v1ZrgadQRbtvWIFH14DZIRnA,5884
10
+ mcp_github_crunchtools/tools/files.py,sha256=3F2nCeGVoP8GKTFuhTPhyOxQBEBxp4lWWTmgv907axQ,3635
11
+ mcp_github_crunchtools/tools/issues.py,sha256=XQ8CWLh4YRsHWEN3mQRnG7XwuO9Q0_6HB82HYOXSfhE,6429
12
+ mcp_github_crunchtools/tools/pull_requests.py,sha256=INN75H71d1XNVvWieMLlx7JAfm89v02i_mCcvDQwklk,9068
13
+ mcp_github_crunchtools/tools/search.py,sha256=FVLA0J7J_GNCh39qZQlFZlMRn4XyVN54ODkxVJbHbQg,1961
14
+ mcp_github_crunchtools-1.0.1.dist-info/METADATA,sha256=s221ntasJ3PZw1jX_lJb0ylHn_0qF9ePLKl9b93zcko,7848
15
+ mcp_github_crunchtools-1.0.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
16
+ mcp_github_crunchtools-1.0.1.dist-info/entry_points.txt,sha256=RwJaHs3zcHeGjhsSDbZgxpGpDEWBKOFIZojcXXCpIMs,71
17
+ mcp_github_crunchtools-1.0.1.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
18
+ mcp_github_crunchtools-1.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mcp-github-crunchtools = mcp_github_crunchtools:main