Skip to content

feat(identity): install a personal connector on the identity plane (DCR)#641

Merged
mgoldsborough merged 2 commits into
mainfrom
feat/identity-target-install
Jul 9, 2026
Merged

feat(identity): install a personal connector on the identity plane (DCR)#641
mgoldsborough merged 2 commits into
mainfrom
feat/identity-target-install

Conversation

@mgoldsborough

Copy link
Copy Markdown
Contributor

What

manage_connectors install gains scope: "identity" — installs a remote MCP connection as a personal connector on the caller's own identity (users/<id>/connectors.json) instead of into a workspace. It's the write-path sibling of the interactive Connect flow (#640): install persists the record, and the existing POST /v1/mcp-auth/initiate-identitystartIdentityAuth authenticates and starts the source into the user registry when the user returns.

This is the write path that lights the personal-connector feature up for standard OAuth connectors — until now nothing wrote connectors.json outside tests, so the identity-plane read paths (#634/#639) resolved to nothing.

Scope — DCR-only

Only dcr (standard dynamic-client-registration OAuth, e.g. Granola/Gmail-style) installs on the identity plane here. The other remote-oauth auth types are workspace/platform-bound and rejected with a clear message:

  • composio keys the Composio-side identity on a workspace (composioUserId(wsId))
  • static reads an operator client secret from the workspace credential store
  • provider is a fleet auth class, eager-started outside the interactive Connect flow

Their identity variants (a user-owned Composio connection, an identity composio-auth route, a user credential home) are a separate slice.

Forbid the same-name collision (both directions)

A serverName can't be both a personal connector and a shared-workspace install — get/set_permissions resolve a personal connector first, so the workspace copy's policy would be unaddressable. Enforced at both install points:

  • identity install → rejected if the name already exists as a shared-workspace install in a workspace the caller belongs to (scan reads persisted workspace.json bundles, so it's pod-independent; the caller's own personal workspace is skipped — it's the legacy personal home, not a shared collision)
  • shared-workspace install → rejected if the caller already has a personal connector of that name

A collision that forms later (the caller joins a workspace that already installs the name) is the documented residualresolvePermissionOwner resolves it personal-first by a stated rule.

Also

list_personal_connectors reads the identity plane (IdentityConnectorStore.list) rather than the personal-workspace registry. Display name/description are enriched from the catalog; connection state reports the static resting value (not_authenticated → "Connect") — live per-user state lands with the deferred reauth slice.

Not in scope

  • Composio/static/provider on the identity plane — separate slice (their credential-location design).
  • Profile → Connectors UI — the surface that calls scope: "identity". Until it lands, this path is reachable only by a direct tool call, so the feature stays effectively dark end-to-end.

Testing

  • New test/integration/identity-target-install.test.ts (6): identity install writes connectors.json + scope:"identity"; list_personal_connectors reads the identity plane; non-remote-oauth rejected; composio rejected; collision rejected in both directions (with a no-partial-write assertion).
  • verify:static ✅ (exit 0) · test:unit 4048 pass · test:integration 623 pass / 0 fail.

`manage_connectors install` gains `scope: "identity"` — installs a remote MCP
connection as a PERSONAL connector on the caller's own identity
(`users/<id>/connectors.json`) instead of into a workspace. The write-path
sibling of the interactive Connect flow (#640): install persists the record,
and `POST /v1/mcp-auth/initiate-identity` → `startIdentityAuth` authenticates
and starts the source into the user registry when the user returns. This is the
write path that lights the personal-connector feature up for standard OAuth
connectors (it was dark — nothing wrote `connectors.json` outside tests).

DCR-only for now. `composio` keys the Composio-side identity on a workspace
(`composioUserId(wsId)`), `static` reads an operator client secret from the
workspace credential store, and `provider` is a fleet auth class eager-started
outside the interactive Connect flow — all workspace/platform-bound. Only `dcr`
(standard dynamic-client-registration OAuth) authenticates cleanly with no
workspace binding; the identity variants of the others are a separate slice and
are rejected here with a clear message.

Forbid the same-name collision at both install points: an identity install is
rejected when the serverName already exists as a shared-workspace install in a
workspace the caller belongs to, and a shared-workspace install is rejected when
the caller already has a personal connector of that name. A connector that is
both would be unaddressable by `get/set_permissions`, which resolve a personal
connector first. A collision formed later (the caller joins a workspace that
already installs the name) is the documented residual — personal wins.

`list_personal_connectors` reads the identity plane (`IdentityConnectorStore`),
not the personal-workspace registry.
@mgoldsborough mgoldsborough added the qa-reviewed QA review completed with no critical issues label Jul 9, 2026
The DCR auth-type gate already narrows the action; validateRemoteOAuthInstall
only checks composio/static/provider, so it's a pass-through for dcr. Use the
narrowed entry.install directly.
@mgoldsborough mgoldsborough merged commit b2aa58e into main Jul 9, 2026
6 checks passed
@mgoldsborough mgoldsborough deleted the feat/identity-target-install branch July 9, 2026 20:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

qa-reviewed QA review completed with no critical issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant