- New: mcp_server.py — FastMCP stdio server exposing 7 tools: gitea_create_issue, gitea_create_pr, gitea_close_issue, gitea_list_issues, gitea_view_issue, gitea_mark_issue, gitea_mirror_refs - New: auth.py — shared authentication and API helpers (get_credentials, get_auth_header, api_request, repo_api_url) - Refactored: create_pr.py, create_issue.py, manage_labels.py to use shared auth module (eliminates credential duplication) - New: tests/test_mcp_server.py — 17 tests for all MCP tools - Updated: tests/test_credentials.py — now tests auth.py directly - Updated: tests/test_create_issue.py — adapted for refactored imports - New: requirements.txt — frozen venv deps (mcp[cli], pytest) - Updated: README.md — MCP server as primary interface - Config: added gitea-tools to mcp_config.json Closes #1. Resolves #2, #5. Relates to #7.
This commit is contained in:
+14
-36
@@ -9,11 +9,8 @@ Usage:
|
||||
./manage_labels.py --dry # print actions without writing
|
||||
"""
|
||||
import sys
|
||||
import json
|
||||
import base64
|
||||
import subprocess
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
from auth import get_auth_header, api_request, repo_api_url
|
||||
|
||||
HOST = "gitea.dadeschools.net"
|
||||
ORG = "Contractor"
|
||||
@@ -50,46 +47,27 @@ MAPPING = {
|
||||
1: ["nice-to-have"],
|
||||
}
|
||||
|
||||
API = f"https://{HOST}/api/v1/repos/{ORG}/{REPO}"
|
||||
|
||||
|
||||
def get_auth_header():
|
||||
p = subprocess.Popen(
|
||||
["git", "credential", "fill"],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True,
|
||||
)
|
||||
out, _ = p.communicate(f"protocol=https\nhost={HOST}\n\n")
|
||||
user = password = ""
|
||||
for line in out.splitlines():
|
||||
if line.startswith("username="):
|
||||
user = line.split("=", 1)[1]
|
||||
if line.startswith("password="):
|
||||
password = line.split("=", 1)[1]
|
||||
if not user or not password:
|
||||
print("Could not get credentials from git credential fill", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
token = base64.b64encode(f"{user}:{password}".encode()).decode()
|
||||
return f"Basic {token}"
|
||||
BASE_URL = repo_api_url(HOST, ORG, REPO)
|
||||
|
||||
|
||||
def api(method, path, auth, payload=None):
|
||||
url = f"{API}{path}"
|
||||
data = json.dumps(payload).encode() if payload is not None else None
|
||||
req = urllib.request.Request(url, data=data, method=method)
|
||||
req.add_header("Authorization", auth)
|
||||
req.add_header("Content-Type", "application/json")
|
||||
"""Thin wrapper around auth.api_request that prepends BASE_URL and
|
||||
handles errors gracefully (returns None instead of raising)."""
|
||||
url = f"{BASE_URL}{path}"
|
||||
try:
|
||||
with urllib.request.urlopen(req) as r:
|
||||
body = r.read().decode()
|
||||
return json.loads(body) if body else None
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f" HTTP {e.code} on {method} {path}: {e.read().decode()}", file=sys.stderr)
|
||||
return api_request(method, url, auth, payload)
|
||||
except RuntimeError as e:
|
||||
print(f" {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
dry = "--dry" in sys.argv
|
||||
auth = get_auth_header()
|
||||
auth = get_auth_header(HOST)
|
||||
if auth is None:
|
||||
print("Could not get credentials from git credential fill",
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 1. Existing labels -> name:id
|
||||
existing = api("GET", "/labels?limit=100", auth) or []
|
||||
|
||||
Reference in New Issue
Block a user