diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index f9e546f095..df6b856ce9 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -7,6 +7,7 @@ export * from "./experiment.js" export * from "./global-settings.js" export * from "./history.js" export * from "./ipc.js" +export * from "./marketplace.js" export * from "./mcp.js" export * from "./message.js" export * from "./mode.js" diff --git a/packages/types/src/marketplace.ts b/packages/types/src/marketplace.ts new file mode 100644 index 0000000000..f2821e1b74 --- /dev/null +++ b/packages/types/src/marketplace.ts @@ -0,0 +1,88 @@ +import { z } from "zod" + +/** + * Schema for MCP parameter definitions + */ +export const mcpParameterSchema = z.object({ + name: z.string().min(1), + key: z.string().min(1), + placeholder: z.string().optional(), + optional: z.boolean().optional().default(false), +}) + +export type McpParameter = z.infer + +/** + * Schema for MCP installation method with name + */ +export const mcpInstallationMethodSchema = z.object({ + name: z.string().min(1), + content: z.string().min(1), + parameters: z.array(mcpParameterSchema).optional(), + prerequisites: z.array(z.string()).optional(), +}) + +export type McpInstallationMethod = z.infer + +/** + * Component type validation + */ +export const marketplaceItemTypeSchema = z.enum(["mode", "mcp"] as const) + +export type MarketplaceItemType = z.infer + +/** + * Base schema for common marketplace item fields + */ +const baseMarketplaceItemSchema = z.object({ + id: z.string().min(1), + name: z.string().min(1, "Name is required"), + description: z.string(), + author: z.string().optional(), + authorUrl: z.string().url("Author URL must be a valid URL").optional(), + tags: z.array(z.string()).optional(), + prerequisites: z.array(z.string()).optional(), +}) + +/** + * Type-specific schemas for YAML parsing (without type field, added programmatically) + */ +export const modeMarketplaceItemSchema = baseMarketplaceItemSchema.extend({ + content: z.string().min(1), // YAML content for modes +}) + +export type ModeMarketplaceItem = z.infer + +export const mcpMarketplaceItemSchema = baseMarketplaceItemSchema.extend({ + url: z.string().url(), // Required url field + content: z.union([z.string().min(1), z.array(mcpInstallationMethodSchema)]), // Single config or array of methods + parameters: z.array(mcpParameterSchema).optional(), +}) + +export type McpMarketplaceItem = z.infer + +/** + * Unified marketplace item schema using discriminated union + */ +export const marketplaceItemSchema = z.discriminatedUnion("type", [ + // Mode marketplace item + modeMarketplaceItemSchema.extend({ + type: z.literal("mode"), + }), + // MCP marketplace item + mcpMarketplaceItemSchema.extend({ + type: z.literal("mcp"), + }), +]) + +export type MarketplaceItem = z.infer + +/** + * Installation options for marketplace items + */ +export const installMarketplaceItemOptionsSchema = z.object({ + target: z.enum(["global", "project"]).optional().default("project"), + parameters: z.record(z.string(), z.any()).optional(), +}) + +export type InstallMarketplaceItemOptions = z.infer diff --git a/src/services/marketplace/MarketplaceManager.ts b/src/services/marketplace/MarketplaceManager.ts index 8f88aa4d57..367fa14888 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 { MarketplaceItem, MarketplaceItemType } from "./types" +import type { MarketplaceItem, MarketplaceItemType } from "@roo-code/types" import { GlobalFileNames } from "../../shared/globalFileNames" import { ensureSettingsDirectoryExists } from "../../utils/globalContext" import { t } from "../../i18n" diff --git a/src/services/marketplace/RemoteConfigLoader.ts b/src/services/marketplace/RemoteConfigLoader.ts index 3b822159dd..a37f619b4d 100644 --- a/src/services/marketplace/RemoteConfigLoader.ts +++ b/src/services/marketplace/RemoteConfigLoader.ts @@ -2,8 +2,8 @@ import axios from "axios" import * as yaml from "yaml" import { z } from "zod" import { getRooCodeApiUrl } from "@roo-code/cloud" -import { MarketplaceItem, MarketplaceItemType } from "./types" -import { modeMarketplaceItemSchema, mcpMarketplaceItemSchema } from "./schemas" +import type { MarketplaceItem, MarketplaceItemType } from "@roo-code/types" +import { modeMarketplaceItemSchema, mcpMarketplaceItemSchema } from "@roo-code/types" // Response schemas for YAML API responses const modeMarketplaceResponse = z.object({ @@ -43,8 +43,8 @@ export class RemoteConfigLoader { const yamlData = yaml.parse(data) const validated = modeMarketplaceResponse.parse(yamlData) - const items = validated.items.map((item) => ({ - type: "mode" as MarketplaceItemType, + const items: MarketplaceItem[] = validated.items.map((item) => ({ + type: "mode" as const, ...item, })) @@ -63,8 +63,8 @@ export class RemoteConfigLoader { const yamlData = yaml.parse(data) const validated = mcpMarketplaceResponse.parse(yamlData) - const items = validated.items.map((item) => ({ - type: "mcp" as MarketplaceItemType, + const items: MarketplaceItem[] = validated.items.map((item) => ({ + type: "mcp" as const, ...item, })) diff --git a/src/services/marketplace/SimpleInstaller.ts b/src/services/marketplace/SimpleInstaller.ts index 82e44696fd..75f14b0d4c 100644 --- a/src/services/marketplace/SimpleInstaller.ts +++ b/src/services/marketplace/SimpleInstaller.ts @@ -2,7 +2,7 @@ import * as vscode from "vscode" import * as path from "path" import * as fs from "fs/promises" import * as yaml from "yaml" -import { MarketplaceItem, MarketplaceItemType, InstallMarketplaceItemOptions, McpParameter } from "./types" +import type { MarketplaceItem, MarketplaceItemType, InstallMarketplaceItemOptions, McpParameter } from "@roo-code/types" import { GlobalFileNames } from "../../shared/globalFileNames" import { ensureSettingsDirectoryExists } from "../../utils/globalContext" @@ -23,7 +23,7 @@ export class SimpleInstaller { case "mcp": return await this.installMcp(item, target, options) default: - throw new Error(`Unsupported item type: ${item.type}`) + throw new Error(`Unsupported item type: ${(item as any).type}`) } } @@ -135,7 +135,8 @@ export class SimpleInstaller { } // Merge parameters (method-specific override global) - const allParameters = [...(item.parameters || []), ...methodParameters] + const itemParameters = item.type === "mcp" ? item.parameters || [] : [] + const allParameters = [...itemParameters, ...methodParameters] const uniqueParameters = Array.from(new Map(allParameters.map((p) => [p.key, p])).values()) // Replace parameters if provided @@ -158,7 +159,8 @@ export class SimpleInstaller { methodParameters = method.parameters || [] // Re-merge parameters with the newly selected method - const allParametersForNewMethod = [...(item.parameters || []), ...methodParameters] + const itemParametersForNewMethod = item.type === "mcp" ? item.parameters || [] : [] + const allParametersForNewMethod = [...itemParametersForNewMethod, ...methodParameters] const uniqueParametersForNewMethod = Array.from( new Map(allParametersForNewMethod.map((p) => [p.key, p])).values(), ) @@ -239,7 +241,7 @@ export class SimpleInstaller { await this.removeMcp(item, target) break default: - throw new Error(`Unsupported item type: ${item.type}`) + throw new Error(`Unsupported item type: ${(item as any).type}`) } } diff --git a/src/services/marketplace/__tests__/MarketplaceManager.test.ts b/src/services/marketplace/__tests__/MarketplaceManager.test.ts index 46781d7f32..a57104f83e 100644 --- a/src/services/marketplace/__tests__/MarketplaceManager.test.ts +++ b/src/services/marketplace/__tests__/MarketplaceManager.test.ts @@ -1,5 +1,5 @@ import { MarketplaceManager } from "../MarketplaceManager" -import { MarketplaceItem } from "../types" +import type { MarketplaceItem } from "@roo-code/types" // Mock axios jest.mock("axios") @@ -116,6 +116,7 @@ describe("MarketplaceManager", () => { name: "Test MCP", description: "A test MCP", type: "mcp", + url: "https://example.com/mcp", content: '{"command": "node", "args": ["server.js"]}', }, ] @@ -204,6 +205,7 @@ describe("MarketplaceManager", () => { name: "Test MCP", description: "A test MCP", type: "mcp", + url: "https://example.com/mcp", content: '{"command": "node", "args": ["server.js"]}', } @@ -244,6 +246,7 @@ describe("MarketplaceManager", () => { name: "Test MCP", description: "A test MCP", type: "mcp", + url: "https://example.com/mcp", content: '{"command": "node", "args": ["server.js"]}', } diff --git a/src/services/marketplace/__tests__/RemoteConfigLoader.test.ts b/src/services/marketplace/__tests__/RemoteConfigLoader.test.ts index 8d78c78fcb..778a22ffe1 100644 --- a/src/services/marketplace/__tests__/RemoteConfigLoader.test.ts +++ b/src/services/marketplace/__tests__/RemoteConfigLoader.test.ts @@ -1,6 +1,6 @@ import axios from "axios" import { RemoteConfigLoader } from "../RemoteConfigLoader" -import { MarketplaceItemType } from "../types" +import type { MarketplaceItemType } from "@roo-code/types" // Mock axios jest.mock("axios") diff --git a/src/services/marketplace/__tests__/SimpleInstaller.test.ts b/src/services/marketplace/__tests__/SimpleInstaller.test.ts index 7ed90d14cb..248d9d3b0a 100644 --- a/src/services/marketplace/__tests__/SimpleInstaller.test.ts +++ b/src/services/marketplace/__tests__/SimpleInstaller.test.ts @@ -2,7 +2,7 @@ import { SimpleInstaller } from "../SimpleInstaller" import * as fs from "fs/promises" import * as yaml from "yaml" import * as vscode from "vscode" -import { MarketplaceItem } from "../types" +import type { MarketplaceItem } from "@roo-code/types" import * as path from "path" jest.mock("fs/promises") @@ -126,6 +126,7 @@ describe("SimpleInstaller", () => { name: "Test MCP", description: "A test MCP server for testing", type: "mcp", + url: "https://example.com/mcp", content: JSON.stringify({ command: "test-server", args: ["--test"], diff --git a/src/services/marketplace/__tests__/marketplace-setting-check.test.ts b/src/services/marketplace/__tests__/marketplace-setting-check.test.ts index 8e2844a4ca..2c0fb07c84 100644 --- a/src/services/marketplace/__tests__/marketplace-setting-check.test.ts +++ b/src/services/marketplace/__tests__/marketplace-setting-check.test.ts @@ -75,6 +75,7 @@ describe("Marketplace Setting Check", () => { type: "mcp" as const, description: "Test description", content: "test content", + url: "https://example.com/test-mcp", }, mpInstallOptions: { target: "project" as const }, } diff --git a/src/services/marketplace/__tests__/nested-parameters.spec.ts b/src/services/marketplace/__tests__/nested-parameters.spec.ts index 67fc4267a3..5eaf839df9 100644 --- a/src/services/marketplace/__tests__/nested-parameters.spec.ts +++ b/src/services/marketplace/__tests__/nested-parameters.spec.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from "vitest" -import { mcpInstallationMethodSchema, mcpMarketplaceItemYamlSchema } from "../schemas" -import { McpInstallationMethod, McpMarketplaceItem } from "../types" +import { mcpInstallationMethodSchema, mcpMarketplaceItemSchema } from "@roo-code/types" +import type { McpInstallationMethod, McpMarketplaceItem } from "@roo-code/types" describe("Nested Parameters", () => { describe("McpInstallationMethod Schema", () => { @@ -95,7 +95,7 @@ describe("Nested Parameters", () => { ], } - const result = mcpMarketplaceItemYamlSchema.parse(item) + const result = mcpMarketplaceItemSchema.parse(item) expect(result.parameters).toHaveLength(1) expect(result.parameters![0].key).toBe("api_key") @@ -131,7 +131,7 @@ describe("Nested Parameters", () => { ], } - const result = mcpMarketplaceItemYamlSchema.parse(item) + const result = mcpMarketplaceItemSchema.parse(item) expect(result.parameters).toHaveLength(1) const methods = result.content as McpInstallationMethod[] @@ -160,7 +160,7 @@ describe("Nested Parameters", () => { ], } - const result = mcpMarketplaceItemYamlSchema.parse(item) + const result = mcpMarketplaceItemSchema.parse(item) expect(result.parameters).toBeUndefined() const methods = result.content as McpInstallationMethod[] @@ -182,7 +182,7 @@ describe("Nested Parameters", () => { ], } - const result = mcpMarketplaceItemYamlSchema.parse(item) + const result = mcpMarketplaceItemSchema.parse(item) expect(result.parameters).toBeUndefined() const methods = result.content as McpInstallationMethod[] @@ -221,7 +221,7 @@ describe("Nested Parameters", () => { } // This should validate successfully - the conflict resolution happens at runtime - const result = mcpMarketplaceItemYamlSchema.parse(item) + const result = mcpMarketplaceItemSchema.parse(item) expect(result.parameters![0].key).toBe("version") const methods = result.content as McpInstallationMethod[] diff --git a/src/services/marketplace/__tests__/optional-parameters.spec.ts b/src/services/marketplace/__tests__/optional-parameters.spec.ts index 1b73c15815..0c5bf96a1b 100644 --- a/src/services/marketplace/__tests__/optional-parameters.spec.ts +++ b/src/services/marketplace/__tests__/optional-parameters.spec.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from "vitest" -import { mcpParameterSchema } from "../schemas" -import { McpParameter } from "../types" +import { mcpParameterSchema } from "@roo-code/types" +import type { McpParameter } from "@roo-code/types" describe("Optional Parameters", () => { describe("McpParameter Schema", () => { @@ -67,23 +67,4 @@ describe("Optional Parameters", () => { }).toThrow() }) }) - - describe("Type Definitions", () => { - it("should allow optional field in McpParameter interface", () => { - const requiredParam: McpParameter = { - name: "Required Param", - key: "required_key", - } - - const optionalParam: McpParameter = { - name: "Optional Param", - key: "optional_key", - optional: true, - } - - // These should compile without errors - expect(requiredParam.optional).toBeUndefined() - expect(optionalParam.optional).toBe(true) - }) - }) }) diff --git a/src/services/marketplace/index.ts b/src/services/marketplace/index.ts index 389d997706..c79113c9a4 100644 --- a/src/services/marketplace/index.ts +++ b/src/services/marketplace/index.ts @@ -1,4 +1,3 @@ export * from "./SimpleInstaller" export * from "./MarketplaceManager" -export * from "./types" -export * from "./schemas" +export type { MarketplaceItemType } from "@roo-code/types" diff --git a/src/services/marketplace/schemas.ts b/src/services/marketplace/schemas.ts deleted file mode 100644 index 42af243393..0000000000 --- a/src/services/marketplace/schemas.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { z } from "zod" - -/** - * Schema for MCP parameter definitions - */ -export const mcpParameterSchema = z.object({ - name: z.string().min(1), - key: z.string().min(1), - placeholder: z.string().optional(), - optional: z.boolean().optional().default(false), -}) - -/** - * Schema for MCP installation method with name - */ -export const mcpInstallationMethodSchema = z.object({ - name: z.string().min(1), - content: z.string().min(1), - parameters: z.array(mcpParameterSchema).optional(), - prerequisites: z.array(z.string()).optional(), -}) - -/** - * Component type validation - */ -export const marketplaceItemTypeSchema = z.enum(["mode", "mcp"] as const) - -/** - * Schema for a marketplace item (supports both mode and mcp types) - */ -export const marketplaceItemSchema = z.object({ - id: z.string().min(1), - name: z.string().min(1, "Name is required"), - description: z.string(), - type: marketplaceItemTypeSchema, - author: z.string().optional(), - authorUrl: z.string().url("Author URL must be a valid URL").optional(), - tags: z.array(z.string()).optional(), - content: z.union([z.string().min(1), z.array(mcpInstallationMethodSchema)]), // Embedded content (YAML for modes, JSON for mcps, or named methods) - prerequisites: z.array(z.string()).optional(), -}) - -/** - * Local marketplace config schema (JSON format) - */ -export const marketplaceConfigSchema = z.object({ - items: z.record(z.string(), marketplaceItemSchema), -}) - -/** - * Local marketplace YAML config schema (uses any for items since they're validated separately by type) - */ -export const marketplaceYamlConfigSchema = z.object({ - items: z.array(z.any()), // Items are validated separately by type-specific schemas -}) - -// Schemas for YAML files (without type field, as type is added programmatically) -export const modeMarketplaceItemYamlSchema = z.object({ - id: z.string(), - name: z.string(), - description: z.string(), - author: z.string().optional(), - authorUrl: z.string().url().optional(), - tags: z.array(z.string()).optional(), - content: z.string(), - prerequisites: z.array(z.string()).optional(), -}) - -export const mcpMarketplaceItemYamlSchema = z.object({ - id: z.string(), - name: z.string(), - description: z.string(), - author: z.string().optional(), - authorUrl: z.string().url().optional(), - url: z.string().url(), // Required url field - tags: z.array(z.string()).optional(), - content: z.union([z.string(), z.array(mcpInstallationMethodSchema)]), - parameters: z.array(mcpParameterSchema).optional(), - prerequisites: z.array(z.string()).optional(), -}) - -// Export aliases for backward compatibility (these are the same as the YAML schemas) -export const modeMarketplaceItemSchema = modeMarketplaceItemYamlSchema -export const mcpMarketplaceItemSchema = mcpMarketplaceItemYamlSchema diff --git a/src/services/marketplace/types.ts b/src/services/marketplace/types.ts deleted file mode 100644 index 52740141c7..0000000000 --- a/src/services/marketplace/types.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Supported component types - */ -export type MarketplaceItemType = "mode" | "mcp" - -/** - * Local marketplace config types - */ -export interface MarketplaceConfig { - items: Record -} - -export interface MarketplaceYamlConfig { - items: T[] -} - -export interface ModeMarketplaceItem { - id: string - name: string - description: string - author?: string - authorUrl?: string - tags?: string[] - content: string // Embedded YAML content for .roomodes - prerequisites?: string[] -} - -export interface McpParameter { - name: string - key: string - placeholder?: string - optional?: boolean // Defaults to false if not provided -} - -export interface McpInstallationMethod { - name: string - content: string - parameters?: McpParameter[] - prerequisites?: string[] -} - -export interface McpMarketplaceItem { - id: string - name: string - description: string - author?: string - authorUrl?: string - url: string // Required url field - tags?: string[] - content: string | McpInstallationMethod[] // Can be a single config or array of named methods - parameters?: McpParameter[] - prerequisites?: string[] -} - -/** - * Unified marketplace item for UI - */ -export interface MarketplaceItem { - id: string - name: string - description: string - type: MarketplaceItemType - author?: string - authorUrl?: string - url?: string // Optional - only MCPs have url - tags?: string[] - content: string | McpInstallationMethod[] // Can be a single config or array of named methods - parameters?: McpParameter[] // Optional parameters for MCPs - prerequisites?: string[] -} - -export interface InstallMarketplaceItemOptions { - /** - * Specify the target scope - * - * @default 'project' - */ - target?: "global" | "project" - /** - * Parameters provided by the user for configurable marketplace items - */ - parameters?: Record -} - -export interface RemoveInstalledMarketplaceItemOptions { - /** - * Specify the target scope - * - * @default 'project' - */ - target?: "global" | "project" -} diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 3f6333dc37..3bb521a296 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -16,7 +16,7 @@ import { GitCommit } from "../utils/git" import { McpServer } from "./mcp" import { Mode } from "./modes" import { RouterModels } from "./api" -import { MarketplaceItem } from "../services/marketplace/types" +import type { MarketplaceItem } from "@roo-code/types" // Indexing status types export interface IndexingStatus { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index ae93e3ae76..5482d6abfe 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -1,8 +1,13 @@ import { z } from "zod" -import type { ProviderSettings, PromptComponent, ModeConfig } from "@roo-code/types" -import { InstallMarketplaceItemOptions, MarketplaceItem } from "../services/marketplace/types" -import { marketplaceItemSchema } from "../services/marketplace/schemas" +import type { + ProviderSettings, + PromptComponent, + ModeConfig, + InstallMarketplaceItemOptions, + MarketplaceItem, +} from "@roo-code/types" +import { marketplaceItemSchema } from "@roo-code/types" import { Mode } from "./modes" @@ -228,7 +233,7 @@ export interface IndexClearedPayload { } export const installMarketplaceItemWithParametersPayloadSchema = z.object({ - item: marketplaceItemSchema.strict(), + item: marketplaceItemSchema, parameters: z.record(z.string(), z.any()), }) diff --git a/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts b/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts index 8009a5b8d1..7f7324f581 100644 --- a/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts +++ b/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts @@ -11,7 +11,7 @@ * 3. Using minimal state updates to avoid resetting scroll position */ -import { MarketplaceItem } from "../../../../src/services/marketplace/types" +import { MarketplaceItem } from "@roo-code/types" import { vscode } from "../../utils/vscode" import { WebviewMessage } from "../../../../src/shared/WebviewMessage" diff --git a/webview-ui/src/components/marketplace/components/MarketplaceInstallModal.tsx b/webview-ui/src/components/marketplace/components/MarketplaceInstallModal.tsx index e8ec5549eb..4333db50f4 100644 --- a/webview-ui/src/components/marketplace/components/MarketplaceInstallModal.tsx +++ b/webview-ui/src/components/marketplace/components/MarketplaceInstallModal.tsx @@ -1,5 +1,5 @@ import React, { useState, useMemo, useEffect } from "react" -import { MarketplaceItem, McpParameter, McpInstallationMethod } from "../../../../../src/services/marketplace/types" +import { MarketplaceItem, McpParameter, McpInstallationMethod } from "@roo-code/types" import { vscode } from "@/utils/vscode" import { useAppTranslation } from "@/i18n/TranslationContext" import { @@ -61,7 +61,7 @@ export const MarketplaceInstallModal: React.FC = ( const effectiveParameters = useMemo(() => { if (!item) return [] - const globalParams = item.parameters || [] + const globalParams = item.type === "mcp" ? item.parameters || [] : [] let methodParams: McpParameter[] = [] // Get method-specific parameters if content is an array @@ -100,7 +100,7 @@ export const MarketplaceInstallModal: React.FC = ( React.useEffect(() => { if (item) { // Get effective parameters for current method - const globalParams = item.parameters || [] + const globalParams = item.type === "mcp" ? item.parameters || [] : [] let methodParams: McpParameter[] = [] if (Array.isArray(item.content)) { diff --git a/webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx b/webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx index 2d20e8cadd..21632365df 100644 --- a/webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx +++ b/webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx @@ -1,5 +1,5 @@ import React, { useMemo, useState } from "react" -import { MarketplaceItem } from "../../../../../src/services/marketplace/types" +import { MarketplaceItem } from "@roo-code/types" import { vscode } from "@/utils/vscode" import { ViewState } from "../MarketplaceViewStateManager" import { useAppTranslation } from "@/i18n/TranslationContext" @@ -54,7 +54,7 @@ export const MarketplaceItemCard: React.FC = ({ item,

- {item.url && isValidUrl(item.url) ? ( + {item.type === "mcp" && item.url && isValidUrl(item.url) ? (