mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add MCP marketplace with free search engine servers (DuckDuckGo, SearXNG)
Adds a "Quick Add Servers" section to the MCP settings view that lets users install pre-configured free search engine MCP servers with one click: - DuckDuckGo Search (via uvx duckduckgo-mcp-server) - no API key needed - SearXNG (via npx mcp-searxng) - requires self-hosted instance URL - Web Search (via npx web-search-mcp) - no API key needed Implementation: - New mcpMarketplaceCatalog with extensible server templates - McpHub.addServer() method for programmatic server installation - installMcpServer webview message type and handler - McpMarketplace UI component with install buttons and env config - i18n translations for marketplace strings - Tests for catalog validation and UI component Closes #12361
This commit is contained in:
parent
e921f9d21e
commit
e6db23b87c
10 changed files with 510 additions and 0 deletions
|
|
@ -451,6 +451,7 @@ export interface WebviewMessage {
|
|||
| "checkpointDiff"
|
||||
| "checkpointRestore"
|
||||
| "deleteMcpServer"
|
||||
| "installMcpServer"
|
||||
| "codebaseIndexEnabled"
|
||||
| "searchFiles"
|
||||
| "toggleApiConfigPin"
|
||||
|
|
|
|||
|
|
@ -1299,6 +1299,24 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
|
|||
}
|
||||
break
|
||||
}
|
||||
case "installMcpServer": {
|
||||
if (!message.serverName || !message.config) {
|
||||
break
|
||||
}
|
||||
|
||||
try {
|
||||
provider.log(`Attempting to install MCP server from marketplace: ${message.serverName}`)
|
||||
await provider.getMcpHub()?.addServer(message.serverName, message.config)
|
||||
provider.log(`Successfully installed MCP server: ${message.serverName}`)
|
||||
|
||||
// Refresh the webview state
|
||||
await provider.postStateToWebview()
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
provider.log(`Failed to install MCP server: ${errorMessage}`)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "restartMcpServer": {
|
||||
try {
|
||||
await provider.getMcpHub()?.restartConnection(message.text!, message.source as "global" | "project")
|
||||
|
|
|
|||
|
|
@ -24,5 +24,9 @@
|
|||
"refreshing_all": "Refreshing all MCP servers...",
|
||||
"all_refreshed": "All MCP servers have been refreshed.",
|
||||
"project_config_deleted": "Project MCP configuration file deleted. All project MCP servers have been disconnected."
|
||||
},
|
||||
"marketplace": {
|
||||
"installed": "Added MCP server: {{serverName}}",
|
||||
"alreadyInstalled": "Server \"{{serverName}}\" already exists in your MCP settings."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1708,6 +1708,56 @@ export class McpHub {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new MCP server to the global settings file.
|
||||
* Used by the MCP Marketplace to install pre-configured servers.
|
||||
*/
|
||||
public async addServer(serverName: string, serverConfig: Record<string, unknown>): Promise<void> {
|
||||
try {
|
||||
const configPath = await this.getMcpSettingsFilePath()
|
||||
|
||||
// Ensure the settings file exists and is accessible
|
||||
try {
|
||||
await fs.access(configPath)
|
||||
} catch {
|
||||
throw new Error("Settings file not accessible")
|
||||
}
|
||||
|
||||
const content = await fs.readFile(configPath, "utf-8")
|
||||
const config = JSON.parse(content)
|
||||
|
||||
if (!config || typeof config !== "object") {
|
||||
throw new Error("Invalid config structure")
|
||||
}
|
||||
|
||||
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
||||
config.mcpServers = {}
|
||||
}
|
||||
|
||||
// Check if server already exists
|
||||
if (config.mcpServers[serverName]) {
|
||||
vscode.window.showWarningMessage(t("mcp:marketplace.alreadyInstalled", { serverName }))
|
||||
return
|
||||
}
|
||||
|
||||
config.mcpServers[serverName] = serverConfig
|
||||
|
||||
const updatedConfig = {
|
||||
mcpServers: config.mcpServers,
|
||||
}
|
||||
|
||||
await safeWriteJson(configPath, updatedConfig, { prettyPrint: true })
|
||||
|
||||
// Trigger server connections update
|
||||
await this.updateServerConnections(config.mcpServers, "global")
|
||||
|
||||
vscode.window.showInformationMessage(t("mcp:marketplace.installed", { serverName }))
|
||||
} catch (error) {
|
||||
this.showErrorMessage(`Failed to add MCP server ${serverName}`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async readResource(serverName: string, uri: string, source?: "global" | "project"): Promise<McpResourceResponse> {
|
||||
const connection = this.findConnection(serverName, source)
|
||||
if (!connection || connection.type !== "connected") {
|
||||
|
|
|
|||
49
src/shared/__tests__/mcpMarketplaceCatalog.spec.ts
Normal file
49
src/shared/__tests__/mcpMarketplaceCatalog.spec.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { mcpMarketplaceCatalog, type McpMarketplaceItem } from "../mcpMarketplaceCatalog"
|
||||
|
||||
describe("mcpMarketplaceCatalog", () => {
|
||||
it("should export a non-empty array of marketplace items", () => {
|
||||
expect(mcpMarketplaceCatalog).toBeInstanceOf(Array)
|
||||
expect(mcpMarketplaceCatalog.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it("should have unique names for each item", () => {
|
||||
const names = mcpMarketplaceCatalog.map((item) => item.name)
|
||||
const uniqueNames = new Set(names)
|
||||
expect(uniqueNames.size).toBe(names.length)
|
||||
})
|
||||
|
||||
it("each item should have required fields", () => {
|
||||
for (const item of mcpMarketplaceCatalog) {
|
||||
expect(item.name).toBeTruthy()
|
||||
expect(item.displayName).toBeTruthy()
|
||||
expect(item.description).toBeTruthy()
|
||||
expect(item.category).toBeTruthy()
|
||||
expect(item.config).toBeDefined()
|
||||
expect(item.config.command).toBeTruthy()
|
||||
expect(item.config.args).toBeInstanceOf(Array)
|
||||
}
|
||||
})
|
||||
|
||||
it("items with requiresSetup should have setupEnvKeys", () => {
|
||||
const setupItems = mcpMarketplaceCatalog.filter((item) => item.requiresSetup)
|
||||
for (const item of setupItems) {
|
||||
expect(item.setupEnvKeys).toBeDefined()
|
||||
expect(item.setupEnvKeys!.length).toBeGreaterThan(0)
|
||||
}
|
||||
})
|
||||
|
||||
it("should include DuckDuckGo search server", () => {
|
||||
const ddg = mcpMarketplaceCatalog.find((item) => item.name === "ddg-search")
|
||||
expect(ddg).toBeDefined()
|
||||
expect(ddg!.config.command).toBe("uvx")
|
||||
expect(ddg!.config.args).toContain("duckduckgo-mcp-server")
|
||||
expect(ddg!.requiresSetup).toBeFalsy()
|
||||
})
|
||||
|
||||
it("should include SearXNG server with setup required", () => {
|
||||
const searxng = mcpMarketplaceCatalog.find((item) => item.name === "searxng")
|
||||
expect(searxng).toBeDefined()
|
||||
expect(searxng!.requiresSetup).toBe(true)
|
||||
expect(searxng!.setupEnvKeys).toContain("SEARXNG_URL")
|
||||
})
|
||||
})
|
||||
76
src/shared/mcpMarketplaceCatalog.ts
Normal file
76
src/shared/mcpMarketplaceCatalog.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* Pre-configured MCP server templates for one-click installation from the MCP Marketplace.
|
||||
* Each entry defines a server configuration that can be written to the user's MCP settings file.
|
||||
*/
|
||||
|
||||
export interface McpMarketplaceItem {
|
||||
/** Unique key used as the server name in mcpServers config */
|
||||
name: string
|
||||
/** Human-readable display name */
|
||||
displayName: string
|
||||
/** Short description of what the server does */
|
||||
description: string
|
||||
/** Category tag for grouping */
|
||||
category: "search" | "tools" | "data"
|
||||
/** The MCP server configuration to write */
|
||||
config: {
|
||||
command: string
|
||||
args: string[]
|
||||
env?: Record<string, string>
|
||||
alwaysAllow?: string[]
|
||||
}
|
||||
/** Whether the server requires user-provided env variables before installation */
|
||||
requiresSetup?: boolean
|
||||
/** Keys in env that need user customization (e.g. instance URLs) */
|
||||
setupEnvKeys?: string[]
|
||||
/** URL for more info / docs */
|
||||
url?: string
|
||||
}
|
||||
|
||||
export const mcpMarketplaceCatalog: McpMarketplaceItem[] = [
|
||||
{
|
||||
name: "ddg-search",
|
||||
displayName: "DuckDuckGo Search",
|
||||
description: "Free web search via DuckDuckGo. No API key required.",
|
||||
category: "search",
|
||||
config: {
|
||||
command: "uvx",
|
||||
args: ["duckduckgo-mcp-server"],
|
||||
env: {
|
||||
DDG_SAFE_SEARCH: "OFF",
|
||||
DDG_REGION: "wt-wt",
|
||||
},
|
||||
alwaysAllow: ["search", "fetch_content"],
|
||||
},
|
||||
url: "https://pypi.org/project/duckduckgo-mcp-server/",
|
||||
},
|
||||
{
|
||||
name: "searxng",
|
||||
displayName: "SearXNG",
|
||||
description: "Privacy-focused metasearch engine. Requires a self-hosted SearXNG instance URL.",
|
||||
category: "search",
|
||||
config: {
|
||||
command: "npx",
|
||||
args: ["-y", "mcp-searxng"],
|
||||
env: {
|
||||
SEARXNG_URL: "https://searxng.example.com",
|
||||
},
|
||||
alwaysAllow: ["searxng_web_search", "web_url_read"],
|
||||
},
|
||||
requiresSetup: true,
|
||||
setupEnvKeys: ["SEARXNG_URL"],
|
||||
url: "https://www.npmjs.com/package/mcp-searxng",
|
||||
},
|
||||
{
|
||||
name: "web-search",
|
||||
displayName: "Web Search (DuckDuckGo)",
|
||||
description: "Lightweight web search via DuckDuckGo. No API key required. Uses npx.",
|
||||
category: "search",
|
||||
config: {
|
||||
command: "npx",
|
||||
args: ["-y", "github:tiagohanna123/web-search-mcp"],
|
||||
alwaysAllow: [],
|
||||
},
|
||||
url: "https://github.com/tiagohanna123/web-search-mcp",
|
||||
},
|
||||
]
|
||||
160
webview-ui/src/components/mcp/McpMarketplace.tsx
Normal file
160
webview-ui/src/components/mcp/McpMarketplace.tsx
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import React, { useState } from "react"
|
||||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { Button } from "@src/components/ui"
|
||||
|
||||
import { mcpMarketplaceCatalog, type McpMarketplaceItem } from "../../../../src/shared/mcpMarketplaceCatalog"
|
||||
|
||||
const McpMarketplace = () => {
|
||||
const { mcpServers: servers } = useExtensionState()
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
const installedServerNames = new Set(servers.map((s) => s.name))
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "15px" }}>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-foreground)",
|
||||
marginBottom: "6px",
|
||||
}}>
|
||||
{t("mcp:marketplace.title")}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "10px",
|
||||
}}>
|
||||
{t("mcp:marketplace.description")}
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
||||
{mcpMarketplaceCatalog.map((item) => (
|
||||
<MarketplaceRow key={item.name} item={item} isInstalled={installedServerNames.has(item.name)} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const MarketplaceRow = ({ item, isInstalled }: { item: McpMarketplaceItem; isInstalled: boolean }) => {
|
||||
const { t } = useAppTranslation()
|
||||
const [envValues, setEnvValues] = useState<Record<string, string>>(() => {
|
||||
const initial: Record<string, string> = {}
|
||||
if (item.setupEnvKeys) {
|
||||
for (const key of item.setupEnvKeys) {
|
||||
initial[key] = item.config.env?.[key] ?? ""
|
||||
}
|
||||
}
|
||||
return initial
|
||||
})
|
||||
|
||||
const handleInstall = () => {
|
||||
const config = { ...item.config }
|
||||
if (item.requiresSetup && item.setupEnvKeys) {
|
||||
config.env = { ...config.env }
|
||||
for (const key of item.setupEnvKeys) {
|
||||
if (envValues[key]) {
|
||||
config.env[key] = envValues[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
vscode.postMessage({
|
||||
type: "installMcpServer",
|
||||
serverName: item.name,
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="rounded bg-vscode-textCodeBlock-background p-2"
|
||||
style={{
|
||||
opacity: isInstalled ? 0.6 : 1,
|
||||
}}>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="codicon codicon-search text-xs" />
|
||||
<span className="font-medium text-[13px] text-vscode-foreground">{item.displayName}</span>
|
||||
{item.requiresSetup && (
|
||||
<span
|
||||
className="text-[10px] px-1 py-0.5 rounded"
|
||||
style={{
|
||||
background: "var(--vscode-editorWarning-foreground)",
|
||||
color: "var(--vscode-editor-background)",
|
||||
}}
|
||||
title={t("mcp:marketplace.requiresSetupHint", {
|
||||
keys: item.setupEnvKeys?.join(", ") ?? "",
|
||||
})}>
|
||||
{t("mcp:marketplace.requiresSetup")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-0.5">{item.description}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{item.url && (
|
||||
<VSCodeLink href={item.url} style={{ fontSize: "11px" }}>
|
||||
{t("mcp:marketplace.learnMore")}
|
||||
</VSCodeLink>
|
||||
)}
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={isInstalled}
|
||||
onClick={handleInstall}
|
||||
style={{ minWidth: "60px", fontSize: "12px" }}>
|
||||
{isInstalled ? (
|
||||
<>
|
||||
<span className="codicon codicon-check mr-1" />
|
||||
Added
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="codicon codicon-add mr-1" />
|
||||
{t("mcp:marketplace.install")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{item.requiresSetup && item.setupEnvKeys && !isInstalled && (
|
||||
<div className="mt-2 flex flex-col gap-1.5">
|
||||
{item.setupEnvKeys.map((key) => (
|
||||
<div key={key} className="flex items-center gap-2">
|
||||
<label
|
||||
className="text-[11px] text-vscode-descriptionForeground shrink-0"
|
||||
style={{ minWidth: "90px" }}>
|
||||
{key}:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={envValues[key] ?? ""}
|
||||
onChange={(e) =>
|
||||
setEnvValues((prev) => ({
|
||||
...prev,
|
||||
[key]: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder={item.config.env?.[key] ?? ""}
|
||||
className="flex-1 rounded px-1.5 py-0.5 text-xs"
|
||||
style={{
|
||||
background: "var(--vscode-input-background)",
|
||||
color: "var(--vscode-input-foreground)",
|
||||
border: "1px solid var(--vscode-input-border, transparent)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default McpMarketplace
|
||||
|
|
@ -26,6 +26,7 @@ import McpToolRow from "./McpToolRow"
|
|||
import McpResourceRow from "./McpResourceRow"
|
||||
import McpEnabledToggle from "./McpEnabledToggle"
|
||||
import { McpErrorRow } from "./McpErrorRow"
|
||||
import McpMarketplace from "./McpMarketplace"
|
||||
|
||||
const McpView = () => {
|
||||
const { mcpServers: servers, alwaysAllowMcp, mcpEnabled } = useExtensionState()
|
||||
|
|
@ -133,6 +134,7 @@ const McpView = () => {
|
|||
{t("mcp:refreshMCP")}
|
||||
</Button>
|
||||
</div>
|
||||
<McpMarketplace />
|
||||
<div
|
||||
style={{
|
||||
marginTop: "15px",
|
||||
|
|
|
|||
135
webview-ui/src/components/mcp/__tests__/McpMarketplace.spec.tsx
Normal file
135
webview-ui/src/components/mcp/__tests__/McpMarketplace.spec.tsx
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import React from "react"
|
||||
import { render, fireEvent, screen } from "@/utils/test-utils"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
|
||||
import McpMarketplace from "../McpMarketplace"
|
||||
|
||||
vi.mock("@src/i18n/TranslationContext", () => ({
|
||||
useAppTranslation: () => ({
|
||||
t: (key: string, params?: Record<string, string>) => {
|
||||
const translations: Record<string, string> = {
|
||||
"mcp:marketplace.title": "Quick Add Servers",
|
||||
"mcp:marketplace.description":
|
||||
"Install popular free MCP servers with one click. No API keys required (unless noted).",
|
||||
"mcp:marketplace.install": "Add",
|
||||
"mcp:marketplace.requiresSetup": "Requires setup",
|
||||
"mcp:marketplace.learnMore": "Learn more",
|
||||
}
|
||||
if (key === "mcp:marketplace.requiresSetupHint" && params) {
|
||||
return `This server requires you to configure ${params.keys} in the MCP settings file after installation.`
|
||||
}
|
||||
return translations[key] || key
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock("@src/utils/vscode", () => ({
|
||||
vscode: {
|
||||
postMessage: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock("@src/context/ExtensionStateContext", () => ({
|
||||
useExtensionState: () => ({
|
||||
mcpServers: [],
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock("@vscode/webview-ui-toolkit/react", () => ({
|
||||
VSCodeLink: function MockVSCodeLink({ children, href }: { children?: React.ReactNode; href?: string }) {
|
||||
return <a href={href}>{children}</a>
|
||||
},
|
||||
}))
|
||||
|
||||
describe("McpMarketplace", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it("renders the marketplace title and description", () => {
|
||||
render(<McpMarketplace />)
|
||||
|
||||
expect(screen.getByText("Quick Add Servers")).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText("Install popular free MCP servers with one click. No API keys required (unless noted)."),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders all catalog items", () => {
|
||||
render(<McpMarketplace />)
|
||||
|
||||
expect(screen.getByText("DuckDuckGo Search")).toBeInTheDocument()
|
||||
expect(screen.getByText("SearXNG")).toBeInTheDocument()
|
||||
expect(screen.getByText("Web Search (DuckDuckGo)")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("shows 'Requires setup' badge for servers that need configuration", () => {
|
||||
render(<McpMarketplace />)
|
||||
|
||||
// SearXNG requires setup
|
||||
expect(screen.getByText("Requires setup")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("shows input fields for servers that require setup env keys", () => {
|
||||
render(<McpMarketplace />)
|
||||
|
||||
// SearXNG requires SEARXNG_URL
|
||||
expect(screen.getByDisplayValue("https://searxng.example.com")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("sends installMcpServer message when Add button is clicked", () => {
|
||||
render(<McpMarketplace />)
|
||||
|
||||
// Click the first "Add" button (DuckDuckGo Search)
|
||||
const addButtons = screen.getAllByText("Add")
|
||||
fireEvent.click(addButtons[0])
|
||||
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "installMcpServer",
|
||||
serverName: "ddg-search",
|
||||
config: {
|
||||
command: "uvx",
|
||||
args: ["duckduckgo-mcp-server"],
|
||||
env: {
|
||||
DDG_SAFE_SEARCH: "OFF",
|
||||
DDG_REGION: "wt-wt",
|
||||
},
|
||||
alwaysAllow: ["search", "fetch_content"],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("sends installMcpServer with custom env values for servers requiring setup", () => {
|
||||
render(<McpMarketplace />)
|
||||
|
||||
// Change the SEARXNG_URL input value
|
||||
const urlInput = screen.getByDisplayValue("https://searxng.example.com")
|
||||
fireEvent.change(urlInput, { target: { value: "https://my-searxng.local" } })
|
||||
|
||||
// Click the Add button for SearXNG (second Add button)
|
||||
const addButtons = screen.getAllByText("Add")
|
||||
fireEvent.click(addButtons[1])
|
||||
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "installMcpServer",
|
||||
serverName: "searxng",
|
||||
config: {
|
||||
command: "npx",
|
||||
args: ["-y", "mcp-searxng"],
|
||||
env: {
|
||||
SEARXNG_URL: "https://my-searxng.local",
|
||||
},
|
||||
alwaysAllow: ["searxng_web_search", "web_url_read"],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("renders learn more links for servers with URLs", () => {
|
||||
render(<McpMarketplace />)
|
||||
|
||||
const learnMoreLinks = screen.getAllByText("Learn more")
|
||||
// All 3 catalog items have URLs
|
||||
expect(learnMoreLinks.length).toBe(3)
|
||||
})
|
||||
})
|
||||
|
|
@ -60,5 +60,20 @@
|
|||
"running": "Running",
|
||||
"completed": "Completed",
|
||||
"error": "Error"
|
||||
},
|
||||
"marketplace": {
|
||||
"title": "Quick Add Servers",
|
||||
"description": "Install popular free MCP servers with one click. No API keys required (unless noted).",
|
||||
"install": "Add",
|
||||
"installed": "Server \"{{serverName}}\" has been added to your MCP settings.",
|
||||
"alreadyInstalled": "Server \"{{serverName}}\" already exists in your MCP settings.",
|
||||
"requiresSetup": "Requires setup",
|
||||
"requiresSetupHint": "This server requires you to configure {{keys}} in the MCP settings file after installation.",
|
||||
"category": {
|
||||
"search": "Search",
|
||||
"tools": "Tools",
|
||||
"data": "Data"
|
||||
},
|
||||
"learnMore": "Learn more"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue