Skip to content

Hosted connectors and the curated plugin catalog

SDK 0.34 adds AsyncHermesConnectors and AsyncPluginCatalog around Hermes's existing RPC transport. They use the owner/account contracts from Hermes v0.22.0, commit e06df9c2eae6901f4c88f464e0ae6e753c0428b8. The source manifest records the exact source files and hashes inspected for these bindings.

SDK 0.35 also exposes the complete catalog through the existing authenticated Desktop REST endpoint; see the scope distinction below.

from edith_hermes import AsyncHermesConnectors, AsyncPluginCatalog

# rpc is the existing authenticated native connection for this exact profile.
connectors = AsyncHermesConnectors(rpc, profile="family-edith")
catalog = await connectors.catalog()
accounts = await connectors.accounts(connector="linear")
tools = await connectors.tools("linear")

# The backend owns browser authentication and its operation deadline.
operation = await connectors.connect(["linear"], alias="work")
snapshot = await connectors.operation_status(operation["op_id"])

plugins = AsyncPluginCatalog(rpc, profile="family-edith")
pin = await plugins.lookup("catalog-plugin-name")
# Review/install remains a separate consent-bearing administration operation.

Use models.connectors and models.plugin_catalog, or their public modules, for the wire-shape TypedDicts. Results retain native keys and unknown future fields, as the other SDK bindings do; they are not runtime-validated models.

Operations and ownership

SDK call Native RPC / action
list(owner=...) connectors.list
catalog() connectors.catalog
tools(slug, refresh=False) connectors.tools
accounts(connector=None) connectors.accounts
connect(connectors, owner=..., reconnect=False, alias=None, connection_id=None) connectors.connect
rename_account(connection_id, alias) connectors.accounts.rename
remove_account(connection_id) connectors.accounts.remove
policy() connectors.policy.get
set_policy(change, expected_revision=...) connectors.policy.set
operation_status(op_id, owner=...) connectors.operation.status
wake_operation(op_id, owner=...) connectors.operation.wake
respond(op_id, result, owner=...) connection.respond
AsyncPluginCatalog.lookup(name) plugins.catalog.lookup
AsyncPluginCatalog.presence(names) plugins.manage, action presence
AsyncPluginCatalog.onboarding() plugins.manage, action onboarding

Every call explicitly includes the concrete owning profile. An RPC connection already bound to another profile is rejected. The owner defaults to {"type": "account"}; session operations require {"type": "session", "session_id": "exact-session-id"}. Persist the profile, owner and op_id together and restore the same scope when reconciling. Accounts route by opaque connection_id, never by alias or display label. A hosted connection_id is separate from the application's Hermes transport connection.

The SDK preserves full operation snapshots, seq, deadline and target states. An application should ignore old or duplicate frames based on the native seq; the SDK does not derive optimistic authorization state. connection.request and connection.update frames remain available on the existing duplex event stream, with shapes ConnectionRequest and ConnectionUpdate.

connect(alias=...) names exactly one connector account. Repairing an opaque account requires one connector, reconnect=True, connection_id=... and no alias. Retired/disabled accounts remain represented by the backend's disabled flag and must not be presented as selectable accounts.

Policy changes are one native connector/tool delta with the exact returned revision. A conflict propagates without replacing the whole policy or retrying. Connection answers may carry the exact credential fields requested by a native MCP operation; send these only to that operation's owning backend. Neither the SDK nor the application should log them. Account authentication and these connection answers do not authorize an agent's tool execution.

Discovery and compatibility

Pass a ConnectorCapabilities snapshot from the owning live deployment when available:

capabilities = {
    "methods": actual_native_method_names,
    "named_accounts": actual_named_account_parameter_support,
    "plugin_presence": actual_presence_action_support,
}
connectors = AsyncHermesConnectors(rpc, profile="family-edith", capabilities=capabilities)

An explicit missing method or disabled action/parameter raises UnsupportedConnectorOperation (a RemoteError with status 501) before an RPC is sent. Omitted fields mean unknown. Without discovery, native RPC errors propagate unchanged; unknown support must not be shown as an enabled feature. supports(method) returns True, False, or None for that method inventory, not for every action the method may accept. Hermes's gateway.capabilities does not enumerate connector methods; do not invent a discovery RPC or infer support from a version string. A method list alone cannot prove new action or parameter support.

The generated client.gateway.connectors API is pinned to the older full OpenRPC snapshot identified in contracts/sources.yaml. It still has the old session-only request signature. Use these dedicated wrappers for current hosted account and owner-based operations; the older generated API is not silently rewritten for every unrelated Hermes method in this release.

