mcp-github-crunchtools 1.3.0__tar.gz → 1.3.1__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 (54) hide show
  1. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/CHANGELOG.md +8 -0
  2. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/PKG-INFO +1 -1
  3. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/pyproject.toml +1 -1
  4. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/src/mcp_github_crunchtools/__init__.py +1 -1
  5. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/src/mcp_github_crunchtools/client.py +19 -4
  6. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/src/mcp_github_crunchtools/server.py +1 -1
  7. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/tests/test_tools.py +23 -0
  8. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/uv.lock +1 -1
  9. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.gatehouse-ignore +0 -0
  10. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  11. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  12. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  13. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.github/dependabot.yml +0 -0
  14. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.github/workflows/ashigaru.yml +0 -0
  15. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.github/workflows/ci.yml +0 -0
  16. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.github/workflows/constitution.yml +0 -0
  17. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.github/workflows/container.yml +0 -0
  18. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.github/workflows/dependabot-automerge.yml +0 -0
  19. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.github/workflows/gatehouse-retriage.yml +0 -0
  20. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.github/workflows/gatehouse.yml +0 -0
  21. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.github/workflows/publish.yml +0 -0
  22. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.github/workflows/security.yml +0 -0
  23. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.gitignore +0 -0
  24. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.pre-commit-config.yaml +0 -0
  25. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.specify/memory/constitution.md +0 -0
  26. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.specify/specs/000-baseline/spec.md +0 -0
  27. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.specify/specs/001-pr-merge-logs-release/spec.md +0 -0
  28. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.specify/specs/002-request-token-merge-gate/spec.md +0 -0
  29. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.specify/templates/plan-template.md +0 -0
  30. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/.specify/templates/spec-template.md +0 -0
  31. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/CLAUDE.md +0 -0
  32. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/Containerfile +0 -0
  33. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/LICENSE +0 -0
  34. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/README.md +0 -0
  35. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/SECURITY.md +0 -0
  36. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/gourmand-exceptions.toml +0 -0
  37. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/gourmand.toml +0 -0
  38. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/server.json +0 -0
  39. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/src/mcp_github_crunchtools/__main__.py +0 -0
  40. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/src/mcp_github_crunchtools/config.py +0 -0
  41. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/src/mcp_github_crunchtools/errors.py +0 -0
  42. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/src/mcp_github_crunchtools/models.py +0 -0
  43. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/src/mcp_github_crunchtools/tools/__init__.py +0 -0
  44. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/src/mcp_github_crunchtools/tools/actions.py +0 -0
  45. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/src/mcp_github_crunchtools/tools/files.py +0 -0
  46. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/src/mcp_github_crunchtools/tools/issues.py +0 -0
  47. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/src/mcp_github_crunchtools/tools/pull_requests.py +0 -0
  48. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/src/mcp_github_crunchtools/tools/releases.py +0 -0
  49. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/src/mcp_github_crunchtools/tools/search.py +0 -0
  50. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/tests/__init__.py +0 -0
  51. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/tests/conftest.py +0 -0
  52. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/tests/test_merge_gate.py +0 -0
  53. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/tests/test_request_token.py +0 -0
  54. {mcp_github_crunchtools-1.3.0 → mcp_github_crunchtools-1.3.1}/tests/test_validation.py +0 -0
@@ -6,6 +6,14 @@ All notable changes to this project are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.3.1] - 2026-10-16
10
+
11
+ ### Fixed
12
+
13
+ - A primary rate limit reported GitHub's `x-ratelimit-reset` epoch as the
14
+ wait ("Retry after 1792152901 seconds"). It now reports the seconds left
15
+ until the reset.
16
+
9
17
  ## [1.3.0] - 2026-10-14
10
18
 
11
19
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mcp-github-crunchtools
3
- Version: 1.3.0
3
+ Version: 1.3.1
4
4
  Summary: Secure MCP server for GitHub issues, pull requests, Actions, releases, files, and search
5
5
  Author: crunchtools.com
6
6
  License-Expression: AGPL-3.0-or-later
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mcp-github-crunchtools"
3
- version = "1.3.0"
3
+ version = "1.3.1"
4
4
  description = "Secure MCP server for GitHub issues, pull requests, Actions, releases, files, and search"
5
5
  requires-python = ">=3.10"
6
6
  license = "AGPL-3.0-or-later"
@@ -29,7 +29,7 @@ import argparse
29
29
 
30
30
  from .server import mcp
31
31
 
32
- __version__ = "1.3.0"
32
+ __version__ = "1.3.1"
33
33
  __all__ = ["main", "mcp"]
