fix: sync registry with official MCP API and fix stdio prefill

- Updated transport types and URLs from registry.modelcontextprotocol.io API
- GitHub: streamable-http at api.githubcopilot.com/mcp/
- GitLab: streamable-http at gitlab.com/api/v4/mcp (remote only)
- Atlassian: SSE at mcp.atlassian.com/v1/sse (remote only)
- Linear: SSE at mcp.linear.app/sse (remote only)
- Notion: SSE at mcp.notion.com/sse (remote only)
- Stripe: streamable-http at mcp.stripe.com (remote only)
- Exa: streamable-http at mcp.exa.ai/mcp (remote only)
- Cloudflare: SSE at bindings.mcp.cloudflare.com/sse (remote only)
- Sentry: stdio via @sentry/mcp-server (npm, correct package)
- Snowflake: stdio via snowflake-labs-mcp (pypi/uvx, not npm)
- Brave Search: stdio via @brave/brave-search-mcp-server (correct package)
- Fixed stdio prefill to generate stdio_config JSON instead of separate fields
- Discovery modal matches create modal width and header style
- Back arrow positioned on left of create modal header
This commit is contained in:
Ishaan Jaffer 2026-02-12 17:16:27 -08:00
parent 78c08b1707
commit 8375866c82
3 changed files with 210 additions and 212 deletions

View file

