@@ -72,6 +72,12 @@ export class AiMemoryMcpServer {
7272 // === Pending Memory Tracker (v2.2.6) ===
7373 private pendingMemoryTracker : PendingMemoryTracker ;
7474
75+ // Tools that should NOT trigger memory reminder (to avoid infinite loops)
76+ private readonly NO_REMINDER_TOOLS = new Set ( [
77+ "record_context" ,
78+ "verify_work_recorded" ,
79+ ] ) ;
80+
7581 // 真实日期记录函数
7682 private getCurrentRealDate ( ) : string {
7783 return new Date ( ) . toISOString ( ) ;
@@ -195,6 +201,62 @@ export class AiMemoryMcpServer {
195201 this . setupHandlers ( ) ;
196202 }
197203
204+ /**
205+ * Generate memory reminder if there are unrecorded files
206+ * Returns null if no reminder needed
207+ */
208+ private generateMemoryReminder ( ) : {
209+ pending_count : number ;
210+ pending_files : string [ ] ;
211+ action : string ;
212+ } | null {
213+ const unrecordedFiles = this . pendingMemoryTracker . getUnrecorded ( ) ;
214+
215+ if ( unrecordedFiles . length === 0 ) {
216+ return null ;
217+ }
218+
219+ return {
220+ pending_count : unrecordedFiles . length ,
221+ pending_files : unrecordedFiles . slice ( 0 , 5 ) , // Show max 5 files
222+ action : "Call record_context NOW to save your work before responding to user." ,
223+ } ;
224+ }
225+
226+ /**
227+ * Wrap tool result with memory reminder if needed
228+ */
229+ private wrapWithReminder (
230+ toolName : string ,
231+ result : { content : Array < { type : string ; text : string } > }
232+ ) : { content : Array < { type : string ; text : string } > } {
233+ // Skip reminder for certain tools
234+ if ( this . NO_REMINDER_TOOLS . has ( toolName ) ) {
235+ return result ;
236+ }
237+
238+ const reminder = this . generateMemoryReminder ( ) ;
239+ if ( ! reminder ) {
240+ return result ;
241+ }
242+
243+ // Add reminder to the result
244+ const reminderText = `\n\n---\n[MEMORY REMINDER] ${ reminder . pending_count } file(s) edited but not recorded: ${ reminder . pending_files . join ( ", " ) } ${ reminder . pending_count > 5 ? ` (+${ reminder . pending_count - 5 } more)` : "" } . ${ reminder . action } ` ;
245+
246+ // Append reminder to the last text content
247+ const newContent = [ ...result . content ] ;
248+ if ( newContent . length > 0 && newContent [ newContent . length - 1 ] . type === "text" ) {
249+ newContent [ newContent . length - 1 ] = {
250+ ...newContent [ newContent . length - 1 ] ,
251+ text : newContent [ newContent . length - 1 ] . text + reminderText ,
252+ } ;
253+ } else {
254+ newContent . push ( { type : "text" , text : reminderText } ) ;
255+ }
256+
257+ return { content : newContent } ;
258+ }
259+
198260 private initializeDatabase ( ) : void {
199261 const dbDir = dirname ( this . config . database_path ! ) ;
200262 if ( ! existsSync ( dbDir ) ) {
@@ -697,100 +759,104 @@ export class AiMemoryMcpServer {
697759 // Ensure args is at least an empty object to prevent destructuring errors
698760 const safeArgs = args || { } ;
699761
762+ // Execute tool and wrap result with memory reminder
763+ const executeAndWrap = async (
764+ handler : ( ) => Promise < { content : Array < { type : string ; text : string } > } >
765+ ) => {
766+ const result = await handler ( ) ;
767+ return this . wrapWithReminder ( name , result ) ;
768+ } ;
769+
700770 switch ( name ) {
701771 case "create_session" :
702- return await this . handleCreateSession (
703- safeArgs as unknown as SessionCreateParams
772+ return executeAndWrap ( ( ) =>
773+ this . handleCreateSession ( safeArgs as unknown as SessionCreateParams )
704774 ) ;
705775 case "record_context" :
706- return await this . handleRecordContext (
776+ return this . handleRecordContext (
707777 safeArgs as unknown as RecordContextParams
708778 ) ;
709779 case "verify_work_recorded" :
710- return await this . handleVerifyWorkRecorded (
780+ return this . handleVerifyWorkRecorded (
711781 safeArgs as { work_summary : string ; project_path ?: string }
712782 ) ;
713783 case "end_session" :
714- return await this . handleEndSession (
715- safeArgs as { session_id : string }
784+ return executeAndWrap ( ( ) =>
785+ this . handleEndSession ( safeArgs as { session_id : string } )
716786 ) ;
717787 case "get_current_session" :
718- return await this . handleGetCurrentSession (
719- safeArgs as { project_path : string }
788+ return executeAndWrap ( ( ) =>
789+ this . handleGetCurrentSession ( safeArgs as { project_path : string } )
720790 ) ;
721791 case "list_projects" :
722- return await this . handleListProjects (
723- safeArgs as { include_stats ?: boolean ; limit ?: number }
792+ return executeAndWrap ( ( ) =>
793+ this . handleListProjects ( safeArgs as { include_stats ?: boolean ; limit ?: number } )
724794 ) ;
725- // extract_file_context removed
726795 case "get_context" :
727- return await this . handleGetContext (
728- safeArgs as { context_ids : string | string [ ] ; relation_type ?: string }
796+ return executeAndWrap ( ( ) =>
797+ this . handleGetContext (
798+ safeArgs as { context_ids : string | string [ ] ; relation_type ?: string }
799+ )
729800 ) ;
730801 case "semantic_search" :
731- return await this . handleSemanticSearch (
732- safeArgs as {
802+ return executeAndWrap ( ( ) =>
803+ this . handleSemanticSearch ( safeArgs as {
733804 query : string ;
734805 project_path ?: string ;
735806 session_id ?: string ;
736807 limit ?: number ;
737808 similarity_threshold ?: number ;
738809 hybrid_weight ?: number ;
739- }
810+ } )
740811 ) ;
741- // generate_embeddings removed
742812 case "list_contexts" :
743- return await this . handleListContexts (
744- safeArgs as {
813+ return executeAndWrap ( ( ) =>
814+ this . handleListContexts ( safeArgs as {
745815 session_id ?: string ;
746816 project_path ?: string ;
747817 limit ?: number ;
748- }
818+ } )
749819 ) ;
750820 case "delete_context" :
751- return await this . handleDeleteContext (
752- safeArgs as { context_id : string }
821+ return executeAndWrap ( ( ) =>
822+ this . handleDeleteContext ( safeArgs as { context_id : string } )
753823 ) ;
754824 case "update_context" :
755- return await this . handleUpdateContext (
756- safeArgs as {
825+ return executeAndWrap ( ( ) =>
826+ this . handleUpdateContext ( safeArgs as {
757827 context_id : string ;
758828 content ?: string ;
759829 tags ?: string [ ] ;
760830 quality_score ?: number ;
761831 metadata ?: object ;
762- }
832+ } )
763833 ) ;
764834 case "delete_session" :
765- return await this . handleDeleteSession (
766- safeArgs as { session_id ?: string ; project_id ?: string }
835+ return executeAndWrap ( ( ) =>
836+ this . handleDeleteSession ( safeArgs as { session_id ?: string ; project_id ?: string } )
767837 ) ;
768838 case "project_analysis_engineer" :
769- return await this . handleProjectAnalysisEngineerTool (
770- safeArgs as {
839+ return executeAndWrap ( ( ) =>
840+ this . handleProjectAnalysisEngineerTool ( safeArgs as {
771841 project_path : string ;
772842 analysis_focus ?: string ;
773843 doc_style ?: string ;
774844 auto_save ?: boolean ;
775845 language ?: string ;
776- }
846+ } )
777847 ) ;
778- // optimize_project_memory removed
779- // update_quality_scores removed
780848 case "export_memory_graph" :
781- return await this . handleExportMemoryGraph (
782- safeArgs as {
849+ return executeAndWrap ( ( ) =>
850+ this . handleExportMemoryGraph ( safeArgs as {
783851 project_id : string ;
784852 max_nodes ?: number ;
785853 focus_type ?: string ;
786854 output_path ?: string ;
787- }
855+ } )
788856 ) ;
789857 case "get_memory_status" :
790- return await this . handleGetMemoryStatus (
791- safeArgs as {
792- project_path ?: string ;
793- }
858+ return executeAndWrap ( ( ) =>
859+ this . handleGetMemoryStatus ( safeArgs as { project_path ?: string } )
794860 ) ;
795861 default :
796862 throw new McpError ( ErrorCode . MethodNotFound , `Unknown tool: ${ name } ` ) ;
0 commit comments