import React, { useState, useEffect, useCallback } from 'react' import { Button, Input } from '@janhq/joi' import { PlusIcon } from 'lucide-react' import { npxFinder, NPMPackage } from 'npx-scope-finder' import { toaster } from '@/containers/Toast' interface MCPConfig { mcpServers: { [key: string]: { command: string args: string[] env: Record } } } const MCPSearch = () => { const [showToast, setShowToast] = useState(false) const [toastMessage, setToastMessage] = useState('') const [toastType, setToastType] = useState<'success' | 'error'>('success') const [orgName, setOrgName] = useState('@modelcontextprotocol') const [packages, setPackages] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState('') const searchOrganizationPackages = useCallback(async () => { if (!orgName) return try { setLoading(true) setError('') // Remove @ symbol if present at the beginning // const scopeName = orgName.startsWith('@') ? orgName.substring(1) : orgName // Use npxFinder to search for packages from the specified organization const result = await npxFinder(orgName) setPackages(result || []) } catch (err) { console.error('Error searching for packages:', err) setError('Failed to search for packages. Please try again.') } finally { setLoading(false) } }, [orgName]) // Search for packages when the component mounts useEffect(() => { searchOrganizationPackages() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) return ( <>

NPX Package List

Search and add npm packages as MCP servers

setOrgName(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter' && orgName) { e.preventDefault() searchOrganizationPackages() } }} className="input w-full" placeholder="Enter npm scope name (e.g. @janhq)" />
{error &&

{error}

}
{packages.length > 0 ? (
{packages.map((pkg, index) => (
{pkg.name?.split('/')[1]}
{pkg.version}

{pkg.description || 'No description'}

Usage: npx {pkg.name}

{`https://www.npmjs.com/package/${pkg.name}`}
))}
) : ( !loading && (

No packages found. Try searching for a different organization.

) )} {showToast && (
{toastMessage}
)} ) // Function to add a package to the MCP configuration async function handleAddToConfig(pkg: NPMPackage) { try { // Get current configuration const currentConfig = await window.core?.api.getMcpConfigs() // Parse the configuration let config: MCPConfig try { config = JSON.parse(currentConfig || '{"mcpServers": {}}') } catch (err) { // If parsing fails, start with an empty configuration config = { mcpServers: {} } } // Generate a unique server name based on the package name const serverName = pkg.name?.split('/')[1] || 'unknown' // Check if this server already exists if (config.mcpServers[serverName]) { toaster({ title: `Add ${serverName} success`, description: `Server ${serverName} already exists in configuration`, type: 'error', }) return } // Add the new server configuration config.mcpServers[serverName] = { command: 'npx', args: ['-y', pkg.name || ''], env: {}, } // Save the updated configuration await window.core?.api?.saveMcpConfigs({ configs: JSON.stringify(config, null, 2), }) await window.core?.api?.restartMcpServers() toaster({ title: `Add ${serverName} success`, description: `Added ${serverName} to MCP configuration`, type: 'success', }) } catch (err) { toaster({ title: `Add ${pkg.name?.split('/')[1] || 'unknown'} failed`, description: `Failed to add package to configuration`, type: 'error', }) console.error('Error adding package to configuration:', err) } } } export default MCPSearch