Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/main/presenter/configPresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,51 @@ export class ConfigPresenter implements IConfigPresenter {
this.knowledgeConfHelper.setKnowledgeConfigs(configs)
}

// 获取NPM Registry缓存
getNpmRegistryCache(): any {
return this.mcpConfHelper.getNpmRegistryCache()
}

// 设置NPM Registry缓存
setNpmRegistryCache(cache: any): void {
return this.mcpConfHelper.setNpmRegistryCache(cache)
}

// 检查NPM Registry缓存是否有效
isNpmRegistryCacheValid(): boolean {
return this.mcpConfHelper.isNpmRegistryCacheValid()
}

// 获取有效的NPM Registry
getEffectiveNpmRegistry(): string | null {
return this.mcpConfHelper.getEffectiveNpmRegistry()
}

// 获取自定义NPM Registry
getCustomNpmRegistry(): string | undefined {
return this.mcpConfHelper.getCustomNpmRegistry()
}

// 设置自定义NPM Registry
setCustomNpmRegistry(registry: string | undefined): void {
this.mcpConfHelper.setCustomNpmRegistry(registry)
}

// 获取自动检测NPM Registry设置
getAutoDetectNpmRegistry(): boolean {
return this.mcpConfHelper.getAutoDetectNpmRegistry()
}

// 设置自动检测NPM Registry
setAutoDetectNpmRegistry(enabled: boolean): void {
this.mcpConfHelper.setAutoDetectNpmRegistry(enabled)
}

// 清除NPM Registry缓存
clearNpmRegistryCache(): void {
this.mcpConfHelper.clearNpmRegistryCache()
}

// 对比知识库配置差异
diffKnowledgeConfigs(newConfigs: BuiltinKnowledgeConfig[]) {
return KnowledgeConfHelper.diffKnowledgeConfigs(
Expand Down
95 changes: 94 additions & 1 deletion src/main/presenter/configPresenter/mcpConfHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@ import { app } from 'electron'
import { compare } from 'compare-versions'
import { presenter } from '..'

// NPM Registry缓存接口
export interface INpmRegistryCache {
registry: string
lastChecked: number
isAutoDetect: boolean
}

// MCP设置的接口
interface IMcpSettings {
mcpServers: Record<string, MCPServerConfig>
defaultServer?: string // 保留旧字段以支持版本兼容
defaultServers: string[] // 新增:多个默认服务器数组
mcpEnabled: boolean // 添加MCP启用状态字段
npmRegistryCache?: INpmRegistryCache // NPM源缓存
customNpmRegistry?: string // 用户自定义NPM源
autoDetectNpmRegistry?: boolean // 是否启用自动检测
[key: string]: unknown // 允许任意键
}
export type MCPServerType = 'stdio' | 'sse' | 'inmemory' | 'http'
Expand Down Expand Up @@ -309,7 +319,10 @@ export class McpConfHelper {
defaults: {
mcpServers: DEFAULT_MCP_SERVERS.mcpServers,
defaultServers: DEFAULT_MCP_SERVERS.defaultServers,
mcpEnabled: DEFAULT_MCP_SERVERS.mcpEnabled
mcpEnabled: DEFAULT_MCP_SERVERS.mcpEnabled,
autoDetectNpmRegistry: true,
npmRegistryCache: undefined,
customNpmRegistry: undefined
}
})
}
Expand Down Expand Up @@ -470,6 +483,86 @@ export class McpConfHelper {
return true
}

// 获取NPM Registry缓存
getNpmRegistryCache(): INpmRegistryCache | undefined {
return this.mcpStore.get('npmRegistryCache')
}

// 设置NPM Registry缓存
setNpmRegistryCache(cache: INpmRegistryCache): void {
this.mcpStore.set('npmRegistryCache', cache)
}

// 检查缓存是否有效(24小时内)
isNpmRegistryCacheValid(): boolean {
const cache = this.getNpmRegistryCache()
if (!cache) return false
const now = Date.now()
const cacheAge = now - cache.lastChecked
const CACHE_DURATION = 24 * 60 * 60 * 1000 // 24小时
return cacheAge < CACHE_DURATION
}

