fix(mcp): expose dreaming status and clarify document acceptance (#1844)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mahesh Sanikommu 2026-10-28 12:20:29 -07:00 • committed by GitHub
parent ad443e155c
commit 0da13138fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 64 additions and 12 deletions

View file

@ -55,9 +55,22 @@ The client discovers the OAuth authorization server through
| `list_memories` | List extracted memory entries and their source document IDs |
| `list_spaces` | List spaces visible to the authenticated account |
| `who_am_i` | Return identity, access, and active-space context |
| `add_memory` | Save a memory (legacy `action: "forget"` remains supported) |
| `add_memory` | Accept a document for asynchronous memory extraction (legacy `action: "forget"` remains supported) |
| `forget_memory` | Forget a memory matching the supplied content |
### Document status
`list_documents` and `get_document` return two status fields:
| Field | Value | Meaning |
| --- | --- | --- |
| `status` | `done` | Content is processed and readable |
| `dreamingStatus` | `dreaming` | Memory extraction is pending, running, or retrying |
| `dreamingStatus` | `done` | Memory extraction settled, possibly with no memories |
`dreamingStatus` appears only when the API returns it. Neither `done` means
memories are searchable yet.
### MCP App launchers
| Tool | Purpose |

View file

@ -37,7 +37,7 @@ describe.skipIf(!OAUTH_CREDENTIALS_AVAILABLE)("MCP — memory behaviors", () =>
action: "save",
})
expect(save.isError).toBeFalsy()
expect(textOf(save)).toMatch(/Memory saved/i)
expect(textOf(save)).toMatch(/Document accepted/i)
const found = await recallUntil(s.client, "test fruit dragonfruit", marker)
expect(found, `recall never returned marker ${marker}`).not.toBeNull()

View file

