"""Client and deployment snippets printed by ``aegis-gitea-mcp setup``. Every function returns plain text and takes only non-secret inputs. Secrets are always shown as placeholders such as ````, so these templates can be printed, logged and reused in the documentation without leaking anything. """ from __future__ import annotations import json import shlex from urllib.parse import urlsplit #: Name used for the server in client configurations. SERVER_NAME = "aegis-gitea" #: Container image used by the docker-compose snippet. DOCKER_IMAGE = "git.hiddenden.cafe/hiddenden/aegisgitea-mcp:dev" TOKEN_PLACEHOLDER = "" # noqa: S105 BEARER_PLACEHOLDER = "" #: Loopback base URL used when no public base URL is configured. LOCAL_BASE_URL = "http://127.0.0.1:8080" def claude_code_stdio(gitea_url: str) -> str: """Return the ``claude mcp add`` command for the local stdio server.""" return ( "claude mcp add --transport stdio " f"--env GITEA_URL={gitea_url} --env GITEA_TOKEN={TOKEN_PLACEHOLDER} " f"{SERVER_NAME} -- uvx aegis-gitea-mcp" ) def mcp_servers_json(gitea_url: str) -> str: """Return a generic ``mcpServers`` JSON block (Claude Desktop, Cursor, and similar).""" block = { "mcpServers": { SERVER_NAME: { "command": "uvx", "args": ["aegis-gitea-mcp"], "env": {"GITEA_URL": gitea_url, "GITEA_TOKEN": TOKEN_PLACEHOLDER}, } } } return json.dumps(block, indent=2) def mcp_endpoint(base_url: str, mcp_path: str = "/mcp") -> str: """Return the full MCP endpoint URL for a base URL.""" return f"{base_url.rstrip('/')}{mcp_path}" def claude_code_http(base_url: str, mcp_path: str = "/mcp") -> str: """Return the ``claude mcp add`` command for the streamable-HTTP endpoint.""" return ( f"claude mcp add --transport http {SERVER_NAME} {mcp_endpoint(base_url, mcp_path)} " f'--header "Authorization: Bearer {BEARER_PLACEHOLDER}"' ) def claude_ai_connector(base_url: str, mcp_path: str = "/mcp", *, oauth: bool) -> str: """Return setup instructions for a claude.ai custom connector.""" lines = [ "Settings > Connectors > Add custom connector", f" URL: {mcp_endpoint(base_url, mcp_path)}", ] if oauth: lines.append( " Authentication: leave the OAuth client fields empty. Claude registers itself " "(dynamic client registration) and sends you to Gitea to sign in." ) else: lines.append( " Authentication: OAuth is off. Use a static bearer token where your plan " "supports it, or enable OAuth and re-run setup." ) return "\n".join(lines) def le_chat_connector(base_url: str, mcp_path: str = "/mcp", *, oauth: bool) -> str: """Return setup instructions for a Mistral Le Chat custom connector.""" lines = [ "Intelligence > Connectors > Add Connector > Custom MCP Connector", f" URL: {mcp_endpoint(base_url, mcp_path)} (HTTPS with a valid certificate)", f" Authentication: HTTP Bearer Token, with the {BEARER_PLACEHOLDER} value", ] if oauth: lines.append( " or OAuth 2.1 with dynamic client registration (users sign in with their " "own Gitea account)." ) return "\n".join(lines) def docker_compose(port: int = 8080) -> str: """Return a hardened docker-compose service for the HTTP transport.""" return f"""\ services: aegis-gitea-mcp: image: {DOCKER_IMAGE} restart: unless-stopped env_file: .env environment: MCP_TRANSPORT: http MCP_HOST: 0.0.0.0 # allowed because authentication is configured DATA_DIR: /var/lib/aegis-mcp ports: - "127.0.0.1:{port}:8080" volumes: - aegis-mcp-data:/var/lib/aegis-mcp read_only: true tmpfs: - /tmp cap_drop: - ALL security_opt: - no-new-privileges:true volumes: aegis-mcp-data: """ def caddy_snippet(public_base_url: str, port: int = 8080) -> str: """Return a Caddyfile site block that terminates TLS and proxies to the container.""" host = urlsplit(public_base_url).netloc or "gitea-mcp.example.com" return f"""\ {host} {{ reverse_proxy 127.0.0.1:{port} {{ flush_interval -1 }} }} """ NGINX_NOTE = ( "nginx: inside the proxying location block set `proxy_buffering off;` so " "streamed responses are not held back." ) def read_secret_command(env_file: str, key: str) -> str: """Return a shell command that prints one value of the env file.""" return f"grep {shlex.quote(f'^{key}=')} {shlex.quote(env_file)} | cut -d= -f2-"