// 获取有效的NPM Registry(按优先级:自定义源 > 缓存 > 默认)
getEffectiveNpmRegistry(): string | null {
const customRegistry = this.getCustomNpmRegistry()
if (customRegistry) {
console.log(`[NPM Registry] Using custom registry: ${customRegistry}`)
return customRegistry
}

if (this.getAutoDetectNpmRegistry() && this.isNpmRegistryCacheValid()) {
const cache = this.getNpmRegistryCache()
if (cache?.registry) {
console.log(`[NPM Registry] Using cached registry: ${cache.registry}`)
return cache.registry
}
}

console.log('[NPM Registry] No effective registry found, will use default or detect')
return null
}

// 获取自定义NPM Registry
getCustomNpmRegistry(): string | undefined {
return this.mcpStore.get('customNpmRegistry')
}

// 标准化NPM Registry URL
private normalizeNpmRegistryUrl(registry: string): string {
let normalized = registry.trim()
if (!normalized.endsWith('/')) {
normalized += '/'
}
return normalized
}
Comment on lines +532 to +538
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add URL validation in normalization method.

The URL normalization should validate that the input is a valid HTTP/HTTPS URL before normalizing.

 private normalizeNpmRegistryUrl(registry: string): string {
   let normalized = registry.trim()
+  // Validate URL format
+  if (!normalized.startsWith('http://') && !normalized.startsWith('https://')) {
+    throw new Error('Registry URL must start with http:// or https://')
+  }
+  try {
+    new URL(normalized) // Validate URL structure
+  } catch {
+    throw new Error('Invalid registry URL format')
+  }
   if (!normalized.endsWith('/')) {
     normalized += '/'
   }
   return normalized
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private normalizeNpmRegistryUrl(registry: string): string {
let normalized = registry.trim()
if (!normalized.endsWith('/')) {
normalized += '/'
}
return normalized
}
private normalizeNpmRegistryUrl(registry: string): string {
let normalized = registry.trim()
// Validate URL format
if (!normalized.startsWith('http://') && !normalized.startsWith('https://')) {
throw new Error('Registry URL must start with http:// or https://')
}
try {
new URL(normalized) // Validate URL structure
} catch {
throw new Error('Invalid registry URL format')
}
if (!normalized.endsWith('/')) {
normalized += '/'
}
return normalized
}
🤖 Prompt for AI Agents
In src/main/presenter/configPresenter/mcpConfHelper.ts around lines 532 to 538,
the normalizeNpmRegistryUrl method currently normalizes the URL by trimming and
appending a trailing slash but does not validate if the input is a valid HTTP or
HTTPS URL. Update this method to first check if the input string is a valid URL
starting with http:// or https://, and if not, handle the invalid input
appropriately (e.g., throw an error or return a default value). Then proceed
with the existing normalization logic.


// 设置自定义NPM Registry
setCustomNpmRegistry(registry: string | undefined): void {
if (registry === undefined) {
this.mcpStore.delete('customNpmRegistry')
} else {
const normalizedRegistry = this.normalizeNpmRegistryUrl(registry)
this.mcpStore.set('customNpmRegistry', normalizedRegistry)
console.log(`[NPM Registry] Normalized custom registry: ${registry} -> ${normalizedRegistry}`)
}
}

// 获取自动检测NPM Registry设置
getAutoDetectNpmRegistry(): boolean {
return this.mcpStore.get('autoDetectNpmRegistry') ?? true
}

// 设置自动检测NPM Registry
setAutoDetectNpmRegistry(enabled: boolean): void {
this.mcpStore.set('autoDetectNpmRegistry', enabled)
}

// 清除NPM Registry缓存
clearNpmRegistryCache(): void {
this.mcpStore.delete('npmRegistryCache')
}

// 移除MCP服务器
async removeMcpServer(name: string): Promise<void> {
const mcpServers = await this.getMcpServers()
Expand Down
78 changes: 71 additions & 7 deletions src/main/presenter/mcpPresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,13 @@ export class McpPresenter implements IMCPPresenter {
this.configPresenter.getMcpDefaultServers()
])

// 先测试npm registry速度
console.log('[MCP] Testing npm registry speed...')
// 初始化npm registry(优先使用缓存)
console.log('[MCP] Initializing npm registry...')
try {
await this.serverManager.testNpmRegistrySpeed()
console.log(
`[MCP] npm registry speed test completed, selected best registry: ${this.serverManager.getNpmRegistry()}`
)
await this.serverManager.testNpmRegistrySpeed(true)
console.log(`[MCP] npm registry initialized: ${this.serverManager.getNpmRegistry()}`)
} catch (error) {
console.error('[MCP] npm registry speed test failed:', error)
console.error('[MCP] npm registry initialization failed:', error)
}

// 检查并启动 deepchat-inmemory/custom-prompts-server
Expand Down Expand Up @@ -173,6 +171,8 @@ export class McpPresenter implements IMCPPresenter {

// 检查并管理自定义提示词服务器
await this.checkAndManageCustomPromptsServer()

this.scheduleBackgroundRegistryUpdate()
} catch (error) {
console.error('[MCP] Initialization failed:', error)
// 即使初始化失败也标记为已完成,避免系统卡在未初始化状态
Expand All @@ -181,6 +181,16 @@ export class McpPresenter implements IMCPPresenter {
}
}

private scheduleBackgroundRegistryUpdate(): void {
setTimeout(async () => {
try {
await this.serverManager.updateNpmRegistryInBackground()
} catch (error) {
console.error('[MCP] Background registry update failed:', error)
}
}, 5000)
}

// 添加获取初始化状态的方法
isReady(): boolean {
return this.isInitialized
Expand Down Expand Up @@ -1127,4 +1137,58 @@ export class McpPresenter implements IMCPPresenter {
throw error
}
}

async getNpmRegistryStatus(): Promise<{
currentRegistry: string | null
isFromCache: boolean
lastChecked?: number
autoDetectEnabled: boolean
customRegistry?: string
}> {
const cache = this.configPresenter.getNpmRegistryCache?.()
const autoDetectEnabled = this.configPresenter.getAutoDetectNpmRegistry?.() ?? true
const customRegistry = this.configPresenter.getCustomNpmRegistry?.()
const currentRegistry = this.serverManager.getNpmRegistry()

let isFromCache = false
if (customRegistry && currentRegistry === customRegistry) {
isFromCache = false
} else if (cache && this.configPresenter.isNpmRegistryCacheValid?.()) {
isFromCache = currentRegistry === cache.registry
}

return {
currentRegistry,
isFromCache,
lastChecked: cache?.lastChecked,
autoDetectEnabled,
customRegistry
}
}

async refreshNpmRegistry(): Promise<string> {
return await this.serverManager.refreshNpmRegistry()
}

async setCustomNpmRegistry(registry: string | undefined): Promise<void> {
this.configPresenter.setCustomNpmRegistry?.(registry)
if (registry) {
console.log(`[MCP] Setting custom NPM registry: ${registry}`)
} else {
console.log('[MCP] Clearing custom NPM registry')
}
this.serverManager.loadRegistryFromCache()
}
Comment on lines +1173 to +1181
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add input validation and error handling.

The implementation is good but could benefit from validation and error handling.

 async setCustomNpmRegistry(registry: string | undefined): Promise<void> {
+  try {
+    // Validate registry URL format if provided
+    if (registry && registry.trim()) {
+      const trimmed = registry.trim()
+      if (!trimmed.startsWith('http://') && !trimmed.startsWith('https://')) {
+        throw new Error('Registry URL must start with http:// or https://')
+      }
+      registry = trimmed
+    }
+    
     this.configPresenter.setCustomNpmRegistry?.(registry)
     if (registry) {
       console.log(`[MCP] Setting custom NPM registry: ${registry}`)
     } else {
       console.log('[MCP] Clearing custom NPM registry')
     }
     this.serverManager.loadRegistryFromCache()
+  } catch (error) {
+    console.error('[MCP] Failed to set custom NPM registry:', error)
+    throw error
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async setCustomNpmRegistry(registry: string | undefined): Promise<void> {
this.configPresenter.setCustomNpmRegistry?.(registry)
if (registry) {
console.log(`[MCP] Setting custom NPM registry: ${registry}`)
} else {
console.log('[MCP] Clearing custom NPM registry')
}
this.serverManager.loadRegistryFromCache()
}
async setCustomNpmRegistry(registry: string | undefined): Promise<void> {
try {
// Validate registry URL format if provided
if (registry && registry.trim()) {
const trimmed = registry.trim()
if (!trimmed.startsWith('http://') && !trimmed.startsWith('https://')) {
throw new Error('Registry URL must start with http:// or https://')
}
registry = trimmed
}
this.configPresenter.setCustomNpmRegistry?.(registry)
if (registry) {
console.log(`[MCP] Setting custom NPM registry: ${registry}`)
} else {
console.log('[MCP] Clearing custom NPM registry')
}
this.serverManager.loadRegistryFromCache()
} catch (error) {
console.error('[MCP] Failed to set custom NPM registry:', error)
throw error
}
}
🤖 Prompt for AI Agents
In src/main/presenter/mcpPresenter/index.ts around lines 1173 to 1181, add input
validation to check if the registry string is a valid URL or matches expected
patterns before proceeding. Also, wrap the logic inside a try-catch block to
handle any potential errors from setCustomNpmRegistry or loadRegistryFromCache
calls, logging or handling errors appropriately to prevent unhandled exceptions.


async setAutoDetectNpmRegistry(enabled: boolean): Promise<void> {
this.configPresenter.setAutoDetectNpmRegistry?.(enabled)
if (enabled) {
this.serverManager.loadRegistryFromCache()
}
}
Comment on lines +1183 to +1188
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for consistency.

The logic is sound but should include error handling consistent with other methods.

 async setAutoDetectNpmRegistry(enabled: boolean): Promise<void> {
+  try {
     this.configPresenter.setAutoDetectNpmRegistry?.(enabled)
     if (enabled) {
       this.serverManager.loadRegistryFromCache()
     }
+  } catch (error) {
+    console.error('[MCP] Failed to set auto-detect NPM registry:', error)
+    throw error
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async setAutoDetectNpmRegistry(enabled: boolean): Promise<void> {
this.configPresenter.setAutoDetectNpmRegistry?.(enabled)
if (enabled) {
this.serverManager.loadRegistryFromCache()
}
}
async setAutoDetectNpmRegistry(enabled: boolean): Promise<void> {
try {
this.configPresenter.setAutoDetectNpmRegistry?.(enabled)
if (enabled) {
this.serverManager.loadRegistryFromCache()
}
} catch (error) {
console.error('[MCP] Failed to set auto-detect NPM registry:', error)
throw error
}
}
🤖 Prompt for AI Agents
In src/main/presenter/mcpPresenter/index.ts around lines 1183 to 1188, the
method setAutoDetectNpmRegistry lacks error handling unlike other methods. Wrap
the existing logic in a try-catch block, call
this.configPresenter.setAutoDetectNpmRegistry with the enabled parameter inside
the try, and also call this.serverManager.loadRegistryFromCache if enabled. In
the catch block, log or handle the error appropriately to maintain consistency
with error handling in similar methods.


async clearNpmRegistryCache(): Promise<void> {
this.configPresenter.clearNpmRegistryCache?.()
console.log('[MCP] NPM Registry cache cleared')
}
Comment on lines +1190 to +1193
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for robustness.

Simple implementation but should include error handling.

 async clearNpmRegistryCache(): Promise<void> {
+  try {
     this.configPresenter.clearNpmRegistryCache?.()
     console.log('[MCP] NPM Registry cache cleared')
+  } catch (error) {
+    console.error('[MCP] Failed to clear NPM registry cache:', error)
+    throw error
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async clearNpmRegistryCache(): Promise<void> {
this.configPresenter.clearNpmRegistryCache?.()
console.log('[MCP] NPM Registry cache cleared')
}
async clearNpmRegistryCache(): Promise<void> {
try {
this.configPresenter.clearNpmRegistryCache?.()
console.log('[MCP] NPM Registry cache cleared')
} catch (error) {
console.error('[MCP] Failed to clear NPM registry cache:', error)
throw error
}
}
🤖 Prompt for AI Agents
In src/main/presenter/mcpPresenter/index.ts around lines 1190 to 1193, the
clearNpmRegistryCache method calls clearNpmRegistryCache on configPresenter
without error handling. Wrap the call in a try-catch block to catch any
potential errors, log or handle the error appropriately, and ensure the method
remains robust and does not fail silently.

}
Loading