@ -35,18 +35,23 @@ export type {
NamespaceInfo,
}
type DreamingStatus = "dreaming" | "done"
export interface DocumentSummary {
id: string
title: string | null
type: string
status: string
dreamingStatus?: DreamingStatus
createdAt: string
updatedAt: string
summary: string | null
url: string | null
}
export type DocumentDetails = GetDocumentsResponse
export type DocumentDetails = GetDocumentsResponse & {
system: { dreamingStatus?: DreamingStatus }
}
export interface DocumentsListResponse {
documents: DocumentSummary[]
@ -104,13 +109,16 @@ function mapSearchResults(results: SearchResponseResultsItem[]): Memory[] {
}
function mapDocumentSummary(
document: ListResponseDocumentsItem,
document: ListResponseDocumentsItem & {
system: { dreamingStatus?: DreamingStatus }
},
): DocumentSummary {
return {
id: document.id,
title: document.title,
type: document.type,
status: document.system.status,
dreamingStatus: document.system.dreamingStatus,
createdAt: document.system.createdAt,
updatedAt: document.system.updatedAt,
summary: document.summary,

View file

@ -18,6 +18,10 @@ function day(value: string | null | undefined): string {
return value?.slice(0, 10) ?? ""
}
function memoryExtractionLabel(status: "dreaming" | "done"): string {
return status === "dreaming" ? "in progress" : "done"
}
function paginationSummary(
currentPage: number,
totalPages: number,
@ -43,6 +47,11 @@ export function formatDocumentsList(response: DocumentsListResponse): string {
const lines = [
`- [${document.id}] "${title}" (${document.type}, ${document.status}, ${day(document.createdAt)})`,
]
if (document.dreamingStatus) {
lines.push(
` Memory extraction: ${memoryExtractionLabel(document.dreamingStatus)}`,
)
}
if (document.summary?.trim()) {
lines.push(` Summary: ${compactText(document.summary)}`)
}
@ -148,6 +157,11 @@ export function formatDocument(document: DocumentDetails): string {
`Document ID: ${document.id}`,
`Type: ${document.type}`,
`Status: ${document.system.status}`,
...(document.system.dreamingStatus
? [
`Memory extraction: ${memoryExtractionLabel(document.system.dreamingStatus)}`,
]
: []),
`Created: ${document.system.createdAt}`,
`Updated: ${document.system.updatedAt}`,
]

View file

@ -31,7 +31,7 @@ export function register(deps: ToolDeps) {
"add_memory",
{
description:
"Save a memory in the user's ACTIVE space. The target space is the one the user selected via select-space; pass namespace only to override it. To remove outdated information, use the separately exposed forget_memory tool. Legacy action='forget' remains supported for existing clients.",
"Save content as a document in the user's ACTIVE space. Memories are extracted from it in the background, so they may not be searchable right away. The target space is the one the user selected via select-space; pass namespace only to override it. To remove outdated information, use the separately exposed forget_memory tool. Legacy action='forget' remains supported for existing clients.",
inputSchema,
outputSchema: addMemoryOutputSchema,
annotations: MEMORY_TOOL_ANNOTATIONS,
@ -43,7 +43,7 @@ export function register(deps: ToolDeps) {
const client = deps.getClient(namespace)
const result = await client.createMemory(args.content)
const message = `Memory saved (ID: ${result.id}, space: ${result.namespace})`
const message = `Document accepted (ID: ${result.id}, space: ${result.namespace}). Memories are extracted in the background and can take a moment to show up in search.`
const structuredContent: AddMemoryOutput = withLegacyNamespace({
action: "save",
success: true,

View file

@ -31,7 +31,7 @@ export function register(deps: ToolDeps) {
{
title: "Get Document",
description:
"Read one stored document by ID from one space, including its summary and available content. Get document IDs from list_documents or from the document shown on a search_memory result. When the document is in a named space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
"Read one stored document by ID from one space, including its summary, available content, and memory extraction status (dreamingStatus) when available. Get document IDs from list_documents or from the document shown on a search_memory result. When the document is in a named space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
inputSchema,
outputSchema: getDocumentOutputSchema,
annotations: READ_ONLY_TOOL_ANNOTATIONS,
@ -49,6 +49,7 @@ export function register(deps: ToolDeps) {
title: document.title,
type: document.type,
status: document.system.status,
dreamingStatus: document.system.dreamingStatus,
createdAt: document.system.createdAt,
updatedAt: document.system.updatedAt,
summary: document.summary,

View file

@ -35,7 +35,7 @@ export function register(deps: ToolDeps) {
{
title: "List Documents",
description:
"List documents in one space with their IDs, titles, types, processing status, dates, and summaries. This does not return full document content; use get_document with an ID from this result to read one document. When the user names a space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
"List documents in one space with their IDs, titles, types, processing status, memory extraction status (dreamingStatus) when available, dates, and summaries. This does not return full document content; use get_document with an ID from this result to read one document. When the user names a space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
inputSchema,
outputSchema: listDocumentsOutputSchema,
annotations: READ_ONLY_TOOL_ANNOTATIONS,
@ -54,6 +54,7 @@ export function register(deps: ToolDeps) {
title: document.title,
type: document.type,
status: document.status,
dreamingStatus: document.dreamingStatus,
createdAt: document.createdAt,
updatedAt: document.updatedAt,
summary: document.summary,

View file

@ -7,11 +7,25 @@ import {
sessionScopeSchema,
} from "../../shared/types"
const documentStatusSchema = z
.string()
.describe(
"Document processing state. done means the content is processed; memories may still be pending, see dreamingStatus.",
)
const dreamingStatusSchema = z
.enum(["dreaming", "done"])
.optional()
.describe(
"Memory extraction status, present when the API returns it. dreaming: pending, running, or retrying after a failure. done: settled, possibly with no memories. Neither done means memories are searchable yet.",
)
const documentSummarySchema = z.object({
id: z.string(),
title: z.string().nullable(),
type: z.string(),
status: z.string(),
status: documentStatusSchema,
dreamingStatus: dreamingStatusSchema,
createdAt: z.string(),
updatedAt: z.string(),
summary: z.string().nullable(),
@ -23,8 +37,8 @@ export const addMemoryOutputSchema = z.object({
namespace: z.string(),
...legacyNamespaceOutput,
message: z.string(),
id: z.string().optional(),
status: z.string().optional(),
id: z.string().optional().describe("Accepted document ID, not a memory ID"),
status: documentStatusSchema.optional(),
forgotten: z
.array(z.object({ id: z.string(), memory: z.string() }))
.optional(),
@ -49,7 +63,8 @@ export const getDocumentOutputSchema = z.object({
id: z.string(),
title: z.string().nullable(),
type: z.string(),
status: z.string(),
status: documentStatusSchema,
dreamingStatus: dreamingStatusSchema,
createdAt: z.string(),
updatedAt: z.string(),
summary: z.string().nullable(),