Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
836c357
feat(integrations): split MCP servers into own page
daryllimyt May 26, 2026
99c5a11
fix(integrations): hide mcp servers from integrations page
daryllimyt May 26, 2026
28b4651
fix(integrations): return mcp oauth to servers page
daryllimyt May 26, 2026
7901743
fix(integrations): show mcp servers as cards
daryllimyt May 26, 2026
7700ee9
feat(integrations): filter mcp-integrations list by source
daryllimyt May 26, 2026
32ddfaa
feat(integrations): unified MCP servers grid with connection actions
daryllimyt May 27, 2026
b517702
refactor(integrations): extract shared hooks, schema, and confirm dialog
daryllimyt May 27, 2026
979a703
fix(integrations): update sentry logo
daryllimyt May 27, 2026
11f41c5
perf(integrations): apply Vercel-review fixes to dialogs and filters
daryllimyt May 27, 2026
9139bc4
fix(integrations): gate integration mutation actions
daryllimyt May 27, 2026
66e7211
fix(integrations): preserve custom mcp providers
daryllimyt May 27, 2026
cc93322
fix(integrations): hide auth-code test action
daryllimyt May 27, 2026
263e5a0
fix: remove MCP rows on provider disconnect
daryllimyt May 27, 2026
1a40421
test: retry transient action doc link checks
daryllimyt May 27, 2026
f03f639
fix(integrations): gate mcp delete action
daryllimyt May 27, 2026
de0e92b
fix(integrations): preserve workspace mcp rows
daryllimyt May 27, 2026
b66be52
fix(integrations): keep provider-backed mcp rows visible
daryllimyt May 27, 2026
731f5d8
fix(integrations): delete duplicate managed mcp rows
daryllimyt May 27, 2026
056f4ab
refactor(integrations): bulk delete managed mcp rows
daryllimyt May 27, 2026
02466f4
fix(integrations): filter connected oauth mcp servers
daryllimyt May 27, 2026
c04a034
fix: escape mcp provider slug cleanup pattern
daryllimyt May 27, 2026
7eff036
fix: version pruned mcp preset refs
daryllimyt May 27, 2026
292b39a
Merge remote-tracking branch 'origin/main' into feat/integrations-ia-…
daryllimyt May 27, 2026
00c7402
fix: version presets after mcp delete
daryllimyt May 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
718 changes: 159 additions & 559 deletions frontend/src/app/workspaces/[workspaceId]/integrations/page.tsx

Large diffs are not rendered by default.

