Development Preview · PR #2103 · a19684b · built
Skip to content

Integrations

The integrations layer provides a unified infrastructure for connecting SynthOrg to external services. It sits underneath every external consumer (MCP servers, providers, notification sinks, tools) and provides:

  • Connection Catalog: typed registry for all external service credentials
  • Secret Backends: pluggable encrypted credential storage
  • OAuth 2.1: authorization code + PKCE, device flow, client credentials
  • Webhook Receiver: signature verification, replay protection, event bus bridge
  • Health Checks: per-type connection health monitoring with background prober
  • Rate Limiting: tool-side rate limiter via @with_connection_rate_limit
  • MCP Catalog: bundled curated MCP server catalog with install flow
  • Tunnel: ngrok adapter for local webhook development

Connection Catalog

Central registry for external service connections. Each connection has a unique name, a typed connection type, encrypted credentials (via SecretRef), optional rate limiting and health check configuration, and a sensitive flag. When sensitive is set, the governed external-access tool routes every call against the connection (read or write) to human approval, not only write methods.

Connection Types

Type Auth Fields Health Check
github token, api_url GET /user
gitlab token, api_url GET /user
gitea token, api_url GET /api/v1/user
forgejo token, api_url GET /api/v1/user
slack token, signing_secret POST auth.test
smtp host, port, username, password SMTP EHLO
database dialect, host, port, username, password, database SELECT 1
generic_http base_url, token / api_key HEAD base_url
oauth_app client_id, client_secret, auth_url, token_url N/A

Secret Storage

Credentials are encrypted at rest via a pluggable SecretBackend:

Backend Description Status
encrypted_sqlite Fernet-encrypted rows in the SQLite connection_secrets table (default when persistence = SQLite) Implemented
encrypted_postgres Fernet-encrypted rows in the Postgres connection_secrets table (auto-selected when persistence = Postgres) Implemented
env_var Read-only, env var passthrough (no at-rest storage, no OAuth persistence) Implemented
secret_manager_vault External secret manager adapter Stub
secret_manager_cloud_a Cloud secret manager (variant A) Stub
secret_manager_cloud_b Cloud secret manager (variant B) Stub

Both encrypted_* backends share the same Fernet key material (AES-128-CBC + HMAC-SHA256, 32 bytes of key, URL-safe base64). The key is read from the environment variable named by master_key_env on each backend's config (default SYNTHORG_MASTER_KEY). Per-secret rotation (via SecretBackend.rotate) writes a new Fernet token under a fresh secret_id without touching other rows; losing the key loses only the stored secrets, not the rest of the org data. Master-key rotation is not currently supported: changing SYNTHORG_MASTER_KEY makes every previously stored ciphertext undecryptable, so the master key is treated as permanent for the life of the install (re-init preserves it for the same reason).

create_app auto-promotes the default encrypted_sqlite config to encrypted_postgres when the active persistence backend is Postgres, so operators do not have to keep the secret backend and persistence backend in manual sync. This automatic selection is the normal path; the only cases that require explicit config are operators who want env_var (no at-rest storage) or a custom master_key_env variable name. When SYNTHORG_MASTER_KEY is unset, both encrypted backends log an error and downgrade to env_var so the integrations subsystem still boots in a degraded-but-functional state; set the key and restart to re-enable at-rest encryption. The selection logic lives in resolve_secret_backend_config (persistence/secret_backends/factory.py) and is covered by unit tests for each branch.

synthorg init generates a fresh Fernet master key, writes it to config.json (master_key), and wires it into the backend container as SYNTHORG_MASTER_KEY whenever the Encrypt secrets at rest advanced toggle is ON (the default). Re-init preserves the existing key so already-stored ciphertext stays decryptable. The toggle can also be flipped via --encrypt-secrets=true|false in non-interactive mode.

At-rest protection of the rest of the database (non-secret rows, full-text backups, snapshots) is the operator's responsibility: use disk/filesystem encryption (LUKS, BitLocker, FileVault, cloud-provider encrypted volumes, RDS-style encryption at rest). Column-level encryption in the app is deliberately narrow: its goal is to prevent a SQL-level reader from lifting OAuth tokens and API keys, not to substitute for OS/volume encryption.

API Endpoints

Method Path Description
GET /api/v1/connections List all connections
GET /api/v1/connections/{name} Get connection by name
POST /api/v1/connections Create a connection
PATCH /api/v1/connections/{name} Update a connection
DELETE /api/v1/connections/{name} Delete a connection
GET /api/v1/connections/{name}/health On-demand health check
GET /api/v1/connections/{name}/secrets/{field} Scoped reveal of a single credential field (audit-logged; returns a generic 404 on any failure to avoid side-channel leakage)

OAuth 2.1

Full OAuth 2.1 implementation with three grant types:

Authorization Code + PKCE (RFC 7636)

Primary web flow. User clicks "Connect" in dashboard, browser redirects to provider, callback handler exchanges code for tokens.

Device Flow (RFC 8628)

For CLI/headless use. Displays user code and verification URL, polls for authorization.

Client Credentials

Machine-to-machine flow. No user interaction.

Token Lifecycle

OAuthTokenManager background service refreshes tokens before expiry (configurable threshold, default 5 minutes).

API Endpoints

Method Path Description
POST /api/v1/oauth/initiate Start OAuth flow
GET /api/v1/oauth/callback OAuth provider callback
GET /api/v1/oauth/status/{connection_name} Token status

