Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .cursorignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!.env.example
38 changes: 0 additions & 38 deletions apps/docs/content/guides/getting-started/mcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,6 @@ Choose your Supabase platform, project, and MCP client and follow the installati

<McpConfigPanel />

<Admonition type="note" title="Authentication" className="mt-3">

Your MCP client will automatically prompt you to login to Supabase during setup. This will open a browser window where you can login to your Supabase account and grant access to the MCP client. Be sure to choose the organization that contains the project you wish to work with. In the future, we'll offer more fine grain control over these permissions.

Previously Supabase MCP required you to generate a personal access token (PAT), but this is no longer required.

</Admonition>

### Next steps

Your AI tool is now connected to your Supabase project or account using remote MCP. Try asking the AI tool to query your database using natural language commands.
Expand Down Expand Up @@ -63,33 +55,3 @@ We recommend the following best practices to mitigate security risks when using
- **Project scoping**: Scope your MCP server to a [specific project](https://github.com/supabase-community/supabase-mcp#project-scoped-mode), limiting access to only that project's resources. This prevents LLMs from accessing data from other projects in your Supabase account.
- **Branching**: Use Supabase's [branching feature](/docs/guides/deployment/branching) to create a development branch for your database. This allows you to test changes in a safe environment before merging them to production.
- **Feature groups**: The server allows you to enable or disable specific [tool groups](https://github.com/supabase-community/supabase-mcp#feature-groups), so you can control which tools are available to the LLM. This helps reduce the attack surface and limits the actions that LLMs can perform to only those that you need.

## MCP for local Supabase instances

The Supabase MCP server connects directly to the cloud platform to access your database. If you are running a local instance of Supabase, you can instead use the [Postgres MCP server](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/postgres) to connect to your local database. This MCP server runs all queries as read-only transactions.

### Step 1: Find your database connection string

To connect to your local Supabase instance, you need to get the connection string for your local database. You can find your connection string by running:

```shell
supabase status
```

or if you are using `npx`:

```shell
npx supabase status
```

This will output a list of details about your local Supabase instance. Copy the `DB URL` field in the output.

### Step 2: Configure the MCP server

Configure your client with the following:

<$Partial path="mcp_postgres_config.mdx" variables={{ "app": "your MCP client" }} />

### Next steps

Your AI tool is now connected to your local Supabase instance using MCP. Try asking the AI tool to query your database using natural language commands.
59 changes: 38 additions & 21 deletions apps/docs/features/ui/McpConfigPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
PopoverContent_Shadcn_,
PopoverTrigger_Shadcn_,
} from 'ui'
import { Admonition } from 'ui-patterns'
import { McpConfigPanel as McpConfigPanelBase } from 'ui-patterns/McpUrlBuilder'
import { useProjectsQuery } from '~/lib/fetch/projects'

Expand Down Expand Up @@ -193,28 +194,44 @@ export function McpConfigPanel() {
const project = isPlatform ? selectedProject : null

return (
<div className="not-prose">
<div className="flex gap-3 mb-3">
<PlatformSelector
selectedPlatform={selectedPlatform}
onPlatformSelect={setSelectedPlatform}
<>
<div className="not-prose">
<div className="flex flex-wrap gap-3 mb-3">
<PlatformSelector
selectedPlatform={selectedPlatform}
onPlatformSelect={setSelectedPlatform}
/>
{isPlatform && (
<ProjectSelector selectedProject={project} onProjectSelect={setSelectedProject} />
)}
</div>
<p className="text-xs text-foreground-lighter">
{isPlatform
? 'Scope the MCP server to a project. If no project is selected, all projects will be accessible.'
: 'Project selection is only available for the hosted platform.'}
</p>
<McpConfigPanelBase
basePath="/docs"
className="mt-6"
projectRef={project?.ref}
theme={theme as 'light' | 'dark'}
isPlatform={isPlatform}
/>
{isPlatform && (
<ProjectSelector selectedProject={project} onProjectSelect={setSelectedProject} />
)}
</div>
<p className="text-xs text-foreground-lighter">
{isPlatform
? 'Scope the MCP server to a project. If no project is selected, all projects will be accessible.'
: 'Project selection is only available for the hosted platform.'}
</p>
<McpConfigPanelBase
basePath="/docs"
className="mt-6"
projectRef={project?.ref}
theme={theme as 'light' | 'dark'}
isPlatform={isPlatform}
/>
</div>
{isPlatform && (
<Admonition type="note" title="Authentication" className="mt-3">
<p>
{
"Your MCP client will automatically prompt you to login to Supabase during setup. This will open a browser window where you can login to your Supabase account and grant access to the MCP client. Be sure to choose the organization that contains the project you wish to work with. In the future, we'll offer more fine grain control over these permissions."
}
</p>
<p>
{
'Previously Supabase MCP required you to generate a personal access token (PAT), but this is no longer required.'
}
</p>
</Admonition>
)}
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { EmptyBucketState } from './EmptyBucketState'

export const AnalyticsBuckets = () => {
// Placeholder component - will be implemented in a later PR
return <EmptyBucketState bucketType="analytics" />
}
132 changes: 75 additions & 57 deletions apps/studio/components/interfaces/Storage/CreateBucketModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { snakeCase } from 'lodash'
import { Edit } from 'lucide-react'
import { Plus } from 'lucide-react'
import { useRouter } from 'next/router'
import { useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
Expand Down Expand Up @@ -51,6 +51,7 @@ import {
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { useIsNewStorageUIEnabled } from '../App/FeaturePreview/FeaturePreviewContext'
import { inverseValidBucketNameRegex, validBucketNameRegex } from './CreateBucketModal.utils'
import { convertFromBytes, convertToBytes } from './StorageSettings/StorageSettings.utils'

Expand Down Expand Up @@ -95,10 +96,23 @@ const formId = 'create-storage-bucket-form'

export type CreateBucketForm = z.infer<typeof FormSchema>

export const CreateBucketModal = () => {
interface CreateBucketModalProps {
buttonSize?: 'tiny' | 'small'
buttonType?: 'default' | 'primary'
buttonClassName?: string
label?: string
}

export const CreateBucketModal = ({
buttonSize = 'tiny',
buttonType = 'default',
buttonClassName,
label = 'New bucket',
}: CreateBucketModalProps) => {
const router = useRouter()
const { ref } = useParams()
const { data: org } = useSelectedOrganizationQuery()
const isStorageV2 = useIsNewStorageUIEnabled()

const [visible, setVisible] = useState(false)
const [selectedUnit, setSelectedUnit] = useState<string>(StorageSizeUnits.MB)
Expand Down Expand Up @@ -189,7 +203,7 @@ export const CreateBucketModal = () => {

setSelectedUnit(StorageSizeUnits.MB)
setVisible(false)
router.push(`/project/${ref}/storage/buckets/${values.name}`)
if (!isStorageV2) router.push(`/project/${ref}/storage/buckets/${values.name}`)
} catch (error: any) {
// Handle specific error cases for inline display
const errorMessage = error.message?.toLowerCase() || ''
Expand Down Expand Up @@ -228,8 +242,10 @@ export const CreateBucketModal = () => {
<DialogTrigger asChild>
<ButtonTooltip
block
type="default"
icon={<Edit />}
size={buttonSize}
type={buttonType}
className={buttonClassName}
icon={<Plus size={14} />}
disabled={!canCreateBuckets}
style={{ justifyContent: 'start' }}
onClick={() => setVisible(true)}
Expand All @@ -242,7 +258,7 @@ export const CreateBucketModal = () => {
},
}}
>
New bucket
{label}
</ButtonTooltip>
</DialogTrigger>

Expand Down Expand Up @@ -281,59 +297,61 @@ export const CreateBucketModal = () => {
)}
/>

<FormField_Shadcn_
key="type"
name="type"
control={form.control}
render={({ field }) => (
<FormItemLayout label="Bucket type">
<FormControl_Shadcn_>
<RadioGroupStacked
id="type"
value={field.value}
onValueChange={(v) => field.onChange(v)}
>
<RadioGroupStackedItem
id="STANDARD"
value="STANDARD"
label="Standard bucket"
description="Compatible with S3 buckets."
showIndicator={false}
/>
{IS_PLATFORM && (
{!isStorageV2 && (
<FormField_Shadcn_
key="type"
name="type"
control={form.control}
render={({ field }) => (
<FormItemLayout label="Bucket type">
<FormControl_Shadcn_>
<RadioGroupStacked
id="type"
value={field.value}
onValueChange={(v) => field.onChange(v)}
>
<RadioGroupStackedItem
id="ANALYTICS"
value="ANALYTICS"
label="Analytics bucket"
id="STANDARD"
value="STANDARD"
label="Standard bucket"
description="Compatible with S3 buckets."
showIndicator={false}
disabled={!icebergCatalogEnabled}
>
<>
<p className="text-foreground-light text-left">
Stores Iceberg files and is optimized for analytical workloads.
</p>

{icebergCatalogEnabled ? null : (
<div className="w-full flex gap-x-2 py-2 items-center">
<WarningIcon />
<span className="text-xs text-left">
This feature is currently in alpha and not yet enabled for your
project. Sign up{' '}
<InlineLink href="https://forms.supabase.com/analytics-buckets">
here
</InlineLink>
.
</span>
</div>
)}
</>
</RadioGroupStackedItem>
)}
</RadioGroupStacked>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
/>
{IS_PLATFORM && (
<RadioGroupStackedItem
id="ANALYTICS"
value="ANALYTICS"
label="Analytics bucket"
showIndicator={false}
disabled={!icebergCatalogEnabled}
>
<>
<p className="text-foreground-light text-left">
Stores Iceberg files and is optimized for analytical workloads.
</p>

{icebergCatalogEnabled ? null : (
<div className="w-full flex gap-x-2 py-2 items-center">
<WarningIcon />
<span className="text-xs text-left">
This feature is currently in alpha and not yet enabled for
your project. Sign up{' '}
<InlineLink href="https://forms.supabase.com/analytics-buckets">
here
</InlineLink>
.
</span>
</div>
)}
</>
</RadioGroupStackedItem>
)}
</RadioGroupStacked>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
)}
</DialogSection>

<DialogSectionSeparator />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const EmptyBucketModal = ({ visible, bucket, onClose }: EmptyBucketModalP
folderName: bucket.name,
index: -1,
})
toast.success(`Successfully deleted bucket ${bucket!.name}`)
toast.success(`Successfully emptied bucket ${bucket!.name}`)
onClose()
},
})
Expand Down
25 changes: 25 additions & 0 deletions apps/studio/components/interfaces/Storage/EmptyBucketState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { CreateBucketModal } from './CreateBucketModal'
import { BUCKET_TYPES } from './Storage.constants'

interface EmptyBucketStateProps {
bucketType: keyof typeof BUCKET_TYPES
}

export const EmptyBucketState = ({ bucketType }: EmptyBucketStateProps) => {
const config = BUCKET_TYPES[bucketType]

return (
<aside className="mt-12 border border-dashed w-full bg-surface-100 rounded-lg px-4 py-10 flex flex-col gap-6 items-center text-center gap-1 text-balance">
<div className="flex flex-col gap-1">
<h3>Create {config.label}</h3>
<p className="text-foreground-light text-sm">{config.valueProp}</p>
</div>

{/* [Joshen] We can render the individual bucket modals here instead - where each modal has its own trigger */}
{/* [Danny] CreateBucketModal needs to be split into CreateFileBucketModal, CreateAnalyticsBucketModal, CreateVectorsBucketModal */}
{bucketType === 'files' && (
<CreateBucketModal buttonSize="tiny" buttonType="primary" buttonClassName="w-fit" />
)}
</aside>
)
}
Loading
Loading