mcp-gitlab 0.0.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 (36) hide show
  1. mcp_gitlab-0.0.1/.env.example +8 -0
  2. mcp_gitlab-0.0.1/.github/workflows/lint.yml +11 -0
  3. mcp_gitlab-0.0.1/.github/workflows/publish.yml +39 -0
  4. mcp_gitlab-0.0.1/.github/workflows/release.yml +91 -0
  5. mcp_gitlab-0.0.1/.github/workflows/tests.yml +21 -0
  6. mcp_gitlab-0.0.1/.gitignore +34 -0
  7. mcp_gitlab-0.0.1/AGENTS.md +34 -0
  8. mcp_gitlab-0.0.1/LICENSE +21 -0
  9. mcp_gitlab-0.0.1/PKG-INFO +245 -0
  10. mcp_gitlab-0.0.1/README.md +221 -0
  11. mcp_gitlab-0.0.1/pyproject.toml +66 -0
  12. mcp_gitlab-0.0.1/src/mcp_gitlab/__init__.py +51 -0
  13. mcp_gitlab-0.0.1/src/mcp_gitlab/__main__.py +5 -0
  14. mcp_gitlab-0.0.1/src/mcp_gitlab/client.py +601 -0
  15. mcp_gitlab-0.0.1/src/mcp_gitlab/config.py +53 -0
  16. mcp_gitlab-0.0.1/src/mcp_gitlab/exceptions.py +39 -0
  17. mcp_gitlab-0.0.1/src/mcp_gitlab/models/__init__.py +0 -0
  18. mcp_gitlab-0.0.1/src/mcp_gitlab/models/approvals.py +25 -0
  19. mcp_gitlab-0.0.1/src/mcp_gitlab/models/base.py +16 -0
  20. mcp_gitlab-0.0.1/src/mcp_gitlab/models/ci.py +50 -0
  21. mcp_gitlab-0.0.1/src/mcp_gitlab/models/common.py +50 -0
  22. mcp_gitlab-0.0.1/src/mcp_gitlab/models/issues.py +25 -0
  23. mcp_gitlab-0.0.1/src/mcp_gitlab/models/merge_requests.py +87 -0
  24. mcp_gitlab-0.0.1/src/mcp_gitlab/models/pipelines.py +38 -0
  25. mcp_gitlab-0.0.1/src/mcp_gitlab/models/projects.py +54 -0
  26. mcp_gitlab-0.0.1/src/mcp_gitlab/models/repositories.py +43 -0
  27. mcp_gitlab-0.0.1/src/mcp_gitlab/servers/__init__.py +0 -0
  28. mcp_gitlab-0.0.1/src/mcp_gitlab/servers/gitlab.py +1883 -0
  29. mcp_gitlab-0.0.1/src/mcp_gitlab/utils/__init__.py +0 -0
  30. mcp_gitlab-0.0.1/tests/__init__.py +0 -0
  31. mcp_gitlab-0.0.1/tests/conftest.py +28 -0
  32. mcp_gitlab-0.0.1/tests/unit/__init__.py +0 -0
  33. mcp_gitlab-0.0.1/tests/unit/test_client.py +140 -0
  34. mcp_gitlab-0.0.1/tests/unit/test_config.py +62 -0
  35. mcp_gitlab-0.0.1/tests/unit/test_exceptions.py +37 -0
  36. mcp_gitlab-0.0.1/uv.lock +1883 -0