453 changes: 453 additions & 0 deletions frontend/src/app/workspaces/[workspaceId]/mcp-servers/page.tsx

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion frontend/src/client/services.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11391,9 +11391,10 @@ export const mcpIntegrationsCreateMcpIntegration = (

/**
* List Mcp Integrations
* List all MCP integrations for the workspace.
* List MCP integrations for the workspace, optionally filtered by source.
* @param data The data for the request.
* @param data.workspaceId
* @param data.source Restrict results to platform-managed or workspace-authored MCP integrations. Defaults to all rows.
* @returns MCPIntegrationRead Successful Response
* @throws ApiError
*/
Expand All @@ -11406,6 +11407,9 @@ export const mcpIntegrationsListMcpIntegrations = (
path: {
workspace_id: data.workspaceId,
},
query: {
source: data.source,
},
errors: {
422: "Validation Error",
},
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/client/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12272,6 +12272,10 @@ export type McpIntegrationsCreateMcpIntegrationData = {
export type McpIntegrationsCreateMcpIntegrationResponse = MCPIntegrationRead

export type McpIntegrationsListMcpIntegrationsData = {
/**
* Restrict results to platform-managed or workspace-authored MCP integrations. Defaults to all rows.
*/
source?: "platform" | "workspace" | null
workspaceId: string
}

Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/catalog/catalog-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { cn } from "@/lib/utils"
export interface CatalogHeaderPillOption<TValue extends string = string> {
value: TValue
label: string
icon: LucideIcon
icon?: LucideIcon
}

export interface CatalogHeaderSelectOption {
Expand Down Expand Up @@ -115,7 +115,9 @@ export function CatalogHeader<TPillValue extends string = string>({
aria-pressed={isActive}
onClick={() => onPillFilterToggle?.(option.value)}
>
<Icon className="size-3.5 text-muted-foreground" />
{Icon ? (
<Icon className="size-3.5 text-muted-foreground" />
) : null}
{option.label}
</button>
)
Expand Down
114 changes: 114 additions & 0 deletions frontend/src/components/confirm-destructive-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"use client"

import { Loader2 } from "lucide-react"
import type { ReactNode } from "react"
import { useState } from "react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

interface ConfirmDestructiveDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
/**
* The exact string the user must type to enable confirmation. Typically
* the name of the resource being destroyed.
*/
confirmPhrase: string
title: ReactNode
description: ReactNode
/**
* Label for the confirmation button. Defaults to "Delete".
*/
confirmLabel?: string
/**
* Optional placeholder for the confirmation input. Defaults to the
* `confirmPhrase`.
*/
inputPlaceholder?: string
isPending?: boolean
onConfirm: () => void | Promise<void>
}

/**
* Destructive-action confirmation that requires the user to type a specific
* phrase before the confirm button is enabled.
*
* Used for disconnecting OAuth integrations and removing workspace MCP
* servers — the type-the-name gate prevents one-click destruction of
* connections that may be in active use.
*/
export function ConfirmDestructiveDialog({
open,
onOpenChange,
confirmPhrase,
title,
description,
confirmLabel = "Delete",
inputPlaceholder,
isPending = false,
onConfirm,
}: ConfirmDestructiveDialogProps) {
const [confirmText, setConfirmText] = useState("")

const matches = confirmText.trim() === confirmPhrase

function handleOpenChange(next: boolean) {
if (!next) setConfirmText("")
onOpenChange(next)
}

return (
<AlertDialog open={open} onOpenChange={handleOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{title}</AlertDialogTitle>
<AlertDialogDescription>{description}</AlertDialogDescription>
</AlertDialogHeader>
<div className="space-y-2">
<Label htmlFor="confirm-destructive-input">
Type <strong>{confirmPhrase}</strong> to confirm:
</Label>
<Input
id="confirm-destructive-input"
value={confirmText}
onChange={(event) => setConfirmText(event.target.value)}
placeholder={inputPlaceholder ?? confirmPhrase}
disabled={isPending}
autoComplete="off"
/>
</div>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
variant="destructive"
disabled={isPending || !matches}
onClick={async (event) => {
event.preventDefault()
if (!matches) return
try {
await onConfirm()
} catch {
// Mutation callbacks handle user-facing errors.
}
}}
>
{isPending ? (
<Loader2 className="mr-2 size-4 animate-spin" />
) : null}
{confirmLabel}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
7 changes: 4 additions & 3 deletions frontend/src/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1685,15 +1685,16 @@ export function SentryIcon({ className, ...rest }: IconProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 50 44"
viewBox="0 0 256 227"
preserveAspectRatio="xMidYMid"
width="100%"
height="100%"
className={cn("origin-center scale-105", className)}
{...rest}
>
<path
d="M29,2.26a4.67,4.67,0,0,0-8,0L14.42,13.53A32.21,32.21,0,0,1,32.17,40.19H27.55A27.68,27.68,0,0,0,12.09,17.47L6,28a15.92,15.92,0,0,1,9.23,12.17H4.62A.76.76,0,0,1,4,39.06l2.94-5a10.74,10.74,0,0,0-3.36-1.9l-2.91,5a4.54,4.54,0,0,0,1.69,6.24A4.66,4.66,0,0,0,4.62,44H19.15a19.4,19.4,0,0,0-8-17.31l2.31-4A23.87,23.87,0,0,1,23.76,44H36.07a35.88,35.88,0,0,0-16.41-31.8l4.67-8a.77.77,0,0,1,1.05-.27c.53.29,20.29,34.77,20.66,35.17a.76.76,0,0,1-.68,1.13H40.6q.09,1.91,0,3.81h4.78A4.59,4.59,0,0,0,50,39.43a4.49,4.49,0,0,0-.62-2.28Z"
fill="#362d59"
fill="#362D59"
d="M148.368 12.403a23.935 23.935 0 0 0-41.003 0L73.64 70.165c52.426 26.174 87.05 78.177 90.975 136.642h-23.679c-3.918-50.113-34.061-94.41-79.238-116.448l-31.213 53.97a81.595 81.595 0 0 1 47.307 62.375h-54.38a3.895 3.895 0 0 1-3.178-5.69l15.069-25.626a55.046 55.046 0 0 0-17.221-9.738L3.167 191.277a23.269 23.269 0 0 0 8.662 31.982 23.884 23.884 0 0 0 11.583 3.075h74.471a99.432 99.432 0 0 0-41.003-88.72l11.84-20.5c35.679 24.504 55.754 66.038 52.79 109.22h63.094c2.99-65.43-29.047-127.512-84.107-162.986l23.935-41.002a3.947 3.947 0 0 1 5.382-1.384c2.716 1.486 103.993 178.208 105.89 180.258a3.895 3.895 0 0 1-3.486 5.792h-24.396c.307 6.526.307 13.035 0 19.528h24.499A23.528 23.528 0 0 0 256 202.91a23.015 23.015 0 0 0-3.178-11.685L148.368 12.403Z"
/>
</svg>
)
Expand Down
17 changes: 2 additions & 15 deletions frontend/src/components/integrations/integrations-header.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
"use client"

import {
Link2,
Lock,
LockKeyhole,
Sparkles,
Unlink2,
WrenchIcon,
} from "lucide-react"
import { Link2, Lock, LockKeyhole, Unlink2 } from "lucide-react"
import {
CatalogHeader,
type CatalogHeaderPillOption,
type CatalogHeaderSelectFilter,
} from "@/components/catalog/catalog-header"

export type IntegrationTypeFilter =
| "oauth"
| "custom_oauth"
| "mcp"
| "custom_mcp"
export type IntegrationTypeFilter = "oauth" | "custom_oauth"
export type ConnectionFilter = "all" | "connected" | "not_connected"

interface IntegrationsHeaderProps {
Expand All @@ -36,8 +25,6 @@ const TYPE_FILTER_OPTIONS: Array<
> = [
{ value: "oauth", label: "OAuth", icon: Lock },
{ value: "custom_oauth", label: "Custom OAuth", icon: LockKeyhole },
{ value: "mcp", label: "MCP", icon: Sparkles },
{ value: "custom_mcp", label: "Custom MCP", icon: WrenchIcon },
]

export function IntegrationsHeader({
Expand Down
Loading
Loading