34
34
 
35
35
 
@@ -7,6 +7,7 @@ All requests go through this client to ensure consistent security practices.
7
7
  import ipaddress
8
8
  import logging
9
9
  import re
10
+ import time
10
11
  from typing import Any
11
12
  from urllib.parse import urlparse
12
13
 
@@ -25,6 +26,23 @@ from .errors import (
25
26
 
26
27
  logger = logging.getLogger(__name__)
27
28
 
29
+
30
+ def _retry_after_seconds(headers: httpx.Headers) -> int | None:
31
+ """Seconds until a rate-limited call may be retried.
32
+
33
+ ``retry-after`` is already a duration; ``x-ratelimit-reset`` is the UTC
34
+ epoch second the window resets, so it is converted to a duration.
35
+ """
36
+ try:
37
+ if (retry_after := headers.get("retry-after")) is not None:
38
+ return max(0, int(retry_after))
39
+ if (reset := headers.get("x-ratelimit-reset")) is not None:
40
+ return max(0, int(reset) - int(time.time()))
41
+ except ValueError:
42
+ return None
43
+ return None
44
+
45
+
28
46
  MAX_RESPONSE_SIZE = 10 * 1024 * 1024
29
47
  # A job log is streamed and only its tail is kept, so it may be larger than a
30
48
  # buffered API response; past this the download is abandoned.
@@ -302,10 +320,7 @@ class GitHubClient:
302
320
  rate_remaining = response.headers.get("x-ratelimit-remaining")
303
321
 
304
322
  if status_code == 429 or (status_code == 403 and rate_remaining == "0"):
305
- retry_after = response.headers.get("retry-after")
306
- if retry_after is None:
307
- retry_after = response.headers.get("x-ratelimit-reset")
308
- raise RateLimitError(int(retry_after) if retry_after else None)
323
+ raise RateLimitError(_retry_after_seconds(response.headers))
309
324
 
310
325
  if status_code == 401:
311
326
  raise PermissionDeniedError("Valid token with required scopes")
@@ -44,7 +44,7 @@ READ_ONLY = {"readOnlyHint": True}
44
44
 
45
45
  mcp = FastMCP(
46
46
  name="mcp-github",
47
- version="1.3.0",
47
+ version="1.3.1",
48
48
  instructions=(
49
49
  "Secure MCP server for GitHub repositories: issues, pull requests "
50
50
  "(open, review discussion, diffs, CI checks, merge), Actions runs and "
@@ -6,6 +6,7 @@ Integration tests with a real GitHub account should be run separately.
6
6
 
7
7
  import base64
8
8
  import os
9
+ import time
9
10
 
10
11
  import httpx
11
12
  import pytest
@@ -1909,6 +1910,28 @@ class TestClientErrorHandling:
1909
1910
  with _patch_client(resp), pytest.raises(RateLimitError):
1910
1911
  await get_issue(owner="o", repo="r", issue_number=1)
1911
1912
 
1913
+ @pytest.mark.asyncio
1914
+ async def test_403_rate_limit_reset_is_a_duration(self) -> None:
1915
+ """x-ratelimit-reset is an epoch second; the error reports the wait,
1916
+ not the timestamp."""
1917
+ from mcp_github_crunchtools.errors import RateLimitError
1918
+ from mcp_github_crunchtools.tools import get_issue
1919
+
1920
+ resp = _mock_response(
1921
+ status_code=403,
1922
+ json_data={"message": "API rate limit exceeded"},
1923
+ headers={
1924
+ "x-ratelimit-remaining": "0",
1925
+ "x-ratelimit-reset": str(int(time.time()) + 600),
1926
+ },
1927
+ )
1928
+
1929
+ with _patch_client(resp), pytest.raises(RateLimitError) as exc_info:
1930
+ await get_issue(owner="o", repo="r", issue_number=1)
1931
+
1932
+ seconds = int(str(exc_info.value).split("Retry after ")[1].split()[0])
1933
+ assert 590 <= seconds <= 600
1934
+
1912
1935
  @pytest.mark.asyncio
1913
1936
  async def test_403_preserves_github_message_when_not_rate_limited(
1914
1937
  self,
@@ -922,7 +922,7 @@ wheels = [
922
922
 
923
923
  [[package]]
924
924
  name = "mcp-github-crunchtools"
925
- version = "1.3.0"
925
+ version = "1.3.1"
926
926
  source = { editable = "." }
927
927
  dependencies = [
928
928
  { name = "fastmcp" },