build: switch to uv, hatchling and ruff-only tooling
This commit is contained in:
1 parent
8b7bb7205c
commit
c0b22c531e
9 files changed
+2378
-280
No files matched your search
+6
-1
@@ -52,10 +52,11 @@ htmlcov/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
!.env.example
|
||||
.env.local
|
||||
.env.backup-*
|
||||
|
||||
# API keys
|
||||
# Keys
|
||||
keys/
|
||||
*.key
|
||||
|
||||
@@ -72,3 +73,7 @@ Thumbs.db
|
||||
|
||||
# Audit logs (development only - production should use volumes)
|
||||
audit.log
|
||||
|
||||
# Local state
|
||||
.data/
|
||||
coverage.xml
|
||||
+5
-22
@@ -1,42 +1,25 @@
|
||||
# Pre-commit hooks mirror `make lint` so local commits enforce the same checks as CI.
|
||||
# Installed via `make install-dev` (runs `pre-commit install`).
|
||||
#
|
||||
# Hooks use `language: system`, i.e. they invoke ruff/black/mypy from PATH rather than
|
||||
# letting pre-commit manage isolated tool environments. This keeps versions identical to
|
||||
# `make lint`/`make format` and lets mypy resolve the project's real dependencies.
|
||||
# Requirement: commit with the dev virtualenv active (so requirements-dev.txt tools are on
|
||||
# PATH). Outside an active venv the hooks will report the tools as "not found".
|
||||
# Hooks mirror `make lint`. Install with: uv run pre-commit install (add pre-commit
|
||||
# yourself, it is not a project dependency) or run the same commands via `make lint`.
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
name: ruff check
|
||||
entry: ruff check
|
||||
entry: uv run ruff check
|
||||
language: system
|
||||
types: [python]
|
||||
args: [src/, tests/]
|
||||
pass_filenames: false
|
||||
|
||||
- id: ruff-format
|
||||
name: ruff format --check
|
||||
entry: ruff format --check
|
||||
entry: uv run ruff format --check
|
||||
language: system
|
||||
types: [python]
|
||||
args: [src/, tests/]
|
||||
pass_filenames: false
|
||||
|
||||
- id: black
|
||||
name: black --check
|
||||
entry: black --check
|
||||
language: system
|
||||
types: [python]
|
||||
args: [src/, tests/]
|
||||
pass_filenames: false
|
||||
|
||||
- id: mypy
|
||||
name: mypy
|
||||
entry: mypy
|
||||
entry: uv run mypy src
|
||||
language: system
|
||||
types: [python]
|
||||
args: [src/]
|
||||
pass_filenames: false
|
||||
@@ -1,89 +1,44 @@
|
||||
.PHONY: help install install-dev test lint format clean build run docker-build docker-up docker-down docker-logs generate-key rotate-key check-key-age validate-audit
|
||||
.PHONY: help install lint format test test-integration run validate-audit docker-build clean
|
||||
|
||||
help:
|
||||
@echo "AegisGitea MCP - Available Commands"
|
||||
@echo ""
|
||||
@echo "Development:"
|
||||
@echo " make install Install production dependencies"
|
||||
@echo " make install-dev Install development dependencies"
|
||||
@echo " make test Run tests with coverage"
|
||||
@echo " make lint Run linters (ruff, mypy)"
|
||||
@echo " make format Format code with black"
|
||||
@echo " make clean Remove build artifacts"
|
||||
@echo ""
|
||||
@echo "API Key Management:"
|
||||
@echo " make generate-key Generate new API key"
|
||||
@echo " make rotate-key Rotate existing API key"
|
||||
@echo " make check-key-age Check API key age and expiration"
|
||||
@echo ""
|
||||
@echo "Local Execution:"
|
||||
@echo " make run Run server locally (requires .env)"
|
||||
@echo ""
|
||||
@echo "Docker:"
|
||||
@echo " make docker-build Build Docker image"
|
||||
@echo " make docker-up Start containers"
|
||||
@echo " make docker-down Stop containers"
|
||||
@echo " make docker-logs View container logs"
|
||||
@echo " make docker-restart Restart MCP container"
|
||||
@echo ""
|
||||
@echo "AegisGitea MCP - available targets"
|
||||
@echo " make install Install dependencies (uv sync)"
|
||||
@echo " make lint ruff check, ruff format --check, mypy"
|
||||
@echo " make format Format and autofix with ruff"
|
||||
@echo " make test Run unit tests with coverage"
|
||||
@echo " make test-integration Run integration tests (needs Docker)"
|
||||
@echo " make run Run the server (uses .env)"
|
||||
@echo " make validate-audit Verify audit log hash-chain integrity"
|
||||
@echo " make docker-build Build the Docker image"
|
||||
@echo " make clean Remove build artifacts and caches"
|
||||
|
||||
install:
|
||||
pip install -r requirements.txt
|
||||
|
||||
install-dev:
|
||||
pip install -r requirements-dev.txt
|
||||
pre-commit install
|
||||
|
||||
test:
|
||||
pytest tests/ -v --cov=aegis_gitea_mcp --cov-report=html --cov-report=term --cov-fail-under=80
|
||||
uv sync
|
||||
|
||||
lint:
|
||||
ruff check src/ tests/
|
||||
ruff format --check src/ tests/
|
||||
black --check src/ tests/
|
||||
mypy src/
|
||||
uv run ruff check .
|
||||
uv run ruff format --check .
|
||||
uv run mypy src
|
||||
|
||||
format:
|
||||
black src/ tests/
|
||||
ruff check --fix src/ tests/
|
||||
uv run ruff format .
|
||||
uv run ruff check --fix .
|
||||
|
||||
clean:
|
||||
rm -rf build/ dist/ *.egg-info .pytest_cache/ .coverage htmlcov/
|
||||
find . -type d -name __pycache__ -exec rm -rf {} +
|
||||
find . -type f -name "*.pyc" -delete
|
||||
test:
|
||||
uv run pytest
|
||||
|
||||
build:
|
||||
python -m build
|
||||
test-integration:
|
||||
AEGIS_INTEGRATION=1 uv run pytest -m integration
|
||||
|
||||
run:
|
||||
python3 -m aegis_gitea_mcp.server
|
||||
|
||||
docker-build:
|
||||
docker-compose build
|
||||
|
||||
docker-up:
|
||||
docker-compose --profile prod up -d
|
||||
|
||||
docker-down:
|
||||
docker-compose down
|
||||
|
||||
docker-logs:
|
||||
docker-compose logs -f aegis-mcp
|
||||
|
||||
docker-restart:
|
||||
docker-compose restart aegis-mcp
|
||||
|
||||
docker-shell:
|
||||
docker-compose exec aegis-mcp /bin/bash
|
||||
|
||||
generate-key:
|
||||
python3 scripts/generate_api_key.py
|
||||
|
||||
rotate-key:
|
||||
python3 scripts/rotate_api_key.py
|
||||
|
||||
check-key-age:
|
||||
python3 scripts/check_key_age.py
|
||||
uv run aegis-gitea-mcp serve
|
||||
|
||||
validate-audit:
|
||||
python3 scripts/validate_audit_log.py
|
||||
uv run python scripts/validate_audit_log.py
|
||||
|
||||
docker-build:
|
||||
docker build -f docker/Dockerfile -t aegis-gitea-mcp:dev .
|
||||
|
||||
clean:
|
||||
rm -rf build/ dist/ *.egg-info .pytest_cache/ .mypy_cache/ .ruff_cache/ .coverage htmlcov/
|
||||
find . -type d -name __pycache__ -not -path "./.venv/*" -exec rm -rf {} +
|
||||
@@ -1,31 +0,0 @@
|
||||
# PLAN — local stdio package + safe full-API coverage
|
||||
|
||||
Branch: `feat/local-package-and-full-coverage` (from `dev`). All PRs target `dev`.
|
||||
Flow: HEAD -> custom branch -> dev -> main. Never push directly to dev/main.
|
||||
|
||||
Baseline (recorded Phase 0): 284 passed, 1 skipped, coverage 84.04%, threshold 80%.
|
||||
|
||||
## Phase checklist
|
||||
|
||||
- [x] Phase 0 — Branch from dev, baseline recorded, PLAN.md committed.
|
||||
- [x] Phase 1 — Extract transport-agnostic core + shared tool registry (+ boundary test).
|
||||
- [x] Phase 2 — stdio adapter (`stdio_app.py`) + packaging (core + `[server]` extra, 0.2.0).
|
||||
- [x] Phase 3 — Resource-type-aware authorization (fail-closed).
|
||||
- [x] Phase 4 — gitea_request classifier + known-path gate (unknown path => deny).
|
||||
- [x] Phase 5 — Tests: authz matrix, write-mode bypass, classifier, stdio adapter, boundary.
|
||||
- [x] Phase 6 — Docs & README (local vs server quickstart, authz model, packaging, CLAUDE/AGENTS).
|
||||
- [ ] Phase 7 — `.gitea/workflows/publish.yml` (uv build + publish to Gitea registry on tag).
|
||||
- [ ] Phase 8 — Verify green + coverage >= baseline, `uv build`, push, open PR into dev.
|
||||
|
||||
Note: version bumped to 0.2.0 (the app already reported 0.2.0; pyproject was 0.1.0).
|
||||
TODO(authz): make `list_organizations` user-scoped (`/users/{login}/orgs`) so it can
|
||||
be allowed rather than denied in service-PAT mode.
|
||||
|
||||
## Key deltas found during orientation
|
||||
|
||||
- No single tool registry today: definitions in `mcp_protocol.AVAILABLE_TOOLS`,
|
||||
handlers in `server.TOOL_HANDLERS`. Phase 1 unifies them.
|
||||
- `tools/raw_tools.py` imports `fastapi.HTTPException` — the only core->web import to break.
|
||||
- Current authz is repo-only and lives in `server._verify_user_repository_access`.
|
||||
- stdio mode must run with `AUTH_ENABLED=false` (config otherwise requires MCP_API_KEYS).
|
||||
- `AGENTS.md` absent at root though CLAUDE.md cites it; create it from the contract.
|
||||
+52
-51
@@ -1,14 +1,14 @@
|
||||
[project]
|
||||
name = "aegis-gitea-mcp"
|
||||
version = "0.2.0"
|
||||
description = "Security-first MCP server for controlled AI access to self-hosted Gitea (local stdio + public HTTP/OAuth)"
|
||||
version = "1.0.0.dev0"
|
||||
description = "MCP server exposing the complete Gitea API over stdio and streamable HTTP, with admin-token and per-user OAuth modes"
|
||||
authors = [
|
||||
{name = "AegisGitea MCP Contributors"}
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
license = {text = "MIT"}
|
||||
keywords = ["mcp", "gitea", "ai", "security", "audit"]
|
||||
keywords = ["mcp", "gitea", "ai", "audit"]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
@@ -17,46 +17,22 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
]
|
||||
|
||||
# Core (default install) powers the local stdio transport. It deliberately
|
||||
# excludes the web/OAuth stack so `uvx aegis-gitea-mcp` stays light; the HTTP
|
||||
# server pulls those in via the [server] extra.
|
||||
dependencies = [
|
||||
"httpx>=0.26.0",
|
||||
"pydantic>=2.5.0",
|
||||
"pydantic-settings>=2.1.0",
|
||||
"PyYAML>=6.0.1",
|
||||
"python-dotenv>=1.0.0",
|
||||
"structlog>=24.1.0",
|
||||
"mcp>=1.2.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
# The public HTTP/OAuth server (aegis-gitea-mcp-server) needs the web stack.
|
||||
server = [
|
||||
"fastapi>=0.109.0",
|
||||
"uvicorn[standard]>=0.27.0",
|
||||
"PyJWT[crypto]>=2.9.0",
|
||||
"python-multipart>=0.0.9",
|
||||
]
|
||||
dev = [
|
||||
"pytest>=7.4.0",
|
||||
"pytest-asyncio>=0.23.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
"pytest-httpx>=0.28.0",
|
||||
"black>=24.1.0",
|
||||
"ruff>=0.1.0",
|
||||
"mypy>=1.8.0",
|
||||
"pre-commit>=3.6.0",
|
||||
"mcp>=2.3,<3",
|
||||
"httpx>=0.27",
|
||||
"pydantic>=2.7",
|
||||
# NoDecode (comma-separated list env vars) needs pydantic-settings 2.7.
|
||||
"pydantic-settings>=2.7",
|
||||
"jsonschema>=4.20",
|
||||
"cryptography>=42",
|
||||
"uvicorn>=0.31",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
# Local stdio MCP server (default install, no web stack required).
|
||||
aegis-gitea-mcp = "aegis_gitea_mcp.stdio_app:main"
|
||||
# Public HTTP/OAuth server; requires the [server] extra. The entry point guards
|
||||
# against a missing web stack with an actionable message.
|
||||
aegis-gitea-mcp-server = "aegis_gitea_mcp.server_entry:main"
|
||||
aegis-gitea-mcp = "aegis_gitea_mcp.cli:main"
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://git.hiddenden.cafe/Hiddenden/AegisGitea-MCP"
|
||||
@@ -64,24 +40,33 @@ Documentation = "https://git.hiddenden.cafe/Hiddenden/AegisGitea-MCP/src/branch/
|
||||
Repository = "https://git.hiddenden.cafe/Hiddenden/AegisGitea-MCP.git"
|
||||
Issues = "https://git.hiddenden.cafe/Hiddenden/AegisGitea-MCP/issues"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest>=8",
|
||||
"pytest-asyncio>=0.23",
|
||||
"pytest-cov>=5",
|
||||
"pytest-httpx>=0.30",
|
||||
"ruff>=0.6",
|
||||
"mypy>=1.10",
|
||||
"types-jsonschema",
|
||||
"pip-audit>=2.7",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=68.0.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = {"" = "src"}
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/aegis_gitea_mcp"]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
target-version = ['py310', 'py311', 'py312']
|
||||
include = '\.pyi?$'
|
||||
[tool.hatch.build.targets.sdist]
|
||||
include = ["src/aegis_gitea_mcp", "README.md", "LICENSE", "pyproject.toml"]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py310"
|
||||
src = ["src", "tests"]
|
||||
extend-exclude = ["docs", "spec"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
@@ -92,14 +77,27 @@ select = [
|
||||
"B", # flake8-bugbear
|
||||
"C4", # flake8-comprehensions
|
||||
"UP", # pyupgrade
|
||||
"S", # flake8-bandit
|
||||
"SIM", # flake8-simplify
|
||||
"RUF", # ruff-specific
|
||||
]
|
||||
ignore = [
|
||||
"E501", # line too long (handled by black)
|
||||
"B008", # do not perform function calls in argument defaults
|
||||
"E501", # line length is handled by ruff format; long strings and URLs are fine
|
||||
"B008", # function calls in argument defaults (pydantic Field style)
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"__init__.py" = ["F401"]
|
||||
"tests/**" = [
|
||||
"S101", # assert is the point of tests
|
||||
"S105", # hardcoded fake tokens in fixtures
|
||||
"S106", # hardcoded fake tokens in fixtures
|
||||
"S108", # fixture paths
|
||||
]
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
line-ending = "lf"
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.10"
|
||||
@@ -119,11 +117,14 @@ module = "tests.*"
|
||||
disallow_untyped_defs = false
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "7.0"
|
||||
minversion = "8.0"
|
||||
addopts = "-ra -q --strict-markers --cov=aegis_gitea_mcp --cov-report=term-missing --cov-fail-under=80"
|
||||
testpaths = ["tests"]
|
||||
pythonpath = ["src"]
|
||||
asyncio_mode = "auto"
|
||||
markers = [
|
||||
"integration: needs a live Gitea in Docker (opt-in via AEGIS_INTEGRATION=1)",
|
||||
]
|
||||
|
||||
[tool.coverage.run]
|
||||
source = ["src"]
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
-r requirements.txt
|
||||
|
||||
pytest>=7.4.0
|
||||
pytest-asyncio>=0.23.0
|
||||
pytest-cov>=4.1.0
|
||||
pytest-httpx>=0.28.0
|
||||
black>=24.1.0
|
||||
ruff>=0.1.0
|
||||
mypy>=1.8.0
|
||||
pre-commit>=3.6.0
|
||||
@@ -1,11 +0,0 @@
|
||||
fastapi>=0.109.0
|
||||
uvicorn[standard]>=0.27.0
|
||||
httpx>=0.26.0
|
||||
pydantic>=2.5.0
|
||||
pydantic-settings>=2.1.0
|
||||
PyYAML>=6.0.1
|
||||
python-dotenv>=1.0.0
|
||||
python-multipart>=0.0.9
|
||||
structlog>=24.1.0
|
||||
PyJWT[crypto]>=2.9.0
|
||||
mcp>=1.2.0
|
||||
@@ -1,79 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Test runner script for AegisGitea MCP
|
||||
|
||||
set -e
|
||||
|
||||
echo "========================================="
|
||||
echo "AegisGitea MCP - Test Suite"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Check if virtual environment exists
|
||||
if [ ! -d "venv" ]; then
|
||||
echo -e "${YELLOW}Virtual environment not found. Creating...${NC}"
|
||||
python3 -m venv venv
|
||||
fi
|
||||
|
||||
# Activate virtual environment
|
||||
source venv/bin/activate
|
||||
|
||||
# Install dependencies
|
||||
echo -e "${YELLOW}Installing dependencies...${NC}"
|
||||
pip install -q -r requirements-dev.txt
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "Running Tests"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Run tests with coverage
|
||||
pytest tests/ \
|
||||
-v \
|
||||
--cov=aegis_gitea_mcp \
|
||||
--cov-report=term-missing \
|
||||
--cov-report=html \
|
||||
--tb=short
|
||||
|
||||
TEST_EXIT_CODE=$?
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "Test Summary"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
if [ $TEST_EXIT_CODE -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ All tests passed!${NC}"
|
||||
echo ""
|
||||
echo "Coverage report generated in htmlcov/"
|
||||
echo "Open htmlcov/index.html in your browser to view detailed coverage."
|
||||
else
|
||||
echo -e "${RED}✗ Some tests failed${NC}"
|
||||
echo ""
|
||||
echo "Please review the errors above and fix them."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "Running Linters"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Run ruff
|
||||
echo "Running ruff..."
|
||||
ruff check src/ tests/ || true
|
||||
|
||||
# Run mypy
|
||||
echo "Running mypy..."
|
||||
mypy src/ || true
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Test suite complete!${NC}"
|
||||
Reference in new issue
Block a user