@ -3,14 +3,12 @@
{
"name": "github",
"title": "GitHub",
"description": "Repository management, file operations, and GitHub API integration",
"description": "Manage repos, issues, PRs, and workflows through natural language",
"icon_url": "https://cdn.simpleicons.org/github",
"category": "Developer Tools",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@modelcontextprotocol/server-github",
"registry_url": "https://registry.modelcontextprotocol.io/servers/io.github.github%2Fgithub-mcp-server",
"transport": "http",
"url": "https://mcp.github.com/sse",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"url": "https://api.githubcopilot.com/mcp/",
"env_vars": [
{"name": "GITHUB_PERSONAL_ACCESS_TOKEN", "description": "GitHub Personal Access Token", "secret": true}
]
@ -18,16 +16,14 @@
{
"name": "gitlab",
"title": "GitLab",
"description": "GitLab API integration for project and repository management",
"description": "Official GitLab MCP Server for project and repository management",
"icon_url": "https://cdn.simpleicons.org/gitlab",
"category": "Developer Tools",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@modelcontextprotocol/server-gitlab",
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-gitlab"],
"registry_url": "https://registry.modelcontextprotocol.io/servers/com.gitlab%2Fmcp",
"transport": "http",
"url": "https://gitlab.com/api/v4/mcp",
"env_vars": [
{"name": "GITLAB_PERSONAL_ACCESS_TOKEN", "description": "GitLab Personal Access Token", "secret": true},
{"name": "GITLAB_API_URL", "description": "GitLab API URL (defaults to https://gitlab.com/api/v4)", "secret": false}
{"name": "GITLAB_PERSONAL_ACCESS_TOKEN", "description": "GitLab Personal Access Token", "secret": true}
]
},
{
@ -36,11 +32,9 @@
"description": "Jira issues, Confluence pages, and Atlassian product integration",
"icon_url": "https://cdn.simpleicons.org/atlassian",
"category": "Developer Tools",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-atlassian",
"transport": "http",
"registry_url": "https://registry.modelcontextprotocol.io/servers/com.atlassian%2Fatlassian-mcp-server",
"transport": "sse",
"url": "https://mcp.atlassian.com/v1/sse",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-atlassian"],
"env_vars": []
},
{
@ -49,28 +43,23 @@
"description": "Issue tracking, project management, and team workflow automation",
"icon_url": "https://cdn.simpleicons.org/linear",
"category": "Developer Tools",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@modelcontextprotocol/server-linear",
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-linear"],
"env_vars": [
{"name": "LINEAR_API_KEY", "description": "Linear API Key", "secret": true}
]
"registry_url": "https://registry.modelcontextprotocol.io/servers/app.linear%2Flinear",
"transport": "sse",
"url": "https://mcp.linear.app/sse",
"env_vars": []
},
{
"name": "sentry",
"title": "Sentry",
"description": "Error tracking, performance monitoring, and issue management",
"description": "Error monitoring, issue tracking, and debugging for AI assistants",
"icon_url": "https://cdn.simpleicons.org/sentry",
"category": "Developer Tools",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@modelcontextprotocol/server-sentry",
"transport": "http",
"url": "https://mcp.sentry.dev/sse",
"registry_url": "https://registry.modelcontextprotocol.io/servers/io.github.getsentry%2Fsentry-mcp",
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-sentry"],
"args": ["-y", "@sentry/mcp-server"],
"env_vars": [
{"name": "SENTRY_AUTH_TOKEN", "description": "Sentry Auth Token", "secret": true},
{"name": "SENTRY_ORGANIZATION", "description": "Sentry Organization Slug", "secret": false}
{"name": "SENTRY_ACCESS_TOKEN", "description": "Sentry Access Token", "secret": true}
]
},
{
@ -79,7 +68,7 @@
"description": "Channel management, messaging, and Slack workspace integration",
"icon_url": "https://cdn.simpleicons.org/slack",
"category": "Communication",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@modelcontextprotocol/server-slack",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
@ -94,7 +83,7 @@
"description": "Discord server management, messaging, and bot integration",
"icon_url": "https://cdn.simpleicons.org/discord",
"category": "Communication",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-discord",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-discord"],
@ -108,7 +97,7 @@
"description": "Query and manage PostgreSQL databases with read-only access",
"icon_url": "https://cdn.simpleicons.org/postgresql",
"category": "Databases",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@modelcontextprotocol/server-postgres",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
@ -122,7 +111,7 @@
"description": "Query and manage SQLite databases",
"icon_url": "https://cdn.simpleicons.org/sqlite",
"category": "Databases",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@modelcontextprotocol/server-sqlite",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-sqlite"],
@ -136,7 +125,7 @@
"description": "Query and manage MySQL databases",
"icon_url": "https://cdn.simpleicons.org/mysql",
"category": "Databases",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-mysql",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-mysql"],
@ -153,7 +142,7 @@
"description": "Query and manage MongoDB databases and collections",
"icon_url": "https://cdn.simpleicons.org/mongodb",
"category": "Databases",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-mongodb",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-mongodb"],
@ -167,7 +156,7 @@
"description": "Interact with Redis key-value stores",
"icon_url": "https://cdn.simpleicons.org/redis",
"category": "Databases",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-redis",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-redis"],
@ -178,31 +167,29 @@
{
"name": "snowflake",
"title": "Snowflake",
"description": "Query and manage Snowflake data warehouses via Snowflake-managed MCP",
"description": "MCP Server for Snowflake from Snowflake Labs",
"icon_url": "https://cdn.simpleicons.org/snowflake",
"category": "Databases",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-snowflake",
"transport": "http",
"url": "https://<account>.snowflakecomputing.com/api/v2/databases/<database>/schemas/<schema>/mcp-servers/<name>",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-snowflake"],
"registry_url": "https://registry.modelcontextprotocol.io/servers/io.github.Snowflake-Labs%2Fmcp",
"transport": "stdio",
"command": "uvx",
"args": ["snowflake-labs-mcp"],
"env_vars": [
{"name": "SNOWFLAKE_ACCOUNT", "description": "Snowflake account identifier (e.g., xy12345.us-east-1)", "secret": false}
{"name": "SNOWFLAKE_ACCOUNT", "description": "Snowflake account identifier (e.g., xy12345.us-east-1)", "secret": false},
{"name": "SNOWFLAKE_USER", "description": "Snowflake username", "secret": false},
{"name": "SNOWFLAKE_PASSWORD", "description": "Snowflake password", "secret": true}
]
},
{
"name": "notion",
"title": "Notion",
"description": "Search, read, and manage Notion pages and databases",
"description": "Official Notion MCP server for pages and databases",
"icon_url": "https://cdn.simpleicons.org/notion",
"category": "Productivity",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@modelcontextprotocol/server-notion",
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-notion"],
"env_vars": [
{"name": "NOTION_API_KEY", "description": "Notion Integration Token", "secret": true}
]
"registry_url": "https://registry.modelcontextprotocol.io/servers/com.notion%2Fmcp",
"transport": "sse",
"url": "https://mcp.notion.com/sse",
"env_vars": []
},
{
"name": "google_drive",
@ -210,7 +197,7 @@
"description": "Search and access files in Google Drive",
"icon_url": "https://cdn.simpleicons.org/googledrive",
"category": "Productivity",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@modelcontextprotocol/server-gdrive",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-gdrive"],
@ -225,7 +212,7 @@
"description": "Manage events and calendars in Google Calendar",
"icon_url": "https://cdn.simpleicons.org/googlecalendar",
"category": "Productivity",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-google-calendar",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-google-calendar"],
@ -240,7 +227,7 @@
"description": "Read, search, and manage Obsidian vault notes and files",
"icon_url": "https://cdn.simpleicons.org/obsidian",
"category": "Productivity",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-obsidian",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-obsidian"],
@ -251,13 +238,13 @@
{
"name": "brave_search",
"title": "Brave Search",
"description": "Web and local search using the Brave Search API",
"description": "Web results, images, videos, and AI summaries via Brave Search API",
"icon_url": "https://cdn.simpleicons.org/brave",
"category": "Search",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@modelcontextprotocol/server-brave-search",
"registry_url": "https://registry.modelcontextprotocol.io/servers/io.github.brave%2Fbrave-search-mcp-server",
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-brave-search"],
"args": ["-y", "@brave/brave-search-mcp-server"],
"env_vars": [
{"name": "BRAVE_API_KEY", "description": "Brave Search API Key", "secret": true}
]
@ -265,13 +252,12 @@
{
"name": "exa",
"title": "Exa",
"description": "AI-powered web search and content retrieval",
"description": "Fast, intelligent web search and web crawling",
"icon_url": "https://cdn.simpleicons.org/exa",
"category": "Search",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-exa",
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-exa"],
"registry_url": "https://registry.modelcontextprotocol.io/servers/ai.exa%2Fexa",
"transport": "http",
"url": "https://mcp.exa.ai/mcp",
"env_vars": [
{"name": "EXA_API_KEY", "description": "Exa API Key", "secret": true}
]
@ -282,7 +268,7 @@
"description": "AI-optimized search engine for research and retrieval",
"icon_url": "https://cdn.simpleicons.org/tavily",
"category": "Search",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-tavily",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-tavily"],
@ -296,7 +282,7 @@
"description": "Browser automation, web scraping, and screenshot capture",
"icon_url": "https://cdn.simpleicons.org/puppeteer",
"category": "Web & Browser",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@modelcontextprotocol/server-puppeteer",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-puppeteer"],
@ -308,7 +294,7 @@
"description": "Browser automation and testing with Playwright",
"icon_url": "https://cdn.simpleicons.org/playwright",
"category": "Web & Browser",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-playwright",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-playwright"],
@ -320,7 +306,7 @@
"description": "Cloud browser automation and session management",
"icon_url": "https://cdn.simpleicons.org/browserbase",
"category": "Web & Browser",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-browserbase",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-browserbase"],
@ -335,7 +321,7 @@
"description": "Interact with Amazon Web Services resources and APIs",
"icon_url": "https://cdn.simpleicons.org/amazonaws",
"category": "Cloud",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-aws",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-aws"],
@ -348,18 +334,13 @@
{
"name": "cloudflare",
"title": "Cloudflare",
"description": "Manage Cloudflare Workers, KV, R2, and D1 resources",
"description": "Manage Cloudflare Workers, KV, R2, D1, and more",
"icon_url": "https://cdn.simpleicons.org/cloudflare",
"category": "Cloud",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-cloudflare",
"transport": "http",
"url": "https://mcp.cloudflare.com/sse",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-cloudflare"],
"env_vars": [
{"name": "CLOUDFLARE_API_TOKEN", "description": "Cloudflare API Token", "secret": true},
{"name": "CLOUDFLARE_ACCOUNT_ID", "description": "Cloudflare Account ID", "secret": false}
]
"registry_url": "https://registry.modelcontextprotocol.io/servers/com.cloudflare.mcp%2Fmcp",
"transport": "sse",
"url": "https://bindings.mcp.cloudflare.com/sse",
"env_vars": []
},
{
"name": "filesystem",
@ -367,7 +348,7 @@
"description": "Read, write, and manage files and directories on disk",
"icon_url": "https://cdn.simpleicons.org/files",
"category": "System",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@modelcontextprotocol/server-filesystem",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem"],
@ -379,7 +360,7 @@
"description": "Manage Docker containers, images, and networks",
"icon_url": "https://cdn.simpleicons.org/docker",
"category": "System",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-docker",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-docker"],
@ -388,16 +369,13 @@
{
"name": "stripe",
"title": "Stripe",
"description": "Manage payments, customers, and subscriptions via the Stripe API",
"description": "Manage payments, customers, and subscriptions via Stripe",
"icon_url": "https://cdn.simpleicons.org/stripe",
"category": "Finance",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-stripe",
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-stripe"],
"env_vars": [
{"name": "STRIPE_API_KEY", "description": "Stripe Secret API Key", "secret": true}
]
"registry_url": "https://registry.modelcontextprotocol.io/servers/com.stripe%2Fmcp",
"transport": "http",
"url": "https://mcp.stripe.com",
"env_vars": []
},
{
"name": "shopify",
@ -405,7 +383,7 @@
"description": "Manage Shopify stores, products, orders, and customers",
"icon_url": "https://cdn.simpleicons.org/shopify",
"category": "E-Commerce",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-shopify",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-shopify"],
@ -420,7 +398,7 @@
"description": "Send SMS, make calls, and manage communication via Twilio",
"icon_url": "https://cdn.simpleicons.org/twilio",
"category": "Communication",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-twilio",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-twilio"],
@ -435,7 +413,7 @@
"description": "Manage Supabase projects, databases, and storage",
"icon_url": "https://cdn.simpleicons.org/supabase",
"category": "Databases",
"registry_url": "https://registry.modelcontextprotocol.io/servers/@anthropic/mcp-server-supabase",
"registry_url": null,
"transport": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/mcp-server-supabase"],

