|
| 1 | +-- ═══════════════════════════════════════════════════════════════ |
| 2 | +-- MCP Servers Table for Registry |
| 3 | +-- |
| 4 | +-- This table stores ALL data from the MCP Registry API plus |
| 5 | +-- additional metadata from the Mesh (tags, categories, etc.) |
| 6 | +-- |
| 7 | +-- Run this in your Supabase SQL Editor |
| 8 | +-- ═══════════════════════════════════════════════════════════════ |
| 9 | + |
| 10 | +CREATE TABLE IF NOT EXISTS mcp_servers ( |
| 11 | + -- ═══════════════════════════════════════════════════════════════ |
| 12 | + -- DADOS ORIGINAIS DO REGISTRY (indexados) |
| 13 | + -- ═══════════════════════════════════════════════════════════════ |
| 14 | + |
| 15 | + -- Identificação (chave primária composta para suportar múltiplas versões) |
| 16 | + name TEXT NOT NULL, -- "ai.exa/exa" |
| 17 | + version TEXT NOT NULL, -- "3.1.3" |
| 18 | + PRIMARY KEY (name, version), |
| 19 | + schema_url TEXT, -- "$schema" URL |
| 20 | + |
| 21 | + -- Conteúdo |
| 22 | + description TEXT, -- Descrição original do registry (duplicada em short_description) |
| 23 | + website_url TEXT, |
| 24 | + |
| 25 | + -- Objetos complexos (JSONB para queries flexíveis) |
| 26 | + repository JSONB, -- {"url": "...", "source": "github"} |
| 27 | + remotes JSONB, -- [{"type": "streamable-http", "url": "..."}] |
| 28 | + packages JSONB, -- [{"type": "npm", "name": "..."}] |
| 29 | + icons JSONB, -- [{"src": "...", "mimeType": "..."}] |
| 30 | + |
| 31 | + -- Metadados oficiais do registry |
| 32 | + registry_status TEXT DEFAULT 'active', -- status do registry oficial |
| 33 | + published_at TIMESTAMPTZ, |
| 34 | + registry_updated_at TIMESTAMPTZ, |
| 35 | + is_latest BOOLEAN DEFAULT TRUE, |
| 36 | + |
| 37 | + -- ═══════════════════════════════════════════════════════════════ |
| 38 | + -- DADOS EXTRAS DA MESH (agregados) |
| 39 | + -- ═══════════════════════════════════════════════════════════════ |
| 40 | + |
| 41 | + -- Metadados descritivos enriquecidos |
| 42 | + friendly_name TEXT, -- Nome amigável para UI |
| 43 | + short_description TEXT, -- Cópia do description (para consistência com outros campos mesh) |
| 44 | + mesh_description TEXT, -- Descrição completa markdown (será populada por IA/manual) |
| 45 | + tags TEXT[], -- ["search", "web", "ai"] |
| 46 | + categories TEXT[], -- ["productivity", "research"] |
| 47 | + |
| 48 | + -- Flags da Mesh (curadas manualmente ou por AI) |
| 49 | + verified BOOLEAN DEFAULT FALSE, -- Verificado pela mesh |
| 50 | + unlisted BOOLEAN DEFAULT TRUE, -- TRUE = não aparece (padrão), FALSE = aparece (allowlist) |
| 51 | + has_oauth BOOLEAN DEFAULT FALSE, -- Requer OAuth/autenticação |
| 52 | + |
| 53 | + -- Flags computadas (preenchidas pelo script de sync) |
| 54 | + has_remote BOOLEAN DEFAULT FALSE, -- remotes IS NOT NULL AND jsonb_array_length(remotes) > 0 |
| 55 | + is_npm BOOLEAN DEFAULT FALSE, -- packages contém type: "npm" |
| 56 | + is_local_repo BOOLEAN DEFAULT FALSE, -- só tem repository, sem remote/npm |
| 57 | + |
| 58 | + -- ═══════════════════════════════════════════════════════════════ |
| 59 | + -- CONTROLE INTERNO |
| 60 | + -- ═══════════════════════════════════════════════════════════════ |
| 61 | + created_at TIMESTAMPTZ DEFAULT NOW(), |
| 62 | + updated_at TIMESTAMPTZ DEFAULT NOW() |
| 63 | +); |
| 64 | + |
| 65 | +-- ═══════════════════════════════════════════════════════════════ |
| 66 | +-- INDEXES |
| 67 | +-- ═══════════════════════════════════════════════════════════════ |
| 68 | + |
| 69 | +-- Filtros principais |
| 70 | +CREATE INDEX IF NOT EXISTS idx_mcp_servers_is_latest ON mcp_servers(is_latest); |
| 71 | +CREATE INDEX IF NOT EXISTS idx_mcp_servers_verified ON mcp_servers(verified); |
| 72 | +CREATE INDEX IF NOT EXISTS idx_mcp_servers_unlisted ON mcp_servers(unlisted); |
| 73 | +CREATE INDEX IF NOT EXISTS idx_mcp_servers_has_remote ON mcp_servers(has_remote); |
| 74 | + |
| 75 | +-- Índice composto para listagem (query mais comum: is_latest=true + unlisted=false) |
| 76 | +CREATE INDEX IF NOT EXISTS idx_mcp_servers_listing ON mcp_servers(is_latest, unlisted, verified DESC, name); |
| 77 | + |
| 78 | +-- Busca por arrays |
| 79 | +CREATE INDEX IF NOT EXISTS idx_mcp_servers_tags ON mcp_servers USING GIN(tags); |
| 80 | +CREATE INDEX IF NOT EXISTS idx_mcp_servers_categories ON mcp_servers USING GIN(categories); |
| 81 | + |
| 82 | +-- Full-text search |
| 83 | +CREATE INDEX IF NOT EXISTS idx_mcp_servers_search ON mcp_servers USING GIN( |
| 84 | + to_tsvector('english', coalesce(name, '') || ' ' || |
| 85 | + coalesce(description, '') || ' ' || |
| 86 | + coalesce(friendly_name, '') || ' ' || |
| 87 | + coalesce(short_description, '')) |
| 88 | +); |
| 89 | + |
| 90 | +-- Ordenação comum (deprecated - use idx_mcp_servers_listing) |
| 91 | +-- CREATE INDEX IF NOT EXISTS idx_mcp_servers_verified_name ON mcp_servers(verified DESC, name); |
| 92 | + |
| 93 | +-- ═══════════════════════════════════════════════════════════════ |
| 94 | +-- TRIGGERS |
| 95 | +-- ═══════════════════════════════════════════════════════════════ |
| 96 | + |
| 97 | +-- Auto-update updated_at timestamp |
| 98 | +CREATE OR REPLACE FUNCTION update_updated_at_column() |
| 99 | +RETURNS TRIGGER AS $$ |
| 100 | +BEGIN |
| 101 | + NEW.updated_at = NOW(); |
| 102 | + RETURN NEW; |
| 103 | +END; |
| 104 | +$$ language 'plpgsql'; |
| 105 | + |
| 106 | +DROP TRIGGER IF EXISTS update_mcp_servers_updated_at ON mcp_servers; |
| 107 | +CREATE TRIGGER update_mcp_servers_updated_at |
| 108 | + BEFORE UPDATE ON mcp_servers |
| 109 | + FOR EACH ROW |
| 110 | + EXECUTE FUNCTION update_updated_at_column(); |
| 111 | + |
| 112 | +-- ═══════════════════════════════════════════════════════════════ |
| 113 | +-- RLS POLICIES (Row Level Security) |
| 114 | +-- ═══════════════════════════════════════════════════════════════ |
| 115 | + |
| 116 | +-- Enable RLS |
| 117 | +ALTER TABLE mcp_servers ENABLE ROW LEVEL SECURITY; |
| 118 | + |
| 119 | +-- Allow public read access (anon key) - only visible (non-unlisted) items |
| 120 | +CREATE POLICY "Allow public read access" ON mcp_servers |
| 121 | + FOR SELECT |
| 122 | + USING (unlisted = false); |
| 123 | + |
| 124 | +-- Allow authenticated users to insert/update (service role key) |
| 125 | +CREATE POLICY "Allow service role full access" ON mcp_servers |
| 126 | + FOR ALL |
| 127 | + USING (auth.role() = 'service_role'); |
| 128 | + |
| 129 | +-- ═══════════════════════════════════════════════════════════════ |
| 130 | +-- COMMENTS |
| 131 | +-- ═══════════════════════════════════════════════════════════════ |
| 132 | + |
| 133 | +COMMENT ON TABLE mcp_servers IS 'MCP servers indexed from the official registry with mesh metadata'; |
| 134 | +COMMENT ON COLUMN mcp_servers.name IS 'Unique server name from registry (e.g., ai.exa/exa)'; |
| 135 | +COMMENT ON COLUMN mcp_servers.verified IS 'Whether the server is verified by mesh'; |
| 136 | +COMMENT ON COLUMN mcp_servers.unlisted IS 'TRUE = hidden (default for new servers), FALSE = visible (allowlist servers)'; |
| 137 | + |
0 commit comments