Webhook Receiver

Generic webhook endpoint that verifies signatures and publishes events to the SynthOrg message bus.

Signature Verifiers

Verifier Algorithm Header
GitHubHmacVerifier HMAC-SHA256 X-Hub-Signature-256
SlackSigningVerifier HMAC-SHA256 (v0 scheme) X-Slack-Signature
GenericHmacVerifier Configurable HMAC-SHA256 Configurable

Replay Protection

In-memory nonce + timestamp dedup window (default 5 minutes).

Event Bus Bridge

Verified events are published to the #webhooks channel on the message bus. ExternalTriggerStrategy subscribes and fires workflows on matching events.

API Endpoints

Method Path Description
POST /api/v1/webhooks/{connection_name}/{event_type} Receive webhook (202)
GET /api/v1/webhooks/{connection_name}/activity Webhook activity log

Health Checks

Per-type health check implementations with a background HealthProberService.

  • Smoothing: N consecutive failures before marking unhealthy (default 3)
  • Interval: Configurable (default 5 minutes)
  • Pattern: Matches ProviderHealthProber

Rate Limiting

@with_connection_rate_limit decorator for tool implementations. Reuses RateLimiter from providers/resilience/rate_limiter.py.


MCP Server Catalog

Static JSON catalog (bundled.json) with 8 curated MCP server entries: GitHub, Slack, Filesystem, PostgreSQL, SQLite, Brave Search, Puppeteer, Memory.

API Endpoints

Method Path Description
GET /api/v1/integrations/mcp/catalog Browse all entries
GET /api/v1/integrations/mcp/catalog/search?q= Search entries
GET /api/v1/integrations/mcp/catalog/installed List installed catalog entries (paginated; powers dashboard hydration on refresh)
GET /api/v1/integrations/mcp/catalog/{entry_id} Get single entry
POST /api/v1/integrations/mcp/catalog/install Install a catalog entry (dashboard-driven, idempotent)
DELETE /api/v1/integrations/mcp/catalog/install/{entry_id} Uninstall a catalog entry (idempotent)

Installed catalog entries are persisted in the mcp_installations table and merged into the effective MCPConfig.servers at bridge startup via merge_installed_servers() in synthorg.integrations.mcp_catalog.install. This keeps dashboard installs out-of-band from the user-owned YAML config and ensures they survive restarts without rewriting the config file.


Tunnel

ngrok adapter for local webhook development. The adapter is a standalone wrapper around pyngrok with no persistence, connections, or OAuth dependencies, so it is wired unconditionally (not gated by integrations.enabled) so the dashboard tunnel toggle is always functional. The adapter only starts a tunnel when the operator explicitly calls /integrations/tunnel/start; configuring an auth token via NGROK_AUTHTOKEN (or the env var named in integrations.tunnel.auth_token_env) unlocks paid-tier behaviour (stable URLs, higher rate caps).

The GET /integrations/tunnel/status response includes has_auth_token: bool so the dashboard can show free-tier vs paid-tier hints without ever transmitting the token itself.

API Endpoints

Method Path Description
POST /api/v1/integrations/tunnel/start Start tunnel
POST /api/v1/integrations/tunnel/stop Stop tunnel
GET /api/v1/integrations/tunnel/status Get tunnel URL and has_auth_token

Configuration

integrations:
  enabled: true
  connections:
    max_connections_per_type: 100
  secret_backend:
    backend_type: "encrypted_sqlite"
  oauth:
    state_expiry_seconds: 3600
    pkce_required: true
    auto_refresh_threshold_seconds: 300
  webhooks:
    rate_limit_rpm: 100
    replay_window_seconds: 300
    max_payload_bytes: 1000000
    verify_signatures: true
  health:
    check_interval_seconds: 300
    unhealthy_threshold: 3
  tunnel:
    auth_token_env: "NGROK_AUTHTOKEN"
  mcp_catalog:
    enabled: true

integrations.tunnel.auth_token_env names the environment variable the adapter reads its auth token from. The default is NGROK_AUTHTOKEN; override only if you keep the token in a differently-named var.


Provider Migration

ProviderConfig now supports a connection_name field that references a connection in the catalog. When set, credentials are resolved from the catalog at runtime instead of using embedded api_key / OAuth fields.

MCP Service Facades

The integrations domain exposes five service facades on AppState for MCP handler shims:

Facade Module Tools shimmed
ClientFacadeService synthorg.integrations.mcp_services synthorg_clients_list/_get/_create/_deactivate/_get_satisfaction
ArtifactFacadeService synthorg.integrations.mcp_services synthorg_artifacts_list/_get/_create/_delete
OntologyFacadeService synthorg.integrations.mcp_services synthorg_ontology_list_entities/_get_entity/_get_relationships/_search
MCPCatalogFacadeService synthorg.integrations.mcp_services synthorg_mcp_catalog_list/_search/_get/_install/_uninstall
OAuthFacadeService synthorg.integrations.mcp_services synthorg_oauth_list_providers/_configure_provider/_remove_provider

All destructive operations (_delete, _deactivate, _uninstall, _remove_provider) route through require_admin_guardrails() and emit MCP_ADMIN_OP_EXECUTED on success. Artifact delete performs storage deletion before index removal so the two cannot diverge silently.