diff --git a/apps/mcp/src/server/client/index.ts b/apps/mcp/src/server/client/index.ts index ad9695a40..a2ccc8e7e 100644 --- a/apps/mcp/src/server/client/index.ts +++ b/apps/mcp/src/server/client/index.ts @@ -7,11 +7,14 @@ import { z } from "zod" import { containerTagSchema, documentsApiResponseSchema, - paginationSchema, + memoriesListSchema, type ContainerTag, type DocumentMemoryEntry, type DocumentsApiResponse, type DocumentWithMemories, + type MemoriesList, + type MemoryEntry, + type MemoryEntryHistory, } from "../../shared/types" const MAX_CHARS = 200000 @@ -34,43 +37,10 @@ export interface DocumentsListResponse { pagination: SdkDocumentListResponse["pagination"] } -const memoryEntryHistorySchema = z.looseObject({ - id: z.string(), - memory: z.string(), - version: z.number(), - createdAt: z.string(), - updatedAt: z.string(), - parentMemoryId: z.string().nullish(), - rootMemoryId: z.string().nullish(), - isLatest: z.boolean().optional(), - isForgotten: z.boolean().optional(), -}) - -export type MemoryEntryHistory = z.infer - -const memoryEntrySchema = z.looseObject({ - id: z.string(), - memory: z.string(), - version: z.number(), - isLatest: z.boolean(), - isForgotten: z.boolean(), - isStatic: z.boolean().optional(), - isInference: z.boolean().optional(), - createdAt: z.string(), - updatedAt: z.string(), - sourceCount: z.number().optional(), - documentIds: z.array(z.string()).optional(), - history: z.array(memoryEntryHistorySchema).optional(), -}) - -export type MemoryEntry = z.infer - -const memoryEntriesResponseSchema = z.object({ - memoryEntries: z.array(memoryEntrySchema), - pagination: paginationSchema, -}) - -export type MemoryEntriesResponse = z.infer +// Memory-entry shapes live in shared/types so the client parser and the +// listMemories output schema share one definition and can't drift. +export type { MemoryEntry, MemoryEntryHistory } +export type MemoryEntriesResponse = MemoriesList export type Memory = | { @@ -452,7 +422,7 @@ export class SupermemoryClient { }) } - return memoryEntriesResponseSchema.parse(await response.json()) + return memoriesListSchema.parse(await response.json()) } catch (error) { this.handleError(error) } diff --git a/apps/mcp/src/server/tools/output-schemas.ts b/apps/mcp/src/server/tools/output-schemas.ts index 0156d9d15..6cb0bf66d 100644 --- a/apps/mcp/src/server/tools/output-schemas.ts +++ b/apps/mcp/src/server/tools/output-schemas.ts @@ -1,6 +1,7 @@ import { z } from "zod" import { containerTagAccessSchema, + memoriesListSchema, paginationSchema, sessionScopeSchema, } from "../../shared/types" @@ -42,33 +43,6 @@ const documentSummarySchema = z.object({ summary: z.string().nullable(), }) -const memoryHistorySchema = z.object({ - id: z.string(), - memory: z.string(), - version: z.number(), - createdAt: z.string(), - updatedAt: z.string(), - parentMemoryId: z.string().nullish(), - rootMemoryId: z.string().nullish(), - isLatest: z.boolean().optional(), - isForgotten: z.boolean().optional(), -}) - -const memoryEntryOutputSchema = z.object({ - id: z.string(), - memory: z.string(), - version: z.number(), - isLatest: z.boolean(), - isForgotten: z.boolean(), - isStatic: z.boolean().optional(), - isInference: z.boolean().optional(), - createdAt: z.string(), - updatedAt: z.string(), - sourceCount: z.number().optional(), - documentIds: z.array(z.string()).optional(), - history: z.array(memoryHistorySchema).optional(), -}) - export const addMemoryOutputSchema = z.object({ action: z.enum(["save", "forget"]), success: z.boolean(), @@ -104,10 +78,9 @@ export const listDocumentsOutputSchema = z.object({ export type ListDocumentsOutput = z.infer -export const listMemoriesOutputSchema = z.object({ - memoryEntries: z.array(memoryEntryOutputSchema), - pagination: paginationSchema, -}) +// Reuse the shared schema so the tool's output contract stays identical to what +// the client parses — the two can't drift. +export const listMemoriesOutputSchema = memoriesListSchema export type ListMemoriesOutput = z.infer diff --git a/apps/mcp/src/shared/types.ts b/apps/mcp/src/shared/types.ts index 838b13fb6..e7a341602 100644 --- a/apps/mcp/src/shared/types.ts +++ b/apps/mcp/src/shared/types.ts @@ -117,6 +117,49 @@ export const documentsApiResponseSchema = z.object({ export type DocumentsApiResponse = z.infer +// Extracted memory entries from /v4/memories/list. Single source of truth for +// both the client parser and the listMemories tool output schema, so the two +// can't drift (a mismatch previously produced Ajv "must NOT have additional +// properties"). z.object strips unknown API fields on parse, keeping parsed data +// matched to the strict MCP output contract while tolerating new API fields. +export const memoryEntryHistorySchema = z.object({ + id: z.string(), + memory: z.string(), + version: z.number(), + createdAt: z.string(), + updatedAt: z.string(), + parentMemoryId: z.string().nullish(), + rootMemoryId: z.string().nullish(), + isLatest: z.boolean().optional(), + isForgotten: z.boolean().optional(), +}) + +export type MemoryEntryHistory = z.infer + +export const memoryEntrySchema = z.object({ + id: z.string(), + memory: z.string(), + version: z.number(), + isLatest: z.boolean(), + isForgotten: z.boolean(), + isStatic: z.boolean().optional(), + isInference: z.boolean().optional(), + createdAt: z.string(), + updatedAt: z.string(), + sourceCount: z.number().optional(), + documentIds: z.array(z.string()).optional(), + history: z.array(memoryEntryHistorySchema).optional(), +}) + +export type MemoryEntry = z.infer + +export const memoriesListSchema = z.object({ + memoryEntries: z.array(memoryEntrySchema), + pagination: paginationSchema, +}) + +export type MemoriesList = z.infer + // ViewMessage — discriminated union returned by app tools as `structuredContent`. // The widget uses an exhaustive switch on `view` to dispatch to the correct view component. // Adding a new view here is a compile error in App.tsx until the case is handled.