Skip to content

Commit ef0233d

Browse files
authored
feat(auth): in-memory and Redis OAuth token storage (#233)
* feat(auth): add in-memory and Redis OAuth token storage to avoid disk writes Replaces the default FileTreeStore (disk-based) with a MemoryStore (default) or RedisStore (when REDIS_HOST_PORT is set) so no OAuth token state is ever written to the filesystem — a security requirement for K8s deployments. * refactor(auth): improve docstrings for clarity and consistency * fix(auth): harden Redis token store configuration - Safe REDIS_HOST_DB parse: ValueError at module load with a clear message instead of a bare int() crash - Add REDIS_PASSWORD env var for Redis AUTH, wired into RedisStore constructor - Move MemoryStore import to top level; parse host/port explicitly so build_token_store() doesn't rely on URL string manipulation - Document REDIS_PASSWORD in README, CLAUDE.md, and K8s manifest (Secret-backed, optional: true) * feat(auth): support TLS Redis via rediss:// URI scheme REDIS_HOST_PORT now accepts a full URI (redis:// or rediss://) in addition to bare host:port. When rediss:// is provided, the Redis client is constructed with ssl=True — bypassing key_value's URL parser which ignores the scheme. Password and db can be embedded in the URI or fall back to REDIS_HOST_DB / REDIS_PASSWORD env vars. * docs: update architecture diagram to include auth layer and token store Add the auth layer (APIKeyVerifier / GitHub OAuth2), MemoryStore, and RedisStore (with TLS) to the README ASCII diagram. * docs: fix architecture diagram alignment for token store / Redis Move token store (MemoryStore / RedisStore) to a right-hand branch off the Auth Layer box so the main vertical flow to PRIssueAnalyser is unbroken — the previous layout left a floating | and v after the Redis box with no visible connection back to the flow. * fix(deps): regenerate requirements.txt to include redis redis>=5.0.0 was added to pyproject.toml and uv.lock but requirements.txt was never regenerated. The Dockerfile installs deps from requirements.txt, so redis was absent from the Docker image, causing a ModuleNotFoundError at runtime when REDIS_HOST_PORT is set. * refactor(auth): remove REDIS_HOST_DB env var The database index can be embedded directly in REDIS_HOST_PORT as a URI path component (redis://host:6379/1), making a separate REDIS_HOST_DB fallback redundant. Bare host:port or URIs without a /db path default to database 0. * refactor(auth): move lazy imports to module level Also adds em-dash style guideline to the MCP server prompt. * style: fix docstring and markdown formatting violations - Add blank line after h1 heading in README.md (MD022) - Convert _PermissiveGitHubProvider docstring to NumPy style (D203, D212, D205) - Convert resolve_token Raises section to NumPy style (D406, D407, D413) * fix(auth): namespace Redis collections by base URL to isolate shared-Redis deployments Wrap RedisStore with PrefixCollectionsWrapper using a 12-char SHA-256 hash of GITHUB_OAUTH_BASE_URL as the collection prefix. Two server instances that share the same Redis instance (e.g. personal and company deployments) now write to fully isolated keyspaces, preventing one server's OAuth session from overwriting or colliding with the other's JTI mappings, upstream tokens, and client registrations. * style: fix docstring and complexity violations in auth.py Extract _build_redis_client() helper to reduce build_token_store() cyclomatic complexity below the limit of 8. Fix D212 and D205 docstring violations in build_token_store() and get_oauth_verifier(). * feat(auth): auto-derive stable JWT signing key for multi-pod OAuth Add _derive_jwt_signing_key() that automatically derives a deterministic JWT signing key from GITHUB_OAUTH_CLIENT_SECRET when JWT_SIGNING_KEY is not explicitly set. This ensures all pods in a multi-replica deployment share the same signing key, preventing token validation failures caused by divergent derived keys. An explicit JWT_SIGNING_KEY env var can still be provided as an override. * test(auth): add tests for Redis client and token store; fix invalid DB validation - Add tests/test_auth.py with 15 tests covering _build_redis_client URI parsing (host:port, redis://, rediss://, db path, password precedence, invalid db raises) and build_token_store backend selection (MemoryStore default, RedisStore construction, PrefixCollectionsWrapper applied/skipped, prefix stability and isolation) - _build_redis_client now raises ValueError for non-integer db path instead of silently defaulting to 0, matching the documented test plan behaviour - Fix resolve_token docstring: move summary onto opening line (D212) and add blank line before body (D205) * style: fix Codacy violations — complexity, docstrings, bandit test exclusion - Extract _parse_redis_db() from _build_redis_client() to bring cyclomatic complexity from 9 to 6 (limit is 8) - Switch multi-line docstrings in auth.py to D213 style (summary on second line) for build_token_store, _derive_jwt_signing_key, and resolve_token - Add blank line before class docstrings in tests (D203) - Add [tool.bandit] exclude_dirs=["tests"] to pyproject.toml so assert statements and test-fixture strings are not flagged as security issues - Suppress ANN annotations for test helpers in per-file-ignores - Update CLAUDE.md: document D213+D203 as the enforced convention (Codacy), note ruff ignores both conflicting pairs, update Testing section
1 parent 518b204 commit ef0233d

11 files changed

Lines changed: 488 additions & 10 deletions

File tree

CLAUDE.md

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
MCP server providing GitHub integration for LLMs. Exposes tools for PR analysis, issue management, tags/releases, user search/activity, and IP info. Runs in stdio mode (IDE integration) or HTTP mode (remote access).
8+
9+
## Architecture
10+
11+
```
12+
MCP Client (IDE/LLM)
13+
|
14+
| stdio or HTTP (streamable-http)
15+
v
16+
PRIssueAnalyser (FastMCP) -> src/mcp_github/issues_pr_analyser.py
17+
|
18+
| calls
19+
v
20+
GitHubIntegration -> src/mcp_github/github_integration.py
21+
|-> REST API (httpx) -> GitHub REST API v3
22+
|-> GraphQL API (GraphQLClient) -> GitHub GraphQL v4
23+
IPIntegration -> src/mcp_github/ip_integration.py
24+
|-> REST API (httpx) -> ipinfo.io
25+
```
26+
27+
**Tool Registration**: `_register_tools()` calls `register_tools()` on both `GitHubIntegration` and `IPIntegration` instances. `register_tools()` uses `inspect.getmembers()` to find all public methods and registers each as an MCP tool via `mcp.add_tool()`. **To add a new tool, add a public method with type-annotated parameters to either class** — it auto-registers.
28+
29+
**Python 3.14 compatibility**: Tool registration uses `inspect.isroutine()` (not `ismethod()` or `isfunction()`) because Python 3.14 changed bound method detection — `inspect.ismethod()` returns `False` for methods accessed through class instances.
30+
31+
**ResponseCachingMiddleware removed**: Was causing `tools/list` to fail with "TTL is invalid" error in FastMCP 3.2.4 when `ttl=0` was set. Removed since list operations are not cached anyway.
32+
33+
**FastMCP providers**: `Choice` and `GenerativeUI` from `fastmcp.apps` are added to the server in `__init__`. `fastmcp[apps]>=3.2.4` is in `pyproject.toml`.
34+
35+
**Skills directory**: `src/mcp_github/skills/` contains markdown skill files exposed as MCP resources via `SkillsDirectoryProvider` under the `skill://` URI scheme. Each subdirectory (e.g. `pr-analysis/`, `issue-management/`) contains a `SKILL.md` with workflow guidance for the LLM.
36+
37+
**Exposed MCP Tools** (from `GitHubIntegration`):
38+
39+
- `get_pr_diff` — raw patch/diff from patch-diff.githubusercontent.com
40+
- `get_pr_content` — PR metadata (title, description, author, state)
41+
- `update_pr_description` — PATCH PR body
42+
- `create_pr` — open a new pull request
43+
- `merge_pr` — merge a PR (merge/squash/rebase)
44+
- `add_pr_comments` — add a general comment to a PR
45+
- `add_inline_pr_comment` — add an inline review comment at a specific file/line
46+
- `update_reviews` — submit a PR review (APPROVE/REQUEST_CHANGES/COMMENT)
47+
- `update_assignees` — assign users to a PR or issue
48+
- `create_issue` — create a GitHub issue (auto-adds `mcp` label)
49+
- `update_issue` — update issue title/body/state/labels
50+
- `list_open_issues_prs` — list open issues or PRs with filtering
51+
- `get_latest_sha` — get HEAD SHA of default branch
52+
- `create_tag` — create an annotated git tag
53+
- `create_release` — publish a GitHub release
54+
- `search_user` — user profile via GraphQL
55+
- `get_user_activities` — commit/PR/issue/review contributions via GraphQL
56+
57+
From `IPIntegration`: `get_ipv4_info`, `get_ipv6_info`
58+
59+
**IPv6 socket override**: `IPIntegration.get_ipv6_info()` uses `httpx.HTTPTransport(local_address="::")` to force IPv6 socket family. Do not refactor this into a persistent setting.
60+
61+
**GraphQL Schema Notes** (from recent fixes):
62+
63+
- `CreatedCommitContribution`: has `commitCount`, `url`, `occurredAt` — NOT `commit { message }`
64+
- `CreatedPullRequestReviewContribution`: has `pullRequestReview { state url }` — NOT `review`
65+
66+
## Development Commands
67+
68+
```bash
69+
# Setup (Python >=3.14 required)
70+
uv sync --group dev
71+
72+
# Run (stdio mode)
73+
export GITHUB_TOKEN="<token>"
74+
uvx ./
75+
76+
# Run (HTTP mode — streamable-http transport)
77+
export GITHUB_TOKEN="<token>"
78+
export MCP_ENABLE_REMOTE=true
79+
uvx ./
80+
81+
# Docker build and run (HTTP mode)
82+
docker build -t mcp-github .
83+
docker run -e GITHUB_TOKEN="<token>" -p 8081:8081 mcp-github
84+
85+
# Code quality
86+
ruff check . --fix
87+
ruff format .
88+
uv run pyright src/mcp_github/
89+
90+
# Tests (pytest is configured in pyproject.toml; no tests exist yet)
91+
uv run pytest
92+
93+
# Regenerate requirements.txt after dependency changes (auto-generated — do not edit manually)
94+
uv export --frozen --no-emit-project > requirements.txt
95+
96+
# Test HTTP auth (after starting server in HTTP mode)
97+
curl -H "Authorization: Bearer <GITHUB_TOKEN>" http://localhost:8081/mcp
98+
```
99+
100+
## Environment Variables
101+
102+
| Variable | Required | Default | Description |
103+
|----------|----------|---------|-------------|
104+
| `GITHUB_TOKEN` | Yes | - | GitHub PAT with `repo` scope; also used as the bearer token in HTTP mode |
105+
| `MCP_ENABLE_REMOTE` | No | unset | Any non-empty string enables HTTP/streamable-http mode |
106+
| `PORT` | No | 8081 | HTTP server port |
107+
| `HOST` | No | `localhost` | HTTP server host |
108+
| `GITHUB_API_TIMEOUT` | No | `5` | Timeout in seconds for both REST and GraphQL requests |
109+
| `GITHUB_OAUTH_CLIENT_ID` | No | unset | GitHub OAuth App client ID — enables OAuth2 auth path via `GitHubProvider` |
110+
| `GITHUB_OAUTH_CLIENT_SECRET` | No | unset | GitHub OAuth App client secret — required alongside `GITHUB_OAUTH_CLIENT_ID` |
111+
| `GITHUB_OAUTH_BASE_URL` | No | unset | Public base URL of the server — required by the OAuth2 redirect flow |
112+
| `JWT_SIGNING_KEY` | No | unset | Secret for signing FastMCP JWT tokens. When set, used as an explicit override. When omitted, a stable key is derived automatically from `GITHUB_OAUTH_CLIENT_SECRET` so all pods share the same key without requiring an additional env var. Rotating the GitHub OAuth App secret invalidates all stored sessions |
113+
| `FASTMCP_HOME` | No | platformdirs user data dir | Directory for FastMCP state (OAuth client registrations, token store). Set to `/tmp` in the Dockerfile so the read-only K8s filesystem is not a problem. State stored here is ephemeral when backed by an emptyDir; clients re-register automatically after pod restarts. |
114+
| `REDIS_HOST_PORT` | No | unset | Redis connection string. Accepts `host:port` or a full URI: `redis://[:password@]host:port[/db]` (plaintext) or `rediss://[:password@]host:port[/db]` (TLS). The scheme controls TLS — no extra env var needed. |
115+
| `REDIS_PASSWORD` | No | unset | Redis AUTH password fallback — used when not embedded in the URI. Store in a K8s Secret, not a ConfigMap. |
116+
117+
**HTTP auth**: In HTTP mode, `APIKeyVerifier` is wired with `GITHUB_TOKEN`. Clients must send `Authorization: Bearer <GITHUB_TOKEN>` — there is no separate API key.
118+
119+
**OAuth2 auth**: If all three `GITHUB_OAUTH_*` vars are set, `_select_auth()` routes to `GitHubProvider` (FastMCP's OAuth proxy) instead of `APIKeyVerifier`. `GitHubProvider` implements the full OAuth 2.1 + Dynamic Client Registration flow: MCP clients register at `/register`, get a UUID `client_id`, then authorize via the consent/GitHub OAuth flow. Token state is stored via `build_token_store()` — in-process `MemoryStore` by default (no disk writes, sessions lost on restart), or a namespaced `RedisStore` when `REDIS_HOST_PORT` is set. When Redis is used, all collection names are automatically prefixed with a 12-char SHA-256 hash of `GITHUB_OAUTH_BASE_URL`, so two server instances sharing the same Redis have fully isolated keyspaces. **If the server restarts in memory-only mode, cached client_ids become stale — instruct users to clear MCP client auth tokens and reconnect.** If two servers use the same `GITHUB_OAUTH_CLIENT_ID` and the same GitHub account, MCP clients may still conflate their OAuth sessions at the client level (both JWTs embed the same `client_id`); use a separate GitHub OAuth App per logical server to avoid this.
120+
121+
**`TokenVerifier` subclass requirement**: Any subclass of `TokenVerifier` (or `AuthProvider`) **must call `super().__init__()`**. The parent sets `base_url`, `required_scopes`, `_mcp_path`, and `_resource_url` — FastMCP accesses these when building the HTTP app and will raise `AttributeError` if they are absent.
122+
123+
## Key Files
124+
125+
- **Entry point**: `src/mcp_github/issues_pr_analyser.py:main()` — also exposed as `mcp-github-pr-issue-analyser` script
126+
- **Auth providers**: `src/mcp_github/auth.py``APIKeyVerifier`, `get_oauth_verifier()`, and token resolution
127+
- **GraphQL queries**: `src/mcp_github/graphql_queries.py` — verify fields against GitHub schema before modifying
128+
- **GraphQL client**: `src/mcp_github/graphql_client.py` — thin wrapper used by `github_integration.py` for all v4 API calls
129+
- **Exception hierarchy**: `src/mcp_github/exceptions.py``MCPGitHubError` -> `GitHubAPIError` -> `GitHubAuthError` / `GitHubRateLimitError` / `GitHubNotFoundError` / `GitHubValidationError`; also `IPInfoError`
130+
- **Return types**: `github_integration.py` defines TypedDicts as PEP 695 type aliases (`type PRContent = TypedDict(...)`) used as return type annotations
131+
- **Skills**: `src/mcp_github/skills/` — MCP resources exposed under `skill://` URIs; loaded via `SkillsDirectoryProvider` at startup
132+
- **Registry manifest**: `registry/server.yaml` — metadata for publishing to MCP server registries
133+
- **Version**: managed by `setuptools-scm` from git tags; `src/mcp_github/_version.py` is auto-generated — do not edit
134+
135+
## Code Style
136+
137+
**No lazy imports**: All imports must be at the top of the file. Never use `import` statements inside functions or conditional blocks. Maintain consistency across all modules in the repo.
138+
139+
**Docstrings (NumPy style)**:
140+
- All docstrings: one blank line after the opening `"""`, summary on the second line (D213). For class docstrings, add one blank line before the `"""` as well (D203).
141+
- Section headers use no trailing colon and a dashed underline on the next line, e.g.:
142+
```
143+
Raises
144+
------
145+
RuntimeError
146+
Description here.
147+
148+
```
149+
Always leave one blank line after the last section before the closing `"""` (D406, D407, D413).
150+
- Ruff ignores both D212/D213 and D203/D211 (they are mutually exclusive pairs). Codacy enforces D213 + D203 — use that convention.
151+
152+
**Markdown headings**: Leave one blank line between a heading and the content that follows it.
153+
154+
## Task Backlog
155+
156+
`tasks/` contains numbered markdown files for planned bug fixes, improvements, and features. Check here before starting new work to avoid duplicating planned effort.
157+
158+
## Testing
159+
160+
Tests live in `tests/` with `test_*.py` naming (configured in `pyproject.toml`). Run with `uv run pytest`. Bandit security scanning excludes the `tests/` directory (configured in `pyproject.toml` under `[tool.bandit]`) — `assert` statements and test-fixture strings are expected and not flagged.
161+
162+
## CI/CD
163+
164+
GitHub Actions in `.github/workflows/`:
165+
166+
- `ci.yml` — CodeQL, dependency review, Docker build/push to `ghcr.io/saidsef/mcp-github-pr-issue-analyser` (multi-platform: amd64, arm64), Trivy scan
167+
- `tag_release.yml` — Automated semantic versioning and releases on push to main
168+
169+
CI auto-approves PRs after successful build. Do not manually update version numbers in PRs.
170+
171+
`uv.lock` should be committed — it is the authoritative lockfile for `uv sync` and ensures reproducible installs.
172+
173+
## Deployment
174+
175+
Kubernetes manifests in `deployment/` use Kustomize (`kustomization.yml`). The Dockerfile sets `MCP_ENABLE_REMOTE=true` and `PORT=8081` as defaults — the container always runs in HTTP mode.
176+
177+
**K8s read-only filesystem**: The pod spec sets `readOnlyRootFilesystem: true`. `requirements.txt` contains `-e .` (editable install, auto-generated by `uv export`) which would cause `setuptools-scm` to write `_version.py` and `.egg-info/` at runtime. The Dockerfile handles this by stripping `-e .` before installing and then doing a non-editable install:
178+
```dockerfile
179+
grep -v "^-e " requirements.txt > /tmp/requirements.txt && \
180+
uv pip install --system -r /tmp/requirements.txt && \
181+
uv pip install --system --no-deps .
182+
```
183+
Do not change `CMD` back to `uv run` — it re-triggers the editable build at pod startup.
184+
185+
To simulate K8s locally:
186+
```sh
187+
docker build -t mcp-test --build-arg SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 .
188+
docker run --read-only --tmpfs /tmp -e GITHUB_TOKEN="<token>" -p 8081:8081 mcp-test
189+
```

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
# MCP for GitHub PR, Issues, Tags and Releases [![CI](https://github.com/saidsef/mcp-github-pr-issue-analyser/actions/workflows/ci.yml/badge.svg)](https://github.com/saidsef/mcp-github-pr-issue-analyser/actions/workflows/ci.yml) [![Tag and Release](https://github.com/saidsef/mcp-github-pr-issue-analyser/actions/workflows/tag_release.yml/badge.svg)](https://github.com/saidsef/mcp-github-pr-issue-analyser/actions/workflows/tag_release.yml) [![Maintainability](https://qlty.sh/gh/saidsef/projects/mcp-github-pr-issue-analyser/maintainability.svg)](https://qlty.sh/gh/saidsef/projects/mcp-github-pr-issue-analyser)
1+
# MCP for GitHub PR, Issues, Tags and Releases
2+
3+
[![CI](https://github.com/saidsef/mcp-github-pr-issue-analyser/actions/workflows/ci.yml/badge.svg)](https://github.com/saidsef/mcp-github-pr-issue-analyser/actions/workflows/ci.yml) [![Tag and Release](https://github.com/saidsef/mcp-github-pr-issue-analyser/actions/workflows/tag_release.yml/badge.svg)](https://github.com/saidsef/mcp-github-pr-issue-analyser/actions/workflows/tag_release.yml) [![Maintainability](https://qlty.sh/gh/saidsef/projects/mcp-github-pr-issue-analyser/maintainability.svg)](https://qlty.sh/gh/saidsef/projects/mcp-github-pr-issue-analyser) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/9ca2ee03cbfa4407944a2450b1719d5d)](https://app.codacy.com/gh/saidsef/mcp-github-pr-issue-analyser/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
24

35
The [Model Context Protocol](https://www.anthropic.com/news/model-context-protocol) (MCP) is an open standard that enables seamless integration between Large Language Models (LLMs) and external tools. Whilst it can be implemented in any AI system, including custom LLM setups, the degree of integration and optimisation varies based on the model's architecture and capabilities.
46

@@ -49,6 +51,8 @@ Two auth modes are supported. The active mode is selected automatically from env
4951
| `GITHUB_OAUTH_CLIENT_ID` | OAuth2 only | GitHub OAuth App client ID |
5052
| `GITHUB_OAUTH_CLIENT_SECRET` | OAuth2 only | GitHub OAuth App client secret |
5153
| `GITHUB_OAUTH_BASE_URL` | OAuth2 only | Public base URL of the MCP server (used for the OAuth2 redirect) |
54+
| `REDIS_HOST_PORT` | No | Redis connection string. Accepts `host:port` or a full URI: `redis://[:password@]host:port[/db]` (plaintext) or `rediss://[:password@]host:port[/db]` (TLS). When set, OAuth token state is stored in Redis instead of in-process memory. |
55+
| `REDIS_PASSWORD` | No | Redis AUTH password fallback — used when the password is not embedded in the URI. |
5256
| `PORT` | No (default `8081`) | HTTP server port |
5357
| `HOST` | No (default `localhost`) | HTTP server host |
5458
| `GITHUB_API_TIMEOUT` | No (default `5`) | Timeout in seconds for GitHub API requests |
@@ -66,6 +70,20 @@ Two auth modes are supported. The active mode is selected automatically from env
6670
|
6771
| (stdio/http)
6872
v
73+
+------------------------+
74+
| Auth Layer +-->+------------------------+
75+
| (auth.py) | | OAuth Token Store |
76+
| | | MemoryStore (default) |
77+
| stdio : no auth | | RedisStore |
78+
| http : APIKeyVerifier | | (REDIS_HOST_PORT set)|
79+
| oauth : GitHub OAuth2 | | redis:// / rediss:// |
80+
| (DCR + token proxy) | +------------------------+
81+
+------------------------+ |
82+
| v
83+
| +----------+
84+
| | Redis |
85+
| +----------+
86+
v
6987
+--------------------+ +------------------------+
7088
| | | PRIssueAnalyser |
7189
| IP Integration | <------------| (FastMCP Server) |
@@ -100,6 +118,7 @@ Two auth modes are supported. The active mode is selected automatically from env
100118
### Main Flows:
101119

102120
- PRIssueAnalyser: Main MCP server handling tool registration and requests
121+
- Auth Layer: Selects APIKeyVerifier (static token) or GitHub OAuth2 provider; token state in MemoryStore or RedisStore
103122
- GitHub Integration: Manages all GitHub API interactions (REST + GraphQL)
104123
- IP Integration: Handles IPv4/IPv6 information retrieval
105124
- MCP Client: Interacts via stdio or streamable HTTP (http)

deployment/base/deployment.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ spec:
4242
name: github-token
4343
key: token
4444
optional: true
45+
- name: REDIS_HOST_PORT
46+
valueFrom:
47+
configMapKeyRef:
48+
name: redis-config
49+
key: host-port
50+
optional: true
51+
- name: REDIS_PASSWORD
52+
valueFrom:
53+
secretKeyRef:
54+
name: redis-config
55+
key: password
56+
optional: true
4557
ports:
4658
- name: http
4759
containerPort: 8081

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ authors = [
1010
dependencies = [
1111
"fastmcp[apps]>=3.2.4",
1212
"httpx>=0.28.1",
13+
"redis>=5.0.0",
1314
]
1415

1516
[project.scripts]
@@ -100,5 +101,9 @@ fixable = ["I", "UP"]
100101

101102
[tool.ruff.lint.per-file-ignores]
102103
"src/mcp_github/__init__.py" = ["D100"]
104+
"tests/**" = ["ANN001", "ANN002", "ANN003", "ANN201", "ANN202"]
105+
106+
[tool.bandit]
107+
exclude_dirs = ["tests", ".venv"]
103108

104109

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,9 @@ pyyaml==6.0.3 \
308308
--hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \
309309
--hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \
310310
--hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b
311+
redis==7.4.0 \
312+
--hash=sha256:64a6ea7bf567ad43c964d2c30d82853f8df927c5c9017766c55a1d1ed95d18ad \
313+
--hash=sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec
311314
referencing==0.37.0 \
312315
--hash=sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231 \
313316
--hash=sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8

0 commit comments

Comments
 (0)