From c27b7ff44da4adfce36d616e89626529a74ecc6b Mon Sep 17 00:00:00 2001 From: John Richmond <5629+jr@users.noreply.github.com> Date: Tue, 29 Jul 2025 10:46:49 -0700 Subject: [PATCH 1/3] Cloud: add organization MCP controls - Add option to hide mcps from the marketplace - Add ability to define new organization mcps --- packages/cloud/src/CloudService.ts | 6 + packages/types/src/cloud.ts | 4 + src/core/webview/ClineProvider.ts | 12 +- .../marketplace/MarketplaceManager.ts | 66 ++++++++- .../marketplace/RemoteConfigLoader.ts | 7 +- .../__tests__/MarketplaceManager.spec.ts | 137 +++++++++++++++++- src/shared/ExtensionMessage.ts | 2 + .../marketplace/MarketplaceListView.tsx | 89 +++++++++--- .../MarketplaceViewStateManager.ts | 27 +++- .../__tests__/MarketplaceListView.spec.tsx | 2 + .../src/i18n/locales/ca/marketplace.json | 4 + .../src/i18n/locales/de/marketplace.json | 4 + .../src/i18n/locales/en/marketplace.json | 4 + .../src/i18n/locales/es/marketplace.json | 4 + .../src/i18n/locales/fr/marketplace.json | 4 + .../src/i18n/locales/hi/marketplace.json | 4 + .../src/i18n/locales/id/marketplace.json | 4 + .../src/i18n/locales/it/marketplace.json | 4 + .../src/i18n/locales/ja/marketplace.json | 4 + .../src/i18n/locales/ko/marketplace.json | 4 + .../src/i18n/locales/nl/marketplace.json | 4 + .../src/i18n/locales/pl/marketplace.json | 4 + .../src/i18n/locales/pt-BR/marketplace.json | 4 + .../src/i18n/locales/ru/marketplace.json | 4 + .../src/i18n/locales/tr/marketplace.json | 4 + .../src/i18n/locales/vi/marketplace.json | 4 + .../src/i18n/locales/zh-CN/marketplace.json | 4 + .../src/i18n/locales/zh-TW/marketplace.json | 4 + 28 files changed, 385 insertions(+), 39 deletions(-) diff --git a/packages/cloud/src/CloudService.ts b/packages/cloud/src/CloudService.ts index 32ea443cd6f..9a32a16fcb1 100644 --- a/packages/cloud/src/CloudService.ts +++ b/packages/cloud/src/CloudService.ts @@ -4,6 +4,7 @@ import type { CloudUserInfo, TelemetryEvent, OrganizationAllowList, + OrganizationSettings, ClineMessage, ShareVisibility, } from "@roo-code/types" @@ -174,6 +175,11 @@ export class CloudService { return this.settingsService!.getAllowList() } + public getOrganizationSettings(): OrganizationSettings | undefined { + this.ensureInitialized() + return this.settingsService!.getSettings() + } + // TelemetryClient public captureEvent(event: TelemetryEvent): void { diff --git a/packages/types/src/cloud.ts b/packages/types/src/cloud.ts index 6df7292dd59..5ef90b6e5a8 100644 --- a/packages/types/src/cloud.ts +++ b/packages/types/src/cloud.ts @@ -1,6 +1,7 @@ import { z } from "zod" import { globalSettingsSchema } from "./global-settings.js" +import { mcpMarketplaceItemSchema } from "./marketplace.js" /** * CloudUserInfo @@ -110,6 +111,9 @@ export const organizationSettingsSchema = z.object({ cloudSettings: organizationCloudSettingsSchema.optional(), defaultSettings: organizationDefaultSettingsSchema, allowList: organizationAllowListSchema, + hiddenMcps: z.array(z.string()).optional(), + hideMarketplaceMcps: z.boolean().optional(), + mcps: z.array(mcpMarketplaceItemSchema).optional(), }) export type OrganizationSettings = z.infer diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index a0739052b28..280ab61a061 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1328,10 +1328,10 @@ export class ClineProvider */ async fetchMarketplaceData() { try { - const [marketplaceItems, marketplaceInstalledMetadata] = await Promise.all([ - this.marketplaceManager.getCurrentItems().catch((error) => { + const [marketplaceResult, marketplaceInstalledMetadata] = await Promise.all([ + this.marketplaceManager.getMarketplaceItems().catch((error) => { console.error("Failed to fetch marketplace items:", error) - return [] as MarketplaceItem[] + return { organizationMcps: [], marketplaceItems: [], errors: [error.message] } }), this.marketplaceManager.getInstallationMetadata().catch((error) => { console.error("Failed to fetch installation metadata:", error) @@ -1342,16 +1342,20 @@ export class ClineProvider // Send marketplace data separately this.postMessageToWebview({ type: "marketplaceData", - marketplaceItems: marketplaceItems || [], + organizationMcps: marketplaceResult.organizationMcps || [], + marketplaceItems: marketplaceResult.marketplaceItems || [], marketplaceInstalledMetadata: marketplaceInstalledMetadata || { project: {}, global: {} }, + errors: marketplaceResult.errors, }) } catch (error) { console.error("Failed to fetch marketplace data:", error) // Send empty data on error to prevent UI from hanging this.postMessageToWebview({ type: "marketplaceData", + organizationMcps: [], marketplaceItems: [], marketplaceInstalledMetadata: { project: {}, global: {} }, + errors: [error instanceof Error ? error.message : String(error)], }) // Show user-friendly error notification for network issues diff --git a/src/services/marketplace/MarketplaceManager.ts b/src/services/marketplace/MarketplaceManager.ts index 5c5b9f6d61c..44371ec1bbe 100644 --- a/src/services/marketplace/MarketplaceManager.ts +++ b/src/services/marketplace/MarketplaceManager.ts @@ -4,12 +4,19 @@ import * as path from "path" import * as yaml from "yaml" import { RemoteConfigLoader } from "./RemoteConfigLoader" import { SimpleInstaller } from "./SimpleInstaller" -import type { MarketplaceItem, MarketplaceItemType } from "@roo-code/types" +import type { MarketplaceItem, MarketplaceItemType, McpMarketplaceItem } from "@roo-code/types" import { GlobalFileNames } from "../../shared/globalFileNames" import { ensureSettingsDirectoryExists } from "../../utils/globalContext" import { t } from "../../i18n" import { TelemetryService } from "@roo-code/telemetry" import type { CustomModesManager } from "../../core/config/CustomModesManager" +import { CloudService } from "@roo-code/cloud" + +export interface MarketplaceItemsResponse { + organizationMcps: MarketplaceItem[] + marketplaceItems: MarketplaceItem[] + errors?: string[] +} export class MarketplaceManager { private configLoader: RemoteConfigLoader @@ -23,17 +30,64 @@ export class MarketplaceManager { this.installer = new SimpleInstaller(context, customModesManager) } - async getMarketplaceItems(): Promise<{ items: MarketplaceItem[]; errors?: string[] }> { + async getMarketplaceItems(): Promise { try { - const items = await this.configLoader.loadAllItems() + let shouldHideMarketplaceMcps = false + let orgSettings: ReturnType | null = null - return { items } + // Check organization settings first to determine if we should load MCPs + try { + if (CloudService.hasInstance() && CloudService.instance.isAuthenticated()) { + orgSettings = CloudService.instance.getOrganizationSettings() + if (orgSettings?.hideMarketplaceMcps) { + shouldHideMarketplaceMcps = true + } + } + } catch (orgError) { + console.warn("Failed to load organization settings:", orgError) + } + + const allMarketplaceItems = await this.configLoader.loadAllItems(shouldHideMarketplaceMcps) + let organizationMcps: MarketplaceItem[] = [] + let marketplaceItems = allMarketplaceItems + const errors: string[] = [] + + try { + if (orgSettings) { + if (orgSettings.mcps && orgSettings.mcps.length > 0) { + organizationMcps = orgSettings.mcps.map( + (mcp: McpMarketplaceItem): MarketplaceItem => ({ + ...mcp, + type: "mcp" as const, + }), + ) + } + + if (orgSettings.hiddenMcps && orgSettings.hiddenMcps.length > 0) { + const hiddenMcpIds = new Set(orgSettings.hiddenMcps) + marketplaceItems = allMarketplaceItems.filter( + (item) => item.type !== "mcp" || !hiddenMcpIds.has(item.id), + ) + } + } + } catch (orgError) { + console.warn("Failed to load organization settings:", orgError) + const orgErrorMessage = orgError instanceof Error ? orgError.message : String(orgError) + errors.push(`Organization settings: ${orgErrorMessage}`) + } + + return { + organizationMcps, + marketplaceItems, + errors: errors.length > 0 ? errors : undefined, + } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) console.error("Failed to load marketplace items:", error) return { - items: [], + organizationMcps: [], + marketplaceItems: [], errors: [errorMessage], } } @@ -41,7 +95,7 @@ export class MarketplaceManager { async getCurrentItems(): Promise { const result = await this.getMarketplaceItems() - return result.items + return [...result.organizationMcps, ...result.marketplaceItems] } filterItems( diff --git a/src/services/marketplace/RemoteConfigLoader.ts b/src/services/marketplace/RemoteConfigLoader.ts index a37f619b4d9..fe66b32be08 100644 --- a/src/services/marketplace/RemoteConfigLoader.ts +++ b/src/services/marketplace/RemoteConfigLoader.ts @@ -23,10 +23,13 @@ export class RemoteConfigLoader { this.apiBaseUrl = getRooCodeApiUrl() } - async loadAllItems(): Promise { + async loadAllItems(hideMarketplaceMcps = false): Promise { const items: MarketplaceItem[] = [] - const [modes, mcps] = await Promise.all([this.fetchModes(), this.fetchMcps()]) + const modesPromise = this.fetchModes() + const mcpsPromise = hideMarketplaceMcps ? Promise.resolve([]) : this.fetchMcps() + + const [modes, mcps] = await Promise.all([modesPromise, mcpsPromise]) items.push(...modes, ...mcps) return items diff --git a/src/services/marketplace/__tests__/MarketplaceManager.spec.ts b/src/services/marketplace/__tests__/MarketplaceManager.spec.ts index 8962f43c5f2..59809b29173 100644 --- a/src/services/marketplace/__tests__/MarketplaceManager.spec.ts +++ b/src/services/marketplace/__tests__/MarketplaceManager.spec.ts @@ -4,14 +4,21 @@ import type { MarketplaceItem } from "@roo-code/types" import { MarketplaceManager } from "../MarketplaceManager" -// Mock axios -vi.mock("axios") - -// Mock the cloud config +// Mock CloudService vi.mock("@roo-code/cloud", () => ({ getRooCodeApiUrl: () => "https://test.api.com", + CloudService: { + hasInstance: vi.fn(), + instance: { + isAuthenticated: vi.fn(), + getOrganizationSettings: vi.fn(), + }, + }, })) +// Mock axios +vi.mock("axios") + // Mock TelemetryService vi.mock("../../../../packages/telemetry/src/TelemetryService", () => ({ TelemetryService: { @@ -165,8 +172,9 @@ describe("MarketplaceManager", () => { const result = await manager.getMarketplaceItems() - expect(result.items).toHaveLength(1) - expect(result.items[0].name).toBe("Test Mode") + expect(result.marketplaceItems).toHaveLength(1) + expect(result.marketplaceItems[0].name).toBe("Test Mode") + expect(result.organizationMcps).toHaveLength(0) }) it("should handle API errors gracefully", async () => { @@ -175,9 +183,124 @@ describe("MarketplaceManager", () => { const result = await manager.getMarketplaceItems() - expect(result.items).toHaveLength(0) + expect(result.marketplaceItems).toHaveLength(0) + expect(result.organizationMcps).toHaveLength(0) expect(result.errors).toEqual(["API request failed"]) }) + + it("should return organization MCPs when available", async () => { + const { CloudService } = await import("@roo-code/cloud") + + // Mock CloudService to return organization settings + vi.mocked(CloudService.hasInstance).mockReturnValue(true) + vi.mocked(CloudService.instance.isAuthenticated).mockReturnValue(true) + vi.mocked(CloudService.instance.getOrganizationSettings).mockReturnValue({ + version: 1, + mcps: [ + { + id: "org-mcp-1", + name: "Organization MCP", + description: "An organization MCP", + url: "https://example.com/org-mcp", + content: '{"command": "node", "args": ["org-server.js"]}', + }, + ], + hiddenMcps: [], + allowList: { allowAll: true, providers: {} }, + defaultSettings: {}, + }) + + // Mock the config loader to return test data + const mockItems: MarketplaceItem[] = [ + { + id: "test-mcp", + name: "Test MCP", + description: "A test MCP", + type: "mcp", + url: "https://example.com/test-mcp", + content: '{"command": "node", "args": ["server.js"]}', + }, + ] + + vi.spyOn(manager["configLoader"], "loadAllItems").mockResolvedValue(mockItems) + + const result = await manager.getMarketplaceItems() + + expect(result.organizationMcps).toHaveLength(1) + expect(result.organizationMcps[0].name).toBe("Organization MCP") + expect(result.marketplaceItems).toHaveLength(1) + expect(result.marketplaceItems[0].name).toBe("Test MCP") + }) + + it("should filter out hidden MCPs from marketplace results", async () => { + const { CloudService } = await import("@roo-code/cloud") + + // Mock CloudService to return organization settings with hidden MCPs + vi.mocked(CloudService.hasInstance).mockReturnValue(true) + vi.mocked(CloudService.instance.isAuthenticated).mockReturnValue(true) + vi.mocked(CloudService.instance.getOrganizationSettings).mockReturnValue({ + version: 1, + mcps: [], + hiddenMcps: ["hidden-mcp"], + allowList: { allowAll: true, providers: {} }, + defaultSettings: {}, + }) + + // Mock the config loader to return test data including a hidden MCP + const mockItems: MarketplaceItem[] = [ + { + id: "visible-mcp", + name: "Visible MCP", + description: "A visible MCP", + type: "mcp", + url: "https://example.com/visible-mcp", + content: '{"command": "node", "args": ["visible.js"]}', + }, + { + id: "hidden-mcp", + name: "Hidden MCP", + description: "A hidden MCP", + type: "mcp", + url: "https://example.com/hidden-mcp", + content: '{"command": "node", "args": ["hidden.js"]}', + }, + ] + + vi.spyOn(manager["configLoader"], "loadAllItems").mockResolvedValue(mockItems) + + const result = await manager.getMarketplaceItems() + + expect(result.marketplaceItems).toHaveLength(1) + expect(result.marketplaceItems[0].name).toBe("Visible MCP") + expect(result.organizationMcps).toHaveLength(0) + }) + + it("should handle CloudService not being available", async () => { + const { CloudService } = await import("@roo-code/cloud") + + // Mock CloudService to not be available + vi.mocked(CloudService.hasInstance).mockReturnValue(false) + + // Mock the config loader to return test data + const mockItems: MarketplaceItem[] = [ + { + id: "test-mcp", + name: "Test MCP", + description: "A test MCP", + type: "mcp", + url: "https://example.com/test-mcp", + content: '{"command": "node", "args": ["server.js"]}', + }, + ] + + vi.spyOn(manager["configLoader"], "loadAllItems").mockResolvedValue(mockItems) + + const result = await manager.getMarketplaceItems() + + expect(result.organizationMcps).toHaveLength(0) + expect(result.marketplaceItems).toHaveLength(1) + expect(result.marketplaceItems[0].name).toBe("Test MCP") + }) }) describe("installMarketplaceItem", () => { diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 5320190a7b9..67f8782e193 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -185,7 +185,9 @@ export interface ExtensionMessage { organizationAllowList?: OrganizationAllowList tab?: string marketplaceItems?: MarketplaceItem[] + organizationMcps?: MarketplaceItem[] marketplaceInstalledMetadata?: MarketplaceInstalledMetadata + errors?: string[] visibility?: ShareVisibility rulesFolderPath?: string settings?: any diff --git a/webview-ui/src/components/marketplace/MarketplaceListView.tsx b/webview-ui/src/components/marketplace/MarketplaceListView.tsx index 0b8810d13d8..c456a574e29 100644 --- a/webview-ui/src/components/marketplace/MarketplaceListView.tsx +++ b/webview-ui/src/components/marketplace/MarketplaceListView.tsx @@ -25,8 +25,13 @@ export function MarketplaceListView({ stateManager, allTags, filteredTags, filte const [isTagPopoverOpen, setIsTagPopoverOpen] = React.useState(false) const [tagSearch, setTagSearch] = React.useState("") const allItems = state.displayItems || [] + const organizationMcps = state.displayOrganizationMcps || [] + + // Filter items by type if specified const items = filterByType ? allItems.filter((item) => item.type === filterByType) : allItems - const isEmpty = items.length === 0 + const orgMcps = filterByType === "mcp" ? organizationMcps : [] + + const isEmpty = items.length === 0 && orgMcps.length === 0 return ( <> @@ -193,24 +198,70 @@ export function MarketplaceListView({ stateManager, allTags, filteredTags, filte )} {!state.isFetching && !isEmpty && ( -
- {items.map((item) => ( - - manager.transition({ - type: "UPDATE_FILTERS", - payload: { filters }, - }) - } - installed={{ - project: marketplaceInstalledMetadata?.project?.[item.id], - global: marketplaceInstalledMetadata?.global?.[item.id], - }} - /> - ))} +
+ {orgMcps.length > 0 && ( +
+
+ +

+ {t("marketplace:sections.organizationMcps")} +

+
+
+
+ {orgMcps.map((item) => ( + + manager.transition({ + type: "UPDATE_FILTERS", + payload: { filters }, + }) + } + installed={{ + project: marketplaceInstalledMetadata?.project?.[item.id], + global: marketplaceInstalledMetadata?.global?.[item.id], + }} + /> + ))} +
+
+ )} + + {items.length > 0 && ( +
+ {orgMcps.length > 0 && ( +
+ +

+ {t("marketplace:sections.marketplace")} +

+
+
+ )} +
+ {items.map((item) => ( + + manager.transition({ + type: "UPDATE_FILTERS", + payload: { filters }, + }) + } + installed={{ + project: marketplaceInstalledMetadata?.project?.[item.id], + global: marketplaceInstalledMetadata?.global?.[item.id], + }} + /> + ))} +
+
+ )}
)} diff --git a/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts b/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts index 7498ebfc591..104f3e7cad2 100644 --- a/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts +++ b/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts @@ -17,7 +17,9 @@ import { WebviewMessage } from "../../../../src/shared/WebviewMessage" export interface ViewState { allItems: MarketplaceItem[] + organizationMcps: MarketplaceItem[] displayItems?: MarketplaceItem[] // Items currently being displayed (filtered or all) + displayOrganizationMcps?: MarketplaceItem[] // Organization MCPs currently being displayed (filtered or all) isFetching: boolean activeTab: "mcp" | "mode" filters: { @@ -54,7 +56,9 @@ export class MarketplaceViewStateManager { private getDefaultState(): ViewState { return { allItems: [], + organizationMcps: [], displayItems: [], // Always initialize as empty array, not undefined + displayOrganizationMcps: [], // Always initialize as empty array, not undefined isFetching: true, // Start with loading state for initial load activeTab: "mcp", filters: { @@ -96,16 +100,22 @@ export class MarketplaceViewStateManager { public getState(): ViewState { // Only create new arrays if they exist and have items const allItems = this.state.allItems.length ? [...this.state.allItems] : [] + const organizationMcps = this.state.organizationMcps.length ? [...this.state.organizationMcps] : [] // Ensure displayItems is always an array, never undefined // If displayItems is undefined or null, fall back to allItems const displayItems = this.state.displayItems ? [...this.state.displayItems] : [...allItems] + const displayOrganizationMcps = this.state.displayOrganizationMcps + ? [...this.state.displayOrganizationMcps] + : [...organizationMcps] const tags = this.state.filters.tags.length ? [...this.state.filters.tags] : [] // Create minimal new state object return { ...this.state, allItems, + organizationMcps, displayItems, + displayOrganizationMcps, filters: { ...this.state.filters, tags, @@ -177,11 +187,14 @@ export class MarketplaceViewStateManager { // Calculate display items based on current filters let newDisplayItems: MarketplaceItem[] + let newDisplayOrganizationMcps: MarketplaceItem[] if (this.isFilterActive()) { newDisplayItems = this.filterItems([...items]) + newDisplayOrganizationMcps = this.filterItems([...this.state.organizationMcps]) } else { // No filters active - show all items newDisplayItems = [...items] + newDisplayOrganizationMcps = [...this.state.organizationMcps] } // Update allItems as source of truth @@ -189,6 +202,7 @@ export class MarketplaceViewStateManager { ...this.state, allItems: [...items], displayItems: newDisplayItems, + displayOrganizationMcps: newDisplayOrganizationMcps, isFetching: false, } @@ -245,13 +259,15 @@ export class MarketplaceViewStateManager { filters: updatedFilters, } - // Apply filters to displayItems with the updated filters + // Apply filters to displayItems and displayOrganizationMcps with the updated filters const newDisplayItems = this.filterItems(this.state.allItems) + const newDisplayOrganizationMcps = this.filterItems(this.state.organizationMcps) // Update state with filtered items this.state = { ...this.state, displayItems: newDisplayItems, + displayOrganizationMcps: newDisplayOrganizationMcps, } // Send filter message @@ -337,11 +353,14 @@ export class MarketplaceViewStateManager { // If no filters are active, show all items // If filters are active, apply filtering let newDisplayItems: MarketplaceItem[] + let newDisplayOrganizationMcps: MarketplaceItem[] if (this.isFilterActive()) { newDisplayItems = this.filterItems(items) + newDisplayOrganizationMcps = this.filterItems(this.state.organizationMcps) } else { // No filters active - show all items newDisplayItems = items + newDisplayOrganizationMcps = this.state.organizationMcps } // Update state in a single operation @@ -350,6 +369,7 @@ export class MarketplaceViewStateManager { isFetching: false, allItems: items, displayItems: newDisplayItems, + displayOrganizationMcps: newDisplayOrganizationMcps, } // Notification is handled below after all state parts are processed } @@ -390,19 +410,24 @@ export class MarketplaceViewStateManager { // Handle marketplace data updates (fetched on demand) if (message.type === "marketplaceData") { const marketplaceItems = message.marketplaceItems + const organizationMcps = message.organizationMcps || [] if (marketplaceItems !== undefined) { // Always use the marketplace items from the extension when they're provided // This ensures fresh data is always displayed const items = [...marketplaceItems] + const orgMcps = [...organizationMcps] const newDisplayItems = this.isFilterActive() ? this.filterItems(items) : items + const newDisplayOrganizationMcps = this.isFilterActive() ? this.filterItems(orgMcps) : orgMcps // Update state in a single operation this.state = { ...this.state, isFetching: false, allItems: items, + organizationMcps: orgMcps, displayItems: newDisplayItems, + displayOrganizationMcps: newDisplayOrganizationMcps, } } diff --git a/webview-ui/src/components/marketplace/__tests__/MarketplaceListView.spec.tsx b/webview-ui/src/components/marketplace/__tests__/MarketplaceListView.spec.tsx index 06078d638cf..d22c3814107 100644 --- a/webview-ui/src/components/marketplace/__tests__/MarketplaceListView.spec.tsx +++ b/webview-ui/src/components/marketplace/__tests__/MarketplaceListView.spec.tsx @@ -18,7 +18,9 @@ vi.mock("@/i18n/TranslationContext", () => ({ const mockTransition = vi.fn() const mockState: ViewState = { allItems: [], + organizationMcps: [], displayItems: [], + displayOrganizationMcps: [], isFetching: false, activeTab: "mcp", filters: { diff --git a/webview-ui/src/i18n/locales/ca/marketplace.json b/webview-ui/src/i18n/locales/ca/marketplace.json index 1c4f1f805c1..81ba04ebd7b 100644 --- a/webview-ui/src/i18n/locales/ca/marketplace.json +++ b/webview-ui/src/i18n/locales/ca/marketplace.json @@ -35,6 +35,10 @@ }, "none": "Cap" }, + "sections": { + "organizationMcps": "MCPs de l'organització", + "marketplace": "Mercat" + }, "type-group": { "modes": "Modes", "mcps": "Servidors MCP" diff --git a/webview-ui/src/i18n/locales/de/marketplace.json b/webview-ui/src/i18n/locales/de/marketplace.json index be83e6d6d3d..955b43e96b4 100644 --- a/webview-ui/src/i18n/locales/de/marketplace.json +++ b/webview-ui/src/i18n/locales/de/marketplace.json @@ -35,6 +35,10 @@ }, "none": "Keine" }, + "sections": { + "organizationMcps": "Organisations-MCPs", + "marketplace": "Marktplatz" + }, "type-group": { "modes": "Modi", "mcps": "MCP-Server" diff --git a/webview-ui/src/i18n/locales/en/marketplace.json b/webview-ui/src/i18n/locales/en/marketplace.json index 7c200605981..53dadbc8f8a 100644 --- a/webview-ui/src/i18n/locales/en/marketplace.json +++ b/webview-ui/src/i18n/locales/en/marketplace.json @@ -35,6 +35,10 @@ }, "none": "None" }, + "sections": { + "organizationMcps": "Organization MCPs", + "marketplace": "Marketplace" + }, "type-group": { "modes": "Modes", "mcps": "MCP Servers" diff --git a/webview-ui/src/i18n/locales/es/marketplace.json b/webview-ui/src/i18n/locales/es/marketplace.json index 39a45407aea..0a449a60a6d 100644 --- a/webview-ui/src/i18n/locales/es/marketplace.json +++ b/webview-ui/src/i18n/locales/es/marketplace.json @@ -35,6 +35,10 @@ }, "none": "Ninguno" }, + "sections": { + "organizationMcps": "MCPs de la organización", + "marketplace": "Mercado" + }, "type-group": { "modes": "Modos", "mcps": "Servidores MCP" diff --git a/webview-ui/src/i18n/locales/fr/marketplace.json b/webview-ui/src/i18n/locales/fr/marketplace.json index 05a17150f7f..9e1149be6ce 100644 --- a/webview-ui/src/i18n/locales/fr/marketplace.json +++ b/webview-ui/src/i18n/locales/fr/marketplace.json @@ -35,6 +35,10 @@ }, "none": "Aucun" }, + "sections": { + "organizationMcps": "MCPs de l'organisation", + "marketplace": "Marché" + }, "type-group": { "modes": "Modes", "mcps": "Serveurs MCP" diff --git a/webview-ui/src/i18n/locales/hi/marketplace.json b/webview-ui/src/i18n/locales/hi/marketplace.json index 07d5be7eb3b..6d11d947b60 100644 --- a/webview-ui/src/i18n/locales/hi/marketplace.json +++ b/webview-ui/src/i18n/locales/hi/marketplace.json @@ -35,6 +35,10 @@ }, "none": "कोई नहीं" }, + "sections": { + "organizationMcps": "संगठन MCPs", + "marketplace": "मार्केटप्लेस" + }, "type-group": { "modes": "मोड", "mcps": "MCP सर्वर" diff --git a/webview-ui/src/i18n/locales/id/marketplace.json b/webview-ui/src/i18n/locales/id/marketplace.json index fc663101d37..1f075613b7f 100644 --- a/webview-ui/src/i18n/locales/id/marketplace.json +++ b/webview-ui/src/i18n/locales/id/marketplace.json @@ -35,6 +35,10 @@ }, "none": "Tidak Ada" }, + "sections": { + "organizationMcps": "MCP Organisasi", + "marketplace": "Marketplace" + }, "type-group": { "modes": "Mode", "mcps": "Server MCP" diff --git a/webview-ui/src/i18n/locales/it/marketplace.json b/webview-ui/src/i18n/locales/it/marketplace.json index ab4aa459fa0..dbfbfb52ff2 100644 --- a/webview-ui/src/i18n/locales/it/marketplace.json +++ b/webview-ui/src/i18n/locales/it/marketplace.json @@ -35,6 +35,10 @@ }, "none": "Nessuno" }, + "sections": { + "organizationMcps": "MCP dell'organizzazione", + "marketplace": "Mercato" + }, "type-group": { "modes": "Modalità", "mcps": "Server MCP" diff --git a/webview-ui/src/i18n/locales/ja/marketplace.json b/webview-ui/src/i18n/locales/ja/marketplace.json index 72388d52248..e975657aeb6 100644 --- a/webview-ui/src/i18n/locales/ja/marketplace.json +++ b/webview-ui/src/i18n/locales/ja/marketplace.json @@ -35,6 +35,10 @@ }, "none": "なし" }, + "sections": { + "organizationMcps": "組織MCP", + "marketplace": "マーケットプレイス" + }, "type-group": { "modes": "モード", "mcps": "MCPサーバー" diff --git a/webview-ui/src/i18n/locales/ko/marketplace.json b/webview-ui/src/i18n/locales/ko/marketplace.json index 54ec83863f2..06a9bc7b81b 100644 --- a/webview-ui/src/i18n/locales/ko/marketplace.json +++ b/webview-ui/src/i18n/locales/ko/marketplace.json @@ -35,6 +35,10 @@ }, "none": "없음" }, + "sections": { + "organizationMcps": "조직 MCP", + "marketplace": "마켓플레이스" + }, "type-group": { "modes": "모드", "mcps": "MCP 서버" diff --git a/webview-ui/src/i18n/locales/nl/marketplace.json b/webview-ui/src/i18n/locales/nl/marketplace.json index b3783753974..c38dfe19c71 100644 --- a/webview-ui/src/i18n/locales/nl/marketplace.json +++ b/webview-ui/src/i18n/locales/nl/marketplace.json @@ -35,6 +35,10 @@ }, "none": "Geen" }, + "sections": { + "organizationMcps": "Organisatie MCP's", + "marketplace": "Marktplaats" + }, "type-group": { "modes": "Modi", "mcps": "MCP-servers" diff --git a/webview-ui/src/i18n/locales/pl/marketplace.json b/webview-ui/src/i18n/locales/pl/marketplace.json index 44bdd290d36..e05adc99ba4 100644 --- a/webview-ui/src/i18n/locales/pl/marketplace.json +++ b/webview-ui/src/i18n/locales/pl/marketplace.json @@ -35,6 +35,10 @@ }, "none": "Brak" }, + "sections": { + "organizationMcps": "MCPs organizacji", + "marketplace": "Rynek" + }, "type-group": { "modes": "Tryby", "mcps": "Serwery MCP" diff --git a/webview-ui/src/i18n/locales/pt-BR/marketplace.json b/webview-ui/src/i18n/locales/pt-BR/marketplace.json index 5f472d4e805..00ce2737e33 100644 --- a/webview-ui/src/i18n/locales/pt-BR/marketplace.json +++ b/webview-ui/src/i18n/locales/pt-BR/marketplace.json @@ -35,6 +35,10 @@ }, "none": "Nenhum" }, + "sections": { + "organizationMcps": "MCPs da Organização", + "marketplace": "Marketplace" + }, "type-group": { "modes": "Modos", "mcps": "Servidores MCP" diff --git a/webview-ui/src/i18n/locales/ru/marketplace.json b/webview-ui/src/i18n/locales/ru/marketplace.json index f32d8554060..2a361218bcd 100644 --- a/webview-ui/src/i18n/locales/ru/marketplace.json +++ b/webview-ui/src/i18n/locales/ru/marketplace.json @@ -35,6 +35,10 @@ }, "none": "Нет" }, + "sections": { + "organizationMcps": "MCP организации", + "marketplace": "Маркетплейс" + }, "type-group": { "modes": "Режимы", "mcps": "MCP серверы" diff --git a/webview-ui/src/i18n/locales/tr/marketplace.json b/webview-ui/src/i18n/locales/tr/marketplace.json index 279ae2c38ae..f27d4622009 100644 --- a/webview-ui/src/i18n/locales/tr/marketplace.json +++ b/webview-ui/src/i18n/locales/tr/marketplace.json @@ -35,6 +35,10 @@ }, "none": "Hiçbiri" }, + "sections": { + "organizationMcps": "Kuruluş MCP'leri", + "marketplace": "Marketplace" + }, "type-group": { "modes": "Modlar", "mcps": "MCP Sunucuları" diff --git a/webview-ui/src/i18n/locales/vi/marketplace.json b/webview-ui/src/i18n/locales/vi/marketplace.json index fcb5beefc49..f936f94a9b4 100644 --- a/webview-ui/src/i18n/locales/vi/marketplace.json +++ b/webview-ui/src/i18n/locales/vi/marketplace.json @@ -35,6 +35,10 @@ }, "none": "Không có" }, + "sections": { + "organizationMcps": "MCP của Tổ chức", + "marketplace": "Marketplace" + }, "type-group": { "modes": "Chế độ", "mcps": "Máy chủ MCP" diff --git a/webview-ui/src/i18n/locales/zh-CN/marketplace.json b/webview-ui/src/i18n/locales/zh-CN/marketplace.json index 598da383d47..e90fc3e4990 100644 --- a/webview-ui/src/i18n/locales/zh-CN/marketplace.json +++ b/webview-ui/src/i18n/locales/zh-CN/marketplace.json @@ -35,6 +35,10 @@ }, "none": "无" }, + "sections": { + "organizationMcps": "组织 MCP", + "marketplace": "Marketplace" + }, "type-group": { "modes": "模式", "mcps": "MCP 服务" diff --git a/webview-ui/src/i18n/locales/zh-TW/marketplace.json b/webview-ui/src/i18n/locales/zh-TW/marketplace.json index 1ac6ed53c23..b221c9223a8 100644 --- a/webview-ui/src/i18n/locales/zh-TW/marketplace.json +++ b/webview-ui/src/i18n/locales/zh-TW/marketplace.json @@ -35,6 +35,10 @@ }, "none": "無" }, + "sections": { + "organizationMcps": "組織 MCP", + "marketplace": "Marketplace" + }, "type-group": { "modes": "模式", "mcps": "MCP 伺服器" From efb748acb71fac55bbddbadfeb2068b8f918decc Mon Sep 17 00:00:00 2001 From: John Richmond <5629+jr@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:21:53 -0700 Subject: [PATCH 2/3] Some roo-inspired feedback --- .../marketplace/MarketplaceManager.ts | 49 ++++++++----------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/src/services/marketplace/MarketplaceManager.ts b/src/services/marketplace/MarketplaceManager.ts index 44371ec1bbe..6cd174a5776 100644 --- a/src/services/marketplace/MarketplaceManager.ts +++ b/src/services/marketplace/MarketplaceManager.ts @@ -4,7 +4,7 @@ import * as path from "path" import * as yaml from "yaml" import { RemoteConfigLoader } from "./RemoteConfigLoader" import { SimpleInstaller } from "./SimpleInstaller" -import type { MarketplaceItem, MarketplaceItemType, McpMarketplaceItem } from "@roo-code/types" +import type { MarketplaceItem, MarketplaceItemType, McpMarketplaceItem, OrganizationSettings } from "@roo-code/types" import { GlobalFileNames } from "../../shared/globalFileNames" import { ensureSettingsDirectoryExists } from "../../utils/globalContext" import { t } from "../../i18n" @@ -32,48 +32,39 @@ export class MarketplaceManager { async getMarketplaceItems(): Promise { try { - let shouldHideMarketplaceMcps = false - let orgSettings: ReturnType | null = null + const errors: string[] = [] - // Check organization settings first to determine if we should load MCPs + let orgSettings: OrganizationSettings | undefined try { if (CloudService.hasInstance() && CloudService.instance.isAuthenticated()) { orgSettings = CloudService.instance.getOrganizationSettings() - if (orgSettings?.hideMarketplaceMcps) { - shouldHideMarketplaceMcps = true - } } } catch (orgError) { console.warn("Failed to load organization settings:", orgError) + const orgErrorMessage = orgError instanceof Error ? orgError.message : String(orgError) + errors.push(`Organization settings: ${orgErrorMessage}`) } - const allMarketplaceItems = await this.configLoader.loadAllItems(shouldHideMarketplaceMcps) + const allMarketplaceItems = await this.configLoader.loadAllItems(orgSettings?.hideMarketplaceMcps) let organizationMcps: MarketplaceItem[] = [] let marketplaceItems = allMarketplaceItems - const errors: string[] = [] - try { - if (orgSettings) { - if (orgSettings.mcps && orgSettings.mcps.length > 0) { - organizationMcps = orgSettings.mcps.map( - (mcp: McpMarketplaceItem): MarketplaceItem => ({ - ...mcp, - type: "mcp" as const, - }), - ) - } + if (orgSettings) { + if (orgSettings.mcps && orgSettings.mcps.length > 0) { + organizationMcps = orgSettings.mcps.map( + (mcp: McpMarketplaceItem): MarketplaceItem => ({ + ...mcp, + type: "mcp" as const, + }), + ) + } - if (orgSettings.hiddenMcps && orgSettings.hiddenMcps.length > 0) { - const hiddenMcpIds = new Set(orgSettings.hiddenMcps) - marketplaceItems = allMarketplaceItems.filter( - (item) => item.type !== "mcp" || !hiddenMcpIds.has(item.id), - ) - } + if (orgSettings.hiddenMcps && orgSettings.hiddenMcps.length > 0) { + const hiddenMcpIds = new Set(orgSettings.hiddenMcps) + marketplaceItems = allMarketplaceItems.filter( + (item) => item.type !== "mcp" || !hiddenMcpIds.has(item.id), + ) } - } catch (orgError) { - console.warn("Failed to load organization settings:", orgError) - const orgErrorMessage = orgError instanceof Error ? orgError.message : String(orgError) - errors.push(`Organization settings: ${orgErrorMessage}`) } return { From aee48a5b187b6d1c17a0bf8b561c9900c8d8fadf Mon Sep 17 00:00:00 2001 From: John Richmond <5629+jr@users.noreply.github.com> Date: Tue, 29 Jul 2025 13:26:23 -0700 Subject: [PATCH 3/3] Use the organization name --- .../src/components/marketplace/MarketplaceListView.tsx | 6 ++++-- webview-ui/src/i18n/locales/ca/marketplace.json | 2 +- webview-ui/src/i18n/locales/de/marketplace.json | 2 +- webview-ui/src/i18n/locales/en/marketplace.json | 2 +- webview-ui/src/i18n/locales/es/marketplace.json | 2 +- webview-ui/src/i18n/locales/fr/marketplace.json | 2 +- webview-ui/src/i18n/locales/hi/marketplace.json | 2 +- webview-ui/src/i18n/locales/id/marketplace.json | 2 +- webview-ui/src/i18n/locales/it/marketplace.json | 2 +- webview-ui/src/i18n/locales/ja/marketplace.json | 2 +- webview-ui/src/i18n/locales/ko/marketplace.json | 2 +- webview-ui/src/i18n/locales/nl/marketplace.json | 2 +- webview-ui/src/i18n/locales/pl/marketplace.json | 2 +- webview-ui/src/i18n/locales/pt-BR/marketplace.json | 2 +- webview-ui/src/i18n/locales/ru/marketplace.json | 2 +- webview-ui/src/i18n/locales/tr/marketplace.json | 2 +- webview-ui/src/i18n/locales/vi/marketplace.json | 2 +- webview-ui/src/i18n/locales/zh-CN/marketplace.json | 2 +- webview-ui/src/i18n/locales/zh-TW/marketplace.json | 2 +- 19 files changed, 22 insertions(+), 20 deletions(-) diff --git a/webview-ui/src/components/marketplace/MarketplaceListView.tsx b/webview-ui/src/components/marketplace/MarketplaceListView.tsx index c456a574e29..8ec13be8e57 100644 --- a/webview-ui/src/components/marketplace/MarketplaceListView.tsx +++ b/webview-ui/src/components/marketplace/MarketplaceListView.tsx @@ -21,7 +21,7 @@ export interface MarketplaceListViewProps { export function MarketplaceListView({ stateManager, allTags, filteredTags, filterByType }: MarketplaceListViewProps) { const [state, manager] = useStateManager(stateManager) const { t } = useAppTranslation() - const { marketplaceInstalledMetadata } = useExtensionState() + const { marketplaceInstalledMetadata, cloudUserInfo } = useExtensionState() const [isTagPopoverOpen, setIsTagPopoverOpen] = React.useState(false) const [tagSearch, setTagSearch] = React.useState("") const allItems = state.displayItems || [] @@ -204,7 +204,9 @@ export function MarketplaceListView({ stateManager, allTags, filteredTags, filte

- {t("marketplace:sections.organizationMcps")} + {t("marketplace:sections.organizationMcps", { + organization: cloudUserInfo?.organizationName, + })}

diff --git a/webview-ui/src/i18n/locales/ca/marketplace.json b/webview-ui/src/i18n/locales/ca/marketplace.json index 81ba04ebd7b..e603da97301 100644 --- a/webview-ui/src/i18n/locales/ca/marketplace.json +++ b/webview-ui/src/i18n/locales/ca/marketplace.json @@ -36,7 +36,7 @@ "none": "Cap" }, "sections": { - "organizationMcps": "MCPs de l'organització", + "organizationMcps": "MCPs de {{organization}}", "marketplace": "Mercat" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/de/marketplace.json b/webview-ui/src/i18n/locales/de/marketplace.json index 955b43e96b4..da89627e31b 100644 --- a/webview-ui/src/i18n/locales/de/marketplace.json +++ b/webview-ui/src/i18n/locales/de/marketplace.json @@ -36,7 +36,7 @@ "none": "Keine" }, "sections": { - "organizationMcps": "Organisations-MCPs", + "organizationMcps": "MCPs von {{organization}}", "marketplace": "Marktplatz" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/en/marketplace.json b/webview-ui/src/i18n/locales/en/marketplace.json index 53dadbc8f8a..1bce41b9863 100644 --- a/webview-ui/src/i18n/locales/en/marketplace.json +++ b/webview-ui/src/i18n/locales/en/marketplace.json @@ -36,7 +36,7 @@ "none": "None" }, "sections": { - "organizationMcps": "Organization MCPs", + "organizationMcps": "{{organization}} MCPs", "marketplace": "Marketplace" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/es/marketplace.json b/webview-ui/src/i18n/locales/es/marketplace.json index 0a449a60a6d..9cad65e0bb0 100644 --- a/webview-ui/src/i18n/locales/es/marketplace.json +++ b/webview-ui/src/i18n/locales/es/marketplace.json @@ -36,7 +36,7 @@ "none": "Ninguno" }, "sections": { - "organizationMcps": "MCPs de la organización", + "organizationMcps": "MCPs de {{organization}}", "marketplace": "Mercado" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/fr/marketplace.json b/webview-ui/src/i18n/locales/fr/marketplace.json index 9e1149be6ce..77a4dc19e96 100644 --- a/webview-ui/src/i18n/locales/fr/marketplace.json +++ b/webview-ui/src/i18n/locales/fr/marketplace.json @@ -36,7 +36,7 @@ "none": "Aucun" }, "sections": { - "organizationMcps": "MCPs de l'organisation", + "organizationMcps": "MCPs de {{organization}}", "marketplace": "Marché" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/hi/marketplace.json b/webview-ui/src/i18n/locales/hi/marketplace.json index 6d11d947b60..3b752d6dbdc 100644 --- a/webview-ui/src/i18n/locales/hi/marketplace.json +++ b/webview-ui/src/i18n/locales/hi/marketplace.json @@ -36,7 +36,7 @@ "none": "कोई नहीं" }, "sections": { - "organizationMcps": "संगठन MCPs", + "organizationMcps": "{{organization}} MCPs", "marketplace": "मार्केटप्लेस" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/id/marketplace.json b/webview-ui/src/i18n/locales/id/marketplace.json index 1f075613b7f..e1a3c450a15 100644 --- a/webview-ui/src/i18n/locales/id/marketplace.json +++ b/webview-ui/src/i18n/locales/id/marketplace.json @@ -36,7 +36,7 @@ "none": "Tidak Ada" }, "sections": { - "organizationMcps": "MCP Organisasi", + "organizationMcps": "MCP {{organization}}", "marketplace": "Marketplace" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/it/marketplace.json b/webview-ui/src/i18n/locales/it/marketplace.json index dbfbfb52ff2..e0e0efcb60e 100644 --- a/webview-ui/src/i18n/locales/it/marketplace.json +++ b/webview-ui/src/i18n/locales/it/marketplace.json @@ -36,7 +36,7 @@ "none": "Nessuno" }, "sections": { - "organizationMcps": "MCP dell'organizzazione", + "organizationMcps": "MCP di {{organization}}", "marketplace": "Mercato" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/ja/marketplace.json b/webview-ui/src/i18n/locales/ja/marketplace.json index e975657aeb6..6eca7414f3a 100644 --- a/webview-ui/src/i18n/locales/ja/marketplace.json +++ b/webview-ui/src/i18n/locales/ja/marketplace.json @@ -36,7 +36,7 @@ "none": "なし" }, "sections": { - "organizationMcps": "組織MCP", + "organizationMcps": "{{organization}} MCPs", "marketplace": "マーケットプレイス" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/ko/marketplace.json b/webview-ui/src/i18n/locales/ko/marketplace.json index 06a9bc7b81b..2ef3353a644 100644 --- a/webview-ui/src/i18n/locales/ko/marketplace.json +++ b/webview-ui/src/i18n/locales/ko/marketplace.json @@ -36,7 +36,7 @@ "none": "없음" }, "sections": { - "organizationMcps": "조직 MCP", + "organizationMcps": "{{organization}} MCPs", "marketplace": "마켓플레이스" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/nl/marketplace.json b/webview-ui/src/i18n/locales/nl/marketplace.json index c38dfe19c71..1d84492f5ae 100644 --- a/webview-ui/src/i18n/locales/nl/marketplace.json +++ b/webview-ui/src/i18n/locales/nl/marketplace.json @@ -36,7 +36,7 @@ "none": "Geen" }, "sections": { - "organizationMcps": "Organisatie MCP's", + "organizationMcps": "MCP's van {{organization}}", "marketplace": "Marktplaats" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/pl/marketplace.json b/webview-ui/src/i18n/locales/pl/marketplace.json index e05adc99ba4..a9d0b2a009a 100644 --- a/webview-ui/src/i18n/locales/pl/marketplace.json +++ b/webview-ui/src/i18n/locales/pl/marketplace.json @@ -36,7 +36,7 @@ "none": "Brak" }, "sections": { - "organizationMcps": "MCPs organizacji", + "organizationMcps": "MCPs {{organization}}", "marketplace": "Rynek" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/pt-BR/marketplace.json b/webview-ui/src/i18n/locales/pt-BR/marketplace.json index 00ce2737e33..67e23c6ad30 100644 --- a/webview-ui/src/i18n/locales/pt-BR/marketplace.json +++ b/webview-ui/src/i18n/locales/pt-BR/marketplace.json @@ -36,7 +36,7 @@ "none": "Nenhum" }, "sections": { - "organizationMcps": "MCPs da Organização", + "organizationMcps": "MCPs da {{organization}}", "marketplace": "Marketplace" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/ru/marketplace.json b/webview-ui/src/i18n/locales/ru/marketplace.json index 2a361218bcd..299ebbf6034 100644 --- a/webview-ui/src/i18n/locales/ru/marketplace.json +++ b/webview-ui/src/i18n/locales/ru/marketplace.json @@ -36,7 +36,7 @@ "none": "Нет" }, "sections": { - "organizationMcps": "MCP организации", + "organizationMcps": "MCPs {{organization}}", "marketplace": "Маркетплейс" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/tr/marketplace.json b/webview-ui/src/i18n/locales/tr/marketplace.json index f27d4622009..44b8cb98b27 100644 --- a/webview-ui/src/i18n/locales/tr/marketplace.json +++ b/webview-ui/src/i18n/locales/tr/marketplace.json @@ -36,7 +36,7 @@ "none": "Hiçbiri" }, "sections": { - "organizationMcps": "Kuruluş MCP'leri", + "organizationMcps": "{{organization}} MCP'leri", "marketplace": "Marketplace" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/vi/marketplace.json b/webview-ui/src/i18n/locales/vi/marketplace.json index f936f94a9b4..7a51d0a29b3 100644 --- a/webview-ui/src/i18n/locales/vi/marketplace.json +++ b/webview-ui/src/i18n/locales/vi/marketplace.json @@ -36,7 +36,7 @@ "none": "Không có" }, "sections": { - "organizationMcps": "MCP của Tổ chức", + "organizationMcps": "MCP của {{organization}}", "marketplace": "Marketplace" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/zh-CN/marketplace.json b/webview-ui/src/i18n/locales/zh-CN/marketplace.json index e90fc3e4990..996da334d54 100644 --- a/webview-ui/src/i18n/locales/zh-CN/marketplace.json +++ b/webview-ui/src/i18n/locales/zh-CN/marketplace.json @@ -36,7 +36,7 @@ "none": "无" }, "sections": { - "organizationMcps": "组织 MCP", + "organizationMcps": "{{organization}} MCPs", "marketplace": "Marketplace" }, "type-group": { diff --git a/webview-ui/src/i18n/locales/zh-TW/marketplace.json b/webview-ui/src/i18n/locales/zh-TW/marketplace.json index b221c9223a8..dd3d30cc2fa 100644 --- a/webview-ui/src/i18n/locales/zh-TW/marketplace.json +++ b/webview-ui/src/i18n/locales/zh-TW/marketplace.json @@ -36,7 +36,7 @@ "none": "無" }, "sections": { - "organizationMcps": "組織 MCP", + "organizationMcps": "{{organization}} MCPs", "marketplace": "Marketplace" }, "type-group": {