Skip to content

feat(agent-applications): auth & identity section with admission editing#3021

Draft
benjackwhite wants to merge 22 commits into
mainfrom
ben/agent-transport-identity
Draft

feat(agent-applications): auth & identity section with admission editing#3021
benjackwhite wants to merge 22 commits into
mainfrom
ben/agent-transport-identity

Conversation

@benjackwhite

Copy link
Copy Markdown
Contributor

Stacked on #2929 (ben/agent-mcp-connections). The agent-builder UI counterpart to posthog PR #66050 (authoritative-provider identity admission).

Adds an Auth & identity section to the agent configuration pane: edit a revision's identity providers and the authoritative_provider that gates admission, alongside the MCP connection work from the base branch.

🤖 Generated with Claude Code

benjackwhite and others added 20 commits June 25, 2026 12:47
Lets an owner attach a shared MCP connection to an agent and edit its
per-tool permissions, reusing PostHog Code's native mcp_store connect flow.
Pairs with the runtime in posthog#66007 (spec.mcps[].connection).

- mcp-server-manager: new shared module — useMcpConnect (installations query +
  connect/reauthorize over the host OAuth callback) + the moved AddCustomServerForm.
  mcp-servers' useMcpServers now consumes the shared mcpKeys/createOAuthCallback.
- agent-applications: editable McpsOverview (add an MCP from a connection, or
  connect a new server inline) + McpBody (connection picker, per-tool
  requires_approval toggles), persisted via useApplyAgentSpec.
- foundation (built on main): api-client updateAgentRevisionSpec + useApplyAgentSpec
  (+ test) — draft-branch-on-save then PATCH. Compatible with the in-flight
  agent-model-policy-ui branch so they merge cleanly.
- drive-by: cast spec.entrypoint in ModelBody to fix a pre-existing @posthog/ui
  typecheck error (AgentSpec index signature) unrelated to this change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- useApplyAgentSpec: invalidate the revision detail via a new
  agentApplicationsKeys.revisionPrefix() instead of a hardcoded key array, so
  the centralised key registry stays the single source of truth.
- McpBody: destructure the ctx fields the apply() callback reads (revisionId,
  revisionState, onSelectRevision) into its dep array; ctx is a fresh object
  literal each render, so depending on it defeated the useCallback memoization.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dock

The Edit-with-AI meta-agent's connect_mcp client tool parks the turn and renders
a PREFILLED AddCustomServerForm in the dock; the user completes the OAuth/api-key
connect themselves (auth never reaches the agent). On success the new mcp_store
connection is attached to the target agent's draft spec (mcps[].connection) and
the session is woken with { connected, connection_id, mcp_id }. Mirrors the
set_secret punch-out.

- agentBuilderStore: pendingMcpConnect state + setter
- useAgentBuilderClientTools: connect_mcp handler (parks + defers)
- AgentBuilderDock: connect form rendering + connect -> attach-to-spec -> resolve
- AddCustomServerForm: initialValues prop for prefill
- useMcpConnect: connectCustomAsync + refetchInstallations

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… legacy integrations

