Skip to content
Open
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
3 changes: 3 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
PORT=3001
FRONTEND_URL=http://localhost:3000
# Externally-reachable backend URL. Used to build the OAuth callback URL for
# MCP connectors. Defaults to http://localhost:${PORT} when unset.
BACKEND_PUBLIC_URL=http://localhost:3001
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SECRET_KEY=your-supabase-service-role-key

Expand Down
50 changes: 50 additions & 0 deletions backend/migrations/000_one_shot_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,56 @@ begin
end;
$$;

-- ---------------------------------------------------------------------------
-- User MCP servers
-- ---------------------------------------------------------------------------

create table if not exists public.user_mcp_servers (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users(id) on delete cascade,
slug text not null,
name text not null,
url text not null,
headers jsonb not null default '{}'::jsonb,
enabled boolean not null default true,
last_error text,
auth_type text not null default 'headers'
check (auth_type in ('headers', 'oauth')),
oauth_metadata jsonb,
oauth_tokens jsonb,
oauth_code_verifier text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint user_mcp_servers_slug_format
check (slug ~ '^[a-z0-9_-]{1,24}$'),
unique (user_id, slug)
);

create index if not exists idx_user_mcp_servers_user
on public.user_mcp_servers(user_id, enabled);

alter table public.user_mcp_servers enable row level security;

drop policy if exists "Users can view their own MCP servers" on public.user_mcp_servers;
create policy "Users can view their own MCP servers"
on public.user_mcp_servers for select
using (auth.uid() = user_id);

drop policy if exists "Users can insert their own MCP servers" on public.user_mcp_servers;
create policy "Users can insert their own MCP servers"
on public.user_mcp_servers for insert
with check (auth.uid() = user_id);

drop policy if exists "Users can update their own MCP servers" on public.user_mcp_servers;
create policy "Users can update their own MCP servers"
on public.user_mcp_servers for update
using (auth.uid() = user_id);

drop policy if exists "Users can delete their own MCP servers" on public.user_mcp_servers;
create policy "Users can delete their own MCP servers"
on public.user_mcp_servers for delete
using (auth.uid() = user_id);

-- ---------------------------------------------------------------------------
-- Tabular reviews
-- ---------------------------------------------------------------------------
Expand Down
49 changes: 49 additions & 0 deletions backend/migrations/001_user_mcp_servers.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
-- User-configurable MCP (Model Context Protocol) servers.
-- Each row points the chat backend at a Streamable-HTTP MCP endpoint that
-- exposes additional tools. Tools discovered from these endpoints are merged
-- into the per-request tool list and routed under the `mcp__<slug>__` prefix.
--
-- Sensitive header values (e.g. Authorization tokens) live in the `headers`
-- jsonb column. Access is gated by Postgres RLS — owner-only — matching the
-- precedent set by user_profiles.claude_api_key / gemini_api_key.

create table if not exists public.user_mcp_servers (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users(id) on delete cascade,
slug text not null,
name text not null,
url text not null,
headers jsonb not null default '{}'::jsonb,
enabled boolean not null default true,
last_error text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint user_mcp_servers_slug_format
check (slug ~ '^[a-z0-9_-]{1,24}$'),
unique (user_id, slug)
);

create index if not exists idx_user_mcp_servers_user
on public.user_mcp_servers(user_id, enabled);

alter table public.user_mcp_servers enable row level security;

drop policy if exists "Users can view their own MCP servers" on public.user_mcp_servers;
create policy "Users can view their own MCP servers"
on public.user_mcp_servers for select
using (auth.uid() = user_id);

drop policy if exists "Users can insert their own MCP servers" on public.user_mcp_servers;
create policy "Users can insert their own MCP servers"
on public.user_mcp_servers for insert
with check (auth.uid() = user_id);

drop policy if exists "Users can update their own MCP servers" on public.user_mcp_servers;
create policy "Users can update their own MCP servers"
on public.user_mcp_servers for update
using (auth.uid() = user_id);

drop policy if exists "Users can delete their own MCP servers" on public.user_mcp_servers;
create policy "Users can delete their own MCP servers"
on public.user_mcp_servers for delete
using (auth.uid() = user_id);
16 changes: 16 additions & 0 deletions backend/migrations/002_user_mcp_servers_oauth.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- OAuth 2.1 support for user-configured MCP connectors.
-- Adds auth-mode toggle + storage for the discovered authorization-server
-- metadata, the dynamically-registered client info, the access/refresh
-- tokens, and the (transient) PKCE verifier between /oauth/start and
-- /oauth/callback.
--
-- Tokens are stored at-rest in jsonb (RLS owner-only). Per-row encryption
-- is intentionally deferred to a separate hardening PR — this matches the
-- existing precedent for user_profiles.{claude,gemini}_api_key.

alter table public.user_mcp_servers
add column if not exists auth_type text not null default 'headers'
check (auth_type in ('headers', 'oauth')),
add column if not exists oauth_metadata jsonb,
add column if not exists oauth_tokens jsonb,
add column if not exists oauth_code_verifier text;
Loading