feat: add MCP server core, stdio and streamable HTTP transports and serve command

Low-level SDK server built from the catalog plus gitea_api_request, with TOOLSETS/READ_ONLY/DISABLED_TOOLS filters, schema validation without value echo, one audit event per call, static bearer verifier, identity resolver hook, /health, DNS-rebinding protection, rate limiting and fail-closed startup checks.
This commit is contained in:
Latte committed 2026-10-14 16:13:11 +02:00
1 parent 581ca67bf8
commit c9e2c481e7
11 files changed
+2846 -14

No files matched your search

+50 -7
View File
@@ -1,16 +1,20 @@
"""Command line entry point (``aegis-gitea-mcp``).
Subcommands: ``serve`` (default), ``setup`` and ``version``. ``serve`` and
``setup`` are filled in by later chunks.
Subcommands: ``serve`` (default), ``setup`` and ``version``.
"""
from __future__ import annotations
import argparse
import os
import sys
from collections.abc import Sequence
from pydantic import ValidationError
from aegis_gitea_mcp import __version__, setup_cmd
from aegis_gitea_mcp.config import Settings, get_settings, reset_settings
from aegis_gitea_mcp.logging_utils import configure_logging
def build_parser() -> argparse.ArgumentParser:
@@ -28,6 +32,15 @@ def build_parser() -> argparse.ArgumentParser:
default=None,
help="transport to serve (default: MCP_TRANSPORT, else stdio)",
)
serve.add_argument("--host", default=None, help="HTTP bind address (default: MCP_HOST)")
serve.add_argument(
"--port",
type=int,
default=None,
choices=range(1, 65536),
metavar="PORT",
help="HTTP port (default: MCP_PORT)",
)
setup = subparsers.add_parser("setup", help="verify a Gitea instance and write the env file")
setup_cmd.add_arguments(setup)
@@ -35,10 +48,40 @@ def build_parser() -> argparse.ArgumentParser:
return parser
def _not_implemented(command: str) -> int:
"""Report an unbuilt command and return the usage-error exit code."""
print(f"aegis-gitea-mcp {command}: not implemented yet", file=sys.stderr)
return 2
def _load_settings(args: argparse.Namespace) -> Settings | None:
"""Load settings with the ``serve`` flags applied; print problems and return None."""
port = getattr(args, "port", None)
overrides = {
"MCP_TRANSPORT": getattr(args, "transport", None),
"MCP_HOST": getattr(args, "host", None),
"MCP_PORT": None if port is None else str(port),
}
for name, value in overrides.items():
if value is not None:
os.environ[name] = value
reset_settings()
try:
return get_settings()
except ValidationError as exc:
# Print field names and messages only: str(exc) would echo the offending values.
print("aegis-gitea-mcp: invalid configuration:", file=sys.stderr)
for error in exc.errors(include_input=False, include_url=False):
field = ".".join(str(part) for part in error["loc"]).upper() or "settings"
print(f" {field}: {error['msg']}", file=sys.stderr)
return None
def run_serve(args: argparse.Namespace) -> int:
"""Run the MCP server over the selected transport and return the exit code."""
from aegis_gitea_mcp import transports
settings = _load_settings(args)
if settings is None:
return 2
configure_logging(settings.log_level)
if settings.mcp_transport == "http":
return transports.run_http(settings)
return transports.run_stdio(settings)
def main(argv: Sequence[str] | None = None) -> int:
@@ -51,7 +94,7 @@ def main(argv: Sequence[str] | None = None) -> int:
return 0
if command == "setup":
return setup_cmd.run_setup(args)
return _not_implemented(command)
return run_serve(args)
if __name__ == "__main__": # pragma: no cover