neuralmemory 1.6.2 → 1.7.0
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.
- package/README.md +27 -22
- package/dist/index.d.ts +5 -1
- package/dist/index.js +66 -27
- package/dist/index.js.map +1 -1
- package/dist/mcp-client.d.ts +11 -0
- package/dist/mcp-client.js +9 -1
- package/dist/mcp-client.js.map +1 -1
- package/dist/tools.d.ts +23 -16
- package/dist/tools.js +139 -97
- package/dist/tools.js.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/index.ts +83 -32
- package/src/mcp-client.ts +19 -1
- package/src/tools.ts +160 -109
package/src/tools.ts
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* NeuralMemory tool
|
|
2
|
+
* NeuralMemory dynamic tool proxy for OpenClaw.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Fetches all available tools from the MCP server via `tools/list` and
|
|
5
|
+
* converts them into OpenClaw tool definitions. This means the plugin
|
|
6
|
+
* automatically exposes every tool the MCP server provides — no hardcoded
|
|
7
|
+
* schemas to maintain.
|
|
5
8
|
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* nmem_recall — Query/search memories
|
|
15
|
-
* nmem_context — Get recent context
|
|
16
|
-
* nmem_todo — Quick TODO shortcut
|
|
17
|
-
* nmem_stats — Brain statistics
|
|
18
|
-
* nmem_health — Brain health diagnostics
|
|
9
|
+
* Provider compatibility:
|
|
10
|
+
* - Strips constraint keywords (`minimum`, `maximum`, `maxLength`,
|
|
11
|
+
* `maxItems`, `minLength`) that some providers reject
|
|
12
|
+
* - Adds `additionalProperties: false` on all object schemas for
|
|
13
|
+
* OpenAI strict mode
|
|
14
|
+
* - Ensures every object type has a `properties` field (required by
|
|
15
|
+
* Anthropic SDK validation)
|
|
16
|
+
* - Uses `number` instead of `integer` for Gemini compatibility
|
|
19
17
|
*/
|
|
20
18
|
|
|
21
|
-
import type { NeuralMemoryMcpClient } from "./mcp-client.js";
|
|
19
|
+
import type { NeuralMemoryMcpClient, McpToolDefinition } from "./mcp-client.js";
|
|
22
20
|
|
|
23
21
|
// ── Types ──────────────────────────────────────────────────
|
|
24
22
|
|
|
@@ -36,10 +34,104 @@ export type ToolDefinition = {
|
|
|
36
34
|
readonly execute: (id: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
37
35
|
};
|
|
38
36
|
|
|
37
|
+
// ── Schema normalization ───────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/** Keywords that some LLM providers reject in function schemas. */
|
|
40
|
+
const STRIP_KEYS = new Set([
|
|
41
|
+
"minimum",
|
|
42
|
+
"maximum",
|
|
43
|
+
"maxLength",
|
|
44
|
+
"minLength",
|
|
45
|
+
"maxItems",
|
|
46
|
+
"minItems",
|
|
47
|
+
"exclusiveMinimum",
|
|
48
|
+
"exclusiveMaximum",
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Recursively normalize a JSON Schema node for provider compatibility:
|
|
53
|
+
* - Strip constraint keywords
|
|
54
|
+
* - Replace `integer` with `number` (Gemini compat)
|
|
55
|
+
* - Add `additionalProperties: false` to objects (OpenAI strict mode)
|
|
56
|
+
* - Ensure every object has `properties` (Anthropic SDK)
|
|
57
|
+
*/
|
|
58
|
+
function normalizeSchema(node: unknown): unknown {
|
|
59
|
+
if (node === null || node === undefined || typeof node !== "object") {
|
|
60
|
+
return node;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (Array.isArray(node)) {
|
|
64
|
+
return node.map(normalizeSchema);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const obj = node as Record<string, unknown>;
|
|
68
|
+
const result: Record<string, unknown> = {};
|
|
69
|
+
|
|
70
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
71
|
+
if (STRIP_KEYS.has(key)) continue;
|
|
72
|
+
|
|
73
|
+
if (key === "type" && value === "integer") {
|
|
74
|
+
result[key] = "number";
|
|
75
|
+
} else if (key === "properties" && typeof value === "object" && value !== null) {
|
|
76
|
+
// Recurse into each property definition
|
|
77
|
+
const props: Record<string, unknown> = {};
|
|
78
|
+
for (const [propName, propSchema] of Object.entries(value as Record<string, unknown>)) {
|
|
79
|
+
props[propName] = normalizeSchema(propSchema);
|
|
80
|
+
}
|
|
81
|
+
result[key] = props;
|
|
82
|
+
} else if (key === "items" && typeof value === "object" && value !== null) {
|
|
83
|
+
result[key] = normalizeSchema(value);
|
|
84
|
+
} else if (
|
|
85
|
+
(key === "anyOf" || key === "oneOf" || key === "allOf") &&
|
|
86
|
+
Array.isArray(value)
|
|
87
|
+
) {
|
|
88
|
+
result[key] = value.map(normalizeSchema);
|
|
89
|
+
} else {
|
|
90
|
+
result[key] = value;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Ensure objects have `properties` and `additionalProperties`
|
|
95
|
+
if (result["type"] === "object") {
|
|
96
|
+
if (!("properties" in result) || result["properties"] === undefined) {
|
|
97
|
+
result["properties"] = {};
|
|
98
|
+
}
|
|
99
|
+
if (!("additionalProperties" in result)) {
|
|
100
|
+
result["additionalProperties"] = false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Convert an MCP inputSchema into a provider-safe OpenClaw JsonSchema.
|
|
109
|
+
* Falls back to an empty-properties object if the schema is missing/invalid.
|
|
110
|
+
*/
|
|
111
|
+
function toSafeSchema(inputSchema?: Record<string, unknown>): JsonSchema {
|
|
112
|
+
if (!inputSchema || typeof inputSchema !== "object") {
|
|
113
|
+
return { type: "object", properties: {}, additionalProperties: false };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const normalized = normalizeSchema(inputSchema) as Record<string, unknown>;
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: (normalized["properties"] ?? {}) as Record<string, unknown>,
|
|
121
|
+
...(Array.isArray(normalized["required"]) && normalized["required"].length > 0
|
|
122
|
+
? { required: normalized["required"] as string[] }
|
|
123
|
+
: {}),
|
|
124
|
+
additionalProperties: false,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
39
128
|
// ── Tool factory ───────────────────────────────────────────
|
|
40
129
|
|
|
41
|
-
|
|
42
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Create a tool call helper that auto-reconnects to MCP.
|
|
132
|
+
*/
|
|
133
|
+
function makeCallFn(mcp: NeuralMemoryMcpClient) {
|
|
134
|
+
return async (
|
|
43
135
|
toolName: string,
|
|
44
136
|
args: Record<string, unknown>,
|
|
45
137
|
): Promise<unknown> => {
|
|
@@ -68,6 +160,43 @@ export function createTools(mcp: NeuralMemoryMcpClient): ToolDefinition[] {
|
|
|
68
160
|
};
|
|
69
161
|
}
|
|
70
162
|
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Convert a single MCP tool definition into an OpenClaw ToolDefinition.
|
|
167
|
+
*/
|
|
168
|
+
function mcpToolToOpenClaw(
|
|
169
|
+
mcpTool: McpToolDefinition,
|
|
170
|
+
call: (name: string, args: Record<string, unknown>) => Promise<unknown>,
|
|
171
|
+
): ToolDefinition {
|
|
172
|
+
return {
|
|
173
|
+
name: mcpTool.name,
|
|
174
|
+
description: mcpTool.description ?? `NeuralMemory tool: ${mcpTool.name}`,
|
|
175
|
+
parameters: toSafeSchema(mcpTool.inputSchema),
|
|
176
|
+
execute: (_id, args) => call(mcpTool.name, args),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Fetch all tools from the MCP server and convert them to OpenClaw format.
|
|
182
|
+
* Must be called after MCP connection is established.
|
|
183
|
+
*/
|
|
184
|
+
export async function createToolsFromMcp(
|
|
185
|
+
mcp: NeuralMemoryMcpClient,
|
|
186
|
+
): Promise<ToolDefinition[]> {
|
|
187
|
+
const mcpTools = await mcp.listTools();
|
|
188
|
+
const call = makeCallFn(mcp);
|
|
189
|
+
return mcpTools.map((t) => mcpToolToOpenClaw(t, call));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Fallback: create minimal hardcoded tools if MCP tools/list fails.
|
|
194
|
+
* Ensures the plugin still works even if the MCP server is an older version.
|
|
195
|
+
*/
|
|
196
|
+
export function createFallbackTools(
|
|
197
|
+
mcp: NeuralMemoryMcpClient,
|
|
198
|
+
): ToolDefinition[] {
|
|
199
|
+
const call = makeCallFn(mcp);
|
|
71
200
|
|
|
72
201
|
return [
|
|
73
202
|
{
|
|
@@ -78,46 +207,27 @@ export function createTools(mcp: NeuralMemoryMcpClient): ToolDefinition[] {
|
|
|
78
207
|
parameters: {
|
|
79
208
|
type: "object",
|
|
80
209
|
properties: {
|
|
81
|
-
content: {
|
|
82
|
-
type: "string",
|
|
83
|
-
description: "The content to remember",
|
|
84
|
-
},
|
|
210
|
+
content: { type: "string", description: "The content to remember" },
|
|
85
211
|
type: {
|
|
86
212
|
type: "string",
|
|
87
213
|
enum: [
|
|
88
|
-
"fact",
|
|
89
|
-
"
|
|
90
|
-
"preference",
|
|
91
|
-
"todo",
|
|
92
|
-
"insight",
|
|
93
|
-
"context",
|
|
94
|
-
"instruction",
|
|
95
|
-
"error",
|
|
96
|
-
"workflow",
|
|
97
|
-
"reference",
|
|
214
|
+
"fact", "decision", "preference", "todo", "insight",
|
|
215
|
+
"context", "instruction", "error", "workflow", "reference",
|
|
98
216
|
],
|
|
99
217
|
description: "Memory type (auto-detected if not specified)",
|
|
100
218
|
},
|
|
101
|
-
priority: {
|
|
102
|
-
type: "number",
|
|
103
|
-
description: "Priority 0-10 (5=normal, 10=critical)",
|
|
104
|
-
},
|
|
219
|
+
priority: { type: "number", description: "Priority 0-10 (5=normal, 10=critical)" },
|
|
105
220
|
tags: {
|
|
106
221
|
type: "array",
|
|
107
222
|
items: { type: "string" },
|
|
108
223
|
description: "Tags for categorization",
|
|
109
224
|
},
|
|
110
|
-
expires_days: {
|
|
111
|
-
type: "number",
|
|
112
|
-
description: "Days until memory expires (1-3650)",
|
|
113
|
-
},
|
|
114
225
|
},
|
|
115
226
|
required: ["content"],
|
|
116
227
|
additionalProperties: false,
|
|
117
228
|
},
|
|
118
229
|
execute: (_id, args) => call("nmem_remember", args),
|
|
119
230
|
},
|
|
120
|
-
|
|
121
231
|
{
|
|
122
232
|
name: "nmem_recall",
|
|
123
233
|
description:
|
|
@@ -126,96 +236,37 @@ export function createTools(mcp: NeuralMemoryMcpClient): ToolDefinition[] {
|
|
|
126
236
|
parameters: {
|
|
127
237
|
type: "object",
|
|
128
238
|
properties: {
|
|
129
|
-
query: {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
},
|
|
133
|
-
depth: {
|
|
134
|
-
type: "number",
|
|
135
|
-
description:
|
|
136
|
-
"Search depth: 0=instant, 1=context, 2=habit, 3=deep",
|
|
137
|
-
},
|
|
138
|
-
max_tokens: {
|
|
139
|
-
type: "number",
|
|
140
|
-
description: "Maximum tokens in response (default: 500)",
|
|
141
|
-
},
|
|
142
|
-
min_confidence: {
|
|
143
|
-
type: "number",
|
|
144
|
-
description: "Minimum confidence threshold (0-1)",
|
|
145
|
-
},
|
|
239
|
+
query: { type: "string", description: "The query to search memories" },
|
|
240
|
+
depth: { type: "number", description: "Search depth: 0=instant, 1=context, 2=habit, 3=deep" },
|
|
241
|
+
max_tokens: { type: "number", description: "Maximum tokens in response (default: 500)" },
|
|
146
242
|
},
|
|
147
243
|
required: ["query"],
|
|
148
244
|
additionalProperties: false,
|
|
149
245
|
},
|
|
150
246
|
execute: (_id, args) => call("nmem_recall", args),
|
|
151
247
|
},
|
|
152
|
-
|
|
153
248
|
{
|
|
154
249
|
name: "nmem_context",
|
|
155
|
-
description:
|
|
156
|
-
"Get recent context from NeuralMemory. Use this at the start of " +
|
|
157
|
-
"tasks to inject relevant recent memories.",
|
|
250
|
+
description: "Get recent context from NeuralMemory.",
|
|
158
251
|
parameters: {
|
|
159
252
|
type: "object",
|
|
160
253
|
properties: {
|
|
161
|
-
limit: {
|
|
162
|
-
type: "number",
|
|
163
|
-
description: "Number of recent memories (default: 10, max: 200)",
|
|
164
|
-
},
|
|
165
|
-
fresh_only: {
|
|
166
|
-
type: "boolean",
|
|
167
|
-
description: "Only include memories less than 30 days old",
|
|
168
|
-
},
|
|
254
|
+
limit: { type: "number", description: "Number of recent memories (default: 10)" },
|
|
169
255
|
},
|
|
170
256
|
additionalProperties: false,
|
|
171
257
|
},
|
|
172
258
|
execute: (_id, args) => call("nmem_context", args),
|
|
173
259
|
},
|
|
174
|
-
|
|
175
|
-
{
|
|
176
|
-
name: "nmem_todo",
|
|
177
|
-
description:
|
|
178
|
-
"Quick shortcut to add a TODO memory with 30-day expiry.",
|
|
179
|
-
parameters: {
|
|
180
|
-
type: "object",
|
|
181
|
-
properties: {
|
|
182
|
-
task: {
|
|
183
|
-
type: "string",
|
|
184
|
-
description: "The task to remember",
|
|
185
|
-
},
|
|
186
|
-
priority: {
|
|
187
|
-
type: "number",
|
|
188
|
-
description: "Priority 0-10 (default: 5)",
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
required: ["task"],
|
|
192
|
-
additionalProperties: false,
|
|
193
|
-
},
|
|
194
|
-
execute: (_id, args) => call("nmem_todo", args),
|
|
195
|
-
},
|
|
196
|
-
|
|
197
260
|
{
|
|
198
261
|
name: "nmem_stats",
|
|
199
|
-
description:
|
|
200
|
-
|
|
201
|
-
parameters: {
|
|
202
|
-
type: "object",
|
|
203
|
-
properties: {},
|
|
204
|
-
additionalProperties: false,
|
|
205
|
-
},
|
|
262
|
+
description: "Get brain statistics including memory counts and freshness.",
|
|
263
|
+
parameters: { type: "object", properties: {}, additionalProperties: false },
|
|
206
264
|
execute: (_id, args) => call("nmem_stats", args),
|
|
207
265
|
},
|
|
208
|
-
|
|
209
266
|
{
|
|
210
267
|
name: "nmem_health",
|
|
211
|
-
description:
|
|
212
|
-
|
|
213
|
-
"and recommendations.",
|
|
214
|
-
parameters: {
|
|
215
|
-
type: "object",
|
|
216
|
-
properties: {},
|
|
217
|
-
additionalProperties: false,
|
|
218
|
-
},
|
|
268
|
+
description: "Get brain health diagnostics including grade and recommendations.",
|
|
269
|
+
parameters: { type: "object", properties: {}, additionalProperties: false },
|
|
219
270
|
execute: (_id, args) => call("nmem_health", args),
|
|
220
271
|
},
|
|
221
272
|
];
|