View file

@ -209,8 +209,19 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
};
if (transport === "stdio") {
prefillValues.command = prefillData.command || "";
prefillValues.args = prefillData.args || [];
const stdioObj: Record<string, any> = {};
if (prefillData.command) stdioObj.command = prefillData.command;
if (prefillData.args && prefillData.args.length > 0) stdioObj.args = prefillData.args;
if (prefillData.env_vars && prefillData.env_vars.length > 0) {
const envObj: Record<string, string> = {};
for (const v of prefillData.env_vars) {
envObj[v.name] = v.description ? `<${v.description}>` : "";
}
stdioObj.env = envObj;
}
if (Object.keys(stdioObj).length > 0) {
prefillValues.stdio_config = JSON.stringify(stdioObj, null, 2);
}
} else if (prefillData.url) {
prefillValues.url = prefillData.url;
}
@ -428,29 +439,27 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
return (
<Modal
title={
<div className="flex items-center justify-between pb-4 border-b border-gray-100">
<div className="flex items-center space-x-3">
<img
src={mcpLogoImg}
alt="MCP Logo"
className="w-8 h-8 object-contain"
style={{
height: "20px",
width: "20px",
marginRight: "8px",
objectFit: "contain",
}}
/>
<h2 className="text-xl font-semibold text-gray-900">Add New MCP Server</h2>
</div>
<div className="flex items-center pb-4 border-b border-gray-100" style={{ gap: 12 }}>
{onBackToDiscovery && (
<button
onClick={onBackToDiscovery}
className="text-sm text-blue-600 hover:text-blue-800 cursor-pointer bg-transparent border-none"
style={{ flexShrink: 0 }}
>
Browse MCP Registry
&#8592;
</button>
)}
<img
src={mcpLogoImg}
alt="MCP Logo"
className="w-8 h-8 object-contain"
style={{
height: "20px",
width: "20px",
objectFit: "contain",
}}
/>
<h2 className="text-xl font-semibold text-gray-900">Add New MCP Server</h2>
</div>
}
open={isModalVisible}

View file

@ -3,6 +3,7 @@ import { Modal, Input } from "antd";
import { Text } from "@tremor/react";
import { fetchDiscoverableMCPServers } from "../networking";
import { DiscoverableMCPServer, DiscoverMCPServersResponse } from "./types";
import { mcpLogoImg } from "./create_mcp_server";
const { Search } = Input;
@ -102,22 +103,24 @@ const MCPDiscovery: React.FC<MCPDiscoveryProps> = ({
return (
<Modal
title={
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<span style={{ fontSize: 14, fontWeight: 600, color: "#111" }}>
Add MCP Server
</span>
<div className="flex items-center justify-between pb-4 border-b border-gray-100">
<div className="flex items-center space-x-3">
<img
src={mcpLogoImg}
alt="MCP Logo"
className="w-8 h-8 object-contain"
style={{
height: "20px",
width: "20px",
marginRight: "8px",
objectFit: "contain",
}}
/>
<h2 className="text-xl font-semibold text-gray-900">Add MCP Server</h2>
</div>
<button
onClick={onCustomServer}
style={{
fontSize: 12,
padding: "3px 10px",
borderRadius: 4,
border: "1px solid #d1d5db",
background: "#fff",
color: "#374151",
cursor: "pointer",
fontWeight: 500,
}}
className="text-sm text-blue-600 hover:text-blue-800 cursor-pointer bg-transparent border-none font-medium"
>
+ Custom Server
</button>
@ -126,14 +129,15 @@ const MCPDiscovery: React.FC<MCPDiscoveryProps> = ({
open={isVisible}
onCancel={onClose}
footer={null}
width={640}
width={1000}
className="top-8"
styles={{
body: { maxHeight: "70vh", overflowY: "auto", padding: "12px 24px 24px" },
header: { padding: "16px 24px 0", border: "none" },
body: { padding: "24px", maxHeight: "70vh", overflowY: "auto" },
header: { padding: "24px 24px 0 24px", border: "none" },
}}
>
{/* Filter pills */}
<div style={{ display: "flex", gap: 4, flexWrap: "wrap", marginBottom: 8 }}>
<div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 12 }}>
{["All", ...categories].map((cat) => {
const isSelected = selectedCategory === cat;
return (
@ -141,13 +145,13 @@ const MCPDiscovery: React.FC<MCPDiscoveryProps> = ({
key={cat}
onClick={() => setSelectedCategory(cat)}
style={{
padding: "2px 8px",
padding: "4px 12px",
borderRadius: 4,
border: isSelected ? "1px solid #111827" : "1px solid #e5e7eb",
background: isSelected ? "#111827" : "#fff",
color: isSelected ? "#fff" : "#4b5563",
cursor: "pointer",
fontSize: 11,
fontSize: 12,
fontWeight: isSelected ? 500 : 400,
lineHeight: "20px",
}}
@ -163,20 +167,19 @@ const MCPDiscovery: React.FC<MCPDiscoveryProps> = ({
placeholder="Search servers..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
style={{ marginBottom: 12 }}
size="small"
style={{ marginBottom: 16 }}
allowClear
/>
{/* Loading skeleton */}
{loading && (
<div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
{Array.from({ length: 8 }).map((_, i) => (
<div
key={i}
style={{
height: 32,
borderRadius: 4,
height: 36,
borderRadius: 6,
background: "#f9fafb",
}}
/>
@ -185,18 +188,18 @@ const MCPDiscovery: React.FC<MCPDiscoveryProps> = ({
)}
{error && (
<div style={{ textAlign: "center", padding: "24px 0", color: "#9ca3af" }}>
<div style={{ textAlign: "center", padding: "32px 0", color: "#9ca3af" }}>
<Text>Failed to load servers: {error}</Text>
</div>
)}
{!loading && !error && filteredServers.length === 0 && (
<div style={{ textAlign: "center", padding: "24px 0", color: "#9ca3af" }}>
<div style={{ textAlign: "center", padding: "32px 0", color: "#9ca3af" }}>
<Text>
No servers found.{" "}
<a
onClick={onCustomServer}
style={{ color: "#2563eb", cursor: "pointer", fontSize: 13 }}
style={{ color: "#2563eb", cursor: "pointer" }}
>
Add a custom server
</a>
@ -204,11 +207,11 @@ const MCPDiscovery: React.FC<MCPDiscoveryProps> = ({
</div>
)}
{/* Server list grouped by category */}
{/* Server list grouped by category — 2 columns */}
{!loading &&
!error &&
Object.entries(groupedServers).map(([category, categoryServers]) => (
<div key={category} style={{ marginBottom: 12 }}>
<div key={category} style={{ marginBottom: 16 }}>
<div
style={{
fontSize: 11,
@ -216,90 +219,98 @@ const MCPDiscovery: React.FC<MCPDiscoveryProps> = ({
color: "#9ca3af",
textTransform: "uppercase",
letterSpacing: "0.05em",
padding: "4px 0",
padding: "6px 0",
borderBottom: "1px solid #f3f4f6",
marginBottom: 2,
marginBottom: 4,
}}
>
{category}
</div>
{categoryServers.map((server) => {
const avatar = getInitialAvatar(server.title || server.name);
return (
<div
key={server.name}
onClick={() => onSelectServer(server)}
style={{
display: "flex",
alignItems: "center",
padding: "6px 8px",
borderRadius: 4,
cursor: "pointer",
transition: "background 0.1s ease",
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = "#f9fafb";
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = "transparent";
}}
>
{server.icon_url ? (
<img
src={server.icon_url}
alt={server.title}
style={{
width: 18,
height: 18,
objectFit: "contain",
flexShrink: 0,
marginRight: 10,
}}
onError={(e) => {
const target = e.currentTarget;
target.style.display = "none";
const next = target.nextElementSibling as HTMLElement;
if (next) next.style.display = "flex";
}}
/>
) : null}
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: "0 16px",
}}
>
{categoryServers.map((server) => {
const avatar = getInitialAvatar(server.title || server.name);
return (
<div
key={server.name}
onClick={() => onSelectServer(server)}
style={{
width: 18,
height: 18,
borderRadius: 3,
backgroundColor: avatar.backgroundColor,
color: "#fff",
display: server.icon_url ? "none" : "flex",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontWeight: 600,
fontSize: 10,
flexShrink: 0,
marginRight: 10,
padding: "8px 10px",
borderRadius: 6,
cursor: "pointer",
transition: "background 0.1s ease",
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = "#f9fafb";
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = "transparent";
}}
>
{avatar.initial}
{server.icon_url ? (
<img
src={server.icon_url}
alt={server.title}
style={{
width: 20,
height: 20,
objectFit: "contain",
flexShrink: 0,
marginRight: 12,
}}
onError={(e) => {
const target = e.currentTarget;
target.style.display = "none";
const next = target.nextElementSibling as HTMLElement;
if (next) next.style.display = "flex";
}}
/>
) : null}
<div
style={{
width: 20,
height: 20,
borderRadius: 4,
backgroundColor: avatar.backgroundColor,
color: "#fff",
display: server.icon_url ? "none" : "flex",
alignItems: "center",
justifyContent: "center",
fontWeight: 600,
fontSize: 11,
flexShrink: 0,
marginRight: 12,
}}
>
{avatar.initial}
</div>
<span
style={{
fontSize: 14,
fontWeight: 400,
color: "#111827",
flex: 1,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{server.title || server.name}
</span>
<span style={{ color: "#d1d5db", fontSize: 14, flexShrink: 0, marginLeft: 8 }}>
&#8250;
</span>
</div>
<span
style={{
fontSize: 13,
fontWeight: 400,
color: "#111827",
flex: 1,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{server.title || server.name}
</span>
<span style={{ color: "#d1d5db", fontSize: 12, flexShrink: 0 }}>
&#8250;
</span>
</div>
);
})}
);
})}
</div>
</div>
))}
</Modal>