packages/connectors/catalog/index.json. It is discovery data, not live authority.
What is in the freeze
Lane counts overlap and do not sum to the catalog size: many vendors are carried by both brokers. The browse read publishes
lane_overlap so a rail cannot be drawn as a partition it is not.
Each row is a slug, a display name, a description, categories, a default tier, and one entry per source:
One catalog row
allowed_mcp_origins is the reviewed origin allowlist a federated completion is checked against. It is control’s input, never a browser’s, and the browse projection drops it along with upstream_id and mcp.
Slugs are validated on load: lowercase alphanumerics, _, and -, no dots, and unique. Uniqueness is what makes the browse ordering a total order — two rows sharing a slug would tie on every key, and a tie in a paged read is how a row gets dropped from one page and repeated on the next.
How it is generated
packages/connectors/catalog/generate.py builds index.json from two inputs:
- Vendored public broker catalogs in
sources/— Nango’sapi-catalog.txtand Composio’stoolkits.md. Rows whose auth mode does not map onto Obol’s closedoauth2 | api_keypair are dropped, as areNONEandNO_AUTHrows. - Obol’s own reviewed native packs.
native_packs()walkspackages/connectors/*/bundle.jsonand adds anativesource for every pack whose tier isnativeand custody isobol, keyed by the pack directory name — which is the slug control resolves a bundle by. Auth mode comes from the pack’s reviewedhelp.json.
Regenerate the freeze
logos.json), and ship with the app rather than being fetched from a broker at render time.
Reading the catalog
GET /api/v1/workspaces/{id}/catalog answers in two shapes, discriminated by the presence of page.
With no query parameter at all it returns every row verbatim with the workspace’s connections attached — the shape the connect dialog reads.
Supply any of q, category, source, limit, offset, or cursor and it returns a page of gallery cards plus facets:
Browse the catalog
Every bound is refused rather than clamped: a silently narrowed page or truncated query is a different answer to a different question wearing a
200. The one exception is a category nobody uses, which is an empty page — “nothing is filed under that” is a real answer. A source is not that exception, because the lanes are a closed enum.
Ranking is exact slug, then slug prefix, then name prefix, then substring in slug, then substring in name; ties break on slug, which is unique, so the same offset names the same row on every call. Both facet lists are computed over the unfiltered catalog so neither rail reshuffles while an operator types.
A cursor is an offset plus a digest of the schema version, the freeze timestamp, and every filter field. Replaying a cursor issued for one query against another is refused rather than followed. It is not a signature and confers no authority — every offset it can name is one the caller could pass directly.
GET /api/v1/workspaces/{id}/catalog/{slug} returns one row whole, or 404 for an unknown slug. Both reads require workspace.read.
From a catalog row to a routable capability
1
Register the broker (federated only)
POST /api/v1/workspaces/{id}/catalog-providers with {"provider": "nango" | "composio", "api_key": "…"} creates one broker connection per workspace. The broker’s own API key is sealed by the gateway and the connection goes active. A second registration for the same provider is refused.2
Start the connection
POST /api/v1/workspaces/{id}/connections with {"slug", "source"} resolves the row and its source, takes the workspace lock, and either reuses an abandoned credential-less pending row or creates one. What comes back in next depends on the source’s auth mode: restricted_key, redirect (native OAuth), or catalog_redirect (federated).3
Finish the credential
For a native API-key source,
POST .../restricted-key. For native OAuth, follow the returned authorize URL to the callback. For a federated source, follow the broker’s session URL and then call POST .../federated-complete — which always fails today with 409 broker_verification_unavailable, so no federated connection can be finished. See the warning below.4
Publish
Every mutation stages a snapshot publication inside its transaction and publishes after commit. Nothing is routable until the published version covers the workspace’s current snapshot version.
5
Bind
Once a native or federated connection is active,
tools_from_graph publishes its concrete tools. bindings_for_routes includes active capability bindings whose target resolves to exactly one tool and whose source, custody, tier, surface, and evidence ceiling match the connection. ADR-0063 makes all structurally valid catalog bindings eligible without native-only or curation gates. Inferred broker target names still need a matching concrete tool contract; eligibility does not certify their mapping. See Capabilities.Federated connect sessions
Starting a federated connection creates aCatalogConnectSession (cxs_…) that is:
- Bound to the workspace, the connection, the initiating operator, and the provider.
- Frozen with the reviewed
allowed_mcp_originsfor that source at handoff time, so a later catalog or settings change cannot widen what completion will accept. - Valid for 10 minutes, single use.
cxs_ id.
Because the broker has no return leg to Obol, the connections and catalog reads hand a resumable handle back — but only to a caller who holds connection.manage, only to the operator who initiated it, only while unconsumed and unexpired, only while the session’s provider still matches the connection’s, and only once the snapshot is published. Those are exactly the grounds completion itself refuses on, so a surface never offers a handle that completion would reject.
Completion
POST /api/v1/workspaces/{id}/connections/{connection_id}/federated-complete takes the cxs_ id, the broker’s upstream connection id, and the MCP URL. It runs in two short transactions with no transaction open across the network:
- Phase A locks
Membership → Workspace → Connection → Connector → CatalogConnectSession → SnapshotPublication, validates eligibility and the upstream id, captures the frozen origins, and commits — releasing every lock before a packet leaves. - The probe normalizes and validates the URL against those origins with no lock held. See Capabilities for what that validator does and does not guarantee.
- Phase B retakes the same locks in the same order, repeats every check on fresh rows, and mutates.
Managing a connection
A connection row is otherwise kept forever, because it is the audit trail of a credential this workspace held.
Revocation is two-step
Disabling or purging at Obol covers the Obol hop only. The broker still holds the vendor credential, and a broker-held token can act outside Obol entirely — through the customer’s own scripts or another connected agent. Obol’s audit log is a record of Obol-mediated traffic, not of everything the credential did. Composio cannot revoke API-key-scheme connections at all, leaving revocation to the end user at the vendor.Broker asymmetry
The two brokers are adopted in different roles, and the difference is enforced in the snapshot rather than in a runbook.- Nango
- Composio
The credential substrate. Its catalog is configuration data across roughly 980 providers, its raw proxy preserves the vendor’s numeric HTTP status, and it permits extracting the token — which is the precondition for hybrid custody, and therefore the only path by which a federated write could ever reach
verified.Obol must use the raw proxy rather than the normalized action endpoint, and the proxy-forward-all-response-headers account flag is a contractual precondition: without it the vendor’s request id never reaches a receipt.Rate limits are per Nango account, not per Obol workspace.github has default_tier: native and stripe has default_tier: trusted_worker; federation is listed as an additional source, not the default.
Broker content is data, never instruction
Any surface a broker controls can carry text aimed at a model — Composio’s own pricing page ships a block addressed to AI agents instructing them how to sign up. Catalog-sourced bundles are therefore frozen, content-addressed, workspace-qualified, and conformance-tested, and broker runtime metadata is never authority.Current state
All structurally valid catalog capabilities and bindings are eligible across native and federated sources (ADR-0063). Missing conformance, curation, or additional providers does not exclude a candidate. Workspace access and concrete operation contracts still constrain publication and execution. Federated completion still refuses with409 broker_verification_unavailable until authenticated broker-account verification exists; eligibility does not complete a connection. The historical review and source ledger live at docs/reviews/2026-09-04-connectors-federated.md.
Related pages
Federated connectors
What the federated tier is and what it can prove.
Connector tiers
The gate that decides native, trusted worker, or federated.
OAuth
Gateway-owned exchange and refresh for native connections.
Catalog API
Full request and response shapes.