From 912cf45b51d69ba33b6ed8638cd4a447316706a9 Mon Sep 17 00:00:00 2001 From: fabiorafaelcoutada Date: Sun, 12 Jul 2026 20:51:08 +0100 Subject: [PATCH] =?UTF-8?q?feat(connectors):=20connector=20hub=20=E2=80=94?= =?UTF-8?q?=20registry=20+=20mirrors,=20devops/cloud/iot=20categories,=201?= =?UTF-8?q?5=20service=20entries=20(forgejo=20live,=20github,=20aws,=20loc?= =?UTF-8?q?alstack,=20minio,=20gcloud,=20azure,=20azurite,=20cloudflare,?= =?UTF-8?q?=20vercel,=20k8s,=20terraform,=20ansible,=20aws-iot,=20mqtt)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .aurelio/connectors/registry.yaml | 284 ++++++++++++++++++++++++++ scripts/generate-connector-mirrors.py | 102 +++++++++ 2 files changed, 386 insertions(+) create mode 100644 .aurelio/connectors/registry.yaml create mode 100644 scripts/generate-connector-mirrors.py diff --git a/.aurelio/connectors/registry.yaml b/.aurelio/connectors/registry.yaml new file mode 100644 index 00000000..b4c1b37e --- /dev/null +++ b/.aurelio/connectors/registry.yaml @@ -0,0 +1,284 @@ +# Aurélio Connector Hub — single source of truth +# +# ONE place external integrations are declared. All surfaces derive from it via +# scripts/generate-connector-mirrors.py: +# +# * .aurelio/connectors/dist/mcp.connectors.json -> MCP fleet / model router +# * .aurelio/connectors/dist/theia.connectors.json -> @aurelio/mcp Connections view +# +# Schema per entry: +# id stable machine id +# label human label (UI) +# category saas | cad | daw | edm | devops | cloud | iot | other +# kind mcp | oauth | api-key | sidecar +# mcp -> remote MCP server (existing *.portugalfuturista.org fleet) +# oauth -> SaaS OAuth2 (authorization-code) — needs a tenant/client id +# api-key -> SaaS token +# sidecar -> local native-host bridge (COM / Remote Script / OSC) +# auth none | oauth2 | api_key | sidecar +# endpoint remote base url (kind=mcp) or api root (oauth/api-key); null for sidecar +# surfaces which downstreams get this connector: mcp | theia | portal +# status declared | provisioned | live +# declared -> catalogued only; no secrets/host wired yet +# provisioned -> secrets present / sidecar built; not exercised +# live -> verified end-to-end against the real host +# scopes OAuth scopes (auth=oauth2) — documentation until provisioned +# host sidecar host platform (kind=sidecar): windows | macos | any +# notes provenance / caveats +# +# Regenerate mirrors after editing: +# python3 scripts/generate-connector-mirrors.py --write-in-place +# python3 scripts/generate-connector-mirrors.py --check # CI guard +# +# NOTE: `status: declared` is the honest default — real OAuth tenants, MCP +# servers, and native sidecars are per-connector follow-ups, NOT implied by +# listing here. Declaring a connector is the catalog step, not the integration. + +version: 1 + +connectors: + # ── DevOps (forges / IaC / k8s) ─────────────────────────────────────────── + - id: forgejo + label: Forgejo (self-hosted git + actions) + category: devops + kind: api-key + auth: api_key + endpoint: https://code.portugalfuturista.org + surfaces: [mcp, theia, portal] + status: live + scopes: [] + host: null + notes: DEFAULT forge (self-hosted first policy). LAN origin 192.168.0.9:3001. CLI = tea. Tokens in Vaultwarden. + + - id: github + label: GitHub + category: devops + kind: api-key + auth: api_key + endpoint: https://api.github.com + surfaces: [mcp, theia] + status: declared + scopes: [] + host: null + notes: Only for public mirrors/upstreams — Forgejo is the default forge. CLI = gh. + + - id: kubernetes + label: Kubernetes + category: devops + kind: sidecar + auth: sidecar + endpoint: null + surfaces: [theia] + status: declared + scopes: [] + host: any + notes: CLI-driven (kubectl/helm). k3s on Proxmox is the self-hosted target; kubeconfig per cluster. + + - id: terraform + label: Terraform / OpenTofu + category: devops + kind: sidecar + auth: sidecar + endpoint: null + surfaces: [theia] + status: declared + scopes: [] + host: any + notes: "CLI-driven IaC. Prefer OpenTofu (license). State backend: MinIO S3 (self-hosted)." + + - id: ansible + label: Ansible + category: devops + kind: sidecar + auth: sidecar + endpoint: null + surfaces: [theia] + status: declared + scopes: [] + host: any + notes: CLI-driven config management for the Proxmox fleet (asus/gigabyte/lattepanda). + + # ── Cloud (public + self-hosted emulators) ──────────────────────────────── + - id: aws + label: AWS + category: cloud + kind: api-key + auth: api_key + endpoint: https://aws.amazon.com + surfaces: [mcp, theia] + status: declared + scopes: [] + host: null + notes: Production AWS only when explicitly required. Dev = aws-localstack/minio endpoints. CLI = aws. + + - id: aws-localstack + label: LocalStack (AWS emulator) + category: cloud + kind: api-key + auth: api_key + endpoint: http://192.168.0.40:4566 + surfaces: [mcp, theia] + status: declared + scopes: [] + host: null + notes: Self-hosted AWS emulation on lattepanda. CLI = aws --endpoint-url or awslocal. + + - id: minio + label: MinIO (S3-compatible, self-hosted) + category: cloud + kind: api-key + auth: api_key + endpoint: http://192.168.0.40:9000 + surfaces: [mcp, theia, portal] + status: declared + scopes: [] + host: null + notes: Self-hosted S3 on lattepanda (console :9001). Default object store + Terraform state backend. + + - id: google-cloud + label: Google Cloud + category: cloud + kind: oauth + auth: oauth2 + endpoint: https://cloudresourcemanager.googleapis.com + surfaces: [mcp, theia] + status: declared + scopes: [cloud-platform] + host: null + notes: CLI = gcloud. No real self-hosted equivalent; firebase emulators / fake-gcs-server for dev. + + - id: azure + label: Microsoft Azure + category: cloud + kind: oauth + auth: oauth2 + endpoint: https://management.azure.com + surfaces: [mcp, theia] + status: declared + scopes: [user_impersonation] + host: null + notes: CLI = az. Dev storage = azure-azurite. + + - id: azure-azurite + label: Azurite (Azure Storage emulator) + category: cloud + kind: api-key + auth: api_key + endpoint: http://192.168.0.40:10000 + surfaces: [mcp, theia] + status: declared + scopes: [] + host: null + notes: Self-hosted Azure Storage emulation on lattepanda (blob :10000, queue :10001, table :10002). + + - id: cloudflare + label: Cloudflare + category: cloud + kind: api-key + auth: api_key + endpoint: https://api.cloudflare.com/client/v4 + surfaces: [mcp, theia] + status: declared + scopes: [] + host: null + notes: Tunnel pf-tunnel-v2 on asus terminates *.portugalfuturista.org. CLIs = cloudflared, wrangler. + + - id: vercel + label: Vercel + category: cloud + kind: api-key + auth: api_key + endpoint: https://api.vercel.com + surfaces: [mcp, theia] + status: declared + scopes: [] + host: null + notes: CLI = vercel. Edge deploys only when Forgejo Pages/static hosting is not enough. + + # ── IoT ─────────────────────────────────────────────────────────────────── + - id: aws-iot + label: AWS IoT Core + category: iot + kind: api-key + auth: api_key + endpoint: https://iot.amazonaws.com + surfaces: [mcp, theia] + status: declared + scopes: [] + host: null + notes: Device registry/shadows for savearth firmware. Uses aws connector credentials. CLIs = aws iot, mosquitto-clients. + + - id: mqtt-local + label: MQTT (self-hosted broker) + category: iot + kind: api-key + auth: api_key + endpoint: null + surfaces: [mcp, theia] + status: declared + scopes: [] + host: null + notes: Self-hosted mosquitto/EMQX broker — endpoint TBD when deployed. CLIs = mosquitto_pub/sub. + + # ── SaaS (Office / Atlassian) ───────────────────────────────────────────── + - id: microsoft-graph + label: Microsoft 365 (Graph) + category: saas + kind: oauth + auth: oauth2 + endpoint: https://graph.microsoft.com/v1.0 + surfaces: [mcp, theia] + status: declared + scopes: [User.Read, Mail.Read, Calendars.Read, Files.Read, offline_access] + host: null + notes: Entra ID app registration required; exposes Outlook/OneDrive/Calendar/Teams via Graph. + + - id: atlassian + label: Atlassian (Jira + Confluence) + category: saas + kind: oauth + auth: oauth2 + endpoint: https://api.atlassian.com + surfaces: [mcp, theia] + status: declared + scopes: [read:jira-work, write:jira-work, read:confluence-content.all, offline_access] + host: null + notes: Atlassian OAuth 2.0 (3LO) app; Jira REST v3 + Confluence REST v2. + + # ── Creative / CAD hosts (sidecar bridges, NOT VSIXs) ───────────────────── + - id: solidworks + label: SolidWorks + category: cad + kind: sidecar + auth: sidecar + endpoint: null + surfaces: [theia] + status: declared + scopes: [] + host: windows + notes: Windows-only. Bridge = COM/ISldWorks automation agent (or VBA add-in) -> MCP over loopback. Surfaces part/assembly/drawing ops. + + - id: ableton-live + label: Ableton Live + category: daw + kind: sidecar + auth: sidecar + endpoint: null + surfaces: [theia] + status: declared + scopes: [] + host: any + notes: Bridge = Ableton Remote Script (Control Surface) or Max-for-Live device -> OSC/MCP. Surfaces transport/track/clip/scene ops. + + # ── Reference: an already-live MCP connector (documents the `live` shape) ── + - id: knowledge-mcp + label: Knowledge MCP + category: other + kind: mcp + auth: none + endpoint: https://knowledge-mcp.portugalfuturista.org/sse + surfaces: [mcp, theia] + status: live + scopes: [] + host: null + notes: Existing fleet member in .aurelio/mcp_config.json — listed as the reference `live` connector shape. diff --git a/scripts/generate-connector-mirrors.py b/scripts/generate-connector-mirrors.py new file mode 100644 index 00000000..30669a39 --- /dev/null +++ b/scripts/generate-connector-mirrors.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +""" +Generate downstream connector-hub mirrors from the single source of truth: + + .aurelio/connectors/registry.yaml + +Outputs (under .aurelio/connectors/dist/ unless --write-in-place is given, +which writes the same files — dist/ IS the in-place location here): + + * mcp.connectors.json — machine-readable catalog the MCP fleet / router + can load (kind=mcp entries map onto mcp_config.json). + * theia.connectors.json — slim shape the @aurelio/mcp Connections view reads: + [{id,label,category,kind,auth,status,host,endpoint}]. + +Usage: + python3 scripts/generate-connector-mirrors.py --write-in-place + python3 scripts/generate-connector-mirrors.py --check # exit 1 if dist stale +""" +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +try: + import yaml # PyYAML +except ImportError: # pragma: no cover + print("PyYAML required: pip install pyyaml", file=sys.stderr) + sys.exit(2) + +REPLICA_ROOT = Path(__file__).resolve().parent.parent +REGISTRY = REPLICA_ROOT / ".aurelio" / "connectors" / "registry.yaml" +DIST_DIR = REPLICA_ROOT / ".aurelio" / "connectors" / "dist" + +THEIA_FIELDS = ("id", "label", "category", "kind", "auth", "status", "host", "endpoint", "surfaces") + + +def load() -> dict: + with REGISTRY.open("r", encoding="utf-8") as f: + return yaml.safe_load(f) + + +def emit_theia(data: dict) -> dict: + conns = [ + {k: c.get(k) for k in THEIA_FIELDS} + for c in data["connectors"] + if "theia" in c.get("surfaces", []) + ] + return {"version": data.get("version", 1), "connectors": conns} + + +def emit_mcp(data: dict) -> dict: + conns = [ + c for c in data["connectors"] + if "mcp" in c.get("surfaces", []) + ] + return {"version": data.get("version", 1), "connectors": conns} + + +def mirrors() -> dict[str, dict]: + data = load() + return { + "theia.connectors.json": emit_theia(data), + "mcp.connectors.json": emit_mcp(data), + } + + +def write_in_place() -> None: + DIST_DIR.mkdir(parents=True, exist_ok=True) + for name, obj in mirrors().items(): + (DIST_DIR / name).write_text(json.dumps(obj, indent=2) + "\n", encoding="utf-8") + print(f"wrote {DIST_DIR / name}") + + +def check() -> int: + stale = [] + for name, obj in mirrors().items(): + p = DIST_DIR / name + want = json.dumps(obj, indent=2) + "\n" + if not p.exists() or p.read_text(encoding="utf-8") != want: + stale.append(name) + if stale: + print("stale connector mirrors (run --write-in-place):", ", ".join(stale), file=sys.stderr) + return 1 + print("connector mirrors up to date") + return 0 + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--write-in-place", action="store_true") + ap.add_argument("--check", action="store_true") + args = ap.parse_args() + if args.check: + return check() + write_in_place() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())