@@ -0,0 +1,8 @@
1
+ # GitLab instance URL (required)
2
+ GITLAB_URL=https://gitlab.example.com
3
+
4
+ # Personal access token with api scope (required)
5
+ GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx
6
+
7
+ # Optional: set to "true" to disable write operations
8
+ # GITLAB_READ_ONLY=false
@@ -0,0 +1,11 @@
1
+ name: Lint
2
+ on: [push, pull_request]
3
+ jobs:
4
+ lint:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - uses: actions/checkout@v4
8
+ - uses: astral-sh/setup-uv@v5
9
+ - run: uv sync
10
+ - run: uv run ruff check .
11
+ - run: uv run ruff format --check .
@@ -0,0 +1,39 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build-n-publish:
10
+ runs-on: ubuntu-latest
11
+ environment: pypi
12
+ permissions:
13
+ id-token: write
14
+ contents: write
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v5
21
+ with:
22
+ version: "latest"
23
+
24
+ - name: Set up Python
25
+ run: uv python install
26
+
27
+ - name: Build package
28
+ run: uv build
29
+
30
+ - name: Publish to PyPI
31
+ uses: pypa/gh-action-pypi-publish@release/v1
32
+
33
+ - name: Create GitHub Release
34
+ uses: softprops/action-gh-release@v2
35
+ with:
36
+ files: dist/*
37
+ generate_release_notes: true
38
+ env:
39
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,91 @@
1
+ name: Semantic Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ bump:
7
+ description: "Version bump type"
8
+ required: true
9
+ type: choice
10
+ options:
11
+ - patch
12
+ - minor
13
+ - major
14
+ dry_run:
15
+ description: "Dry run (no tag/push)"
16
+ required: false
17
+ type: boolean
18
+ default: false
19
+
20
+ jobs:
21
+ release:
22
+ runs-on: ubuntu-latest
23
+ permissions:
24
+ contents: write
25
+
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ with:
29
+ fetch-depth: 0
30
+
31
+ - name: Install uv
32
+ uses: astral-sh/setup-uv@v5
33
+ with:
34
+ version: "latest"
35
+
36
+ - name: Set up Python
37
+ run: uv python install
38
+
39
+ - name: Get current version
40
+ id: current
41
+ run: |
42
+ version=$(grep '^version' pyproject.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
43
+ echo "version=$version" >> "$GITHUB_OUTPUT"
44
+ echo "Current version: $version"
45
+
46
+ - name: Compute next version
47
+ id: next
48
+ run: |
49
+ IFS='.' read -r major minor patch <<< "${{ steps.current.outputs.version }}"
50
+ case "${{ inputs.bump }}" in
51
+ major) major=$((major + 1)); minor=0; patch=0 ;;
52
+ minor) minor=$((minor + 1)); patch=0 ;;
53
+ patch) patch=$((patch + 1)) ;;
54
+ esac
55
+ version="$major.$minor.$patch"
56
+ echo "version=$version" >> "$GITHUB_OUTPUT"
57
+ echo "Next version: $version"
58
+
59
+ - name: Update pyproject.toml version
60
+ run: |
61
+ sed -i "s/^version = \".*\"/version = \"${{ steps.next.outputs.version }}\"/" pyproject.toml
62
+ cat pyproject.toml | grep '^version'
63
+
64
+ - name: Verify build
65
+ run: uv build
66
+
67
+ - name: Commit and tag
68
+ if: ${{ !inputs.dry_run }}
69
+ run: |
70
+ git config user.name "github-actions[bot]"
71
+ git config user.email "github-actions[bot]@users.noreply.github.com"
72
+ git add pyproject.toml
73
+ git commit -m "chore(release): ${{ steps.next.outputs.version }}"
74
+ git tag "v${{ steps.next.outputs.version }}"
75
+ git push origin main
76
+ git push origin "v${{ steps.next.outputs.version }}"
77
+
78
+ - name: Dry run summary
79
+ if: ${{ inputs.dry_run }}
80
+ run: |
81
+ echo "## Dry Run Summary" >> "$GITHUB_STEP_SUMMARY"
82
+ echo "- Current: ${{ steps.current.outputs.version }}" >> "$GITHUB_STEP_SUMMARY"
83
+ echo "- Next: ${{ steps.next.outputs.version }}" >> "$GITHUB_STEP_SUMMARY"
84
+ echo "- Bump: ${{ inputs.bump }}" >> "$GITHUB_STEP_SUMMARY"
85
+ echo "- No changes pushed." >> "$GITHUB_STEP_SUMMARY"
86
+
87
+ - name: Release summary
88
+ if: ${{ !inputs.dry_run }}
89
+ run: |
90
+ echo "## Release v${{ steps.next.outputs.version }}" >> "$GITHUB_STEP_SUMMARY"
91
+ echo "- Tag pushed. Publish workflow will deploy to PyPI." >> "$GITHUB_STEP_SUMMARY"
@@ -0,0 +1,21 @@
1
+ name: Tests
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ strategy:
7
+ matrix:
8
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ - uses: astral-sh/setup-uv@v5
12
+ with:
13
+ python-version: ${{ matrix.python-version }}
14
+ - run: uv sync --all-extras
15
+ - run: uv run pytest --cov --cov-report=xml
16
+ - name: Upload coverage
17
+ if: matrix.python-version == '3.13'
18
+ uses: codecov/codecov-action@v4
19
+ with:
20
+ file: coverage.xml
21
+ continue-on-error: true
@@ -0,0 +1,34 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ *.egg
9
+ .eggs/
10
+ .venv/
11
+ venv/
12
+ env/
13
+ .env
14
+ .env.local
15
+ *.log
16
+ .mypy_cache/
17
+ .pytest_cache/
18
+ .ruff_cache/
19
+ htmlcov/
20
+ .coverage
21
+ .coverage.*
22
+ coverage.xml
23
+ *.cover
24
+ .tox/
25
+ .nox/
26
+ .hypothesis/
27
+ *.swp
28
+ *.swo
29
+ *~
30
+ .DS_Store
31
+ Thumbs.db
32
+ .idea/
33
+ .vscode/
34
+ *.iml
@@ -0,0 +1,34 @@
1
+ # mcp-gitlab — Agent Context
2
+
3
+ MCP server providing 65+ tools for the GitLab REST API v4.
4
+
5
+ ## Architecture
6
+
7
+ - **Entry point**: `src/mcp_gitlab/__init__.py` — click CLI, loads env, runs FastMCP server
8
+ - **Client**: `src/mcp_gitlab/client.py` — async httpx client with all GitLab API methods
9
+ - **Tools**: `src/mcp_gitlab/servers/gitlab.py` — all FastMCP tool registrations
10
+ - **Models**: `src/mcp_gitlab/models/` — Pydantic models for typed API responses
11
+ - **Config**: `src/mcp_gitlab/config.py` — `GitLabConfig` dataclass from env vars
12
+ - **Exceptions**: `src/mcp_gitlab/exceptions.py` — `GitLabApiError`, `GitLabAuthError`, etc.
13
+
14
+ ## Patterns
15
+
16
+ - All tools are `async def` returning JSON strings
17
+ - Error handling: try/except wrapping every tool, returning `{"error": ...}` JSON
18
+ - Write access control: `_check_write(ctx)` raises `GitLabWriteDisabledError` when `GITLAB_READ_ONLY=true`
19
+ - Tags: every tool tagged with `{"gitlab", "<category>", "read"|"write"}`
20
+ - Parameters use `Annotated[type, Field(description=...)]`
21
+ - Client uses httpx with `PRIVATE-TOKEN` header auth
22
+ - Project/group IDs can be numeric or URL-encoded paths
23
+
24
+ ## Tool Categories
25
+
26
+ Projects (4), Approvals (10), Groups (6), Branches (3), Commits (4), Merge Requests (9), MR Notes (6), MR Discussions (4), Pipelines (5), Jobs (4), Tags (4), Releases (5), CI/CD Variables (8), Issues (5)
27
+
28
+ ## Environment Variables
29
+
30
+ - `GITLAB_URL` (required) — GitLab instance base URL
31
+ - `GITLAB_TOKEN` or `GITLAB_PAT` (required) — Personal access token
32
+ - `GITLAB_READ_ONLY` — disable mutations
33
+ - `GITLAB_TIMEOUT` — request timeout seconds
34
+ - `GITLAB_SSL_VERIFY` — SSL verification toggle
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vis
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,245 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-gitlab
3
+ Version: 0.0.1
4
+ Summary: MCP server for GitLab API — projects, MRs, pipelines, CI/CD variables, approvals, and more
5
+ Author: vish288
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: ai,automation,gitlab,llm,mcp
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
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
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: click>=8.1.7
19
+ Requires-Dist: fastmcp>=2.13.0
20
+ Requires-Dist: httpx>=0.28.0
21
+ Requires-Dist: pydantic<3.0,>=2.10.0
22
+ Requires-Dist: python-dotenv>=1.0.1
23
+ Description-Content-Type: text/markdown
24
+
25
+ # mcp-gitlab
26
+
27
+ MCP server for the GitLab REST API. Provides 65+ tools for projects, merge requests, pipelines, CI/CD variables, approvals, issues, and more.
28
+
29
+ Built with [FastMCP](https://github.com/jlowin/fastmcp), [httpx](https://www.python-httpx.org/), and [Pydantic](https://docs.pydantic.dev/).
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ # From PyPI
35
+ uv pip install mcp-gitlab
36
+
37
+ # From source
38
+ uv pip install git+https://github.com/vish288/mcp-gitlab.git
39
+ ```
40
+
41
+ ## Configuration
42
+
43
+ Set these environment variables:
44
+
45
+ | Variable | Required | Description |
46
+ |----------|----------|-------------|
47
+ | `GITLAB_URL` | Yes | GitLab instance URL (e.g. `https://gitlab.example.com`) |
48
+ | `GITLAB_TOKEN` | Yes | Personal access token with `api` scope |
49
+ | `GITLAB_READ_ONLY` | No | Set to `true` to disable write operations |
50
+ | `GITLAB_TIMEOUT` | No | Request timeout in seconds (default: 30) |
51
+ | `GITLAB_SSL_VERIFY` | No | Set to `false` to skip SSL verification |
52
+
53
+ ## Usage
54
+
55
+ ### Claude Code / Cursor
56
+
57
+ Add to your MCP config (`~/.claude/settings.json` or `.cursor/mcp.json`):
58
+
59
+ ```json
60
+ {
61
+ "mcpServers": {
62
+ "gitlab": {
63
+ "command": "uvx",
64
+ "args": ["mcp-gitlab"],
65
+ "env": {
66
+ "GITLAB_URL": "https://gitlab.example.com",
67
+ "GITLAB_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx"
68
+ }
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### Standalone
75
+
76
+ ```bash
77
+ # stdio transport (default)
78
+ mcp-gitlab
79
+
80
+ # HTTP transport
81
+ mcp-gitlab --transport sse --port 8000
82
+ ```
83
+
84
+ ### Python
85
+
86
+ ```python
87
+ from mcp_gitlab.servers.gitlab import mcp
88
+
89
+ # Run with stdio
90
+ mcp.run()
91
+ ```
92
+
93
+ ## Tools (65)
94
+
95
+ ### Projects (4)
96
+ | Tool | Description |
97
+ |------|-------------|
98
+ | `gitlab_get_project` | Get project details |
99
+ | `gitlab_create_project` | Create a new project |
100
+ | `gitlab_delete_project` | Delete a project |
101
+ | `gitlab_update_project_merge_settings` | Update merge settings |
102
+
103
+ ### Project Approvals (8)
104
+ | Tool | Description |
105
+ |------|-------------|
106
+ | `gitlab_get_project_approvals` | Get approval config |
107
+ | `gitlab_update_project_approvals` | Update approval settings |
108
+ | `gitlab_list_project_approval_rules` | List approval rules |
109
+ | `gitlab_create_project_approval_rule` | Create approval rule |
110
+ | `gitlab_update_project_approval_rule` | Update approval rule |
111
+ | `gitlab_delete_project_approval_rule` | Delete approval rule |
112
+ | `gitlab_list_mr_approval_rules` | List MR approval rules |
113
+ | `gitlab_create_mr_approval_rule` | Create MR approval rule |
114
+ | `gitlab_update_mr_approval_rule` | Update MR approval rule |
115
+ | `gitlab_delete_mr_approval_rule` | Delete MR approval rule |
116
+
117
+ ### Groups (6)
118
+ | Tool | Description |
119
+ |------|-------------|
120
+ | `gitlab_list_groups` | List groups |
121
+ | `gitlab_get_group` | Get group details |
122
+ | `gitlab_share_project_with_group` | Share project with group |
123
+ | `gitlab_unshare_project_with_group` | Unshare project from group |
124
+ | `gitlab_share_group_with_group` | Share group with group |
125
+ | `gitlab_unshare_group_with_group` | Unshare group from group |
126
+
127
+ ### Branches (3)
128
+ | Tool | Description |
129
+ |------|-------------|
130
+ | `gitlab_list_branches` | List branches |
131
+ | `gitlab_create_branch` | Create a branch |
132
+ | `gitlab_delete_branch` | Delete a branch |
133
+
134
+ ### Commits (4)
135
+ | Tool | Description |
136
+ |------|-------------|
137
+ | `gitlab_list_commits` | List commits |
138
+ | `gitlab_get_commit` | Get commit (with optional diff) |
139
+ | `gitlab_create_commit` | Create commit with file actions |
140
+ | `gitlab_compare` | Compare branches/tags/commits |
141
+
142
+ ### Merge Requests (9)
143
+ | Tool | Description |
144
+ |------|-------------|
145
+ | `gitlab_list_mrs` | List merge requests |
146
+ | `gitlab_get_mr` | Get MR details |
147
+ | `gitlab_create_mr` | Create merge request |
148
+ | `gitlab_update_mr` | Update merge request |
149
+ | `gitlab_merge_mr` | Merge a merge request |
150
+ | `gitlab_merge_mr_sequence` | Merge multiple MRs in order |
151
+ | `gitlab_rebase_mr` | Rebase a merge request |
152
+ | `gitlab_mr_changes` | Get MR file changes |
153
+
154
+ ### MR Notes (6)
155
+ | Tool | Description |
156
+ |------|-------------|
157
+ | `gitlab_list_mr_notes` | List MR comments |
158
+ | `gitlab_add_mr_note` | Add comment to MR |
159
+ | `gitlab_delete_mr_note` | Delete MR comment |
160
+ | `gitlab_update_mr_note` | Update MR comment |
161
+ | `gitlab_award_emoji` | Award emoji to note |
162
+ | `gitlab_remove_emoji` | Remove emoji from note |
163
+
164
+ ### MR Discussions (4)
165
+ | Tool | Description |
166
+ |------|-------------|
167
+ | `gitlab_list_mr_discussions` | List discussions |
168
+ | `gitlab_create_mr_discussion` | Create discussion (inline + multi-line) |
169
+ | `gitlab_reply_to_discussion` | Reply to discussion |
170
+ | `gitlab_resolve_discussion` | Resolve/unresolve discussion |
171
+
172
+ ### Pipelines (5)
173
+ | Tool | Description |
174
+ |------|-------------|
175
+ | `gitlab_list_pipelines` | List pipelines |
176
+ | `gitlab_get_pipeline` | Get pipeline (with optional jobs) |
177
+ | `gitlab_create_pipeline` | Trigger pipeline |
178
+ | `gitlab_retry_pipeline` | Retry failed jobs |
179
+ | `gitlab_cancel_pipeline` | Cancel pipeline |
180
+
181
+ ### Jobs (4)
182
+ | Tool | Description |
183
+ |------|-------------|
184
+ | `gitlab_retry_job` | Retry a job |
185
+ | `gitlab_play_job` | Trigger manual job |
186
+ | `gitlab_cancel_job` | Cancel a job |
187
+ | `gitlab_get_job_log` | Get job log output |
188
+
189
+ ### Tags (4)
190
+ | Tool | Description |
191
+ |------|-------------|
192
+ | `gitlab_list_tags` | List tags |
193
+ | `gitlab_get_tag` | Get tag details |
194
+ | `gitlab_create_tag` | Create a tag |
195
+ | `gitlab_delete_tag` | Delete a tag |
196
+
197
+ ### Releases (5)
198
+ | Tool | Description |
199
+ |------|-------------|
200
+ | `gitlab_list_releases` | List releases |
201
+ | `gitlab_get_release` | Get release details |
202
+ | `gitlab_create_release` | Create a release |
203
+ | `gitlab_update_release` | Update a release |
204
+ | `gitlab_delete_release` | Delete a release |
205
+
206
+ ### CI/CD Variables (8)
207
+ | Tool | Description |
208
+ |------|-------------|
209
+ | `gitlab_list_variables` | List project variables |
210
+ | `gitlab_create_variable` | Create project variable |
211
+ | `gitlab_update_variable` | Update project variable |
212
+ | `gitlab_delete_variable` | Delete project variable |
213
+ | `gitlab_list_group_variables` | List group variables |
214
+ | `gitlab_create_group_variable` | Create group variable |
215
+ | `gitlab_update_group_variable` | Update group variable |
216
+ | `gitlab_delete_group_variable` | Delete group variable |
217
+
218
+ ### Issues (5)
219
+ | Tool | Description |
220
+ |------|-------------|
221
+ | `gitlab_list_issues` | List issues |
222
+ | `gitlab_get_issue` | Get issue details |
223
+ | `gitlab_create_issue` | Create an issue |
224
+ | `gitlab_update_issue` | Update an issue |
225
+ | `gitlab_add_issue_comment` | Add comment to issue |
226
+
227
+ ## Development
228
+
229
+ ```bash
230
+ # Clone and install
231
+ git clone https://github.com/vish288/mcp-gitlab.git
232
+ cd mcp-gitlab
233
+ uv sync --all-extras
234
+
235
+ # Run tests
236
+ uv run pytest --cov
237
+
238
+ # Lint
239
+ uv run ruff check .
240
+ uv run ruff format --check .
241
+ ```
242
+
243
+ ## License
244
+
245
+ MIT