mem-os-python 1.6.27__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mem_os_python-1.6.27.dist-info/METADATA +1032 -0
- mem_os_python-1.6.27.dist-info/RECORD +253 -0
- mem_os_python-1.6.27.dist-info/WHEEL +4 -0
- mem_os_python-1.6.27.dist-info/entry_points.txt +2 -0
- mem_os_python-1.6.27.dist-info/licenses/LICENSE +21 -0
- memos/__init__.py +16 -0
- memos/_js/adapters/anthropic.d.ts +279 -0
- memos/_js/adapters/anthropic.d.ts.map +1 -0
- memos/_js/adapters/anthropic.js +348 -0
- memos/_js/adapters/anthropic.js.map +1 -0
- memos/_js/adapters/base.d.ts +10 -0
- memos/_js/adapters/base.d.ts.map +1 -0
- memos/_js/adapters/base.js +2 -0
- memos/_js/adapters/base.js.map +1 -0
- memos/_js/adapters/openai.d.ts +264 -0
- memos/_js/adapters/openai.d.ts.map +1 -0
- memos/_js/adapters/openai.js +342 -0
- memos/_js/adapters/openai.js.map +1 -0
- memos/_js/apps/explorer.d.ts +244 -0
- memos/_js/apps/explorer.d.ts.map +1 -0
- memos/_js/apps/explorer.js +738 -0
- memos/_js/apps/explorer.js.map +1 -0
- memos/_js/citations.d.ts +65 -0
- memos/_js/citations.d.ts.map +1 -0
- memos/_js/citations.js +143 -0
- memos/_js/citations.js.map +1 -0
- memos/_js/cli-browse.d.ts +3 -0
- memos/_js/cli-browse.d.ts.map +1 -0
- memos/_js/cli-browse.js +129 -0
- memos/_js/cli-browse.js.map +1 -0
- memos/_js/cli-completion.d.ts +31 -0
- memos/_js/cli-completion.d.ts.map +1 -0
- memos/_js/cli-completion.js +340 -0
- memos/_js/cli-completion.js.map +1 -0
- memos/_js/cli-help.d.ts +18 -0
- memos/_js/cli-help.d.ts.map +1 -0
- memos/_js/cli-help.js +357 -0
- memos/_js/cli-help.js.map +1 -0
- memos/_js/cli.d.ts +28 -0
- memos/_js/cli.d.ts.map +1 -0
- memos/_js/cli.js +2880 -0
- memos/_js/cli.js.map +1 -0
- memos/_js/compression.d.ts +25 -0
- memos/_js/compression.d.ts.map +1 -0
- memos/_js/compression.js +319 -0
- memos/_js/compression.js.map +1 -0
- memos/_js/confidence-machine.d.ts +99 -0
- memos/_js/confidence-machine.d.ts.map +1 -0
- memos/_js/confidence-machine.js +222 -0
- memos/_js/confidence-machine.js.map +1 -0
- memos/_js/context-pack.d.ts +506 -0
- memos/_js/context-pack.d.ts.map +1 -0
- memos/_js/context-pack.js +1435 -0
- memos/_js/context-pack.js.map +1 -0
- memos/_js/contradictions.d.ts +160 -0
- memos/_js/contradictions.d.ts.map +1 -0
- memos/_js/contradictions.js +255 -0
- memos/_js/contradictions.js.map +1 -0
- memos/_js/dashboard.d.ts +30 -0
- memos/_js/dashboard.d.ts.map +1 -0
- memos/_js/dashboard.js +302 -0
- memos/_js/dashboard.js.map +1 -0
- memos/_js/ebbinghaus.d.ts +29 -0
- memos/_js/ebbinghaus.d.ts.map +1 -0
- memos/_js/ebbinghaus.js +32 -0
- memos/_js/ebbinghaus.js.map +1 -0
- memos/_js/embedding-queue.d.ts +190 -0
- memos/_js/embedding-queue.d.ts.map +1 -0
- memos/_js/embedding-queue.js +316 -0
- memos/_js/embedding-queue.js.map +1 -0
- memos/_js/embeddings.d.ts +187 -0
- memos/_js/embeddings.d.ts.map +1 -0
- memos/_js/embeddings.js +633 -0
- memos/_js/embeddings.js.map +1 -0
- memos/_js/entity-extraction.d.ts +54 -0
- memos/_js/entity-extraction.d.ts.map +1 -0
- memos/_js/entity-extraction.js +325 -0
- memos/_js/entity-extraction.js.map +1 -0
- memos/_js/entity-index.d.ts +93 -0
- memos/_js/entity-index.d.ts.map +1 -0
- memos/_js/entity-index.js +165 -0
- memos/_js/entity-index.js.map +1 -0
- memos/_js/event-memory.d.ts +147 -0
- memos/_js/event-memory.d.ts.map +1 -0
- memos/_js/event-memory.js +290 -0
- memos/_js/event-memory.js.map +1 -0
- memos/_js/external-import.d.ts +98 -0
- memos/_js/external-import.d.ts.map +1 -0
- memos/_js/external-import.js +669 -0
- memos/_js/external-import.js.map +1 -0
- memos/_js/fidelity.d.ts +367 -0
- memos/_js/fidelity.d.ts.map +1 -0
- memos/_js/fidelity.js +911 -0
- memos/_js/fidelity.js.map +1 -0
- memos/_js/graph-expansion.d.ts +119 -0
- memos/_js/graph-expansion.d.ts.map +1 -0
- memos/_js/graph-expansion.js +213 -0
- memos/_js/graph-expansion.js.map +1 -0
- memos/_js/graph-mermaid.d.ts +13 -0
- memos/_js/graph-mermaid.d.ts.map +1 -0
- memos/_js/graph-mermaid.js +32 -0
- memos/_js/graph-mermaid.js.map +1 -0
- memos/_js/graph.d.ts +170 -0
- memos/_js/graph.d.ts.map +1 -0
- memos/_js/graph.js +482 -0
- memos/_js/graph.js.map +1 -0
- memos/_js/harness.d.ts +63 -0
- memos/_js/harness.d.ts.map +1 -0
- memos/_js/harness.js +93 -0
- memos/_js/harness.js.map +1 -0
- memos/_js/importance.d.ts +118 -0
- memos/_js/importance.d.ts.map +1 -0
- memos/_js/importance.js +144 -0
- memos/_js/importance.js.map +1 -0
- memos/_js/index.d.ts +51 -0
- memos/_js/index.d.ts.map +1 -0
- memos/_js/index.js +53 -0
- memos/_js/index.js.map +1 -0
- memos/_js/mcp.d.ts +58 -0
- memos/_js/mcp.d.ts.map +1 -0
- memos/_js/mcp.js +1853 -0
- memos/_js/mcp.js.map +1 -0
- memos/_js/memory.d.ts +1762 -0
- memos/_js/memory.d.ts.map +1 -0
- memos/_js/memory.js +6283 -0
- memos/_js/memory.js.map +1 -0
- memos/_js/procedural.d.ts +74 -0
- memos/_js/procedural.d.ts.map +1 -0
- memos/_js/procedural.js +125 -0
- memos/_js/procedural.js.map +1 -0
- memos/_js/project-config.d.ts +68 -0
- memos/_js/project-config.d.ts.map +1 -0
- memos/_js/project-config.js +59 -0
- memos/_js/project-config.js.map +1 -0
- memos/_js/proof-of-forgetting.d.ts +57 -0
- memos/_js/proof-of-forgetting.d.ts.map +1 -0
- memos/_js/proof-of-forgetting.js +80 -0
- memos/_js/proof-of-forgetting.js.map +1 -0
- memos/_js/provenance.d.ts +122 -0
- memos/_js/provenance.d.ts.map +1 -0
- memos/_js/provenance.js +182 -0
- memos/_js/provenance.js.map +1 -0
- memos/_js/quarantine-commands.d.ts +23 -0
- memos/_js/quarantine-commands.d.ts.map +1 -0
- memos/_js/quarantine-commands.js +42 -0
- memos/_js/quarantine-commands.js.map +1 -0
- memos/_js/quarantine.d.ts +120 -0
- memos/_js/quarantine.d.ts.map +1 -0
- memos/_js/quarantine.js +873 -0
- memos/_js/quarantine.js.map +1 -0
- memos/_js/redact.d.ts +12 -0
- memos/_js/redact.d.ts.map +1 -0
- memos/_js/redact.js +26 -0
- memos/_js/redact.js.map +1 -0
- memos/_js/repl.d.ts +57 -0
- memos/_js/repl.d.ts.map +1 -0
- memos/_js/repl.js +295 -0
- memos/_js/repl.js.map +1 -0
- memos/_js/rerank-local.d.ts +61 -0
- memos/_js/rerank-local.d.ts.map +1 -0
- memos/_js/rerank-local.js +210 -0
- memos/_js/rerank-local.js.map +1 -0
- memos/_js/retain-filter.d.ts +98 -0
- memos/_js/retain-filter.d.ts.map +1 -0
- memos/_js/retain-filter.js +232 -0
- memos/_js/retain-filter.js.map +1 -0
- memos/_js/retrieval.d.ts +265 -0
- memos/_js/retrieval.d.ts.map +1 -0
- memos/_js/retrieval.js +1018 -0
- memos/_js/retrieval.js.map +1 -0
- memos/_js/revert.d.ts +77 -0
- memos/_js/revert.d.ts.map +1 -0
- memos/_js/revert.js +252 -0
- memos/_js/revert.js.map +1 -0
- memos/_js/scope.d.ts +30 -0
- memos/_js/scope.d.ts.map +1 -0
- memos/_js/scope.js +71 -0
- memos/_js/scope.js.map +1 -0
- memos/_js/semantic-screen.d.ts +152 -0
- memos/_js/semantic-screen.d.ts.map +1 -0
- memos/_js/semantic-screen.js +290 -0
- memos/_js/semantic-screen.js.map +1 -0
- memos/_js/session-hooks.d.ts +127 -0
- memos/_js/session-hooks.d.ts.map +1 -0
- memos/_js/session-hooks.js +482 -0
- memos/_js/session-hooks.js.map +1 -0
- memos/_js/storage/sqlite.d.ts +427 -0
- memos/_js/storage/sqlite.d.ts.map +1 -0
- memos/_js/storage/sqlite.js +2428 -0
- memos/_js/storage/sqlite.js.map +1 -0
- memos/_js/sync.d.ts +213 -0
- memos/_js/sync.d.ts.map +1 -0
- memos/_js/sync.js +389 -0
- memos/_js/sync.js.map +1 -0
- memos/_js/tamper-log.d.ts +98 -0
- memos/_js/tamper-log.d.ts.map +1 -0
- memos/_js/tamper-log.js +327 -0
- memos/_js/tamper-log.js.map +1 -0
- memos/_js/temporal.d.ts +92 -0
- memos/_js/temporal.d.ts.map +1 -0
- memos/_js/temporal.js +536 -0
- memos/_js/temporal.js.map +1 -0
- memos/_js/time-travel.d.ts +38 -0
- memos/_js/time-travel.d.ts.map +1 -0
- memos/_js/time-travel.js +46 -0
- memos/_js/time-travel.js.map +1 -0
- memos/_js/timestamp-format.d.ts +18 -0
- memos/_js/timestamp-format.d.ts.map +1 -0
- memos/_js/timestamp-format.js +52 -0
- memos/_js/timestamp-format.js.map +1 -0
- memos/_js/triple-extraction.d.ts +31 -0
- memos/_js/triple-extraction.d.ts.map +1 -0
- memos/_js/triple-extraction.js +116 -0
- memos/_js/triple-extraction.js.map +1 -0
- memos/_js/types.d.ts +2474 -0
- memos/_js/types.d.ts.map +1 -0
- memos/_js/types.js +20 -0
- memos/_js/types.js.map +1 -0
- memos/_js/updater.d.ts +95 -0
- memos/_js/updater.d.ts.map +1 -0
- memos/_js/updater.js +268 -0
- memos/_js/updater.js.map +1 -0
- memos/_js/version.d.ts +17 -0
- memos/_js/version.d.ts.map +1 -0
- memos/_js/version.js +34 -0
- memos/_js/version.js.map +1 -0
- memos/_js/vision/clip-query-cache.d.ts +58 -0
- memos/_js/vision/clip-query-cache.d.ts.map +1 -0
- memos/_js/vision/clip-query-cache.js +79 -0
- memos/_js/vision/clip-query-cache.js.map +1 -0
- memos/_js/vision/clip.d.ts +113 -0
- memos/_js/vision/clip.d.ts.map +1 -0
- memos/_js/vision/clip.js +224 -0
- memos/_js/vision/clip.js.map +1 -0
- memos/_js/vision/fake-clip.d.ts +55 -0
- memos/_js/vision/fake-clip.d.ts.map +1 -0
- memos/_js/vision/fake-clip.js +122 -0
- memos/_js/vision/fake-clip.js.map +1 -0
- memos/_js/vision/index.d.ts +15 -0
- memos/_js/vision/index.d.ts.map +1 -0
- memos/_js/vision/index.js +13 -0
- memos/_js/vision/index.js.map +1 -0
- memos/_js/vision/store.d.ts +137 -0
- memos/_js/vision/store.d.ts.map +1 -0
- memos/_js/vision/store.js +383 -0
- memos/_js/vision/store.js.map +1 -0
- memos/adapters/__init__.py +0 -0
- memos/adapters/crewai.py +385 -0
- memos/adapters/langchain.py +301 -0
- memos/adapters/ollama.py +396 -0
- memos/server/__init__.py +0 -0
- memos/server/main.py +547 -0
- memos/server/routes.py +360 -0
memos/_js/mcp.js
ADDED
|
@@ -0,0 +1,1853 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server for MemOS — Model Context Protocol 2026-07-28.
|
|
3
|
+
*
|
|
4
|
+
* Built on the official `@modelcontextprotocol/server` v2 SDK, which
|
|
5
|
+
* implements the 2026-07-28 spec (stateless core, `server/discover`,
|
|
6
|
+
* per-request `_meta` envelope, `ttlMs`/`cacheScope` on list results,
|
|
7
|
+
* `outputSchema` on tools). The SDK's `serveStdio` entry is dual-era:
|
|
8
|
+
* it speaks the modern 2026-07-28 protocol AND falls back to the legacy
|
|
9
|
+
* 2025-era `initialize` handshake for older clients.
|
|
10
|
+
*
|
|
11
|
+
* Transport: stdio only (the shipped transport). HTTP/SSE is deferred.
|
|
12
|
+
*/
|
|
13
|
+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server";
|
|
14
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
import { MemOS } from "./memory.js";
|
|
17
|
+
import { listReminders } from "./event-memory.js";
|
|
18
|
+
import { graphToMermaid } from "./graph-mermaid.js";
|
|
19
|
+
import { ExplorerUriTemplate, buildExplorerFallbackMarkdown, buildExplorerHtml, buildExplorerSnapshot, resolveExplorerMode, } from "./apps/explorer.js";
|
|
20
|
+
import { getSdkVersion } from "./version.js";
|
|
21
|
+
import { citationToken } from "./citations.js";
|
|
22
|
+
import { decorateTrustFlags, isUntrustedForAgent } from "./provenance.js";
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Zod schemas for MemoryNode / MemoryEdge / ScoredMemory (output validation)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
const memoryNodeSchema = z.object({
|
|
27
|
+
id: z.string(),
|
|
28
|
+
content: z.string(),
|
|
29
|
+
summary: z.string(),
|
|
30
|
+
type: z.string(),
|
|
31
|
+
metadata: z.record(z.string(), z.unknown()),
|
|
32
|
+
importance: z.number(),
|
|
33
|
+
createdAt: z.number(),
|
|
34
|
+
updatedAt: z.number(),
|
|
35
|
+
accessCount: z.number(),
|
|
36
|
+
lastAccessed: z.number(),
|
|
37
|
+
tags: z.array(z.string()),
|
|
38
|
+
expiresAt: z.number().nullable(),
|
|
39
|
+
namespace: z.string(),
|
|
40
|
+
validFrom: z.number().nullable(),
|
|
41
|
+
validTo: z.number().nullable(),
|
|
42
|
+
source: z.string(),
|
|
43
|
+
trustScore: z.number(),
|
|
44
|
+
confidence: z.number().optional(),
|
|
45
|
+
evidenceCount: z.number().optional(),
|
|
46
|
+
// Provenance-trust layer (optional: third-party storage adapters may
|
|
47
|
+
// not populate them).
|
|
48
|
+
provenance: z.string().optional(),
|
|
49
|
+
quarantined: z.boolean().optional(),
|
|
50
|
+
quarantinedAt: z.number().nullable().optional(),
|
|
51
|
+
quarantineReason: z.string().nullable().optional(),
|
|
52
|
+
});
|
|
53
|
+
const memoryEdgeSchema = z.object({
|
|
54
|
+
id: z.string(),
|
|
55
|
+
sourceId: z.string(),
|
|
56
|
+
targetId: z.string(),
|
|
57
|
+
relation: z.string(),
|
|
58
|
+
weight: z.number(),
|
|
59
|
+
metadata: z.record(z.string(), z.unknown()),
|
|
60
|
+
createdAt: z.number(),
|
|
61
|
+
});
|
|
62
|
+
const scoredMemorySchema = z.object({
|
|
63
|
+
node: memoryNodeSchema,
|
|
64
|
+
score: z.number(),
|
|
65
|
+
scores: z
|
|
66
|
+
.object({
|
|
67
|
+
keyword: z.number().optional(),
|
|
68
|
+
semantic: z.number().optional(),
|
|
69
|
+
hybrid: z.number().optional(),
|
|
70
|
+
provenance: z.number().optional(),
|
|
71
|
+
})
|
|
72
|
+
.optional(),
|
|
73
|
+
// Provenance-trust read-time policy: low-trust channels
|
|
74
|
+
// (imported/tool-output tiers, quarantined-then-released) are flagged
|
|
75
|
+
// so the host agent confirms instead of silently injecting them.
|
|
76
|
+
provenance: z.string().optional(),
|
|
77
|
+
citation: z.string().optional(),
|
|
78
|
+
untrustedSource: z.boolean().optional(),
|
|
79
|
+
untrusted_source: z.boolean().optional(),
|
|
80
|
+
});
|
|
81
|
+
/**
|
|
82
|
+
* Shape a scored memory for the MCP wire: provenance tier, citable
|
|
83
|
+
* `[mem:hex]` token tracing the memory to its source episode, and the
|
|
84
|
+
* read-time trust flag (`untrusted_source: true` for low-trust
|
|
85
|
+
* channels). `provenance` carries the tier string; `untrusted_source`
|
|
86
|
+
* is the snake_case flag host agents check.
|
|
87
|
+
*/
|
|
88
|
+
function toMcpResult(r) {
|
|
89
|
+
return {
|
|
90
|
+
...r,
|
|
91
|
+
provenance: r.provenance ?? r.node.provenance,
|
|
92
|
+
citation: r.citation ?? citationToken(r.node.id),
|
|
93
|
+
harness: r.node.harness ?? "unknown",
|
|
94
|
+
untrustedSource: r.untrustedSource ?? isUntrustedForAgent(r.node),
|
|
95
|
+
untrusted_source: r.untrustedSource ?? isUntrustedForAgent(r.node),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Shared multi-scope input (composed into a namespace string in fixed
|
|
99
|
+
// order user -> agent -> run; hierarchical match by default).
|
|
100
|
+
const scopeInputSchema = z
|
|
101
|
+
.object({
|
|
102
|
+
user: z.string().optional().describe("User scope key."),
|
|
103
|
+
agent: z.string().optional().describe("Agent scope key."),
|
|
104
|
+
run: z.string().optional().describe("Run/session scope key."),
|
|
105
|
+
})
|
|
106
|
+
.optional();
|
|
107
|
+
/** Annotation profile for a pure read: no side effects, repeatable. */
|
|
108
|
+
const ANNOTATE_READ = {
|
|
109
|
+
readOnlyHint: true,
|
|
110
|
+
destructiveHint: false,
|
|
111
|
+
idempotentHint: true,
|
|
112
|
+
openWorldHint: false,
|
|
113
|
+
};
|
|
114
|
+
/** Annotation profile for a non-destructive write. */
|
|
115
|
+
const ANNOTATE_WRITE = {
|
|
116
|
+
readOnlyHint: false,
|
|
117
|
+
destructiveHint: false,
|
|
118
|
+
idempotentHint: false,
|
|
119
|
+
openWorldHint: false,
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Per-tool annotations, keyed by tool name. Shared by the live server
|
|
123
|
+
* registration (the wire path) and the static TOOL_METADATA below so the
|
|
124
|
+
* two can never disagree.
|
|
125
|
+
*/
|
|
126
|
+
export const TOOL_ANNOTATIONS = {
|
|
127
|
+
// Creates a new memory row: write, non-destructive, not idempotent.
|
|
128
|
+
memos_store: ANNOTATE_WRITE,
|
|
129
|
+
memos_search: ANNOTATE_READ,
|
|
130
|
+
memos_retrieve: ANNOTATE_READ,
|
|
131
|
+
// Deletes a memory by ID. Re-running with the same ID reaches the same
|
|
132
|
+
// end state, so it is also idempotent.
|
|
133
|
+
memos_forget: {
|
|
134
|
+
readOnlyHint: false,
|
|
135
|
+
destructiveHint: true,
|
|
136
|
+
idempotentHint: true,
|
|
137
|
+
openWorldHint: false,
|
|
138
|
+
},
|
|
139
|
+
// Creates a typed edge between two memories.
|
|
140
|
+
memos_link: ANNOTATE_WRITE,
|
|
141
|
+
// Releases a memory from quarantine (reversible state change).
|
|
142
|
+
memos_quarantine_release: ANNOTATE_WRITE,
|
|
143
|
+
memos_graph: ANNOTATE_READ,
|
|
144
|
+
memos_context: ANNOTATE_READ,
|
|
145
|
+
memos_context_pack: ANNOTATE_READ,
|
|
146
|
+
memos_search_temporal: ANNOTATE_READ,
|
|
147
|
+
// Setting the same validity window twice reaches the same end state.
|
|
148
|
+
memos_set_validity: {
|
|
149
|
+
readOnlyHint: false,
|
|
150
|
+
destructiveHint: false,
|
|
151
|
+
idempotentHint: true,
|
|
152
|
+
openWorldHint: false,
|
|
153
|
+
},
|
|
154
|
+
// Marks a memory historical (add-only; never deletes).
|
|
155
|
+
memos_supersede: ANNOTATE_WRITE,
|
|
156
|
+
memos_set_trust: ANNOTATE_WRITE,
|
|
157
|
+
// Image memory: `add` writes a node plus a blob (content-addressed,
|
|
158
|
+
// so re-storing identical bytes is a no-op → idempotent);
|
|
159
|
+
// `recall`/`search` are pure reads.
|
|
160
|
+
memos_image_add: {
|
|
161
|
+
readOnlyHint: false,
|
|
162
|
+
destructiveHint: false,
|
|
163
|
+
idempotentHint: true,
|
|
164
|
+
openWorldHint: false,
|
|
165
|
+
},
|
|
166
|
+
memos_image_recall: ANNOTATE_READ,
|
|
167
|
+
memos_image_search: ANNOTATE_READ,
|
|
168
|
+
// Pure read by default, but autoStore=true writes — so not read-only.
|
|
169
|
+
memos_extract_facts: ANNOTATE_WRITE,
|
|
170
|
+
memos_diagnostics: ANNOTATE_READ,
|
|
171
|
+
// Re-embed pass: safe to re-run; purgeStale only drops vectors of other
|
|
172
|
+
// models, not memories.
|
|
173
|
+
memos_reindex: {
|
|
174
|
+
readOnlyHint: false,
|
|
175
|
+
destructiveHint: false,
|
|
176
|
+
idempotentHint: true,
|
|
177
|
+
openWorldHint: false,
|
|
178
|
+
},
|
|
179
|
+
// Merges near-duplicates, archives stale memories, supersedes decayed
|
|
180
|
+
// ones. State-changing and not repeatable (dryRun=true skips the writes).
|
|
181
|
+
memos_consolidate: {
|
|
182
|
+
readOnlyHint: false,
|
|
183
|
+
destructiveHint: true,
|
|
184
|
+
idempotentHint: false,
|
|
185
|
+
openWorldHint: false,
|
|
186
|
+
},
|
|
187
|
+
memos_usage: ANNOTATE_READ,
|
|
188
|
+
memos_history: ANNOTATE_READ,
|
|
189
|
+
// Closes a validity interval and reactivates a predecessor: state-changing.
|
|
190
|
+
memos_revert: {
|
|
191
|
+
readOnlyHint: false,
|
|
192
|
+
destructiveHint: true,
|
|
193
|
+
idempotentHint: false,
|
|
194
|
+
openWorldHint: false,
|
|
195
|
+
},
|
|
196
|
+
memos_reminders: ANNOTATE_READ,
|
|
197
|
+
};
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// Tool registration
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
function registerTools(server, memos) {
|
|
202
|
+
server.registerTool("memos_store", {
|
|
203
|
+
title: "Store Memory",
|
|
204
|
+
annotations: TOOL_ANNOTATIONS.memos_store,
|
|
205
|
+
description: "Store a durable local memory in MemOS.",
|
|
206
|
+
inputSchema: z.object({
|
|
207
|
+
content: z.string().describe("Memory text to store."),
|
|
208
|
+
type: z
|
|
209
|
+
.string()
|
|
210
|
+
.optional()
|
|
211
|
+
.describe("Memory type, such as fact, preference, or context."),
|
|
212
|
+
tags: z
|
|
213
|
+
.array(z.string())
|
|
214
|
+
.optional()
|
|
215
|
+
.describe("Optional tags for filtering."),
|
|
216
|
+
ttl: z.number().optional().describe("Optional expiration in seconds."),
|
|
217
|
+
namespace: z
|
|
218
|
+
.string()
|
|
219
|
+
.optional()
|
|
220
|
+
.describe("Optional namespace when experimental namespaces are enabled."),
|
|
221
|
+
pool: z
|
|
222
|
+
.enum(["event", "note", "procedure"])
|
|
223
|
+
.optional()
|
|
224
|
+
.describe("Retrieval pool. Default event (raw statements). Use procedure " +
|
|
225
|
+
"for workflow/how-to knowledge."),
|
|
226
|
+
scope: scopeInputSchema,
|
|
227
|
+
context: z
|
|
228
|
+
.string()
|
|
229
|
+
.optional()
|
|
230
|
+
.describe("One line of situational context (contextual retrieval) — " +
|
|
231
|
+
"prepended to the embedded text and shown in context packs."),
|
|
232
|
+
provenance: z
|
|
233
|
+
.enum(["user-verified", "user", "tool-output", "chat", "imported"])
|
|
234
|
+
.optional()
|
|
235
|
+
.describe('Provenance tier override. Pass "tool-output" when storing ' +
|
|
236
|
+
'results returned by tools, "imported" for bulk imports. ' +
|
|
237
|
+
"Defaults from the content's origin when omitted."),
|
|
238
|
+
filter_retain: z
|
|
239
|
+
.boolean()
|
|
240
|
+
.optional()
|
|
241
|
+
.describe("Run the write-side salience triage before storing: trivial " +
|
|
242
|
+
"acknowledgements and near-duplicates are skipped (the tool " +
|
|
243
|
+
"returns a skip notice instead of a node). Default false — " +
|
|
244
|
+
"agent writes are never silently dropped unless opted in."),
|
|
245
|
+
}),
|
|
246
|
+
outputSchema: z.object({
|
|
247
|
+
node: memoryNodeSchema,
|
|
248
|
+
links: z.array(memoryEdgeSchema),
|
|
249
|
+
}),
|
|
250
|
+
}, async ({ content, type, tags, ttl, namespace, pool, context, scope, provenance, filter_retain, }) => {
|
|
251
|
+
let stored;
|
|
252
|
+
try {
|
|
253
|
+
stored = await memos.store(content, {
|
|
254
|
+
type: type ?? "fact",
|
|
255
|
+
...(tags ? { tags } : {}),
|
|
256
|
+
...(ttl !== undefined ? { ttl } : {}),
|
|
257
|
+
...(namespace ? { namespace } : {}),
|
|
258
|
+
...(pool ? { pool } : {}),
|
|
259
|
+
...(context ? { context } : {}),
|
|
260
|
+
...(scope ? { scope } : {}),
|
|
261
|
+
...(provenance ? { provenance } : {}),
|
|
262
|
+
...(filter_retain ? { filterRetain: true } : {}),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
// Salience triage skip: surface as a plain-text notice, not a
|
|
267
|
+
// protocol error — the write was judged not worth keeping.
|
|
268
|
+
if (filter_retain &&
|
|
269
|
+
err instanceof Error &&
|
|
270
|
+
err.name === "MemorySkippedError") {
|
|
271
|
+
return {
|
|
272
|
+
content: [
|
|
273
|
+
{
|
|
274
|
+
type: "text",
|
|
275
|
+
text: `Skipped by the salience triage (${err.message}). ` +
|
|
276
|
+
`Re-send with filter_retain=false to store it anyway.`,
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
structuredContent: { skipped: true, reason: err.message },
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
throw err;
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
content: [
|
|
286
|
+
{ type: "text", text: `Stored memory ${stored.node.id}.` },
|
|
287
|
+
],
|
|
288
|
+
structuredContent: stored,
|
|
289
|
+
};
|
|
290
|
+
});
|
|
291
|
+
server.registerTool("memos_search", {
|
|
292
|
+
title: "Search Memories",
|
|
293
|
+
annotations: TOOL_ANNOTATIONS.memos_search,
|
|
294
|
+
description: "Search local memories by full-text query. Pass compact: true for token-lean output. " +
|
|
295
|
+
"Results carry provenance trust flags: `provenance` (tier), `citation` ([mem:hex] token " +
|
|
296
|
+
"tracing the memory to its source episode), and `untrusted_source: true` for low-trust " +
|
|
297
|
+
"channels (imported/tool-output tiers, quarantined-then-released) — confirm those with " +
|
|
298
|
+
"the user instead of silently injecting them as context.",
|
|
299
|
+
inputSchema: z.object({
|
|
300
|
+
query: z.string().describe("Search query."),
|
|
301
|
+
limit: z.number().optional().describe("Maximum result count."),
|
|
302
|
+
tags: z.array(z.string()).optional().describe("Optional tag filter."),
|
|
303
|
+
namespace: z.string().optional().describe("Optional namespace filter."),
|
|
304
|
+
provenance: z
|
|
305
|
+
.enum(["user-verified", "user", "tool-output", "chat", "imported"])
|
|
306
|
+
.optional()
|
|
307
|
+
.describe("Filter by provenance tier."),
|
|
308
|
+
includeQuarantined: z
|
|
309
|
+
.boolean()
|
|
310
|
+
.optional()
|
|
311
|
+
.describe("Include quarantined memories in results. Default false " +
|
|
312
|
+
"(quarantined memories are excluded from recall)."),
|
|
313
|
+
pool: z
|
|
314
|
+
.union([
|
|
315
|
+
z.enum(["event", "note", "procedure"]),
|
|
316
|
+
z.array(z.enum(["event", "note", "procedure"])),
|
|
317
|
+
])
|
|
318
|
+
.optional()
|
|
319
|
+
.describe("Filter by retrieval pool (event, note, or procedure)."),
|
|
320
|
+
scope: scopeInputSchema,
|
|
321
|
+
namespaces: z
|
|
322
|
+
.array(z.string())
|
|
323
|
+
.optional()
|
|
324
|
+
.describe("Read across several raw namespaces at once (e.g. " +
|
|
325
|
+
'["project:alpha", "project:beta"]). The shared `default` ' +
|
|
326
|
+
"namespace is always unioned in, like `scope`."),
|
|
327
|
+
harness: z
|
|
328
|
+
.string()
|
|
329
|
+
.optional()
|
|
330
|
+
.describe("Filter by authoring harness (e.g. claude-code, cline, codex). " +
|
|
331
|
+
'Default "all": cross-harness recall.'),
|
|
332
|
+
compact: z
|
|
333
|
+
.boolean()
|
|
334
|
+
.optional()
|
|
335
|
+
.describe("Return trimmed results (id, content, score, type, tags) " +
|
|
336
|
+
"instead of full node objects — far fewer tokens."),
|
|
337
|
+
}),
|
|
338
|
+
outputSchema: z.union([
|
|
339
|
+
z.object({ results: z.array(scoredMemorySchema) }),
|
|
340
|
+
z.object({
|
|
341
|
+
results: z.array(z.object({
|
|
342
|
+
id: z.string(),
|
|
343
|
+
content: z.string(),
|
|
344
|
+
score: z.number(),
|
|
345
|
+
type: z.string().optional(),
|
|
346
|
+
tags: z.array(z.string()).optional(),
|
|
347
|
+
provenance: z.string().optional(),
|
|
348
|
+
citation: z.string().optional(),
|
|
349
|
+
harness: z.string().optional(),
|
|
350
|
+
untrusted_source: z.boolean().optional(),
|
|
351
|
+
})),
|
|
352
|
+
compact: z.literal(true),
|
|
353
|
+
}),
|
|
354
|
+
]),
|
|
355
|
+
}, async ({ query, limit, tags, namespace, provenance, includeQuarantined, pool, scope, namespaces, harness, compact, }) => {
|
|
356
|
+
const found = await memos.search({
|
|
357
|
+
query,
|
|
358
|
+
limit: limit ?? 10,
|
|
359
|
+
...(tags ? { tags } : {}),
|
|
360
|
+
...(namespace ? { namespace } : {}),
|
|
361
|
+
...(provenance ? { provenance } : {}),
|
|
362
|
+
...(includeQuarantined !== undefined ? { includeQuarantined } : {}),
|
|
363
|
+
...(pool !== undefined ? { pool } : {}),
|
|
364
|
+
...(scope ? { scope } : {}),
|
|
365
|
+
...(namespaces && namespaces.length > 0 ? { namespaces } : {}),
|
|
366
|
+
...(harness ? { harness } : {}),
|
|
367
|
+
});
|
|
368
|
+
// Read-time trust policy: flag low-trust channels so the host
|
|
369
|
+
// agent confirms instead of silently injecting them as context.
|
|
370
|
+
const flagged = decorateTrustFlags(found);
|
|
371
|
+
if (compact) {
|
|
372
|
+
const trimmed = flagged.map((r) => ({
|
|
373
|
+
id: r.node.id,
|
|
374
|
+
content: r.node.content,
|
|
375
|
+
score: Number(r.score.toFixed(4)),
|
|
376
|
+
...(r.node.type ? { type: r.node.type } : {}),
|
|
377
|
+
...(r.node.tags.length > 0 ? { tags: r.node.tags } : {}),
|
|
378
|
+
provenance: r.provenance ?? r.node.provenance,
|
|
379
|
+
citation: r.citation ?? citationToken(r.node.id),
|
|
380
|
+
harness: r.node.harness ?? "unknown",
|
|
381
|
+
untrusted_source: r.untrustedSource ?? false,
|
|
382
|
+
}));
|
|
383
|
+
return {
|
|
384
|
+
content: [
|
|
385
|
+
{
|
|
386
|
+
type: "text",
|
|
387
|
+
text: trimmed
|
|
388
|
+
.map((r) => `[${r.id.slice(0, 8)}] (${r.score}) ${r.type ?? "fact"}${r.untrusted_source ? ` [untrusted:${r.provenance}]` : ""}: ${r.content}`)
|
|
389
|
+
.join("\n") || "No memories found.",
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
structuredContent: { results: trimmed, compact: true },
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
content: [
|
|
397
|
+
{ type: "text", text: `Found ${flagged.length} memories.` },
|
|
398
|
+
],
|
|
399
|
+
structuredContent: { results: flagged.map(toMcpResult) },
|
|
400
|
+
};
|
|
401
|
+
});
|
|
402
|
+
server.registerTool("memos_retrieve", {
|
|
403
|
+
title: "Retrieve Memory",
|
|
404
|
+
annotations: TOOL_ANNOTATIONS.memos_retrieve,
|
|
405
|
+
description: "Retrieve one memory by ID.",
|
|
406
|
+
inputSchema: z.object({
|
|
407
|
+
id: z.string().describe("Memory ID."),
|
|
408
|
+
}),
|
|
409
|
+
outputSchema: z.object({
|
|
410
|
+
node: memoryNodeSchema.nullable(),
|
|
411
|
+
}),
|
|
412
|
+
}, async ({ id }) => {
|
|
413
|
+
const node = await memos.retrieve(id);
|
|
414
|
+
return {
|
|
415
|
+
content: [
|
|
416
|
+
{
|
|
417
|
+
type: "text",
|
|
418
|
+
text: node ? node.content : `Memory not found: ${id}`,
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
structuredContent: { node },
|
|
422
|
+
};
|
|
423
|
+
});
|
|
424
|
+
server.registerTool("memos_forget", {
|
|
425
|
+
title: "Forget Memory",
|
|
426
|
+
annotations: TOOL_ANNOTATIONS.memos_forget,
|
|
427
|
+
description: "Delete one memory by ID.",
|
|
428
|
+
inputSchema: z.object({
|
|
429
|
+
id: z.string().describe("Memory ID."),
|
|
430
|
+
}),
|
|
431
|
+
outputSchema: z.object({
|
|
432
|
+
id: z.string(),
|
|
433
|
+
deleted: z.boolean(),
|
|
434
|
+
}),
|
|
435
|
+
}, async ({ id }) => {
|
|
436
|
+
const deleted = await memos.forget(id);
|
|
437
|
+
return {
|
|
438
|
+
content: [
|
|
439
|
+
{
|
|
440
|
+
type: "text",
|
|
441
|
+
text: deleted ? `Forgot memory ${id}.` : `Memory not found: ${id}.`,
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
structuredContent: { id, deleted },
|
|
445
|
+
};
|
|
446
|
+
});
|
|
447
|
+
server.registerTool("memos_link", {
|
|
448
|
+
title: "Link Two Memories",
|
|
449
|
+
annotations: TOOL_ANNOTATIONS.memos_link,
|
|
450
|
+
description: "Create a typed edge between two memories (e.g. relates_to, " +
|
|
451
|
+
"supports, contradicts). Used by the MCP Apps explorer's " +
|
|
452
|
+
"UI→tool consent bridge; callable directly too.",
|
|
453
|
+
inputSchema: z.object({
|
|
454
|
+
sourceId: z.string().describe("Source memory ID."),
|
|
455
|
+
targetId: z.string().describe("Target memory ID."),
|
|
456
|
+
relation: z
|
|
457
|
+
.enum([
|
|
458
|
+
"relates_to",
|
|
459
|
+
"contradicts",
|
|
460
|
+
"supports",
|
|
461
|
+
"derived_from",
|
|
462
|
+
"part_of",
|
|
463
|
+
"temporal_precedes",
|
|
464
|
+
"custom",
|
|
465
|
+
])
|
|
466
|
+
.optional()
|
|
467
|
+
.describe("Relation type. Default relates_to."),
|
|
468
|
+
weight: z
|
|
469
|
+
.number()
|
|
470
|
+
.min(0)
|
|
471
|
+
.max(1)
|
|
472
|
+
.optional()
|
|
473
|
+
.describe("Edge weight [0,1]. Default 0.5."),
|
|
474
|
+
}),
|
|
475
|
+
outputSchema: z.object({ edge: memoryEdgeSchema }),
|
|
476
|
+
}, async ({ sourceId, targetId, relation, weight }) => {
|
|
477
|
+
const edge = await memos.link(sourceId, targetId, relation ?? "relates_to", weight ?? 0.5);
|
|
478
|
+
return {
|
|
479
|
+
content: [
|
|
480
|
+
{
|
|
481
|
+
type: "text",
|
|
482
|
+
text: `Linked ${sourceId} → ${targetId} (${edge.relation}).`,
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
structuredContent: { edge },
|
|
486
|
+
};
|
|
487
|
+
});
|
|
488
|
+
server.registerTool("memos_quarantine_release", {
|
|
489
|
+
title: "Release Memory From Quarantine",
|
|
490
|
+
annotations: TOOL_ANNOTATIONS.memos_quarantine_release,
|
|
491
|
+
description: "Release a quarantined memory back into recall. Idempotent; the " +
|
|
492
|
+
"quarantine audit trail (quarantinedAt/reason) is preserved. Used " +
|
|
493
|
+
"by the MCP Apps explorer's UI→tool consent bridge.",
|
|
494
|
+
inputSchema: z.object({
|
|
495
|
+
id: z.string().describe("Quarantined memory ID."),
|
|
496
|
+
}),
|
|
497
|
+
outputSchema: z.object({ node: memoryNodeSchema }),
|
|
498
|
+
}, async ({ id }) => {
|
|
499
|
+
const node = await memos.releaseFromQuarantine(id);
|
|
500
|
+
return {
|
|
501
|
+
content: [
|
|
502
|
+
{
|
|
503
|
+
type: "text",
|
|
504
|
+
text: `Released ${id} from quarantine.`,
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
structuredContent: { node },
|
|
508
|
+
};
|
|
509
|
+
});
|
|
510
|
+
server.registerTool("memos_graph", {
|
|
511
|
+
title: "Memory Graph",
|
|
512
|
+
annotations: TOOL_ANNOTATIONS.memos_graph,
|
|
513
|
+
description: "Return the current memory graph.",
|
|
514
|
+
inputSchema: z.object({}),
|
|
515
|
+
outputSchema: z.object({
|
|
516
|
+
nodes: z.array(memoryNodeSchema),
|
|
517
|
+
edges: z.array(memoryEdgeSchema),
|
|
518
|
+
}),
|
|
519
|
+
}, async () => {
|
|
520
|
+
const graph = await memos.getGraph();
|
|
521
|
+
return {
|
|
522
|
+
content: [
|
|
523
|
+
{
|
|
524
|
+
type: "text",
|
|
525
|
+
text: `Memory graph has ${graph.nodes.length} nodes and ${graph.edges.length} edges.`,
|
|
526
|
+
},
|
|
527
|
+
],
|
|
528
|
+
structuredContent: graph,
|
|
529
|
+
};
|
|
530
|
+
});
|
|
531
|
+
server.registerTool("memos_context", {
|
|
532
|
+
title: "Memory Context",
|
|
533
|
+
annotations: TOOL_ANNOTATIONS.memos_context,
|
|
534
|
+
description: "Build graph-neighbour context around a memory.",
|
|
535
|
+
inputSchema: z.object({
|
|
536
|
+
id: z.string().describe("Memory ID."),
|
|
537
|
+
depth: z.number().optional().describe("Graph walk depth."),
|
|
538
|
+
maxChars: z
|
|
539
|
+
.number()
|
|
540
|
+
.optional()
|
|
541
|
+
.describe("Maximum returned context length."),
|
|
542
|
+
}),
|
|
543
|
+
outputSchema: z.object({
|
|
544
|
+
id: z.string(),
|
|
545
|
+
context: z.string(),
|
|
546
|
+
}),
|
|
547
|
+
}, async ({ id, depth, maxChars }) => {
|
|
548
|
+
const context = await memos.injectContext(id, depth ?? 1, maxChars ?? 2000);
|
|
549
|
+
return {
|
|
550
|
+
content: [{ type: "text", text: context }],
|
|
551
|
+
structuredContent: { id, context },
|
|
552
|
+
};
|
|
553
|
+
});
|
|
554
|
+
server.registerTool("memos_context_pack", {
|
|
555
|
+
title: "Build Context Pack",
|
|
556
|
+
annotations: TOOL_ANNOTATIONS.memos_context_pack,
|
|
557
|
+
description: "Build a token-budgeted, relevance-ranked slice of memories for " +
|
|
558
|
+
"injection into a prompt. This is the tool to use when you want " +
|
|
559
|
+
"everything the agent should remember about a topic in one call.",
|
|
560
|
+
inputSchema: z.object({
|
|
561
|
+
query: z.string().describe("What to remember about."),
|
|
562
|
+
tokenBudget: z
|
|
563
|
+
.number()
|
|
564
|
+
.optional()
|
|
565
|
+
.describe("Token budget for the pack (default 2000)."),
|
|
566
|
+
namespace: z.string().optional(),
|
|
567
|
+
scope: scopeInputSchema,
|
|
568
|
+
format: z
|
|
569
|
+
.enum(["json", "toon", "toon-compact"])
|
|
570
|
+
.optional()
|
|
571
|
+
.describe("Serialization format. Default toon-compact — ~70% fewer " +
|
|
572
|
+
"tokens than JSON with identical information; pass " +
|
|
573
|
+
'"json" for human-readable output.'),
|
|
574
|
+
includeSummary: z.boolean().optional(),
|
|
575
|
+
semanticDedup: z
|
|
576
|
+
.boolean()
|
|
577
|
+
.optional()
|
|
578
|
+
.describe("Drop near-duplicate pack items via embedding cosine."),
|
|
579
|
+
multiStream: z
|
|
580
|
+
.boolean()
|
|
581
|
+
.optional()
|
|
582
|
+
.describe("Query each granularity pool (event/note/procedure) separately " +
|
|
583
|
+
"and fuse. Default true; automatically skipped on event-only " +
|
|
584
|
+
"stores."),
|
|
585
|
+
}),
|
|
586
|
+
outputSchema: z.object({
|
|
587
|
+
pack: z.unknown(),
|
|
588
|
+
format: z.string(),
|
|
589
|
+
}),
|
|
590
|
+
}, async ({ query, tokenBudget, namespace, scope, format, includeSummary, semanticDedup, multiStream, }) => {
|
|
591
|
+
// Token-lean by default: the pack feeds an LLM, not a human.
|
|
592
|
+
const chosenFormat = format ?? "toon-compact";
|
|
593
|
+
const pack = await memos.contextPack({
|
|
594
|
+
query,
|
|
595
|
+
tokenBudget: tokenBudget ?? 2000,
|
|
596
|
+
...(namespace ? { namespace } : {}),
|
|
597
|
+
...(scope ? { scope } : {}),
|
|
598
|
+
format: chosenFormat,
|
|
599
|
+
...(includeSummary !== undefined ? { includeSummary } : {}),
|
|
600
|
+
...(semanticDedup !== undefined ? { semanticDedup } : {}),
|
|
601
|
+
...(multiStream !== undefined ? { multiStream } : {}),
|
|
602
|
+
});
|
|
603
|
+
const text = typeof pack === "string" ? pack : JSON.stringify(pack, null, 2);
|
|
604
|
+
return {
|
|
605
|
+
content: [{ type: "text", text }],
|
|
606
|
+
structuredContent: { pack, format: chosenFormat },
|
|
607
|
+
};
|
|
608
|
+
});
|
|
609
|
+
server.registerTool("memos_search_temporal", {
|
|
610
|
+
title: "Search Memories Valid At a Time",
|
|
611
|
+
annotations: TOOL_ANNOTATIONS.memos_search_temporal,
|
|
612
|
+
description: "Search memories that were valid at a specific point in time " +
|
|
613
|
+
"(unix ms). Superseded/historical memories stay queryable here.",
|
|
614
|
+
inputSchema: z.object({
|
|
615
|
+
query: z.string(),
|
|
616
|
+
atTime: z
|
|
617
|
+
.number()
|
|
618
|
+
.describe("Unix epoch MILLISECONDS to evaluate validity at."),
|
|
619
|
+
limit: z.number().optional(),
|
|
620
|
+
namespace: z.string().optional(),
|
|
621
|
+
}),
|
|
622
|
+
outputSchema: z.object({ results: z.array(scoredMemorySchema) }),
|
|
623
|
+
}, async ({ query, atTime, limit, namespace }) => {
|
|
624
|
+
const results = await memos.searchTemporal(query, atTime, {
|
|
625
|
+
...(limit ? { limit } : {}),
|
|
626
|
+
...(namespace ? { namespace } : {}),
|
|
627
|
+
});
|
|
628
|
+
const flagged = decorateTrustFlags(results);
|
|
629
|
+
return {
|
|
630
|
+
content: [
|
|
631
|
+
{ type: "text", text: `Found ${flagged.length} memories.` },
|
|
632
|
+
],
|
|
633
|
+
structuredContent: { results: flagged.map(toMcpResult) },
|
|
634
|
+
};
|
|
635
|
+
});
|
|
636
|
+
server.registerTool("memos_set_validity", {
|
|
637
|
+
title: "Set Memory Validity Window",
|
|
638
|
+
annotations: TOOL_ANNOTATIONS.memos_set_validity,
|
|
639
|
+
description: "Mark a memory's temporal validity window (unix ms). A validTo in " +
|
|
640
|
+
"the past makes the memory historical: excluded from default " +
|
|
641
|
+
"search, still queryable via memos_search_temporal.",
|
|
642
|
+
inputSchema: z.object({
|
|
643
|
+
id: z.string(),
|
|
644
|
+
validFrom: z.number().nullable().optional(),
|
|
645
|
+
validTo: z.number().nullable().optional(),
|
|
646
|
+
}),
|
|
647
|
+
outputSchema: z.object({ node: memoryNodeSchema.nullable() }),
|
|
648
|
+
}, async ({ id, validFrom, validTo }) => {
|
|
649
|
+
const node = await memos.setValidity(id, validFrom ?? null, validTo ?? null);
|
|
650
|
+
return {
|
|
651
|
+
content: [
|
|
652
|
+
{
|
|
653
|
+
type: "text",
|
|
654
|
+
text: node
|
|
655
|
+
? `Updated validity for ${id}.`
|
|
656
|
+
: `Memory ${id} not found.`,
|
|
657
|
+
},
|
|
658
|
+
],
|
|
659
|
+
structuredContent: { node },
|
|
660
|
+
};
|
|
661
|
+
});
|
|
662
|
+
server.registerTool("memos_supersede", {
|
|
663
|
+
title: "Supersede Memory",
|
|
664
|
+
annotations: TOOL_ANNOTATIONS.memos_supersede,
|
|
665
|
+
description: "Mark a memory as superseded (historical) and optionally link its " +
|
|
666
|
+
"replacement with a temporal_precedes edge.",
|
|
667
|
+
inputSchema: z.object({
|
|
668
|
+
id: z.string().describe("The outdated memory."),
|
|
669
|
+
replacementId: z
|
|
670
|
+
.string()
|
|
671
|
+
.optional()
|
|
672
|
+
.describe("The memory that replaces it."),
|
|
673
|
+
}),
|
|
674
|
+
outputSchema: z.object({ node: memoryNodeSchema.nullable() }),
|
|
675
|
+
}, async ({ id, replacementId }) => {
|
|
676
|
+
const node = await memos.supersede(id, replacementId);
|
|
677
|
+
return {
|
|
678
|
+
content: [
|
|
679
|
+
{
|
|
680
|
+
type: "text",
|
|
681
|
+
text: node ? `Superseded ${id}.` : `Memory ${id} not found.`,
|
|
682
|
+
},
|
|
683
|
+
],
|
|
684
|
+
structuredContent: { node },
|
|
685
|
+
};
|
|
686
|
+
});
|
|
687
|
+
server.registerTool("memos_set_trust", {
|
|
688
|
+
title: "Set Memory Trust",
|
|
689
|
+
annotations: TOOL_ANNOTATIONS.memos_set_trust,
|
|
690
|
+
description: "Set the trust score [0,1] of a memory. High-trust memories rank " +
|
|
691
|
+
"higher in hybrid search.",
|
|
692
|
+
inputSchema: z.object({
|
|
693
|
+
id: z.string(),
|
|
694
|
+
score: z.number().min(0).max(1),
|
|
695
|
+
}),
|
|
696
|
+
outputSchema: z.object({ node: memoryNodeSchema.nullable() }),
|
|
697
|
+
}, async ({ id, score }) => {
|
|
698
|
+
const node = await memos.setTrust(id, score);
|
|
699
|
+
return {
|
|
700
|
+
content: [
|
|
701
|
+
{
|
|
702
|
+
type: "text",
|
|
703
|
+
text: node
|
|
704
|
+
? `Trust of ${id} set to ${score}.`
|
|
705
|
+
: `Memory ${id} not found.`,
|
|
706
|
+
},
|
|
707
|
+
],
|
|
708
|
+
structuredContent: { node },
|
|
709
|
+
};
|
|
710
|
+
});
|
|
711
|
+
server.registerTool("memos_extract_facts", {
|
|
712
|
+
title: "Extract Facts From Conversation",
|
|
713
|
+
annotations: TOOL_ANNOTATIONS.memos_extract_facts,
|
|
714
|
+
description: "Rule-based local extraction of preferences, entities and facts " +
|
|
715
|
+
"from conversation messages, optionally storing them as memories.",
|
|
716
|
+
inputSchema: z.object({
|
|
717
|
+
messages: z
|
|
718
|
+
.array(z.object({
|
|
719
|
+
role: z.string(),
|
|
720
|
+
content: z.string(),
|
|
721
|
+
}))
|
|
722
|
+
.describe("Conversation turns to mine."),
|
|
723
|
+
autoStore: z
|
|
724
|
+
.boolean()
|
|
725
|
+
.optional()
|
|
726
|
+
.describe("Store extracted facts (default false)."),
|
|
727
|
+
minConfidence: z.number().optional(),
|
|
728
|
+
namespace: z.string().optional(),
|
|
729
|
+
}),
|
|
730
|
+
outputSchema: z.object({
|
|
731
|
+
facts: z.array(z.unknown()),
|
|
732
|
+
storedIds: z.array(z.string()),
|
|
733
|
+
}),
|
|
734
|
+
}, async ({ messages, autoStore, minConfidence, namespace }) => {
|
|
735
|
+
const result = await memos.extractFacts(messages.map((m) => ({
|
|
736
|
+
role: m.role,
|
|
737
|
+
content: m.content,
|
|
738
|
+
})), {
|
|
739
|
+
...(autoStore !== undefined ? { autoStore } : {}),
|
|
740
|
+
...(minConfidence !== undefined ? { minConfidence } : {}),
|
|
741
|
+
...(namespace ? { namespace } : {}),
|
|
742
|
+
});
|
|
743
|
+
return {
|
|
744
|
+
content: [
|
|
745
|
+
{
|
|
746
|
+
type: "text",
|
|
747
|
+
text: `Extracted ${result.facts.length} facts (${result.storedIds.length} stored).`,
|
|
748
|
+
},
|
|
749
|
+
],
|
|
750
|
+
structuredContent: { facts: result.facts, storedIds: result.storedIds },
|
|
751
|
+
};
|
|
752
|
+
});
|
|
753
|
+
server.registerTool("memos_diagnostics", {
|
|
754
|
+
title: "Memory Diagnostics",
|
|
755
|
+
annotations: TOOL_ANNOTATIONS.memos_diagnostics,
|
|
756
|
+
description: "Health report: counts, embedding coverage, temporal stats, " +
|
|
757
|
+
"storage capabilities and database size.",
|
|
758
|
+
inputSchema: z.object({}),
|
|
759
|
+
outputSchema: z.object({ diagnostics: z.unknown() }),
|
|
760
|
+
}, async () => {
|
|
761
|
+
const diagnostics = await memos.diagnostics();
|
|
762
|
+
return {
|
|
763
|
+
content: [
|
|
764
|
+
{ type: "text", text: JSON.stringify(diagnostics, null, 2) },
|
|
765
|
+
],
|
|
766
|
+
structuredContent: { diagnostics },
|
|
767
|
+
};
|
|
768
|
+
});
|
|
769
|
+
server.registerTool("memos_reindex", {
|
|
770
|
+
title: "Re-embed All Memories",
|
|
771
|
+
annotations: TOOL_ANNOTATIONS.memos_reindex,
|
|
772
|
+
description: "Re-embed every memory with the currently configured embedding " +
|
|
773
|
+
"model. Required after switching models. Can be slow on large " +
|
|
774
|
+
"stores; purgeStale also deletes vectors from other models.",
|
|
775
|
+
inputSchema: z.object({
|
|
776
|
+
purgeStale: z.boolean().optional(),
|
|
777
|
+
}),
|
|
778
|
+
outputSchema: z.object({
|
|
779
|
+
reembedded: z.number(),
|
|
780
|
+
purged: z.number(),
|
|
781
|
+
failed: z.number(),
|
|
782
|
+
model: z.string(),
|
|
783
|
+
}),
|
|
784
|
+
}, async ({ purgeStale }) => {
|
|
785
|
+
const summary = await memos.reindexEmbeddings({ purgeStale });
|
|
786
|
+
return {
|
|
787
|
+
content: [
|
|
788
|
+
{
|
|
789
|
+
type: "text",
|
|
790
|
+
text: `Re-embedded ${summary.reembedded} memories (purged ${summary.purged}, failed ${summary.failed}).`,
|
|
791
|
+
},
|
|
792
|
+
],
|
|
793
|
+
structuredContent: summary,
|
|
794
|
+
};
|
|
795
|
+
});
|
|
796
|
+
server.registerTool("memos_consolidate", {
|
|
797
|
+
title: "Consolidate Memories",
|
|
798
|
+
annotations: TOOL_ANNOTATIONS.memos_consolidate,
|
|
799
|
+
description: "Offline maintenance pass ('dreaming'): merge near-duplicates, " +
|
|
800
|
+
"archive stale low-importance memories, supersede decayed ones " +
|
|
801
|
+
"(marked historical — never deleted), and distill cluster summary " +
|
|
802
|
+
"notes into the note pool. Run periodically or while idle.",
|
|
803
|
+
inputSchema: z.object({
|
|
804
|
+
namespace: z.string().optional(),
|
|
805
|
+
dryRun: z
|
|
806
|
+
.boolean()
|
|
807
|
+
.optional()
|
|
808
|
+
.describe("Report what would happen without mutating anything."),
|
|
809
|
+
summarize: z
|
|
810
|
+
.boolean()
|
|
811
|
+
.optional()
|
|
812
|
+
.describe("Generate cluster summary notes. Default true."),
|
|
813
|
+
decay: z
|
|
814
|
+
.boolean()
|
|
815
|
+
.optional()
|
|
816
|
+
.describe("Run decay-based forgetting. Default true."),
|
|
817
|
+
decayHalfLifeDays: z
|
|
818
|
+
.number()
|
|
819
|
+
.optional()
|
|
820
|
+
.describe("Recency half-life in days for the retention score."),
|
|
821
|
+
minRetentionScore: z
|
|
822
|
+
.number()
|
|
823
|
+
.optional()
|
|
824
|
+
.describe("Retention scores below this supersede the memory."),
|
|
825
|
+
olderThanDays: z
|
|
826
|
+
.number()
|
|
827
|
+
.optional()
|
|
828
|
+
.describe("Only memories untouched this long are decay-eligible."),
|
|
829
|
+
}),
|
|
830
|
+
outputSchema: z.object({ report: z.unknown() }),
|
|
831
|
+
}, async (args) => {
|
|
832
|
+
const report = await memos.consolidate({
|
|
833
|
+
...(args.namespace ? { namespace: args.namespace } : {}),
|
|
834
|
+
...(args.dryRun !== undefined ? { dryRun: args.dryRun } : {}),
|
|
835
|
+
...(args.summarize !== undefined ? { summarize: args.summarize } : {}),
|
|
836
|
+
...(args.decay !== undefined ? { decay: args.decay } : {}),
|
|
837
|
+
...(args.decayHalfLifeDays !== undefined
|
|
838
|
+
? { decayHalfLifeDays: args.decayHalfLifeDays }
|
|
839
|
+
: {}),
|
|
840
|
+
...(args.minRetentionScore !== undefined
|
|
841
|
+
? { minRetentionScore: args.minRetentionScore }
|
|
842
|
+
: {}),
|
|
843
|
+
...(args.olderThanDays !== undefined
|
|
844
|
+
? { olderThanDays: args.olderThanDays }
|
|
845
|
+
: {}),
|
|
846
|
+
});
|
|
847
|
+
const text = `Consolidated: ${report.merges.length} merged, ${report.moves.length} archived, ${report.decayed.length} decayed, ${report.clusters.length} note(s)${report.dryRun ? " (dry run — nothing changed)" : ""}.`;
|
|
848
|
+
return {
|
|
849
|
+
content: [{ type: "text", text }],
|
|
850
|
+
structuredContent: { report },
|
|
851
|
+
};
|
|
852
|
+
});
|
|
853
|
+
server.registerTool("memos_usage", {
|
|
854
|
+
title: "Token Savings Telemetry",
|
|
855
|
+
annotations: TOOL_ANNOTATIONS.memos_usage,
|
|
856
|
+
description: "Lifetime context-pack token telemetry for this server process: " +
|
|
857
|
+
"packs built, tokens actually injected, the naive raw-JSON " +
|
|
858
|
+
"baseline for the same candidates, and the savings percentage.",
|
|
859
|
+
inputSchema: z.object({}),
|
|
860
|
+
outputSchema: z.object({ usage: z.unknown() }),
|
|
861
|
+
}, async () => {
|
|
862
|
+
const usage = memos.usageStats();
|
|
863
|
+
return {
|
|
864
|
+
content: [
|
|
865
|
+
{
|
|
866
|
+
type: "text",
|
|
867
|
+
text: `${usage.packsBuilt} pack(s) · ${usage.packTokens} tok injected vs ${usage.naiveBaselineTokens} naive (${usage.savedPct}% saved).`,
|
|
868
|
+
},
|
|
869
|
+
],
|
|
870
|
+
structuredContent: { usage },
|
|
871
|
+
};
|
|
872
|
+
});
|
|
873
|
+
server.registerTool("memos_history", {
|
|
874
|
+
title: "Memory Version Timeline",
|
|
875
|
+
annotations: TOOL_ANNOTATIONS.memos_history,
|
|
876
|
+
description: "Audit timeline for one memory: the versions it superseded, the " +
|
|
877
|
+
"versions that superseded it, and consolidated notes derived from " +
|
|
878
|
+
"it. Use when recalling something that may be outdated and you " +
|
|
879
|
+
"need the full history.",
|
|
880
|
+
inputSchema: z.object({
|
|
881
|
+
id: z.string().describe("Memory ID."),
|
|
882
|
+
threshold: z
|
|
883
|
+
.number()
|
|
884
|
+
.optional()
|
|
885
|
+
.describe("Similarity threshold for related versions (default 0.8)."),
|
|
886
|
+
limit: z
|
|
887
|
+
.number()
|
|
888
|
+
.optional()
|
|
889
|
+
.describe("Cap on related versions per direction (default 10)."),
|
|
890
|
+
}),
|
|
891
|
+
outputSchema: z.object({ history: z.unknown() }),
|
|
892
|
+
}, async ({ id, threshold, limit }) => {
|
|
893
|
+
const history = await memos.history(id, {
|
|
894
|
+
...(threshold !== undefined ? { threshold } : {}),
|
|
895
|
+
...(limit !== undefined ? { limit } : {}),
|
|
896
|
+
});
|
|
897
|
+
const text = `${history.supersedes.length} older / ${history.supersededBy.length} newer version(s) · ${history.derivedNotes.length} derived note(s).`;
|
|
898
|
+
return {
|
|
899
|
+
content: [{ type: "text", text }],
|
|
900
|
+
structuredContent: { history },
|
|
901
|
+
};
|
|
902
|
+
});
|
|
903
|
+
server.registerTool("memos_revert", {
|
|
904
|
+
title: "Revert Memory to Previous Version",
|
|
905
|
+
annotations: TOOL_ANNOTATIONS.memos_revert,
|
|
906
|
+
description: "Command-driven belief revision: revert a memory to the version " +
|
|
907
|
+
'it superseded ("revert that", "undo what I just told you", ' +
|
|
908
|
+
'"that last thing was wrong"). Closes the target\'s validity ' +
|
|
909
|
+
"interval and reactivates the predecessor — the reverted-away " +
|
|
910
|
+
"version stays in history (add-only, never deleted), and the " +
|
|
911
|
+
"revert is recorded as an audit event. " +
|
|
912
|
+
"CONFIRM SCOPE FIRST: call with dryRun=true (the default) and show " +
|
|
913
|
+
"the would-be target to the user; only call with dryRun=false " +
|
|
914
|
+
"after they confirm.",
|
|
915
|
+
inputSchema: z.object({
|
|
916
|
+
text: z
|
|
917
|
+
.string()
|
|
918
|
+
.optional()
|
|
919
|
+
.describe("Natural-language revert request, e.g. 'revert that' or " +
|
|
920
|
+
"'revert what I said about the dentist'."),
|
|
921
|
+
id: z.string().optional().describe("Memory ID to revert directly."),
|
|
922
|
+
last: z
|
|
923
|
+
.boolean()
|
|
924
|
+
.optional()
|
|
925
|
+
.describe("Revert the most recent memory in the namespace."),
|
|
926
|
+
about: z
|
|
927
|
+
.string()
|
|
928
|
+
.optional()
|
|
929
|
+
.describe("Entity/topic to resolve the target memory by."),
|
|
930
|
+
dryRun: z
|
|
931
|
+
.boolean()
|
|
932
|
+
.optional()
|
|
933
|
+
.describe("Resolve and report the target without writing anything. " +
|
|
934
|
+
"Default true — confirm scope before executing."),
|
|
935
|
+
reason: z
|
|
936
|
+
.string()
|
|
937
|
+
.optional()
|
|
938
|
+
.describe("Why the revert was issued (recorded on the audit event)."),
|
|
939
|
+
actor: z
|
|
940
|
+
.string()
|
|
941
|
+
.optional()
|
|
942
|
+
.describe("Who issued the revert (recorded on the audit event)."),
|
|
943
|
+
namespace: z.string().optional().describe("Namespace (default)."),
|
|
944
|
+
}),
|
|
945
|
+
outputSchema: z.object({ revert: z.unknown() }),
|
|
946
|
+
}, async ({ text, id, last, about, dryRun, reason, actor, namespace }) => {
|
|
947
|
+
const scope = id
|
|
948
|
+
? { kind: "id", id }
|
|
949
|
+
: last
|
|
950
|
+
? { kind: "last" }
|
|
951
|
+
: about
|
|
952
|
+
? { kind: "entity", entity: about }
|
|
953
|
+
: text
|
|
954
|
+
? { kind: "text", text }
|
|
955
|
+
: null;
|
|
956
|
+
if (!scope) {
|
|
957
|
+
return {
|
|
958
|
+
content: [
|
|
959
|
+
{
|
|
960
|
+
type: "text",
|
|
961
|
+
text: "No revert target given: pass text, id, last, or about.",
|
|
962
|
+
},
|
|
963
|
+
],
|
|
964
|
+
structuredContent: { revert: { error: "missing scope" } },
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
try {
|
|
968
|
+
const result = await memos.revert(scope, {
|
|
969
|
+
dryRun: dryRun ?? true,
|
|
970
|
+
...(reason !== undefined ? { reason } : {}),
|
|
971
|
+
...(actor !== undefined ? { actor } : {}),
|
|
972
|
+
...(namespace !== undefined ? { namespace } : {}),
|
|
973
|
+
});
|
|
974
|
+
const summarize = (n) => ({
|
|
975
|
+
id: n.id,
|
|
976
|
+
summary: n.content.slice(0, 120),
|
|
977
|
+
createdAt: n.createdAt,
|
|
978
|
+
});
|
|
979
|
+
const textOut = result.dryRun
|
|
980
|
+
? `Dry run — would revert ${result.target.id.slice(0, 8)} ("${result.target.content.slice(0, 60)}") and restore predecessor ${result.predecessor.id.slice(0, 8)}. Confirm with dryRun=false to execute.`
|
|
981
|
+
: `Reverted ${result.target.id.slice(0, 8)} — predecessor ${result.predecessor.id.slice(0, 8)} is now the believed version. Audit event: ${result.auditId}.`;
|
|
982
|
+
return {
|
|
983
|
+
content: [{ type: "text", text: textOut }],
|
|
984
|
+
structuredContent: {
|
|
985
|
+
revert: {
|
|
986
|
+
dryRun: result.dryRun,
|
|
987
|
+
target: summarize(result.target),
|
|
988
|
+
predecessor: summarize(result.predecessor),
|
|
989
|
+
alternatives: result.alternatives.map((a) => summarize(a.node)),
|
|
990
|
+
auditId: result.auditId,
|
|
991
|
+
at: result.at,
|
|
992
|
+
},
|
|
993
|
+
},
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
catch (err) {
|
|
997
|
+
const message = err.message;
|
|
998
|
+
return {
|
|
999
|
+
content: [
|
|
1000
|
+
{ type: "text", text: `Revert failed: ${message}` },
|
|
1001
|
+
],
|
|
1002
|
+
structuredContent: { revert: { error: message } },
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
server.registerTool("memos_reminders", {
|
|
1007
|
+
title: "List Event Reminders",
|
|
1008
|
+
annotations: TOOL_ANNOTATIONS.memos_reminders,
|
|
1009
|
+
description: "List scheduled event reminders from memory (deterministic " +
|
|
1010
|
+
"temporal event memory — no LLM involved). Events are extracted " +
|
|
1011
|
+
'at store() time from utterances like "I have a meeting tomorrow", ' +
|
|
1012
|
+
'and natural-language updates ("that meeting got moved 3 days ' +
|
|
1013
|
+
'later", "the meeting is cancelled") reschedule or cancel them ' +
|
|
1014
|
+
"across harnesses sharing the same DB file. " +
|
|
1015
|
+
"HARNESS POLLING PATTERN: call this on the harness's own schedule " +
|
|
1016
|
+
"(e.g. each turn); MemOS surfaces due reminders but does NOT " +
|
|
1017
|
+
"push-notify — delivery is the harness's job.",
|
|
1018
|
+
inputSchema: z.object({
|
|
1019
|
+
due: z
|
|
1020
|
+
.boolean()
|
|
1021
|
+
.optional()
|
|
1022
|
+
.describe("When true, only reminders due as of now (reminder_at <= now). " +
|
|
1023
|
+
"Default false: all scheduled reminders, earliest first."),
|
|
1024
|
+
namespace: z.string().optional().describe("Namespace (default)."),
|
|
1025
|
+
}),
|
|
1026
|
+
outputSchema: z.object({ reminders: z.unknown() }),
|
|
1027
|
+
}, async ({ due, namespace }) => {
|
|
1028
|
+
const reminders = await listReminders(memos, {
|
|
1029
|
+
now: new Date(),
|
|
1030
|
+
dueOnly: due ?? false,
|
|
1031
|
+
...(namespace !== undefined ? { namespace } : {}),
|
|
1032
|
+
});
|
|
1033
|
+
const lines = reminders.map((r) => `${new Date(r.reminderAt).toLocaleString()} — ${r.label}` +
|
|
1034
|
+
(r.due ? " (due)" : ""));
|
|
1035
|
+
const text = reminders.length === 0
|
|
1036
|
+
? "No scheduled reminders."
|
|
1037
|
+
: `${reminders.length} scheduled reminder(s):\n` +
|
|
1038
|
+
lines.map((l) => `• ${l}`).join("\n");
|
|
1039
|
+
return {
|
|
1040
|
+
content: [{ type: "text", text }],
|
|
1041
|
+
structuredContent: { reminders },
|
|
1042
|
+
};
|
|
1043
|
+
});
|
|
1044
|
+
// ---- Image memory (vision) -------------------------------------------
|
|
1045
|
+
//
|
|
1046
|
+
// Three tools mirroring the CLI's `image` subcommands. They are
|
|
1047
|
+
// always registered (so the tool list is stable) but fail soft with a
|
|
1048
|
+
// plain message when vision is disabled, rather than vanishing from
|
|
1049
|
+
// the surface — an agent that sees the tool knows it exists and how
|
|
1050
|
+
// to turn it on.
|
|
1051
|
+
server.registerTool("memos_image_add", {
|
|
1052
|
+
title: "Store Image Memory",
|
|
1053
|
+
annotations: TOOL_ANNOTATIONS.memos_image_add,
|
|
1054
|
+
description: "Store an image as a durable memory. The image is written to a " +
|
|
1055
|
+
"content-addressed blob next to the database (identical bytes " +
|
|
1056
|
+
"are stored once) and a caption becomes the memory's text, so " +
|
|
1057
|
+
"ordinary search and context packs find it. Requires the MemOS " +
|
|
1058
|
+
"instance to be constructed with vision: { enabled: true }. " +
|
|
1059
|
+
"Pass `data` as base64 plus `mime`, or `path`/`url` to fetch. " +
|
|
1060
|
+
"Use a DESCRIPTIVE caption: it is what text retrieval matches, " +
|
|
1061
|
+
"and with CLIP disabled (the default) it is the only way the " +
|
|
1062
|
+
"image is found.",
|
|
1063
|
+
inputSchema: z.object({
|
|
1064
|
+
path: z
|
|
1065
|
+
.string()
|
|
1066
|
+
.optional()
|
|
1067
|
+
.describe("Filesystem path to the image (or a data: URL)."),
|
|
1068
|
+
url: z
|
|
1069
|
+
.string()
|
|
1070
|
+
.optional()
|
|
1071
|
+
.describe("http(s)/file URL to fetch the image from."),
|
|
1072
|
+
data: z
|
|
1073
|
+
.string()
|
|
1074
|
+
.optional()
|
|
1075
|
+
.describe("Base64-encoded image bytes (with `mime`)."),
|
|
1076
|
+
mime: z
|
|
1077
|
+
.string()
|
|
1078
|
+
.optional()
|
|
1079
|
+
.describe("MIME type for `data` (e.g. image/png)."),
|
|
1080
|
+
caption: z
|
|
1081
|
+
.string()
|
|
1082
|
+
.optional()
|
|
1083
|
+
.describe("What the image shows. Be specific — this is the searchable text."),
|
|
1084
|
+
kind: z
|
|
1085
|
+
.enum([
|
|
1086
|
+
"photo",
|
|
1087
|
+
"screenshot",
|
|
1088
|
+
"chart",
|
|
1089
|
+
"diagram",
|
|
1090
|
+
"memo",
|
|
1091
|
+
"meme",
|
|
1092
|
+
"other",
|
|
1093
|
+
])
|
|
1094
|
+
.optional()
|
|
1095
|
+
.describe("Coarse taxonomy. Defaults from the image format."),
|
|
1096
|
+
tags: z.array(z.string()).optional().describe("Optional filter tags."),
|
|
1097
|
+
namespace: z.string().optional().describe("Target namespace."),
|
|
1098
|
+
conversationId: z
|
|
1099
|
+
.string()
|
|
1100
|
+
.optional()
|
|
1101
|
+
.describe("Conversation this image was shared in."),
|
|
1102
|
+
}),
|
|
1103
|
+
outputSchema: z.object({
|
|
1104
|
+
node: memoryNodeSchema,
|
|
1105
|
+
ref: z.string(),
|
|
1106
|
+
kind: z.string(),
|
|
1107
|
+
width: z.number(),
|
|
1108
|
+
height: z.number(),
|
|
1109
|
+
bytes: z.number(),
|
|
1110
|
+
mime: z.string(),
|
|
1111
|
+
clipStatus: z.string(),
|
|
1112
|
+
deduped: z.boolean(),
|
|
1113
|
+
}),
|
|
1114
|
+
}, async ({ path, url, data, mime, caption, kind, tags, namespace, conversationId, }) => {
|
|
1115
|
+
const input = data !== undefined
|
|
1116
|
+
? { data, mime: mime ?? "image/png" }
|
|
1117
|
+
: url !== undefined
|
|
1118
|
+
? { url }
|
|
1119
|
+
: path;
|
|
1120
|
+
if (input === undefined) {
|
|
1121
|
+
return {
|
|
1122
|
+
content: [
|
|
1123
|
+
{
|
|
1124
|
+
type: "text",
|
|
1125
|
+
text: "Provide `path`, `url`, or `data`+`mime`.",
|
|
1126
|
+
},
|
|
1127
|
+
],
|
|
1128
|
+
isError: true,
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
try {
|
|
1132
|
+
const result = await memos.addImageMemory(input, {
|
|
1133
|
+
...(caption !== undefined ? { caption } : {}),
|
|
1134
|
+
...(kind !== undefined ? { kind } : {}),
|
|
1135
|
+
...(tags ? { tags } : {}),
|
|
1136
|
+
...(namespace !== undefined ? { namespace } : {}),
|
|
1137
|
+
...(conversationId !== undefined ? { conversationId } : {}),
|
|
1138
|
+
});
|
|
1139
|
+
const text = result.deduped
|
|
1140
|
+
? `Image already stored: ${result.image.ref} (node ${result.node.id})`
|
|
1141
|
+
: `Stored image ${result.image.ref} ` +
|
|
1142
|
+
`(${result.image.kind}, ${result.image.width}x${result.image.height}, ` +
|
|
1143
|
+
`${result.image.bytes} bytes) as node ${result.node.id}.` +
|
|
1144
|
+
(result.clipStatus !== "skipped"
|
|
1145
|
+
? ` CLIP: ${result.clipStatus}.`
|
|
1146
|
+
: "");
|
|
1147
|
+
return {
|
|
1148
|
+
content: [{ type: "text", text }],
|
|
1149
|
+
structuredContent: {
|
|
1150
|
+
node: result.node,
|
|
1151
|
+
ref: result.image.ref,
|
|
1152
|
+
kind: result.image.kind,
|
|
1153
|
+
width: result.image.width,
|
|
1154
|
+
height: result.image.height,
|
|
1155
|
+
bytes: result.image.bytes,
|
|
1156
|
+
mime: result.image.mime,
|
|
1157
|
+
clipStatus: result.clipStatus,
|
|
1158
|
+
deduped: result.deduped,
|
|
1159
|
+
},
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
catch (err) {
|
|
1163
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1164
|
+
return {
|
|
1165
|
+
content: [
|
|
1166
|
+
{ type: "text", text: `Image store failed: ${message}` },
|
|
1167
|
+
],
|
|
1168
|
+
isError: true,
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
server.registerTool("memos_image_recall", {
|
|
1173
|
+
title: "Resolve Image",
|
|
1174
|
+
annotations: TOOL_ANNOTATIONS.memos_image_recall,
|
|
1175
|
+
description: "Resolve an image reference (the `[img:...]` token a context pack " +
|
|
1176
|
+
"carries, or a memory node id) back to its bytes. Returns the " +
|
|
1177
|
+
"blob as base64 plus its MIME type so a vision-capable host can " +
|
|
1178
|
+
"actually see the image. The bytes are NEVER inlined into a " +
|
|
1179
|
+
"context pack — call this when you need them.",
|
|
1180
|
+
inputSchema: z.object({
|
|
1181
|
+
ref: z
|
|
1182
|
+
.string()
|
|
1183
|
+
.describe("Image ref (`img_<sha256[0:12]>`) or a node id."),
|
|
1184
|
+
}),
|
|
1185
|
+
outputSchema: z.object({
|
|
1186
|
+
ref: z.string(),
|
|
1187
|
+
mime: z.string(),
|
|
1188
|
+
bytes: z.number(),
|
|
1189
|
+
data: z.string(),
|
|
1190
|
+
}),
|
|
1191
|
+
}, async ({ ref }) => {
|
|
1192
|
+
const resolved = await memos.imageMemory(ref);
|
|
1193
|
+
if (!resolved) {
|
|
1194
|
+
return {
|
|
1195
|
+
content: [{ type: "text", text: `Image not found: ${ref}` }],
|
|
1196
|
+
isError: true,
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
return {
|
|
1200
|
+
content: [
|
|
1201
|
+
{
|
|
1202
|
+
type: "text",
|
|
1203
|
+
text: `${resolved.ref} — ${resolved.mime}, ${resolved.buffer.length} bytes`,
|
|
1204
|
+
},
|
|
1205
|
+
],
|
|
1206
|
+
structuredContent: {
|
|
1207
|
+
ref: resolved.ref,
|
|
1208
|
+
mime: resolved.mime,
|
|
1209
|
+
bytes: resolved.buffer.length,
|
|
1210
|
+
data: resolved.buffer.toString("base64"),
|
|
1211
|
+
},
|
|
1212
|
+
};
|
|
1213
|
+
});
|
|
1214
|
+
server.registerTool("memos_image_search", {
|
|
1215
|
+
title: "Search Images",
|
|
1216
|
+
annotations: TOOL_ANNOTATIONS.memos_image_search,
|
|
1217
|
+
description: "Search stored images by text query. The caption leg (keyword + " +
|
|
1218
|
+
"semantic) always runs; the CLIP pixel leg runs only when the " +
|
|
1219
|
+
"instance was constructed with vision.clip.enabled, and is what " +
|
|
1220
|
+
"finds an image whose caption shares no vocabulary with the " +
|
|
1221
|
+
"query. Returns refs — resolve them with memos_image_recall.",
|
|
1222
|
+
inputSchema: z.object({
|
|
1223
|
+
query: z.string().describe("What to look for."),
|
|
1224
|
+
limit: z.number().optional().describe("Maximum result count."),
|
|
1225
|
+
namespace: z.string().optional().describe("Namespace filter."),
|
|
1226
|
+
kind: z
|
|
1227
|
+
.enum([
|
|
1228
|
+
"photo",
|
|
1229
|
+
"screenshot",
|
|
1230
|
+
"chart",
|
|
1231
|
+
"diagram",
|
|
1232
|
+
"memo",
|
|
1233
|
+
"meme",
|
|
1234
|
+
"other",
|
|
1235
|
+
])
|
|
1236
|
+
.optional()
|
|
1237
|
+
.describe("Restrict to one image kind."),
|
|
1238
|
+
}),
|
|
1239
|
+
outputSchema: z.object({
|
|
1240
|
+
results: z.array(z.object({
|
|
1241
|
+
id: z.string(),
|
|
1242
|
+
ref: z.string(),
|
|
1243
|
+
kind: z.string(),
|
|
1244
|
+
width: z.number(),
|
|
1245
|
+
height: z.number(),
|
|
1246
|
+
caption: z.string(),
|
|
1247
|
+
score: z.number(),
|
|
1248
|
+
})),
|
|
1249
|
+
}),
|
|
1250
|
+
}, async ({ query, limit, namespace, kind }) => {
|
|
1251
|
+
const hits = await memos.searchImages(query, {
|
|
1252
|
+
limit: limit ?? 10,
|
|
1253
|
+
...(namespace !== undefined ? { namespace } : {}),
|
|
1254
|
+
...(kind !== undefined ? { kind } : {}),
|
|
1255
|
+
});
|
|
1256
|
+
const results = hits.map((hit) => ({
|
|
1257
|
+
id: hit.node.id,
|
|
1258
|
+
ref: hit.image.ref,
|
|
1259
|
+
kind: hit.image.kind,
|
|
1260
|
+
width: hit.image.width,
|
|
1261
|
+
height: hit.image.height,
|
|
1262
|
+
caption: hit.image.caption,
|
|
1263
|
+
score: Number(hit.score.toFixed(4)),
|
|
1264
|
+
}));
|
|
1265
|
+
const text = results.length === 0
|
|
1266
|
+
? "No matching images found."
|
|
1267
|
+
: `${results.length} image(s):\n` +
|
|
1268
|
+
results
|
|
1269
|
+
.map((r) => `• ${r.ref} [${r.kind}] (${r.score}) — ${r.caption}`)
|
|
1270
|
+
.join("\n");
|
|
1271
|
+
return {
|
|
1272
|
+
content: [{ type: "text", text }],
|
|
1273
|
+
structuredContent: { results },
|
|
1274
|
+
};
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
// ---------------------------------------------------------------------------
|
|
1278
|
+
// Resources & prompts
|
|
1279
|
+
// ---------------------------------------------------------------------------
|
|
1280
|
+
/**
|
|
1281
|
+
* Resources give MCP clients pull-based access to memory state (some
|
|
1282
|
+
* harnesses surface resources natively rather than calling tools); prompts
|
|
1283
|
+
* are reusable templates the client can launch with one argument.
|
|
1284
|
+
*/
|
|
1285
|
+
function registerResourcesAndPrompts(server, memos) {
|
|
1286
|
+
server.registerResource("context", "memos://context", {
|
|
1287
|
+
title: "Memory Brief",
|
|
1288
|
+
description: "Zero-query snapshot of what MemOS remembers: a summary plus the " +
|
|
1289
|
+
"most recent memories. Pull this at session start for instant context.",
|
|
1290
|
+
mimeType: "text/markdown",
|
|
1291
|
+
}, async (uri) => {
|
|
1292
|
+
const summary = await memos.summarize();
|
|
1293
|
+
const graph = await memos.getGraph();
|
|
1294
|
+
const recent = [...graph.nodes]
|
|
1295
|
+
.sort((a, b) => b.createdAt - a.createdAt)
|
|
1296
|
+
.slice(0, 20);
|
|
1297
|
+
const sections = [
|
|
1298
|
+
"# MemOS memory brief",
|
|
1299
|
+
"",
|
|
1300
|
+
summary,
|
|
1301
|
+
"",
|
|
1302
|
+
`## Recent memories (${recent.length})`,
|
|
1303
|
+
"",
|
|
1304
|
+
...recent.map((node) => `- [${node.type}] ${node.content} _(trust ${node.trustScore.toFixed(2)})_`),
|
|
1305
|
+
];
|
|
1306
|
+
return {
|
|
1307
|
+
contents: [
|
|
1308
|
+
{
|
|
1309
|
+
uri: uri.href,
|
|
1310
|
+
text: sections.join("\n"),
|
|
1311
|
+
mimeType: "text/markdown",
|
|
1312
|
+
},
|
|
1313
|
+
],
|
|
1314
|
+
};
|
|
1315
|
+
});
|
|
1316
|
+
server.registerResource("graph", "memos://graph", {
|
|
1317
|
+
title: "Memory Graph (Mermaid)",
|
|
1318
|
+
description: "The full memory graph — nodes are memories, edges are relations — " +
|
|
1319
|
+
"rendered as a Mermaid flowchart.",
|
|
1320
|
+
mimeType: "text/plain",
|
|
1321
|
+
}, async (uri) => {
|
|
1322
|
+
const graph = await memos.getGraph();
|
|
1323
|
+
return {
|
|
1324
|
+
contents: [
|
|
1325
|
+
{
|
|
1326
|
+
uri: uri.href,
|
|
1327
|
+
text: graphToMermaid(graph),
|
|
1328
|
+
mimeType: "text/plain",
|
|
1329
|
+
},
|
|
1330
|
+
],
|
|
1331
|
+
};
|
|
1332
|
+
});
|
|
1333
|
+
server.registerResource("explorer", new ResourceTemplate(new ExplorerUriTemplate(), { list: undefined }), {
|
|
1334
|
+
title: "MemOS Memory Explorer (MCP App)",
|
|
1335
|
+
description: "Interactive memory-graph explorer (MCP App): nodes colored by " +
|
|
1336
|
+
"provenance tier, edges labeled by relation, plus a bitemporal " +
|
|
1337
|
+
"as-of scrubber for time-travel. Query variables: asOf (unix ms, " +
|
|
1338
|
+
"render the graph at a past timestamp), mode (app forces the HTML " +
|
|
1339
|
+
"app, text forces the markdown fallback), nodeLimit, edgeLimit. " +
|
|
1340
|
+
"Without ?mode=app — or a host MCP Apps signal in _meta — this " +
|
|
1341
|
+
"serves a text/markdown fallback (mermaid + summary) instead of " +
|
|
1342
|
+
"HTML. Mutating UI actions arrive as intents translated to normal " +
|
|
1343
|
+
"tool calls (memos_forget, memos_link, memos_quarantine_release) " +
|
|
1344
|
+
"under the host's consent UI; the app itself cannot write.",
|
|
1345
|
+
mimeType: "text/html",
|
|
1346
|
+
}, async (uri, variables, ctx) => {
|
|
1347
|
+
const strVar = (v) => typeof v === "string" ? v : Array.isArray(v) ? String(v[0]) : null;
|
|
1348
|
+
const asOfRaw = strVar(variables.asOf);
|
|
1349
|
+
const asOf = asOfRaw !== null && asOfRaw !== "" ? Number(asOfRaw) : NaN;
|
|
1350
|
+
const hasAsOf = Number.isFinite(asOf);
|
|
1351
|
+
const nodeLimitRaw = strVar(variables.nodeLimit);
|
|
1352
|
+
const edgeLimitRaw = strVar(variables.edgeLimit);
|
|
1353
|
+
const nodeLimit = nodeLimitRaw !== null && nodeLimitRaw !== ""
|
|
1354
|
+
? Number(nodeLimitRaw)
|
|
1355
|
+
: undefined;
|
|
1356
|
+
const edgeLimit = edgeLimitRaw !== null && edgeLimitRaw !== ""
|
|
1357
|
+
? Number(edgeLimitRaw)
|
|
1358
|
+
: undefined;
|
|
1359
|
+
const graph = hasAsOf
|
|
1360
|
+
? await memos.getGraphAtTime(asOf)
|
|
1361
|
+
: await memos.getGraph();
|
|
1362
|
+
const snapshot = buildExplorerSnapshot(graph, {
|
|
1363
|
+
...(nodeLimit !== undefined ? { nodeCap: nodeLimit } : {}),
|
|
1364
|
+
...(edgeLimit !== undefined ? { edgeCap: edgeLimit } : {}),
|
|
1365
|
+
...(hasAsOf ? { asOf } : {}),
|
|
1366
|
+
});
|
|
1367
|
+
const mode = resolveExplorerMode({
|
|
1368
|
+
modeParam: strVar(variables.mode),
|
|
1369
|
+
meta: ctx.mcpReq._meta,
|
|
1370
|
+
});
|
|
1371
|
+
if (mode === "app") {
|
|
1372
|
+
return {
|
|
1373
|
+
contents: [
|
|
1374
|
+
{
|
|
1375
|
+
uri: uri.href,
|
|
1376
|
+
text: buildExplorerHtml(snapshot, {
|
|
1377
|
+
...(hasAsOf ? { asOf } : {}),
|
|
1378
|
+
}),
|
|
1379
|
+
mimeType: "text/html",
|
|
1380
|
+
},
|
|
1381
|
+
],
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
return {
|
|
1385
|
+
contents: [
|
|
1386
|
+
{
|
|
1387
|
+
uri: uri.href,
|
|
1388
|
+
text: buildExplorerFallbackMarkdown(snapshot, {
|
|
1389
|
+
...(hasAsOf ? { asOf } : {}),
|
|
1390
|
+
}),
|
|
1391
|
+
mimeType: "text/markdown",
|
|
1392
|
+
},
|
|
1393
|
+
],
|
|
1394
|
+
};
|
|
1395
|
+
});
|
|
1396
|
+
server.registerPrompt("recall", {
|
|
1397
|
+
title: "Recall Memories",
|
|
1398
|
+
description: "Search MemOS for memories about a topic and summarize findings " +
|
|
1399
|
+
"with trust and recency caveats.",
|
|
1400
|
+
argsSchema: {
|
|
1401
|
+
topic: z
|
|
1402
|
+
.string()
|
|
1403
|
+
.describe("What to recall, e.g. 'deployment preferences'."),
|
|
1404
|
+
},
|
|
1405
|
+
}, ({ topic }) => ({
|
|
1406
|
+
messages: [
|
|
1407
|
+
{
|
|
1408
|
+
role: "user",
|
|
1409
|
+
content: {
|
|
1410
|
+
type: "text",
|
|
1411
|
+
text: `Search MemOS for memories relevant to: ${topic}\n\n` +
|
|
1412
|
+
"1. Call memos_search with the topic, then refine with a " +
|
|
1413
|
+
"second query if the first pass looks thin.\n" +
|
|
1414
|
+
"2. Summarize what you found as a short bulleted list.\n" +
|
|
1415
|
+
"3. Flag each bullet's trust score and age — say explicitly " +
|
|
1416
|
+
"when a memory is old or low-trust rather than presenting it " +
|
|
1417
|
+
"as current fact.\n" +
|
|
1418
|
+
"4. If nothing relevant is stored, say so plainly.",
|
|
1419
|
+
},
|
|
1420
|
+
},
|
|
1421
|
+
],
|
|
1422
|
+
}));
|
|
1423
|
+
}
|
|
1424
|
+
// ---------------------------------------------------------------------------
|
|
1425
|
+
// Server factory
|
|
1426
|
+
// ---------------------------------------------------------------------------
|
|
1427
|
+
/**
|
|
1428
|
+
* Create a fully-configured McpServer instance for MemOS.
|
|
1429
|
+
* Exported for testing and advanced use cases.
|
|
1430
|
+
*/
|
|
1431
|
+
export function createMcpServer(memos) {
|
|
1432
|
+
const server = new McpServer({ name: "memos", version: getSdkVersion() }, {
|
|
1433
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
1434
|
+
instructions: "MemOS local-first persistent memory. PROACTIVELY store durable " +
|
|
1435
|
+
"facts (user preferences, project decisions, environment setup, " +
|
|
1436
|
+
"corrections) with memos_store, and recall them with " +
|
|
1437
|
+
"memos_context_pack before answering questions that may depend on " +
|
|
1438
|
+
"prior sessions. memos_search finds raw results; " +
|
|
1439
|
+
"memos_context_pack returns a token-budgeted slice ready for " +
|
|
1440
|
+
"prompt injection. Superseded facts stay queryable via " +
|
|
1441
|
+
"memos_search_temporal. Images: memos_image_add stores one with a " +
|
|
1442
|
+
"caption (be specific — the caption is what text search matches), " +
|
|
1443
|
+
"memos_image_search finds them, memos_image_recall resolves an " +
|
|
1444
|
+
"[img:...] ref to bytes. All data stays on this machine.",
|
|
1445
|
+
cacheHints: {
|
|
1446
|
+
"tools/list": { ttlMs: 300_000, cacheScope: "private" },
|
|
1447
|
+
"server/discover": { ttlMs: 600_000, cacheScope: "private" },
|
|
1448
|
+
},
|
|
1449
|
+
});
|
|
1450
|
+
registerTools(server, memos);
|
|
1451
|
+
registerResourcesAndPrompts(server, memos);
|
|
1452
|
+
return server;
|
|
1453
|
+
}
|
|
1454
|
+
// ---------------------------------------------------------------------------
|
|
1455
|
+
// Entry point
|
|
1456
|
+
// ---------------------------------------------------------------------------
|
|
1457
|
+
/**
|
|
1458
|
+
* Run the MemOS MCP server over stdio.
|
|
1459
|
+
*
|
|
1460
|
+
* Speaks MCP protocol revision 2026-07-28 (modern stateless core with
|
|
1461
|
+
* `server/discover`) and falls back to the legacy 2025-era `initialize`
|
|
1462
|
+
* handshake for older clients. Blocks until the connection closes.
|
|
1463
|
+
*/
|
|
1464
|
+
export async function runMcpServer(config = {}) {
|
|
1465
|
+
const memos = new MemOS({
|
|
1466
|
+
// Embeddings default to on anyway; stating it here keeps the MCP
|
|
1467
|
+
// server's semantic search working even if the SDK default changes.
|
|
1468
|
+
embeddings: { enabled: true },
|
|
1469
|
+
...config,
|
|
1470
|
+
experimental: {
|
|
1471
|
+
graphViz: true,
|
|
1472
|
+
namespaces: true,
|
|
1473
|
+
contextInjection: true,
|
|
1474
|
+
...config.experimental,
|
|
1475
|
+
},
|
|
1476
|
+
});
|
|
1477
|
+
await memos.init();
|
|
1478
|
+
const handle = serveStdio(() => createMcpServer(memos));
|
|
1479
|
+
let cleaned = false;
|
|
1480
|
+
const cleanup = async () => {
|
|
1481
|
+
if (cleaned)
|
|
1482
|
+
return;
|
|
1483
|
+
cleaned = true;
|
|
1484
|
+
await handle.close();
|
|
1485
|
+
await memos.close();
|
|
1486
|
+
};
|
|
1487
|
+
process.once("SIGINT", () => {
|
|
1488
|
+
void cleanup().then(() => process.exit(0));
|
|
1489
|
+
});
|
|
1490
|
+
// Block until stdin closes (client disconnects). The StdioServerTransport
|
|
1491
|
+
// keeps the event loop alive via its stdin "data" listener; when the
|
|
1492
|
+
// client disconnects, stdin emits "end" and we clean up.
|
|
1493
|
+
await new Promise((resolve) => {
|
|
1494
|
+
process.stdin.once("end", () => resolve());
|
|
1495
|
+
});
|
|
1496
|
+
await cleanup();
|
|
1497
|
+
}
|
|
1498
|
+
const TOOL_METADATA = [
|
|
1499
|
+
{
|
|
1500
|
+
name: "memos_store",
|
|
1501
|
+
annotations: TOOL_ANNOTATIONS.memos_store,
|
|
1502
|
+
description: "Store a durable local memory in MemOS.",
|
|
1503
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1504
|
+
content: z.string(),
|
|
1505
|
+
type: z.string().optional(),
|
|
1506
|
+
tags: z.array(z.string()).optional(),
|
|
1507
|
+
ttl: z.number().optional(),
|
|
1508
|
+
namespace: z.string().optional(),
|
|
1509
|
+
})),
|
|
1510
|
+
outputSchema: z.toJSONSchema(z.object({ node: memoryNodeSchema, links: z.array(memoryEdgeSchema) })),
|
|
1511
|
+
},
|
|
1512
|
+
{
|
|
1513
|
+
name: "memos_search",
|
|
1514
|
+
annotations: TOOL_ANNOTATIONS.memos_search,
|
|
1515
|
+
description: "Search local memories by full-text query. Pass compact: true for token-lean output.",
|
|
1516
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1517
|
+
query: z.string(),
|
|
1518
|
+
limit: z.number().optional(),
|
|
1519
|
+
tags: z.array(z.string()).optional(),
|
|
1520
|
+
namespace: z.string().optional(),
|
|
1521
|
+
})),
|
|
1522
|
+
outputSchema: z.toJSONSchema(z.object({ results: z.array(scoredMemorySchema) })),
|
|
1523
|
+
},
|
|
1524
|
+
{
|
|
1525
|
+
name: "memos_retrieve",
|
|
1526
|
+
annotations: TOOL_ANNOTATIONS.memos_retrieve,
|
|
1527
|
+
description: "Retrieve one memory by ID.",
|
|
1528
|
+
inputSchema: z.toJSONSchema(z.object({ id: z.string() })),
|
|
1529
|
+
outputSchema: z.toJSONSchema(z.object({ node: memoryNodeSchema.nullable() })),
|
|
1530
|
+
},
|
|
1531
|
+
{
|
|
1532
|
+
name: "memos_forget",
|
|
1533
|
+
annotations: TOOL_ANNOTATIONS.memos_forget,
|
|
1534
|
+
description: "Delete one memory by ID.",
|
|
1535
|
+
inputSchema: z.toJSONSchema(z.object({ id: z.string() })),
|
|
1536
|
+
outputSchema: z.toJSONSchema(z.object({ id: z.string(), deleted: z.boolean() })),
|
|
1537
|
+
},
|
|
1538
|
+
{
|
|
1539
|
+
name: "memos_link",
|
|
1540
|
+
annotations: TOOL_ANNOTATIONS.memos_link,
|
|
1541
|
+
description: "Create a typed edge between two memories.",
|
|
1542
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1543
|
+
sourceId: z.string(),
|
|
1544
|
+
targetId: z.string(),
|
|
1545
|
+
relation: z
|
|
1546
|
+
.enum([
|
|
1547
|
+
"relates_to",
|
|
1548
|
+
"contradicts",
|
|
1549
|
+
"supports",
|
|
1550
|
+
"derived_from",
|
|
1551
|
+
"part_of",
|
|
1552
|
+
"temporal_precedes",
|
|
1553
|
+
"custom",
|
|
1554
|
+
])
|
|
1555
|
+
.optional(),
|
|
1556
|
+
weight: z.number().min(0).max(1).optional(),
|
|
1557
|
+
})),
|
|
1558
|
+
outputSchema: z.toJSONSchema(z.object({ edge: memoryEdgeSchema })),
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
name: "memos_quarantine_release",
|
|
1562
|
+
annotations: TOOL_ANNOTATIONS.memos_quarantine_release,
|
|
1563
|
+
description: "Release a quarantined memory back into recall.",
|
|
1564
|
+
inputSchema: z.toJSONSchema(z.object({ id: z.string() })),
|
|
1565
|
+
outputSchema: z.toJSONSchema(z.object({ node: memoryNodeSchema })),
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
name: "memos_graph",
|
|
1569
|
+
annotations: TOOL_ANNOTATIONS.memos_graph,
|
|
1570
|
+
description: "Return the current memory graph.",
|
|
1571
|
+
inputSchema: z.toJSONSchema(z.object({})),
|
|
1572
|
+
outputSchema: z.toJSONSchema(z.object({
|
|
1573
|
+
nodes: z.array(memoryNodeSchema),
|
|
1574
|
+
edges: z.array(memoryEdgeSchema),
|
|
1575
|
+
})),
|
|
1576
|
+
},
|
|
1577
|
+
{
|
|
1578
|
+
name: "memos_context",
|
|
1579
|
+
annotations: TOOL_ANNOTATIONS.memos_context,
|
|
1580
|
+
description: "Build graph-neighbour context around a memory.",
|
|
1581
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1582
|
+
id: z.string(),
|
|
1583
|
+
depth: z.number().optional(),
|
|
1584
|
+
maxChars: z.number().optional(),
|
|
1585
|
+
})),
|
|
1586
|
+
outputSchema: z.toJSONSchema(z.object({ id: z.string(), context: z.string() })),
|
|
1587
|
+
},
|
|
1588
|
+
{
|
|
1589
|
+
name: "memos_context_pack",
|
|
1590
|
+
annotations: TOOL_ANNOTATIONS.memos_context_pack,
|
|
1591
|
+
description: "Build a token-budgeted, relevance-ranked slice of memories for " +
|
|
1592
|
+
"injection into a prompt. This is the tool to use when you want " +
|
|
1593
|
+
"everything the agent should remember about a topic in one call.",
|
|
1594
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1595
|
+
query: z.string(),
|
|
1596
|
+
tokenBudget: z.number().optional(),
|
|
1597
|
+
namespace: z.string().optional(),
|
|
1598
|
+
format: z.enum(["json", "toon", "toon-compact"]).optional(),
|
|
1599
|
+
includeSummary: z.boolean().optional(),
|
|
1600
|
+
semanticDedup: z.boolean().optional(),
|
|
1601
|
+
})),
|
|
1602
|
+
outputSchema: z.toJSONSchema(z.object({ pack: z.unknown(), format: z.string() })),
|
|
1603
|
+
},
|
|
1604
|
+
{
|
|
1605
|
+
name: "memos_search_temporal",
|
|
1606
|
+
annotations: TOOL_ANNOTATIONS.memos_search_temporal,
|
|
1607
|
+
description: "Search memories that were valid at a specific point in time " +
|
|
1608
|
+
"(unix ms). Superseded/historical memories stay queryable here.",
|
|
1609
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1610
|
+
query: z.string(),
|
|
1611
|
+
atTime: z.number(),
|
|
1612
|
+
limit: z.number().optional(),
|
|
1613
|
+
namespace: z.string().optional(),
|
|
1614
|
+
})),
|
|
1615
|
+
outputSchema: z.toJSONSchema(z.object({ results: z.array(scoredMemorySchema) })),
|
|
1616
|
+
},
|
|
1617
|
+
{
|
|
1618
|
+
name: "memos_set_validity",
|
|
1619
|
+
annotations: TOOL_ANNOTATIONS.memos_set_validity,
|
|
1620
|
+
description: "Mark a memory's temporal validity window (unix ms). A validTo in " +
|
|
1621
|
+
"the past makes the memory historical: excluded from default " +
|
|
1622
|
+
"search, still queryable via memos_search_temporal.",
|
|
1623
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1624
|
+
id: z.string(),
|
|
1625
|
+
validFrom: z.number().nullable().optional(),
|
|
1626
|
+
validTo: z.number().nullable().optional(),
|
|
1627
|
+
})),
|
|
1628
|
+
outputSchema: z.toJSONSchema(z.object({ node: memoryNodeSchema.nullable() })),
|
|
1629
|
+
},
|
|
1630
|
+
{
|
|
1631
|
+
name: "memos_supersede",
|
|
1632
|
+
annotations: TOOL_ANNOTATIONS.memos_supersede,
|
|
1633
|
+
description: "Mark a memory as superseded (historical) and optionally link its " +
|
|
1634
|
+
"replacement with a temporal_precedes edge.",
|
|
1635
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1636
|
+
id: z.string(),
|
|
1637
|
+
replacementId: z.string().optional(),
|
|
1638
|
+
})),
|
|
1639
|
+
outputSchema: z.toJSONSchema(z.object({ node: memoryNodeSchema.nullable() })),
|
|
1640
|
+
},
|
|
1641
|
+
{
|
|
1642
|
+
name: "memos_set_trust",
|
|
1643
|
+
annotations: TOOL_ANNOTATIONS.memos_set_trust,
|
|
1644
|
+
description: "Set the trust score [0,1] of a memory. High-trust memories rank " +
|
|
1645
|
+
"higher in hybrid search.",
|
|
1646
|
+
inputSchema: z.toJSONSchema(z.object({ id: z.string(), score: z.number().min(0).max(1) })),
|
|
1647
|
+
outputSchema: z.toJSONSchema(z.object({ node: memoryNodeSchema.nullable() })),
|
|
1648
|
+
},
|
|
1649
|
+
{
|
|
1650
|
+
name: "memos_extract_facts",
|
|
1651
|
+
annotations: TOOL_ANNOTATIONS.memos_extract_facts,
|
|
1652
|
+
description: "Rule-based local extraction of preferences, entities and facts " +
|
|
1653
|
+
"from conversation messages, optionally storing them as memories.",
|
|
1654
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1655
|
+
messages: z.array(z.object({ role: z.string(), content: z.string() })),
|
|
1656
|
+
autoStore: z.boolean().optional(),
|
|
1657
|
+
minConfidence: z.number().optional(),
|
|
1658
|
+
namespace: z.string().optional(),
|
|
1659
|
+
})),
|
|
1660
|
+
outputSchema: z.toJSONSchema(z.object({ facts: z.array(z.unknown()), storedIds: z.array(z.string()) })),
|
|
1661
|
+
},
|
|
1662
|
+
{
|
|
1663
|
+
name: "memos_diagnostics",
|
|
1664
|
+
annotations: TOOL_ANNOTATIONS.memos_diagnostics,
|
|
1665
|
+
description: "Health report: counts, embedding coverage, temporal stats, " +
|
|
1666
|
+
"storage capabilities and database size.",
|
|
1667
|
+
inputSchema: z.toJSONSchema(z.object({})),
|
|
1668
|
+
outputSchema: z.toJSONSchema(z.object({ diagnostics: z.unknown() })),
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
name: "memos_reindex",
|
|
1672
|
+
annotations: TOOL_ANNOTATIONS.memos_reindex,
|
|
1673
|
+
description: "Re-embed every memory with the currently configured embedding " +
|
|
1674
|
+
"model. Required after switching models. Can be slow on large " +
|
|
1675
|
+
"stores; purgeStale also deletes vectors from other models.",
|
|
1676
|
+
inputSchema: z.toJSONSchema(z.object({ purgeStale: z.boolean().optional() })),
|
|
1677
|
+
outputSchema: z.toJSONSchema(z.object({
|
|
1678
|
+
reembedded: z.number(),
|
|
1679
|
+
purged: z.number(),
|
|
1680
|
+
failed: z.number(),
|
|
1681
|
+
model: z.string(),
|
|
1682
|
+
})),
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
name: "memos_consolidate",
|
|
1686
|
+
annotations: TOOL_ANNOTATIONS.memos_consolidate,
|
|
1687
|
+
description: "Offline maintenance pass ('dreaming'): merge near-duplicates, " +
|
|
1688
|
+
"archive stale low-importance memories, supersede decayed ones " +
|
|
1689
|
+
"(marked historical — never deleted), and distill cluster summary " +
|
|
1690
|
+
"notes into the note pool. Run periodically or while idle.",
|
|
1691
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1692
|
+
namespace: z.string().optional(),
|
|
1693
|
+
dryRun: z.boolean().optional(),
|
|
1694
|
+
summarize: z.boolean().optional(),
|
|
1695
|
+
decay: z.boolean().optional(),
|
|
1696
|
+
decayHalfLifeDays: z.number().optional(),
|
|
1697
|
+
minRetentionScore: z.number().optional(),
|
|
1698
|
+
olderThanDays: z.number().optional(),
|
|
1699
|
+
})),
|
|
1700
|
+
outputSchema: z.toJSONSchema(z.object({ report: z.unknown() })),
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
name: "memos_usage",
|
|
1704
|
+
annotations: TOOL_ANNOTATIONS.memos_usage,
|
|
1705
|
+
description: "Lifetime context-pack token telemetry for this server process: " +
|
|
1706
|
+
"packs built, tokens injected, naive raw-JSON baseline, savings %.",
|
|
1707
|
+
inputSchema: z.toJSONSchema(z.object({})),
|
|
1708
|
+
outputSchema: z.toJSONSchema(z.object({ usage: z.unknown() })),
|
|
1709
|
+
},
|
|
1710
|
+
{
|
|
1711
|
+
name: "memos_history",
|
|
1712
|
+
annotations: TOOL_ANNOTATIONS.memos_history,
|
|
1713
|
+
description: "Audit timeline for one memory: supersedes, superseded by, derived notes.",
|
|
1714
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1715
|
+
id: z.string(),
|
|
1716
|
+
threshold: z.number().optional(),
|
|
1717
|
+
limit: z.number().optional(),
|
|
1718
|
+
})),
|
|
1719
|
+
outputSchema: z.toJSONSchema(z.object({ history: z.unknown() })),
|
|
1720
|
+
},
|
|
1721
|
+
{
|
|
1722
|
+
name: "memos_revert",
|
|
1723
|
+
annotations: TOOL_ANNOTATIONS.memos_revert,
|
|
1724
|
+
description: "Revert a memory to the version it superseded (command-driven belief " +
|
|
1725
|
+
"revision). Closes the target's validity interval and reactivates the " +
|
|
1726
|
+
"predecessor; the reverted-away version stays in history (add-only) " +
|
|
1727
|
+
"and the revert is recorded as an audit event. Confirm scope first: " +
|
|
1728
|
+
"call with dryRun=true (default) and show the would-be target, then " +
|
|
1729
|
+
"call again with dryRun=false to execute.",
|
|
1730
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1731
|
+
text: z.string().optional(),
|
|
1732
|
+
id: z.string().optional(),
|
|
1733
|
+
last: z.boolean().optional(),
|
|
1734
|
+
about: z.string().optional(),
|
|
1735
|
+
dryRun: z.boolean().optional(),
|
|
1736
|
+
reason: z.string().optional(),
|
|
1737
|
+
actor: z.string().optional(),
|
|
1738
|
+
namespace: z.string().optional(),
|
|
1739
|
+
})),
|
|
1740
|
+
outputSchema: z.toJSONSchema(z.object({ revert: z.unknown() })),
|
|
1741
|
+
},
|
|
1742
|
+
{
|
|
1743
|
+
name: "memos_reminders",
|
|
1744
|
+
annotations: TOOL_ANNOTATIONS.memos_reminders,
|
|
1745
|
+
description: "List scheduled event reminders (deterministic temporal event memory). " +
|
|
1746
|
+
"Pass due=true for reminders due as of now; otherwise all scheduled " +
|
|
1747
|
+
"reminders, earliest first. Poll on the harness's own schedule — " +
|
|
1748
|
+
"MemOS surfaces due reminders but does not push-notify.",
|
|
1749
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1750
|
+
due: z.boolean().optional(),
|
|
1751
|
+
namespace: z.string().optional(),
|
|
1752
|
+
})),
|
|
1753
|
+
outputSchema: z.toJSONSchema(z.object({ reminders: z.unknown() })),
|
|
1754
|
+
},
|
|
1755
|
+
{
|
|
1756
|
+
name: "memos_image_add",
|
|
1757
|
+
annotations: TOOL_ANNOTATIONS.memos_image_add,
|
|
1758
|
+
description: "Store an image as a durable memory. The image is written to a " +
|
|
1759
|
+
"content-addressed blob next to the database and a caption becomes " +
|
|
1760
|
+
"the memory's text. Requires vision: { enabled: true }.",
|
|
1761
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1762
|
+
path: z.string().optional(),
|
|
1763
|
+
url: z.string().optional(),
|
|
1764
|
+
data: z.string().optional(),
|
|
1765
|
+
mime: z.string().optional(),
|
|
1766
|
+
caption: z.string().optional(),
|
|
1767
|
+
kind: z
|
|
1768
|
+
.enum([
|
|
1769
|
+
"photo",
|
|
1770
|
+
"screenshot",
|
|
1771
|
+
"chart",
|
|
1772
|
+
"diagram",
|
|
1773
|
+
"memo",
|
|
1774
|
+
"meme",
|
|
1775
|
+
"other",
|
|
1776
|
+
])
|
|
1777
|
+
.optional(),
|
|
1778
|
+
tags: z.array(z.string()).optional(),
|
|
1779
|
+
namespace: z.string().optional(),
|
|
1780
|
+
conversationId: z.string().optional(),
|
|
1781
|
+
})),
|
|
1782
|
+
outputSchema: z.toJSONSchema(z.object({
|
|
1783
|
+
node: z.unknown(),
|
|
1784
|
+
ref: z.string(),
|
|
1785
|
+
kind: z.string(),
|
|
1786
|
+
width: z.number(),
|
|
1787
|
+
height: z.number(),
|
|
1788
|
+
bytes: z.number(),
|
|
1789
|
+
mime: z.string(),
|
|
1790
|
+
clipStatus: z.string(),
|
|
1791
|
+
deduped: z.boolean(),
|
|
1792
|
+
})),
|
|
1793
|
+
},
|
|
1794
|
+
{
|
|
1795
|
+
name: "memos_image_recall",
|
|
1796
|
+
annotations: TOOL_ANNOTATIONS.memos_image_recall,
|
|
1797
|
+
description: "Resolve an [img:...] ref (or a node id) back to its bytes, as " +
|
|
1798
|
+
"base64 plus MIME type.",
|
|
1799
|
+
inputSchema: z.toJSONSchema(z.object({ ref: z.string() })),
|
|
1800
|
+
outputSchema: z.toJSONSchema(z.object({
|
|
1801
|
+
ref: z.string(),
|
|
1802
|
+
mime: z.string(),
|
|
1803
|
+
bytes: z.number(),
|
|
1804
|
+
data: z.string(),
|
|
1805
|
+
})),
|
|
1806
|
+
},
|
|
1807
|
+
{
|
|
1808
|
+
name: "memos_image_search",
|
|
1809
|
+
annotations: TOOL_ANNOTATIONS.memos_image_search,
|
|
1810
|
+
description: "Search stored images by text query. Caption leg always; CLIP " +
|
|
1811
|
+
"pixel leg only when vision.clip.enabled.",
|
|
1812
|
+
inputSchema: z.toJSONSchema(z.object({
|
|
1813
|
+
query: z.string(),
|
|
1814
|
+
limit: z.number().optional(),
|
|
1815
|
+
namespace: z.string().optional(),
|
|
1816
|
+
kind: z
|
|
1817
|
+
.enum([
|
|
1818
|
+
"photo",
|
|
1819
|
+
"screenshot",
|
|
1820
|
+
"chart",
|
|
1821
|
+
"diagram",
|
|
1822
|
+
"memo",
|
|
1823
|
+
"meme",
|
|
1824
|
+
"other",
|
|
1825
|
+
])
|
|
1826
|
+
.optional(),
|
|
1827
|
+
})),
|
|
1828
|
+
outputSchema: z.toJSONSchema(z.object({
|
|
1829
|
+
results: z.array(z.object({
|
|
1830
|
+
id: z.string(),
|
|
1831
|
+
ref: z.string(),
|
|
1832
|
+
kind: z.string(),
|
|
1833
|
+
width: z.number(),
|
|
1834
|
+
height: z.number(),
|
|
1835
|
+
caption: z.string(),
|
|
1836
|
+
score: z.number(),
|
|
1837
|
+
})),
|
|
1838
|
+
})),
|
|
1839
|
+
},
|
|
1840
|
+
];
|
|
1841
|
+
/**
|
|
1842
|
+
* Return static metadata about the MCP tools exposed by MemOS.
|
|
1843
|
+
* Useful for programmatic consumers and tests that need tool definitions
|
|
1844
|
+
* without spinning up a live server connection.
|
|
1845
|
+
*
|
|
1846
|
+
* Kept in the SAME ORDER as the live `registerTools` registrations —
|
|
1847
|
+
* the annotations test asserts the two surfaces match exactly, so a
|
|
1848
|
+
* reorder here must be mirrored there.
|
|
1849
|
+
*/
|
|
1850
|
+
export function getMcpTools() {
|
|
1851
|
+
return TOOL_METADATA;
|
|
1852
|
+
}
|
|
1853
|
+
//# sourceMappingURL=mcp.js.map
|