Skip to main content
Glama
leesgit

claude-session-continuity-mcp

memory_search

Search stored memories from previous sessions using keywords or semantic similarity. Filter by type, project, tags, and importance to quickly retrieve relevant context.

Instructions

Search stored memories using FTS5 full-text search or semantic/embedding similarity. Default mode returns compact index entries (id, type, truncated content) to save tokens — set detail=true for full content. Supports filtering by type, project, tags, and minimum importance. Read-only. Use memory_get to fetch full content for specific IDs found in search results. Use memory_related to explore graph connections from a known memory.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesNatural language search query
typeNoFilter by memory type (default: "all")
projectNoFilter by project (optional)
tagsNoFilter by tags — matches if any tag is present (optional)
semanticNoUse embedding-based semantic search instead of keyword FTS5 (default: false)
minImportanceNoMinimum importance threshold 1-10 (default: 1)
limitNoMax results to return (default: 10)
detailNoReturn full content per memory (default: false — returns compact index only)

Implementation Reference

  • Main handler for memory_search tool. Validates input via MemorySearchSchema, then delegates to performSemanticSearch (if semantic=true) or performFTSSearch.
    export async function handleMemorySearch(args: unknown): Promise<CallToolResult> {
      return logger.withTool('memory_search', async () => {
        // 입력 검증
        const parsed = MemorySearchSchema.safeParse(args);
        if (!parsed.success) {
          return {
            content: [{ type: 'text' as const, text: `Validation error: ${parsed.error.message}` }],
            isError: true
          };
        }
    
        const { query, type, project, semantic, limit, minImportance, detail } = parsed.data;
    
        // 시맨틱 검색
        if (semantic) {
          return performSemanticSearch(query, type, project, limit, minImportance, detail);
        }
    
        // FTS 검색
        return performFTSSearch(query, type, project, limit, minImportance, detail);
      }, args as Record<string, unknown>);
    }
  • FTS (Full-Text Search) implementation. Uses SQLite FTS5 MATCH with decay-based re-ranking, returns results with progressive disclosure (summary or detail).
    async function performFTSSearch(
      query: string,
      type?: string,
      project?: string,
      limit: number = 10,
      minImportance: number = 1,
      detail: boolean = false
    ): Promise<CallToolResult> {
      const ftsQuery = query.split(/\s+/).filter(w => w.length > 1).join(' OR ');
    
      let sql = `
        SELECT m.id, m.content, m.memory_type, m.tags, m.project, m.importance, m.created_at, m.access_count
        FROM memories_fts fts
        JOIN memories m ON m.id = fts.rowid
        WHERE memories_fts MATCH ?
        AND m.importance >= ?
      `;
      const params: unknown[] = [ftsQuery || query, minImportance];
    
      if (type) {
        sql += ` AND m.memory_type = ?`;
        params.push(type);
      }
    
      if (project) {
        sql += ` AND m.project = ?`;
        params.push(project);
      }
    
      // Fetch more rows for decay re-ranking
      sql += ` ORDER BY m.importance DESC, m.accessed_at DESC LIMIT ?`;
      params.push(limit * 3);
    
      const stmt = db.prepare(sql);
      const rows = stmt.all(...params) as Array<{
        id: number;
        content: string;
        memory_type: string;
        tags: string | null;
        project: string | null;
        importance: number;
        created_at: string;
        access_count: number;
      }>;
    
      // Decay 적용 후 re-rank
      const scored = rows.map(row => ({
        ...row,
        decayedScore: calculateDecayedScore(row.importance, row.memory_type, row.created_at, row.access_count)
      })).sort((a, b) => b.decayedScore - a.decayedScore).slice(0, limit);
    
      // 접근 카운트 업데이트
      const updateStmt = db.prepare(`
        UPDATE memories SET accessed_at = CURRENT_TIMESTAMP, access_count = access_count + 1
        WHERE id = ?
      `);
      scored.forEach(row => updateStmt.run(row.id));
    
      const memories = scored.map(row => {
        if (detail) {
          return {
            id: row.id,
            content: row.content.length > 300 ? row.content.slice(0, 300) + '...' : row.content,
            type: row.memory_type,
            tags: parseTags(row.tags),
            project: row.project,
            importance: row.importance,
            createdAt: row.created_at
          };
        }
        // Progressive disclosure: index-only mode (~50 tokens per result)
        return {
          id: row.id,
          summary: row.content.slice(0, 80) + (row.content.length > 80 ? '...' : ''),
          type: row.memory_type,
          tags: parseTags(row.tags),
          importance: row.importance,
          createdAt: row.created_at
        };
      });
    
      return {
        content: [{
          type: 'text' as const,
          text: JSON.stringify({
            query,
            searchType: 'fts',
            found: memories.length,
            hint: detail ? undefined : 'Use memory_get({ ids: [...] }) to fetch full content',
            memories
          }, null, 2)
        }]
      };
    }
  • Semantic search implementation. Generates query embedding, compares via cosine similarity (>0.3 threshold), returns ranked results with progressive disclosure.
    async function performSemanticSearch(
      query: string,
      type?: string,
      project?: string,
      limit: number = 10,
      minImportance: number = 1,
      detail: boolean = false
    ): Promise<CallToolResult> {
      // 쿼리 임베딩 생성
      const queryEmbedding = await generateEmbedding(query);
      if (!queryEmbedding) {
        return {
          content: [{ type: 'text' as const, text: 'Failed to generate query embedding' }],
          isError: true
        };
      }
    
      // 필터 조건 구성
      let sql = `
        SELECT m.id, m.content, m.memory_type, m.tags, m.project, m.importance, m.created_at,
               e.embedding
        FROM memories m
        JOIN embeddings e ON m.id = e.memory_id
        WHERE m.importance >= ?
      `;
      const params: unknown[] = [minImportance];
    
      if (type) {
        sql += ` AND m.memory_type = ?`;
        params.push(type);
      }
    
      if (project) {
        sql += ` AND m.project = ?`;
        params.push(project);
      }
    
      const stmt = db.prepare(sql);
      const rows = stmt.all(...params) as Array<{
        id: number;
        content: string;
        memory_type: string;
        tags: string | null;
        project: string | null;
        importance: number;
        created_at: string;
        embedding: Buffer;
      }>;
    
      // 유사도 계산 및 정렬
      const scored = rows.map(row => ({
        ...row,
        similarity: cosineSimilarity(queryEmbedding, bufferToEmbedding(row.embedding))
      }))
        .filter(r => r.similarity > 0.3) // 최소 유사도
        .sort((a, b) => b.similarity - a.similarity)
        .slice(0, limit);
    
      // 접근 카운트 업데이트
      const updateStmt = db.prepare(`
        UPDATE memories SET accessed_at = CURRENT_TIMESTAMP, access_count = access_count + 1
        WHERE id = ?
      `);
      scored.forEach(row => updateStmt.run(row.id));
    
      const memories = scored.map(row => {
        if (detail) {
          return {
            id: row.id,
            content: row.content.length > 300 ? row.content.slice(0, 300) + '...' : row.content,
            type: row.memory_type,
            tags: parseTags(row.tags),
            project: row.project,
            importance: row.importance,
            similarity: Math.round(row.similarity * 100) / 100,
            createdAt: row.created_at
          };
        }
        return {
          id: row.id,
          summary: row.content.slice(0, 80) + (row.content.length > 80 ? '...' : ''),
          type: row.memory_type,
          importance: row.importance,
          similarity: Math.round(row.similarity * 100) / 100,
          createdAt: row.created_at
        };
      });
    
      return {
        content: [{
          type: 'text' as const,
          text: JSON.stringify({
            query,
            searchType: 'semantic',
            found: memories.length,
            hint: detail ? undefined : 'Use memory_get({ ids: [...] }) to fetch full content',
            memories
          }, null, 2)
        }]
      };
    }
  • Zod schema for memory_search input validation. Fields: query (required), type, project, semantic, limit (default 10), minImportance (default 1), detail.
    export const MemorySearchSchema = z.object({
      query: z.string().min(1).max(500).describe('검색 쿼리'),
      type: MemoryTypeSchema.optional(),
      project: ProjectNameSchema.optional(),
      semantic: z.boolean().default(false).describe('시맨틱 검색 사용'),
      limit: z.number().min(1).max(50).default(10).describe('최대 결과 수'),
      minImportance: z.number().min(1).max(10).default(1).describe('최소 중요도'),
      detail: z.boolean().default(false).describe('true면 전체 content 반환, false면 요약 인덱스만')
    }).describe('메모리 검색 (FTS 또는 시맨틱)');
  • Tool definition registration for memory_search with name, description, and inputSchema (JSON Schema) specifying query, type, project, semantic, limit, minImportance.
      {
        name: 'memory_search',
        description: `메모리 검색. FTS 또는 시맨틱 검색 지원.
    - query: 검색 쿼리 (필수)
    - type: 메모리 유형 필터
    - project: 프로젝트 필터
    - semantic: true면 시맨틱 검색 (의미 기반)
    - limit: 최대 결과 수 (기본 10)
    - minImportance: 최소 중요도 (기본 1)`,
        inputSchema: {
          type: 'object',
          properties: {
            query: { type: 'string', description: '검색 쿼리' },
            type: { type: 'string', description: '메모리 유형 필터' },
            project: { type: 'string', description: '프로젝트 필터' },
            semantic: { type: 'boolean', description: '시맨틱 검색 사용' },
            limit: { type: 'number', description: '최대 결과 수' },
            minImportance: { type: 'number', description: '최소 중요도' }
          },
          required: ['query']
        }
      },
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses that the tool is read-only, explains the default compact output vs. detailed mode, and describes the truncation of content. It does not mention response format fields but covers essential behaviors.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three sentences. It front-loads the main purpose, then details default behavior and filtering, ending with sibling guidance. It is efficient and clear, though the second sentence could be slightly more structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 8 parameters (all documented in schema) and no output schema, the description adequately covers the purpose and usage. However, it does not specify the full output structure beyond 'id, type, truncated content', leaving some return fields unmentioned.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, so the baseline is 3. The description adds minimal new semantics beyond the schema, mostly listing filter options already present. It echoes schema descriptions for 'semantic' and 'detail' but does not introduce new meaning.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly specifies the verb 'search' and the resource 'stored memories', distinguishing it from siblings like memory_get and memory_related. It mentions both FTS5 keyword search and semantic embedding similarity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states when to use alternative tools: 'Use memory_get to fetch full content for specific IDs found in search results. Use memory_related to explore graph connections from a known memory.' It also clarifies the read-only nature.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/leesgit/claude-session-continuity-mcp'

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