Failure and catalog boundaries

Complete catalog discovery (SDK 0.35)

await desktop.plugins.catalog() uses GET /api/dashboard/plugins/catalog. It first checks AsyncHermesDesktop.capabilities() for that exact GET operation. If the caller already discovered the same deployment, it can pass that DesktopCapabilities snapshot with capabilities=... to avoid a second discovery request. A missing operation raises RemoteError with status 501; an explicitly disconnected discovery snapshot raises status 503. Authentication, network and native server errors propagate rather than returning fixture rows. The SDK sends the bound profile and backend authentication token as usual.

The native response includes entries, removed and generated_at. Entries carry the reviewed SHA, repository, optional subdirectory, description, declared capabilities, environment-key names, host requirements and available catalog artwork. Removed entries retain the backend's recall reason and date. Types are available from models.desktop_plugins and edith_hermes.desktop_plugin_catalog. Results retain native fields and do not install, execute or authenticate any plugin.

Scope: this existing Hermes REST route also merges its launch-home installed, installed_sha, update_available and runtime_status values. Its handler does not scope those annotations to the profile query parameter. They are preserved as native wire data and are not proof that the bound client profile owns that installation. Applications must discard those four annotations and join catalog metadata with the exact profile's plugins.manage list inventory before displaying installed or runtime state. Do not mutate plugins through global dashboard install routes to compensate.

Mutation and RPC boundaries

No wrapper retries any call. A timeout, disconnect or cancelled local await can leave a mutation accepted remotely. Reconcile the original operation via operation_status or read the exact account/policy before deciding on another mutation. Cancelling a coroutine does not send an implicit approval, denial, remote cancel, reconnect or second connect. wake_operation tells the existing watcher that the browser returned; it is not a connect retry. Calls return native errors, including ownership, authentication, policy conflict and expired or unknown-operation errors, without converting them into successful fixtures.

lookup preserves found, invalid_name, unknown and unavailable as different results. A found entry carries the backend-reviewed SHA, repository and subdirectory. It does not bypass plugin installation consent or its capability review. presence(names) describes actual host requirements for the requested catalog names, preserving the native order and omission of unknown names. onboarding() returns an intentionally curated subset. Neither is a full catalog search API. This release does not claim all catalog plugins can load into every renderer or that a hosted provider account is authenticated.

Focused tests cover exact native request shapes, account/session/profile ownership, old/new capability snapshots, stable opaque identities, native status preservation, policy revision validation, credentials passed only to the owning transport, timeout/disconnect/cancellation without replay, and catalog action compatibility. Live account authorization needs an actual provider identity and remains separate from transport/contract validation.

Verification for 0.34: all 568 SDK tests passed, including 44 focused connector and catalog cases and a real TCP/WebSocket test against a simulated gateway. Full Ruff and strict mypy checks passed. A separately isolated native Hermes v0.22.0 instance also exercised these wrappers: known exact-SHA plugin lookup, unknown lookup, ordered host presence, onboarding, session connector availability, and preservation of native disabled-service errors for catalog, accounts, tools and policy. Those disabled-service checks do not certify an authenticated hosted account. Provider OAuth, account rename/removal and policy mutation still require a deliberate live account test with credentials.

SDK 0.34.1 also supports Python 3.14 (>=3.10,<3.15). This was verified in an independent Windows CPython 3.14.7 SDK environment using the frozen SDK lock with every extra and the development group: all 568 SDK tests passed, as did full Ruff and strict mypy checks. CI now includes Python 3.14 on Linux and Windows. The test runner's existing pytest-asyncio version emits Python 3.14 event-loop-policy deprecation warnings; those APIs are scheduled for removal in Python 3.16, outside the supported range. Vendored contract files use LF checkouts so their published SHA-256 values remain valid on Windows.

Verification for 0.35: all 577 SDK tests passed on Windows CPython 3.14.7 and 3.12, including nine focused full-catalog REST cases; full Ruff, strict mypy and contract validation passed. The built wheel was installed normally into an independent Python 3.14.7 client and read the real isolated v0.22.0 and previous-runtime Desktop endpoints. Each returned 614 catalog entries with 614 exact SHA pins and two removed entries at verification time. Missing and incorrect credentials were rejected with HTTP 401. Cross-profile metadata checks also confirmed why launch-home installed annotations must be discarded instead of being attributed to the requesting profile. No provider account, plugin install or existing user profile was changed for these checks.