Skip to main content
Glama
PLAN-002-persona-management.md15.7 kB
# PLAN-002: 人格管理系统开发 ## 📋 任务概述 实现聚义厅MCP客户端的人格管理系统,包括人格仓库、数据源管理、缓存机制和基础工具实现。 ## 🎯 目标成果 - [ ] RemotePersonaRepository 人格仓库实现 - [ ] 多源数据获取策略完成 - [ ] 智能缓存机制运行正常 - [ ] summon_persona 工具完整实现 - [ ] 本地人格优先级覆盖功能 - [ ] 错误处理和降级策略完善 ## 📅 时间规划 **预计耗时**: 4-5天 **优先级**: 高 **依赖**: PLAN-001 项目初始化完成 ## 🔧 第一步:创建人格数据源配置 ### 创建 src/persona-sources.ts ```typescript import { Persona } from './types.js'; // 人格数据源URLs export const PERSONA_SOURCES = [ 'https://gitee.com/juyitingmcp/juyitingmcp/raw/main/personas.json', 'https://raw.githubusercontent.com/juyitingmcp/juyitingmcp/main/personas.json', 'https://cdn.jsdelivr.net/gh/juyitingmcp/juyitingmcp@main/personas.json' ]; // 默认人格数据(用作降级方案) export const defaultPersonas: Persona[] = [ { id: 'grumpy_bro', name: '暴躁老哥', rule: '你是暴躁老哥,要每次都用审视的目光,仔细看我的输入的潜在的问题,你要犀利的提醒在出我的问题。并给出明显在我思考框架之外的建议。你要觉得我说的太离谱了,你就骂回来,帮助我瞬间清醒。', goal: '用审视的目光发现问题,提供框架外的思维角度', version: '1.0', description: '专门挑战传统思维,发现潜在风险和问题', category: '批判思维', tags: ['批判', '风险', '挑战'], source: 'default' }, { id: 'reflection_sis', name: '自省姐', rule: '你是自省姐,总是不断挑战自己输出有没有思考的透漏,尝试突破思维边界,找到第一性原理,然后根据挑战再补充回答,达到完整。你要挑战你自己的输出是不是足够有深度和逻辑性。', goal: '深度思考,查漏补缺,追求完整性', version: '1.0', description: '不断自我挑战,追求思考的深度和完整性', category: '深度思考', tags: ['自省', '深度', '完整'], source: 'default' }, { id: 'fan_girl', name: '粉丝妹', rule: '你是粉丝妹,总是能发现别人的亮点和优势,用积极的态度去分析问题,找到事物的价值和潜力。你会用鼓励的语气,但同时保持客观和专业。', goal: '发现亮点,放大优势,积极分析', version: '1.0', description: '善于发现亮点和优势,提供积极的分析视角', category: '积极思维', tags: ['积极', '优势', '鼓励'], source: 'default' } ]; ``` ### 验证步骤 - [ ] 文件创建成功 - [ ] 默认人格数据格式正确 - [ ] TypeScript编译无错误 ## 🔧 第二步:实现缓存管理工具 ### 创建 src/utils/cache.ts ```typescript import { CacheEntry } from '../types.js'; export class CacheManager { private cache = new Map<string, CacheEntry>(); private readonly maxSize: number; private readonly defaultTTL: number; constructor(maxSize = 1000, defaultTTL = 5 * 60 * 1000) { this.maxSize = maxSize; this.defaultTTL = defaultTTL; } set(key: string, value: any, ttl?: number): void { // LRU淘汰策略 if (this.cache.size >= this.maxSize) { const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } this.cache.set(key, { value, expiry: Date.now() + (ttl || this.defaultTTL) }); } get(key: string): any | null { const entry = this.cache.get(key); if (!entry) return null; // 检查是否过期 if (Date.now() > entry.expiry) { this.cache.delete(key); return null; } // 更新访问顺序(LRU) this.cache.delete(key); this.cache.set(key, entry); return entry.value; } has(key: string): boolean { return this.get(key) !== null; } clear(): void { this.cache.clear(); } size(): number { return this.cache.size; } // 清理过期条目 cleanup(): void { const now = Date.now(); for (const [key, entry] of this.cache.entries()) { if (now > entry.expiry) { this.cache.delete(key); } } } } ``` ### 验证步骤 - [ ] 缓存管理器实现完成 - [ ] LRU策略正常工作 - [ ] TTL过期机制正确 ## 🔧 第三步:实现网络请求工具 ### 创建 src/utils/network.ts ```typescript export class NetworkUtils { private static readonly DEFAULT_TIMEOUT = 15000; private static readonly MAX_RETRIES = 3; static async fetchWithTimeout( url: string, options: RequestInit = {}, timeout = this.DEFAULT_TIMEOUT ): Promise<Response> { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { ...options, signal: controller.signal, headers: { 'Accept': 'application/json', 'User-Agent': 'JuYiTing-MCP-Client/1.0', ...options.headers } }); clearTimeout(timeoutId); return response; } catch (error) { clearTimeout(timeoutId); throw error; } } static async fetchWithRetry( url: string, options: RequestInit = {}, maxRetries = this.MAX_RETRIES ): Promise<Response> { let lastError: Error; for (let i = 0; i <= maxRetries; i++) { try { const response = await this.fetchWithTimeout(url, options); if (response.ok) { return response; } throw new Error(`HTTP ${response.status}: ${response.statusText}`); } catch (error) { lastError = error as Error; if (i === maxRetries) break; // 指数退避 const delay = Math.min(1000 * Math.pow(2, i), 10000); await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError!; } static async fetchJson<T>(url: string, options?: RequestInit): Promise<T> { const response = await this.fetchWithRetry(url, options); return await response.json(); } } ``` ### 验证步骤 - [ ] 网络工具实现完成 - [ ] 超时控制正常工作 - [ ] 重试机制正确实现 ## 🔧 第四步:实现人格仓库核心逻辑 ### 创建 src/persona-repository.ts(第一部分) ```typescript import { Persona, PersonaRepository, PersonaConfig } from './types.js'; import { PERSONA_SOURCES, defaultPersonas } from './persona-sources.js'; import { CacheManager } from './utils/cache.js'; import { NetworkUtils } from './utils/network.js'; export class RemotePersonaRepository implements PersonaRepository { private cache: CacheManager; private localPersonas: Persona[] = []; private lastFetchTime: number = 0; private readonly cacheDuration: number = 5 * 60 * 1000; // 5分钟 constructor(localPersonas: Persona[] = []) { this.cache = new CacheManager(); this.localPersonas = this.validateLocalPersonas(localPersonas); this.warmUpCache(); } // 验证本地人格格式 private validateLocalPersonas(personas: Persona[]): Persona[] { return personas.filter(persona => { const required = ['id', 'name', 'rule', 'goal', 'version']; const isValid = required.every(field => persona[field as keyof Persona]); if (!isValid) { console.warn(`无效的本地人格被跳过:`, persona); } return isValid; }).map(p => ({ ...p, source: 'local' as const })); } // 预热缓存 private warmUpCache(): void { // 异步预热,不阻塞构造函数 setTimeout(() => { this.getAllPersonas().catch(error => { console.warn('缓存预热失败:', error.message); }); }, 100); } } ``` ### 验证步骤 - [ ] 基础类结构创建完成 - [ ] 本地人格验证逻辑正确 - [ ] 缓存预热机制实现 ## 🔧 第五步:完成人格仓库核心方法 ### 继续完善 src/persona-repository.ts(第二部分) ```typescript // 获取所有人格(主要方法) async getAllPersonas(): Promise<Persona[]> { const cacheKey = 'all_personas'; // 检查缓存 if (this.isCacheValid()) { const cached = this.cache.get(cacheKey); if (cached) { return this.mergePersonas(cached); } } // 尝试从远程源获取 for (const url of PERSONA_SOURCES) { try { console.error(`正在从 ${url} 获取人格数据...`); const freshPersonas = await this.fetchPersonasFromSource(url); if (freshPersonas && freshPersonas.length > 0) { this.updateCache(cacheKey, freshPersonas); console.error(`成功获取 ${freshPersonas.length} 个远程人格`); return this.mergePersonas(freshPersonas); } } catch (error) { console.warn(`从 ${url} 获取人格失败:`, error.message); continue; } } // 降级到默认人格 console.warn('所有远程源均失败,使用默认人格'); return this.fallbackToDefault(); } // 根据ID获取单个人格 async getPersonaById(id: string): Promise<Persona | null> { const allPersonas = await this.getAllPersonas(); return allPersonas.find(p => p.id === id) || null; } // 从配置更新人格库 async updateFromConfig(config: PersonaConfig): Promise<void> { // 将配置中的人格标记为远程来源 const configPersonas = config.personas.map(p => ({ ...p, source: 'remote' as const })); // 更新缓存 this.updateCache('config_personas', configPersonas); console.log(`从配置更新了 ${configPersonas.length} 个人格`); } ``` ### 验证步骤 - [ ] getAllPersonas 方法实现完成 - [ ] getPersonaById 方法正常工作 - [ ] updateFromConfig 方法实现 ## 🔧 第六步:完成人格仓库辅助方法 ### 继续完善 src/persona-repository.ts(第三部分 - 辅助方法) ```typescript // 从单个源获取人格数据 private async fetchPersonasFromSource(url: string): Promise<Persona[] | null> { try { const data = await NetworkUtils.fetchJson<Persona[]>(url); return Array.isArray(data) ? data.map(p => ({ ...p, source: 'remote' as const })) : null; } catch (error) { if (error.name === 'AbortError') { console.warn(`获取超时: ${url}`); } else { console.warn(`获取失败 ${url}:`, error.message); } return null; } } // 检查缓存是否有效 private isCacheValid(): boolean { const now = Date.now(); return (now - this.lastFetchTime < this.cacheDuration); } // 更新缓存 private updateCache(key: string, personas: Persona[]): void { this.cache.set(key, personas); this.lastFetchTime = Date.now(); } // 合并人格(本地优先) private mergePersonas(basePersonas: Persona[]): Persona[] { const mergedMap = new Map<string, Persona>(); // 1. 添加基础人格(远程或默认) basePersonas.forEach(persona => { mergedMap.set(persona.id, persona); }); // 2. 本地人格覆盖同ID人格(优先级最高) this.localPersonas.forEach(persona => { if (mergedMap.has(persona.id)) { console.log(`本地人格覆盖远程人格: ${persona.name} (${persona.id})`); } mergedMap.set(persona.id, persona); }); return Array.from(mergedMap.values()); } // 降级到默认人格 private fallbackToDefault(): Persona[] { const defaultWithSource = defaultPersonas.map(p => ({ ...p, source: 'default' as const })); this.updateCache('default_personas', defaultWithSource); return this.mergePersonas(defaultWithSource); } } ``` ### 验证步骤 - [ ] 辅助方法实现完成 - [ ] 人格合并逻辑正确 - [ ] 降级策略正常工作 ## 🔧 第七步:完善server.ts中的summon_persona工具 ### 更新 src/server.ts 中的人格召唤功能 ```typescript import { RemotePersonaRepository } from './persona-repository.js'; import { z } from 'zod'; export class PersonaSummonerServer { private server: Server; private repository: RemotePersonaRepository; constructor(localPersonas: Persona[] = []) { this.repository = new RemotePersonaRepository(localPersonas); this.server = new Server({ name: 'juyiting-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, }); this.setupHandlers(); } private async handleSummonPersona(args: any): Promise<MCPResponse> { // 参数验证 const schema = z.object({ persona_name: z.string().min(1, '人格名称不能为空') }); try { const { persona_name } = schema.parse(args); // 获取所有人格 const personas = await this.repository.getAllPersonas(); // 查找指定人格(支持名称和ID) const persona = personas.find(p => p.name === persona_name || p.id === persona_name ); if (!persona) { const availablePersonas = personas .map(p => `- ${p.name} (${p.id})`) .join('\n'); return { content: [{ type: 'text', text: `❌ 找不到人格:${persona_name}\n\n📋 **可用人格列表**:\n${availablePersonas}` }] }; } // 格式化人格详情 const personaDetails = [ `🎭 **${persona.name}** (${persona.id}) 已召唤!`, `**🎯 目标**: ${persona.goal}`, persona.description ? `**📝 描述**: ${persona.description}` : '', persona.category ? `**🏷️ 分类**: ${persona.category}` : '', persona.tags?.length ? `**🔖 标签**: ${persona.tags.join(', ')}` : '', `**📍 来源**: ${this.getSourceLabel(persona.source)}`, `\n**📜 人格规则**:\n${persona.rule}` ].filter(Boolean).join('\n'); return { content: [{ type: 'text', text: personaDetails }] }; } catch (error) { if (error instanceof z.ZodError) { return { content: [{ type: 'text', text: `❌ 参数错误: ${error.errors[0].message}` }] }; } return { content: [{ type: 'text', text: `❌ 召唤失败: ${error.message}` }] }; } } private getSourceLabel(source?: string): string { switch (source) { case 'local': return '本地配置'; case 'remote': return '远程仓库'; case 'default': return '内置默认'; default: return '未知'; } } } ``` ### 验证步骤 - [ ] summon_persona 工具完整实现 - [ ] 参数验证正常工作 - [ ] 错误处理机制完善 - [ ] 人格详情格式化正确 ## 📋 完整测试清单 ### 功能测试 - [ ] 远程人格数据获取正常 - [ ] 本地人格优先级覆盖正确 - [ ] 缓存机制正常工作 - [ ] 降级策略有效 - [ ] summon_persona 工具响应正确 ### 集成测试 - [ ] 在Cursor中成功召唤人格 - [ ] 网络异常时降级正常 - [ ] 多次调用性能良好 ## 🚨 常见问题排查 ### 问题1: 远程人格获取失败 **检查**: 网络连接、URL可访问性、超时设置 ### 问题2: 本地人格不生效 **检查**: 文件格式、ID冲突、验证逻辑 ### 问题3: 缓存未命中 **检查**: TTL设置、缓存键名、过期清理 ## 📋 下一步计划 完成人格管理系统后,继续执行PLAN-003: 配置同步器开发

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/juyitingmcp/juyitingmcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server