Skip to content

Commit 9be95aa

Browse files
authored
Merge pull request #1 from jaseci-labs/mcp-list
Add MCP Servers List page
2 parents 68eec30 + 0d10c73 commit 9be95aa

3 files changed

Lines changed: 282 additions & 4 deletions

File tree

pages/layout.jac

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ cl {
3333
<Link to="/playground" className="px-3 py-1.5 rounded-lg text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors">
3434
Playground
3535
</Link>
36+
<Link to="/mcpregistry" className="px-3 py-1.5 rounded-lg text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors">
37+
MCP Servers List
38+
</Link>
3639
<a href="https://modelcontextprotocol.io" target="_blank" rel="noopener noreferrer" className="flex items-center gap-1 px-3 py-1.5 rounded-lg text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors">
3740
Docs
3841
<ExternalLink size={11} className="opacity-50"/>

pages/mcpregistry.jac

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
"""MCP Servers List – browse and discover available MCP servers from the registry."""
2+
3+
cl import from "@jac/runtime" { useNavigate }
4+
cl import from "lucide-react" {
5+
Server, Globe, Shield, Wifi, Copy, Play, Search, Check, Zap
6+
}
7+
8+
cl {
9+
def RegistryServerCard(props: Any) -> JsxElement {
10+
server = props.server;
11+
navigate = useNavigate();
12+
has copied: bool = False;
13+
14+
def handleTestServer -> None {
15+
params = f"url={encodeURIComponent(server.url)}&transport=streamable-http&auth={server.auth}&name={encodeURIComponent(server.name)}";
16+
navigate(f"/testmcp?{params}");
17+
}
18+
19+
def handleCopyUrl -> None {
20+
navigator.clipboard.writeText(server.url);
21+
copied = True;
22+
setTimeout(lambda -> None { copied = False; }, 1500);
23+
}
24+
25+
transportColors = {
26+
"http": "text-info bg-info/10 border-info/20",
27+
"sse": "text-warning bg-warning/10 border-warning/20",
28+
"stdio": "text-muted-foreground bg-muted border-border"
29+
};
30+
transportLabels = {
31+
"http": "HTTP",
32+
"sse": "SSE",
33+
"stdio": "STDIO"
34+
};
35+
transportCls = transportColors[server.transport] if server.transport in transportColors else transportColors["http"];
36+
transportLabel = transportLabels[server.transport] if server.transport in transportLabels else server.transport.toUpperCase();
37+
38+
return <div className="group relative bg-card border border-border rounded-xl overflow-hidden hover:border-primary/30 hover:shadow-lg hover:shadow-primary/5 transition-all duration-300 flex flex-col">
39+
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-primary/60 via-primary/30 to-transparent opacity-0 group-hover:opacity-100 transition-opacity"/>
40+
41+
<div className="p-5 flex-1">
42+
<div className="flex items-start gap-3 mb-3">
43+
<div className="w-10 h-10 rounded-xl bg-primary/10 border border-primary/20 flex items-center justify-center flex-shrink-0 group-hover:bg-primary/15 transition-colors">
44+
<Server size={18} className="text-primary"/>
45+
</div>
46+
<div className="min-w-0 flex-1">
47+
<h3 className="text-sm font-semibold text-foreground truncate">{server.name}</h3>
48+
<div className="flex items-center gap-1.5 mt-1.5 flex-wrap">
49+
<span className="text-xs text-muted-foreground bg-muted px-2 py-0.5 rounded-full font-mono">{server.namespace}</span>
50+
<span className="text-xs text-success bg-success/10 px-2 py-0.5 rounded-full border border-success/20 font-medium">Official</span>
51+
<span className={f"inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full border {transportCls}"}>{transportLabel}</span>
52+
</div>
53+
</div>
54+
</div>
55+
56+
<p className="text-sm text-muted-foreground leading-relaxed mb-4">{server.description}</p>
57+
58+
{server.authRequired and
59+
<div className="flex items-center gap-1.5 mb-3 text-xs text-muted-foreground">
60+
<Shield size={12} className="text-warning flex-shrink-0"/>
61+
<span>Auth required · Bearer token</span>
62+
</div>
63+
}
64+
65+
<div className="flex items-center gap-2 p-2.5 bg-muted/50 rounded-lg border border-border">
66+
<Globe size={11} className="text-muted-foreground flex-shrink-0"/>
67+
<span className="text-xs text-muted-foreground truncate font-mono flex-1">{server.url}</span>
68+
<button
69+
onClick={lambda(e: Any) -> None { e.stopPropagation(); handleCopyUrl(); }}
70+
className="p-1 rounded hover:bg-muted transition-colors cursor-pointer flex-shrink-0"
71+
title="Copy URL"
72+
>
73+
{copied and <Check size={11} className="text-success"/>}
74+
{not copied and <Copy size={11} className="text-muted-foreground"/>}
75+
</button>
76+
</div>
77+
</div>
78+
79+
<div className="p-4 pt-0">
80+
<button
81+
onClick={handleTestServer}
82+
className="w-full flex items-center justify-center gap-2 bg-primary hover:bg-primary/90 text-primary-foreground font-semibold px-4 py-2.5 rounded-xl transition-all duration-200 cursor-pointer text-sm shadow-sm shadow-primary/20 hover:shadow-primary/30"
83+
>
84+
<Play size={13}/>
85+
Test Server
86+
</button>
87+
</div>
88+
</div>;
89+
}
90+
91+
def:pub page -> JsxElement {
92+
has activeTransport: str = "all";
93+
has searchQuery: str = "";
94+
95+
servers = [
96+
{
97+
"id": "github-copilot",
98+
"name": "GitHub Copilot",
99+
"namespace": "github.com",
100+
"transport": "http",
101+
"description": "Official GitHub Copilot MCP server. Access Copilot-powered AI capabilities via Streamable HTTP.",
102+
"url": "https://api.githubcopilot.com/mcp/",
103+
"auth": "bearer",
104+
"authRequired": True
105+
},
106+
{
107+
"id": "webzum",
108+
"name": "WebZum",
109+
"namespace": "webzum.com",
110+
"transport": "http",
111+
"description": "WebZum MCP server providing web-based tools and integrations via Streamable HTTP.",
112+
"url": "https://webzum.com/api/mcp",
113+
"auth": "none",
114+
"authRequired": False
115+
},
116+
{
117+
"id": "astro-docs",
118+
"name": "Astro Docs",
119+
"namespace": "docs.astro.build",
120+
"transport": "http",
121+
"description": "Official Astro documentation MCP server. Query and explore the full Astro framework documentation.",
122+
"url": "https://mcp.docs.astro.build/mcp",
123+
"auth": "none",
124+
"authRequired": False
125+
},
126+
{
127+
"id": "exa-search",
128+
"name": "Exa Search",
129+
"namespace": "exa.ai",
130+
"transport": "http",
131+
"description": "Exa AI-powered search MCP server. Perform semantic web searches and retrieve structured results.",
132+
"url": "https://mcp.exa.ai/mcp",
133+
"auth": "none",
134+
"authRequired": False
135+
},
136+
{
137+
"id": "hugging-face",
138+
"name": "Hugging Face",
139+
"namespace": "hf.co",
140+
"transport": "http",
141+
"description": "Official Hugging Face MCP server. Access models, datasets, and AI resources from the Hugging Face hub.",
142+
"url": "https://hf.co/mcp",
143+
"auth": "none",
144+
"authRequired": False
145+
},
146+
{
147+
"id": "remote-mcp",
148+
"name": "Remote MCP",
149+
"namespace": "remote-mcp.com",
150+
"transport": "http",
151+
"description": "Remote MCP server providing remote tool execution and integration capabilities via Streamable HTTP.",
152+
"url": "https://mcp.remote-mcp.com",
153+
"auth": "none",
154+
"authRequired": False
155+
},
156+
{
157+
"id": "manifold",
158+
"name": "Manifold",
159+
"namespace": "manifold.markets",
160+
"transport": "http",
161+
"description": "Manifold Markets MCP server. Access prediction market data, questions, and trading information.",
162+
"url": "https://api.manifold.markets/v0/mcp",
163+
"auth": "none",
164+
"authRequired": False
165+
},
166+
{
167+
"id": "javadocs",
168+
"name": "Javadocs",
169+
"namespace": "javadocs.dev",
170+
"transport": "http",
171+
"description": "Javadocs MCP server. Search and query Java API documentation from across the Java ecosystem.",
172+
"url": "https://www.javadocs.dev/mcp",
173+
"auth": "none",
174+
"authRequired": False
175+
},
176+
{
177+
"id": "ferryhopper",
178+
"name": "Ferryhopper",
179+
"namespace": "ferryhopper.com",
180+
"transport": "http",
181+
"description": "Ferryhopper MCP server. Access ferry routes, schedules, and booking data across the Mediterranean.",
182+
"url": "https://mcp.ferryhopper.com/mcp",
183+
"auth": "none",
184+
"authRequired": False
185+
}
186+
];
187+
188+
def serverMatchesFilter(s: Any) -> bool {
189+
if activeTransport != "all" and s.transport != activeTransport {
190+
return False;
191+
}
192+
q = searchQuery.trim().toLowerCase();
193+
if q and not s.name.toLowerCase().includes(q) and not s.description.toLowerCase().includes(q) and not s.namespace.toLowerCase().includes(q) {
194+
return False;
195+
}
196+
return True;
197+
}
198+
199+
filteredServers = servers.filter(serverMatchesFilter);
200+
201+
transportFilters = [["all", "All"], ["http", "HTTP"], ["sse", "SSE"], ["stdio", "STDIO"]];
202+
203+
return <div className="min-h-[calc(100vh-3.5rem)] bg-background">
204+
<div className="max-w-screen-xl mx-auto px-6 py-10">
205+
206+
<div className="mb-8">
207+
<div className="flex items-center gap-3 mb-2">
208+
<h1 className="text-2xl font-bold text-foreground">MCP Servers List</h1>
209+
<span className="inline-flex items-center gap-1.5 text-xs font-medium text-success border border-success/20 bg-success/10 px-2.5 py-1 rounded-full">
210+
<span className="w-1.5 h-1.5 rounded-full bg-success animate-pulse-dot"/>
211+
Official Registry
212+
</span>
213+
</div>
214+
<p className="text-sm text-muted-foreground max-w-3xl leading-relaxed">
215+
{"Browse MCP servers from the "}
216+
<span className="font-semibold text-foreground">official MCP Registry</span>
217+
{". Find integrations for databases, dev tools, productivity, cloud services, and more. Test any server with a remote URL instantly."}
218+
</p>
219+
</div>
220+
221+
<div className="flex flex-col sm:flex-row gap-4 mb-6">
222+
<div className="relative flex-1 max-w-sm">
223+
<Search size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground pointer-events-none"/>
224+
<input
225+
type="text"
226+
value={searchQuery}
227+
onChange={lambda(e: Any) -> None { searchQuery = e.target.value; }}
228+
placeholder="Search MCP servers by name..."
229+
className="w-full bg-background border border-input rounded-lg pl-9 pr-4 py-2.5 text-sm text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring/50 focus:border-ring transition-colors"
230+
/>
231+
</div>
232+
</div>
233+
234+
<div className="flex items-center gap-3 mb-6 pb-6 border-b border-border">
235+
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Transport</span>
236+
<div className="flex items-center gap-1">
237+
{transportFilters.map(lambda(item: Any) -> Any {
238+
return <button
239+
key={item[0]}
240+
onClick={lambda -> None { activeTransport = item[0]; }}
241+
className={f"px-3 py-1.5 rounded-lg text-xs font-medium transition-all cursor-pointer {
242+
'bg-primary/10 text-primary border border-primary/30' if activeTransport == item[0] else 'text-muted-foreground hover:text-foreground hover:bg-muted border border-transparent'
243+
}"}
244+
>{item[1]}</button>;
245+
})}
246+
</div>
247+
</div>
248+
249+
<div className="flex items-center justify-between mb-5">
250+
<p className="text-sm text-muted-foreground">
251+
<span className="font-semibold text-foreground">{len(filteredServers)}</span>
252+
{f" server{'s' if len(filteredServers) != 1 else ''}"}
253+
</p>
254+
</div>
255+
256+
{len(filteredServers) == 0 and
257+
<div className="flex flex-col items-center justify-center py-24 text-center">
258+
<div className="w-16 h-16 rounded-2xl bg-primary/5 border border-primary/10 flex items-center justify-center mb-4">
259+
<Server size={26} className="text-primary/30"/>
260+
</div>
261+
<p className="text-foreground font-semibold mb-1">No servers found</p>
262+
<p className="text-sm text-muted-foreground">Try adjusting your filters or search query</p>
263+
</div>
264+
}
265+
266+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
267+
{filteredServers.map(lambda(server: Any) -> Any {
268+
return <RegistryServerCard key={server.id} server={server}/>;
269+
})}
270+
</div>
271+
</div>
272+
</div>;
273+
}
274+
}

pages/testmcp.jac

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ cl {
1515
setActiveConnection = useMcpStore(lambda(s: Any) -> Any { return s.setActiveConnection; });
1616
client = useMcpClient();
1717

18-
has transport: str = "streamable-http";
19-
has name: str = "";
20-
has url: str = "";
21-
has authType: str = "none";
18+
_qp = Reflect.construct(URLSearchParams, [window.location.search]);
19+
has transport: str = _qp.get("transport") or "streamable-http";
20+
has name: str = _qp.get("name") or "";
21+
has url: str = _qp.get("url") or "";
22+
has authType: str = _qp.get("auth") or "none";
2223
has authValue: str = "";
2324
has authHeaderName: str = "X-API-Key";
2425
has urlError: str = "";

0 commit comments

Comments
 (0)