Skip to content

Commit a4f3ce9

Browse files
Add marketplace tab and related localization for MCP view
1 parent d0a46d6 commit a4f3ce9

File tree

3 files changed

+331
-62
lines changed

3 files changed

+331
-62
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import { useEffect, useState } from "react"
2+
import { VSCodeButton, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
3+
import { useAppTranslation } from "../../i18n/TranslationContext"
4+
import { vscode } from "@/utils/vscode"
5+
import { McpMarketplaceItem } from "../../../../src/shared/mcp"
6+
7+
const McpMarketplaceView = () => {
8+
const { t } = useAppTranslation()
9+
const [isLoading, setIsLoading] = useState(true)
10+
const [error, setError] = useState<string | null>(null)
11+
const [items, setItems] = useState<McpMarketplaceItem[]>([])
12+
13+
useEffect(() => {
14+
// Request marketplace data when component mounts
15+
vscode.postMessage({ type: "fetchMcpMarketplace" })
16+
17+
const handleMessage = (event: MessageEvent) => {
18+
const message = event.data
19+
if (message.type === "mcpMarketplaceCatalog") {
20+
setIsLoading(false)
21+
if (message.error) {
22+
setError(message.error)
23+
} else if (message.mcpMarketplaceCatalog) {
24+
setItems(message.mcpMarketplaceCatalog.items)
25+
}
26+
}
27+
}
28+
29+
window.addEventListener("message", handleMessage)
30+
return () => window.removeEventListener("message", handleMessage)
31+
}, [])
32+
33+
const handleInstall = (mcpId: string) => {
34+
vscode.postMessage({
35+
type: "downloadMcp",
36+
mcpId,
37+
})
38+
}
39+
40+
if (isLoading) {
41+
return (
42+
<div
43+
style={{
44+
display: "flex",
45+
flexDirection: "column",
46+
alignItems: "center",
47+
justifyContent: "center",
48+
padding: "40px 20px",
49+
}}>
50+
<VSCodeProgressRing style={{ marginBottom: "16px" }} />
51+
<span>{t("mcp:marketplace.loading")}</span>
52+
</div>
53+
)
54+
}
55+
56+
if (error) {
57+
return (
58+
<div
59+
style={{
60+
display: "flex",
61+
flexDirection: "column",
62+
alignItems: "center",
63+
justifyContent: "center",
64+
padding: "40px 20px",
65+
color: "var(--vscode-errorForeground)",
66+
textAlign: "center",
67+
}}>
68+
<span className="codicon codicon-error" style={{ fontSize: "48px", marginBottom: "16px" }} />
69+
<p style={{ margin: 0, marginBottom: "16px" }}>{error}</p>
70+
<VSCodeButton
71+
onClick={() => {
72+
setIsLoading(true)
73+
setError(null)
74+
vscode.postMessage({ type: "fetchMcpMarketplace" })
75+
}}>
76+
{t("mcp:marketplace.retry")}
77+
</VSCodeButton>
78+
</div>
79+
)
80+
}
81+
82+
return (
83+
<div style={{ padding: "0 10px" }}>
84+
{items.length === 0 ? (
85+
<div
86+
style={{
87+
display: "flex",
88+
flexDirection: "column",
89+
alignItems: "center",
90+
justifyContent: "center",
91+
padding: "40px 20px",
92+
color: "var(--vscode-descriptionForeground)",
93+
textAlign: "center",
94+
}}>
95+
<span className="codicon codicon-inbox" style={{ fontSize: "48px", marginBottom: "16px" }} />
96+
<p style={{ margin: 0 }}>{t("mcp:marketplace.noServers")}</p>
97+
</div>
98+
) : (
99+
<div
100+
style={{
101+
display: "grid",
102+
gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
103+
gap: "16px",
104+
padding: "16px 0",
105+
}}>
106+
{items.map((item) => (
107+
<div
108+
key={item.mcpId}
109+
style={{
110+
background: "var(--vscode-textCodeBlock-background)",
111+
borderRadius: "6px",
112+
overflow: "hidden",
113+
}}>
114+
{/* Header with logo */}
115+
<div
116+
style={{
117+
display: "flex",
118+
alignItems: "center",
119+
padding: "12px",
120+
borderBottom: "1px solid var(--vscode-widget-border)",
121+
}}>
122+
{item.logoUrl ? (
123+
<img
124+
src={item.logoUrl}
125+
alt={item.name}
126+
style={{
127+
width: 32,
128+
height: 32,
129+
borderRadius: "4px",
130+
marginRight: "12px",
131+
}}
132+
/>
133+
) : (
134+
<span
135+
className={`codicon codicon-${item.codiconIcon || "server"}`}
136+
style={{
137+
fontSize: "24px",
138+
marginRight: "12px",
139+
color: "var(--vscode-textLink-foreground)",
140+
}}
141+
/>
142+
)}
143+
<div style={{ flex: 1 }}>
144+
<div style={{ fontWeight: 600, marginBottom: "4px" }}>{item.name}</div>
145+
<div
146+
style={{
147+
fontSize: "12px",
148+
color: "var(--vscode-descriptionForeground)",
149+
}}>
150+
{t("mcp:marketplace.by", { author: item.author })}
151+
</div>
152+
</div>
153+
</div>
154+
155+
{/* Description */}
156+
<div
157+
style={{
158+
padding: "12px",
159+
fontSize: "13px",
160+
color: "var(--vscode-foreground)",
161+
minHeight: "60px",
162+
}}>
163+
{item.description}
164+
</div>
165+
166+
{/* Stats and Install */}
167+
<div
168+
style={{
169+
display: "flex",
170+
alignItems: "center",
171+
padding: "12px",
172+
borderTop: "1px solid var(--vscode-widget-border)",
173+
background: "var(--vscode-widget-shadow)",
174+
}}>
175+
<div
176+
style={{
177+
flex: 1,
178+
display: "flex",
179+
alignItems: "center",
180+
gap: "16px",
181+
fontSize: "12px",
182+
color: "var(--vscode-descriptionForeground)",
183+
}}>
184+
<div>
185+
<span className="codicon codicon-star-full" style={{ marginRight: "4px" }} />
186+
{item.githubStars}
187+
</div>
188+
<div>
189+
<span
190+
className="codicon codicon-cloud-download"
191+
style={{ marginRight: "4px" }}
192+
/>
193+
{item.downloadCount}
194+
</div>
195+
</div>
196+
<VSCodeButton onClick={() => handleInstall(item.mcpId)} style={{ minWidth: "80px" }}>
197+
{t("mcp:marketplace.install")}
198+
</VSCodeButton>
199+
</div>
200+
</div>
201+
))}
202+
</div>
203+
)}
204+
</div>
205+
)
206+
}
207+
208+
export default McpMarketplaceView

0 commit comments

Comments
 (0)