feat: add MCP server + shared auth module (#7, #1)

- 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:
2026-06-21 20:08:07 -04:00
parent dd6f1308c1
commit b7e195e426
11 changed files with 978 additions and 214 deletions
+4 -33
View File
@@ -26,42 +26,15 @@ import sys
import json
import base64
import argparse
import subprocess
import urllib.request
import urllib.error
REMOTES = {
"dadeschools": {"host": "gitea.dadeschools.net", "org": "Contractor",
"repo": "Timesheet"},
"prgs": {"host": "gitea.prgs.cc", "org": "Scaled-Tech-Consulting",
"repo": "Timesheet"},
}
def get_credentials(host):
"""Return (user, password) for `host` via `git credential fill`."""
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():
# split(maxsplit=1): tokens/passwords can themselves contain '='.
if line.startswith("username="):
user = line.split("=", 1)[1]
elif line.startswith("password="):
password = line.split("=", 1)[1]
return user, password
from auth import get_credentials, resolve_remote, add_remote_args
def main(argv=None):
parser = argparse.ArgumentParser(description="Create a Gitea pull request.")
parser.add_argument("--remote", choices=sorted(REMOTES), default="dadeschools",
help="Known Gitea instance (default: dadeschools).")
parser.add_argument("--host", help="Override the Gitea host.")
parser.add_argument("--org", help="Override the owner/org.")
parser.add_argument("--repo", help="Override the repository.")
add_remote_args(parser)
parser.add_argument("--title", required=True, help="PR title.")
parser.add_argument("--head", required=True, help="Source branch.")
parser.add_argument("--base", default="main", help="Target branch (default: main).")
@@ -69,10 +42,7 @@ def main(argv=None):
parser.add_argument("--body-file", help="Read PR body from this file ('-' for stdin).")
args = parser.parse_args(argv)
profile = REMOTES[args.remote]
host = args.host or profile["host"]
org = args.org or profile["org"]
repo = args.repo or profile["repo"]
host, org, repo = resolve_remote(args)
body = args.body
if args.body_file:
@@ -110,3 +80,4 @@ def main(argv=None):
if __name__ == "__main__":
sys.exit(main())