diff --git a/src/renderer/src/pages/settings/MCPSettings/NpxSearch.tsx b/src/renderer/src/pages/settings/MCPSettings/NpxSearch.tsx index 7154729e..32a7d6e3 100644 --- a/src/renderer/src/pages/settings/MCPSettings/NpxSearch.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/NpxSearch.tsx @@ -5,6 +5,7 @@ import { Center, HStack } from '@renderer/components/Layout' import { useMCPServers } from '@renderer/hooks/useMCPServers' import { builtinMCPServers } from '@renderer/store/mcp' import { MCPServer } from '@renderer/types' +import { getMcpConfigSampleFromReadme } from '@renderer/utils' import { Button, Card, Flex, Input, Space, Spin, Tag, Typography } from 'antd' import { npxFinder } from 'npx-scope-finder' import { type FC, useEffect, useState } from 'react' @@ -19,6 +20,7 @@ interface SearchResult { npmLink: string fullName: string type: MCPServer['type'] + configSample?: MCPServer['configSample'] } const npmScopes = ['@cherry', '@modelcontextprotocol', '@gongrzhe', '@mcpmarket'] @@ -73,9 +75,13 @@ const NpxSearch: FC<{ try { // Call npxFinder to search for packages const packages = await npxFinder(searchScope) - // Map the packages to our desired format const formattedResults: SearchResult[] = packages.map((pkg) => { + let configSample + if (pkg.original?.readme) { + configSample = getMcpConfigSampleFromReadme(pkg.original.readme) + } + return { key: pkg.name, name: pkg.name?.split('/')[1] || '', @@ -84,7 +90,8 @@ const NpxSearch: FC<{ usage: `npx ${pkg.name}`, npmLink: pkg.links?.npm || `https://www.npmjs.com/package/${pkg.name}`, fullName: pkg.name || '', - type: 'stdio' + type: 'stdio', + configSample } }) @@ -199,9 +206,11 @@ const NpxSearch: FC<{ name: record.name, description: `${record.description}\n\n${t('settings.mcp.npx_list.usage')}: ${record.usage}\n${t('settings.mcp.npx_list.npm')}: ${record.npmLink}`, command: 'npx', - args: ['-y', record.fullName], + args: record.configSample?.args ?? ['-y', record.fullName], + env: record.configSample?.env, isActive: false, - type: record.type + type: record.type, + configSample: record.configSample } addMCPServer(newServer) diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 61c5a6d6..952b766b 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -374,6 +374,12 @@ export interface MCPServerParameter { description: string } +export interface MCPConfigSample { + command: string + args: string[] + env?: Record | undefined +} + export interface MCPServer { id: string name: string @@ -386,6 +392,7 @@ export interface MCPServer { env?: Record isActive: boolean disabledTools?: string[] // List of tool names that are disabled for this server + configSample?: MCPConfigSample headers?: Record // Custom headers to be sent with requests to this server } diff --git a/src/renderer/src/utils/index.ts b/src/renderer/src/utils/index.ts index 7be1b5e0..79805aa2 100644 --- a/src/renderer/src/utils/index.ts +++ b/src/renderer/src/utils/index.ts @@ -501,4 +501,30 @@ export function hasObjectKey(obj: any, key: string) { return Object.keys(obj).includes(key) } +/** + * 从npm readme中提取 npx mcp config + * @param readme readme字符串 + * @returns mcp config sample + */ +export function getMcpConfigSampleFromReadme(readme: string) { + if (readme) { + // 使用正则表达式匹配 mcpServers 对象内容 + const regex = /"mcpServers"\s*:\s*({(?:[^{}]*|{(?:[^{}]*|{[^{}]*})*})*})/ + const match = readme.match(regex) + console.log('match', match) + if (match && match[1]) { + // 添加缺失的闭合括号检测 + try { + let orgSample = JSON.parse(match[1]) + orgSample = orgSample[Object.keys(orgSample)[0] ?? ''] + if (orgSample.command === 'npx') { + return orgSample + } + } catch (e) { + console.log(e) + } + } + } +} + export { classNames }