fix(mcp): strip API extras from listMemories entries (#1539)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dhravya Shah 2026-08-19 14:05:52 -07:00 committed by GitHub
parent 7b1175cb1a
commit 818a83a381
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 56 additions and 70 deletions

View file

@ -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<typeof memoryEntryHistorySchema>
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<typeof memoryEntrySchema>
const memoryEntriesResponseSchema = z.object({
memoryEntries: z.array(memoryEntrySchema),
pagination: paginationSchema,
})
export type MemoryEntriesResponse = z.infer<typeof memoryEntriesResponseSchema>
// 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)
}

View file

@ -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<typeof listDocumentsOutputSchema>
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<typeof listMemoriesOutputSchema>

View file

@ -117,6 +117,49 @@ export const documentsApiResponseSchema = z.object({
export type DocumentsApiResponse = z.infer<typeof documentsApiResponseSchema>
// 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<typeof memoryEntryHistorySchema>
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<typeof memoryEntrySchema>
export const memoriesListSchema = z.object({
memoryEntries: z.array(memoryEntrySchema),
pagination: paginationSchema,
})
export type MemoriesList = z.infer<typeof memoriesListSchema>
// 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.