- buildTree renders the authorable folder sections (triggers, secrets, skills,
  tools, mcps, identities) even when empty, so the add/connect affordance is
  reachable on a fresh agent (you couldn't add an MCP to an agent that had none).
- Remove the deprecated integrations UI from the config pane (tree section,
  overview/body, routing, description, the legacy mcps[].auth.integration row,
  now-unused LinkIcon). Removing integrations from the agent-platform spec +
  runtime is a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The agent-builder dock now sends the kind:'client' tool ids it can execute
(AGENT_BUILDER_CLIENT_TOOLS — set_secret, connect_mcp, focus_*, toast,
get_context) in the /run body so the runner knows it can punch out the
interactive connect_mcp form here instead of relaying a URL. Threaded
AGENT_BUILDER_CLIENT_TOOLS → useAgentChat (supportedClientTools option) →
AgentChatSession → agentChatService → runAgentSession (api-client) → POST /run
body. Replaces relying on the never-sent x-posthog-client header.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The inline punch-out crammed the full AddCustomServerForm into the dock's narrow
above-composer strip — ugly. Move connect_mcp to a proper Dialog
(AgentBuilderMcpConnectDialog); the set_secret one-line punch-out stays inline.
Adds a hideHeader prop to AddCustomServerForm so the dialog owns the title/close
chrome (no in-form Back button / duplicate heading).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Producer counterpart to the agent-platform server change that gates
client-tool exposure on a per-run capability list. The agent-builder dock now
declares the `kind:'client'` tool ids it can fulfil and sends them in the /run
body as `supported_client_tools`, so the runner exposes only those to the model
(spec ∩ supported).

- `runAgentSession` (api-client): optional `supportedClientTools`, added to the
  /run body when non-empty.
- `AgentChatSession` (core): carries `supportedClientTools`; `agentChatService`
  forwards it to `runAgentSession`.
- `useAgentChat` (ui): new `supportedClientTools` option threaded into the
  session config.
- `AGENT_BUILDER_CLIENT_TOOLS` declares the dock's fulfillable ids
  (set_secret, focus_*, toast, get_context) — verified to exactly match the
  agent-builder spec's `kind:'client'` tools — and is passed from the dock.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The agent-config MCP sections expanded the custom-server form inline under
a Connect-new/Cancel toggle. Pop it out in a dialog instead (matching the
agent builder's connect_mcp punch-out). Extract AddCustomServerDialog as the
shared modal chrome and refactor AgentBuilderMcpConnectDialog to delegate to
it, so both the manual UI and the punch-out share one wrapper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…' into ben/agent-mcp-connections

# Conflicts:
#	packages/api-client/src/posthog-client.ts
#	packages/core/src/agent-chat/identifiers.ts
#	packages/ui/src/features/agent-applications/agent-builder/useAgentBuilderClientTools.ts
#	packages/ui/src/features/agent-applications/hooks/useAgentChat.ts
The MCP config could add (Connect new / Add from a connection) and edit the
connection, but had no way to drop an mcps[] entry — only clear its connection.
Add a Remove server action to the MCP detail that filters the entry out of the
spec and returns to the list. The shared mcp_store installation is untouched;
only the agent's reference goes away.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ions

# Conflicts:
#	packages/ui/src/features/agent-applications/agent-builder/useAgentBuilderClientTools.ts
#	packages/ui/src/features/agent-applications/hooks/useAgentChat.ts
…ntSpec

The UI removal of the integrations section (IntegrationsOverview/IntegrationBody,
cfg:integrations node, SECTION_INFO entry) left `AgentSpec.integrations?: string[]`
behind — a soft regression where a shipped spec's integrations would be silently
un-editable. Nothing in the app reads the field (the agent-platform schema already
dropped it), so remove it for a total removal. Per review: this is a deliberate
"nobody uses integrations" call, not collateral from the MCP refactor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ions

# Conflicts:
#	packages/ui/src/features/agent-applications/components/AgentConfigurationPane.tsx
#	packages/ui/src/features/agent-applications/hooks/agentApplicationsKeys.ts
#	packages/ui/src/features/agent-applications/hooks/useApplyAgentSpec.test.ts
#	packages/ui/src/features/agent-applications/hooks/useApplyAgentSpec.ts
…+ per-tool overrides)

The connection MCP detail now shows the server's live tool catalog
(useMcpInstallationTools) with a connection-wide default permission and per-tool
overrides, reusing the standalone manager's ToolRow / ToolPolicyToggle. Levels
map allow/approve/deny <-> approved/needs_approval/do_not_use at the boundary;
edits persist to spec.mcps[].default_tool_approval + tools[].level via the
existing draft-branch-then-PATCH apply(). Add-from-connection now stamps a safe
default_tool_approval: 'approve' so the runtime and UI agree from first save.

Also clarifies the agent-level (shared connection — owner connects once, every
asker reuses) vs principal-level (per-asker identity provider) distinction in
the connection description.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…issions

Extract the searchable/expandable tool list (counts, per-tool policy toggle, expandable descriptions) into a presentational ToolPermissionList beside the mcp-servers parts, and wire McpBody to it. The parent owns config + persistence (spec default_tool_approval + per-tool level overrides); the component is callback-driven so it can also back the global MCP-servers manager unchanged. ServerDetailView is left as-is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… on MCP detail

Replace the implicit "set a connection vs wire auth.provider" model with an
explicit credential-model dropdown reading/writing the new required
spec.mcps[].kind:

- Agent-level → connection selector + per-tool permissions.
- Principal-level → identity-provider selector (+ jump, or a prompt to declare
  one). Switching modes clears the opposite credential and stamps `kind`.
- Tidy the layout: the mode dropdown and "Remove server" share one compact
  header row at the top with a single contextual helper line, instead of a
  verbose blurb up top and a remove button buried at the bottom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the principal-level identity UI nicer: the provider dropdown shows a
fingerprint icon per option, and the selected identity renders as a JumpRow
preview card (icon + provider + "per-asker linked identity" status, amber
"undeclared" badge when auth.provider isn't in identity_providers[]) that jumps
to the identity detail — replacing the plain text link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
main dropped the `sonner` dependency (toast moved to @posthog/ui/primitives/
toast, #2965), but these two files still imported from it — passing local
typecheck (sonner still in node_modules) yet breaking CI's clean build/
typecheck ("Rollup failed to resolve import 'sonner'"). Point both at the
ui primitive like the rest of the app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Renames the read-only Identities section to 'auth & identity' and moves it
above triggers — admission gates whether any trigger can start a session.

Adds full editing for the transport-identity admission model:
- authoritative_provider picker (None / declared providers; providers that
  can't prove a subject are disabled), with a validation warning when the
  selected provider is undeclared or non-subject-proving (mirrors the zod
  superRefine).
- Add a PostHog identity (one click) or a bring-your-own oauth2 provider
  (inline form; userinfo_url is what makes it eligible as authoritative).
- Per-provider make/clear authoritative + remove, and an 'auth'/'authoritative'
  badge in the tree and overview.

Persists via useApplyAgentSpec (draft-branch-then-PATCH, whole-spec replace).
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown

React Doctor found 3 issues in 1 file · 3 warnings.

3 warnings

src/features/agent-applications/components/AgentConfigurationPane.tsx

Reviewed by React Doctor for commit 8935e14.

@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "feat(agent-applications): auth & identit..." | Re-trigger Greptile

Comment on lines +2179 to +2195
const submit = () => {
if (!valid) return;
const entry: Record<string, unknown> = {
kind: "oauth2",
id: trimmedId,
binding: "principal",
authorize_url: authorizeUrl.trim(),
token_url: tokenUrl.trim(),
client_id: clientId.trim(),
scopes: scopes
.split(/[\s,]+/)
.map((s) => s.trim())
.filter(Boolean),
};
const ui = userinfoUrl.trim();
if (ui) entry.userinfo_url = ui;
onAdd(entry);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 client_secret absent from the OAuth2 entry

The submit function builds the provider entry with client_id but no client_secret. Standard OAuth2 authorization-code flow requires the secret server-side to exchange the authorization code for a token at token_url. If the backend spec (posthog PR #66050) expects a client_secret field, any session gated on this provider will fail silently at the token-exchange step — the error won't surface until a real user tries to authenticate.

If PKCE is used exclusively (no secret needed), a comment here would prevent the same question from landing in every review. If the secret must be supplied, a client_secret field (or an explicit reference to which env-key holds it) is missing from the form. Is the OAuth2 token exchange PKCE-only, or does the backend spec require a client_secret on the provider entry? If the latter, this form needs a field for it (or a way to reference a secret key).

Comment on lines +1978 to +1994
const saveSpec = (next: AgentSpec, onDone?: () => void) => {
if (!ctx.revisionState) return;
applySpec.mutate(
{
revision: { id: ctx.revisionId, state: ctx.revisionState },
spec: next,
},
{
onSuccess: (rev) => {
if (rev.id !== ctx.revisionId) ctx.onSelectRevision?.(rev.id);
onDone?.();
},
onError: (e) =>
toast.error(e.message || "Failed to save identity providers"),
},
);
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 saveSpec defined identically in two components

This function is defined in IdentitiesOverview (here) and again in IdentityBody (~line 2290) with the only difference being the error-message noun ("providers" vs "provider"). Both have identical mutation logic, onSuccess redirect, and error handling. Extracting this into a shared hook (e.g. useApplySpec(ctx, defaultErrorMessage)) would make future changes to the retry/redirect logic atomic.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

// `spec.authoritative_provider` = the one identity_providers[] id that gates
// admission. It can only be authoritative if it proves a subject (kind posthog,
// or oauth2 + userinfo_url) — mirrors AgentSpecSchema.superRefine.
const NONE_PROVIDER = "__none__";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 NONE_PROVIDER sentinel can collide with a real provider id

If a user names their OAuth2 provider __none__, the select dropdown will contain two <Select.Item> elements with the same value ("__none__"). Radix UI's Select doesn't deduplicate — both items render, and onValueChange fires "__none__" regardless of which is chosen, so the setAuthoritative handler will always clear authoritative_provider rather than set it to the provider. A Symbol or a value that is structurally impossible as a spec id (e.g. the empty string, which the backend likely rejects) would eliminate the collision risk.

benjackwhite and others added 2 commits June 30, 2026 16:35
The runtime no longer enforces the shared connection owner's per-tool approval
marks — the agent's own allow/approve/deny config is authoritative. Surface that
in the MCP "Tool permissions" section so authors know the owner's marks are
informational here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Base automatically changed from ben/agent-mcp-connections to main June 30, 2026 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant