diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80600ae5..e7634819 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,5 +29,9 @@ jobs: - name: Run TypeScript type checking run: bunx turbo run check-types --filter='@supermemory/ai-sdk' --filter='@supermemory/memory-graph' + - name: Run MCP unit tests + working-directory: apps/mcp + run: bun run test:unit + - name: Run Biome CI (format & lint on changed files) run: bunx biome ci --changed --since=origin/main --no-errors-on-unmatched diff --git a/apps/mcp/src/server/format.test.ts b/apps/mcp/src/server/format.test.ts new file mode 100644 index 00000000..42d0ec3a --- /dev/null +++ b/apps/mcp/src/server/format.test.ts @@ -0,0 +1,107 @@ +import { describe, expect, it } from "vitest" +import type { MemoryEntriesResponse, MemoryEntry } from "./client" +import { formatMemoryEntriesList } from "./format" + +function memoryEntry(overrides: Partial = {}): MemoryEntry { + return { + id: "memory-1", + memory: "Current fact", + version: 1, + isLatest: true, + isForgotten: false, + createdAt: "2026-08-15T10:00:00.000Z", + updatedAt: "2026-08-15T10:00:00.000Z", + ...overrides, + } +} + +function memoryResponse( + overrides: Partial = {}, +): MemoryEntriesResponse { + return { + memoryEntries: [], + pagination: { + currentPage: 1, + limit: 10, + totalItems: 0, + totalPages: 1, + }, + ...overrides, + } +} + +describe("formatMemoryEntriesList", () => { + it("reports a truly empty store", () => { + expect(formatMemoryEntriesList(memoryResponse())).toBe( + "No active memories stored yet.", + ) + }) + + it("continues past a filtered-empty page when a later page has active data", () => { + const firstPage = formatMemoryEntriesList( + memoryResponse({ + memoryEntries: [ + memoryEntry({ id: "forgotten", isForgotten: true }), + memoryEntry({ id: "superseded", isLatest: false }), + ], + pagination: { + currentPage: 1, + limit: 2, + totalItems: 3, + totalPages: 2, + }, + }), + ) + const secondPage = formatMemoryEntriesList( + memoryResponse({ + memoryEntries: [memoryEntry({ id: "still-active" })], + pagination: { + currentPage: 2, + limit: 2, + totalItems: 3, + totalPages: 2, + }, + }), + ) + + expect(firstPage).toBe( + "No active memories on page 1 (2 pages total).\n\nMore available - call listMemories with page: 2.", + ) + expect(secondPage).toContain("[still-active] Current fact") + }) + + it("does not offer another page from a filtered-empty last page", () => { + const result = formatMemoryEntriesList( + memoryResponse({ + memoryEntries: [memoryEntry({ isForgotten: true })], + pagination: { + currentPage: 2, + limit: 10, + totalItems: 11, + totalPages: 2, + }, + }), + ) + + expect(result).toBe("No active memories on page 2 (2 pages total).") + expect(result).not.toContain("More available") + }) + + it("keeps active-memory output unchanged", () => { + const result = formatMemoryEntriesList( + memoryResponse({ + memoryEntries: [memoryEntry()], + pagination: { + currentPage: 1, + limit: 10, + totalItems: 1, + totalPages: 1, + }, + }), + ) + + expect(result).toBe( + "1 active memory (page 1 of 1, 1 memory entry total), newest first.\n\n- [memory-1] Current fact\n version 1 | updated 2026-08-15", + ) + }) +}) diff --git a/apps/mcp/src/server/format.ts b/apps/mcp/src/server/format.ts index c9bb3f11..e3fffef3 100644 --- a/apps/mcp/src/server/format.ts +++ b/apps/mcp/src/server/format.ts @@ -81,9 +81,20 @@ export function formatMemoryEntriesList( ) if (activeEntries.length === 0) { - return pagination.currentPage > 1 - ? `No active memories on page ${pagination.currentPage} (${pagination.totalPages} page${pagination.totalPages === 1 ? "" : "s"} total).` - : "No active memories stored yet." + if (pagination.currentPage === 1 && pagination.totalItems === 0) { + return "No active memories stored yet." + } + + const parts = [ + `No active memories on page ${pagination.currentPage} (${pagination.totalPages} page${pagination.totalPages === 1 ? "" : "s"} total).`, + ] + if (pagination.currentPage < pagination.totalPages) { + parts.push( + "", + `More available - call listMemories with page: ${pagination.currentPage + 1}.`, + ) + } + return parts.join("\n") } const blocks = activeEntries.map((entry) => {