Merge pull request #94 from y574444354/feat/server-refactor-90331-2

feat(favorite): optimize list caching and expand MCP type support
This commit is contained in:
linkai0924 2026-05-14 19:09:36 +08:00 committed by GitHub
commit 1c22fe8d2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -8,8 +8,14 @@ import { clearSkillCaches } from '../../skills/loadSkillsDir.js'
import { parseFrontmatter } from '../../utils/frontmatterParser.js' import { parseFrontmatter } from '../../utils/frontmatterParser.js'
import type { McpServerConfig } from '../../services/mcp/types.js' import type { McpServerConfig } from '../../services/mcp/types.js'
const FAVORITE_PAGE_SIZE = 100 const FAVORITE_PAGE_SIZE = 20
const FAVORITE_MAX_PAGES = 20 const FAVORITE_MAX_PAGES = 20
const FAVORITE_LIST_CACHE_TTL_MS = 30000
let _listFavoriteItemsCache: {
promise: Promise<FavoriteItemWithStatus[]>
expiry: number
} | null = null
export type FavoriteItemType = 'skill' | 'agent' | 'command' | 'mcp' export type FavoriteItemType = 'skill' | 'agent' | 'command' | 'mcp'
@ -395,8 +401,19 @@ async function ensureInstalled(slugOrId: string): Promise<FavoriteStateRecord> {
} }
} }
const SUPPORTED_MCP_TYPES = [
'stdio',
'sse',
'http',
'ws',
'sdk',
'claudeai-proxy',
'sse-ide',
'ws-ide',
]
function convertMcpConfig(config: Record<string, unknown>): Record<string, unknown> | undefined { function convertMcpConfig(config: Record<string, unknown>): Record<string, unknown> | undefined {
if (typeof config.type === 'string' && (config.type === 'local' || config.type === 'remote')) { if (typeof config.type === 'string' && SUPPORTED_MCP_TYPES.includes(config.type)) {
return config return config
} }
if (config.mcpServers && typeof config.mcpServers === 'object' && !Array.isArray(config.mcpServers)) { if (config.mcpServers && typeof config.mcpServers === 'object' && !Array.isArray(config.mcpServers)) {
@ -430,11 +447,14 @@ function convertSingleMcpServer(server: Record<string, unknown>): Record<string,
} }
if (cmdArray.length === 0) return undefined if (cmdArray.length === 0) return undefined
const result: Record<string, unknown> = { const result: Record<string, unknown> = {
type: 'local', type: 'stdio',
command: cmdArray, command: cmdArray[0],
}
if (cmdArray.length > 1) {
result.args = cmdArray.slice(1)
} }
if (typeof server.environment === 'object' && server.environment !== null) { if (typeof server.environment === 'object' && server.environment !== null) {
result.environment = server.environment result.env = server.environment
} }
return result return result
} }
@ -572,18 +592,29 @@ async function readItemForConfig(installed: FavoriteStateRecord): Promise<Favori
} }
export async function listFavoriteItems(type?: FavoriteItemType): Promise<FavoriteItemWithStatus[]> { export async function listFavoriteItems(type?: FavoriteItemType): Promise<FavoriteItemWithStatus[]> {
const cacheKey = type ?? 'all'
const now = Date.now()
if (
_listFavoriteItemsCache &&
_listFavoriteItemsCache.expiry > now &&
cacheKey === 'all'
) {
return _listFavoriteItemsCache.promise
}
const storeTypes = type ? [LOCAL_TO_STORE_TYPE[type]] : Object.values(LOCAL_TO_STORE_TYPE) const storeTypes = type ? [LOCAL_TO_STORE_TYPE[type]] : Object.values(LOCAL_TO_STORE_TYPE)
const errors: Error[] = [] const errors: Error[] = []
const candidatePages = await Promise.all( const candidates: Array<{ id: string } & Record<string, unknown>> = []
[...new Set(storeTypes)].map(async (st) =>
listRemoteCandidates(st, { favorited: 'true' }).catch((error) => { for (const st of [...new Set(storeTypes)]) {
console.warn('failed to fetch remote favorite candidates', { type: st, error }) try {
if (error instanceof Error) errors.push(error) const page = await listRemoteCandidates(st, { favorited: 'true' })
return [] candidates.push(...page)
}), } catch (error) {
), console.warn('failed to fetch remote favorite candidates', { type: st, error })
) if (error instanceof Error) errors.push(error)
const candidates = candidatePages.flat() }
}
// If all remote fetches failed, propagate the first error so the UI can show it // If all remote fetches failed, propagate the first error so the UI can show it
if (candidates.length === 0 && errors.length > 0) { if (candidates.length === 0 && errors.length > 0) {
@ -604,6 +635,7 @@ export async function listFavoriteItems(type?: FavoriteItemType): Promise<Favori
for (const candidate of candidates) { for (const candidate of candidates) {
const item = parseFavoriteListItem(candidate) const item = parseFavoriteListItem(candidate)
if (!item) continue if (!item) continue
if (!item.favorited) continue
if (seen.has(item.slug)) continue if (seen.has(item.slug)) continue
seen.add(item.slug) seen.add(item.slug)
const local = state.items[item.slug] const local = state.items[item.slug]
@ -632,7 +664,16 @@ export async function listFavoriteItems(type?: FavoriteItemType): Promise<Favori
} }
} }
return result.sort((a, b) => a.name.localeCompare(b.name)) const sorted = result.sort((a, b) => a.name.localeCompare(b.name))
if (!type) {
_listFavoriteItemsCache = {
promise: Promise.resolve(sorted),
expiry: Date.now() + FAVORITE_LIST_CACHE_TTL_MS,
}
}
return sorted
} }
export async function viewFavoriteItem(slugOrId: string): Promise<FavoriteItemWithStatus> { export async function viewFavoriteItem(slugOrId: string): Promise<FavoriteItemWithStatus> {