@scitrera/memorylayer-mcp-server 0.0.3
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 +350 -0
- package/dist/bin/memorylayer-hook.d.ts +19 -0
- package/dist/bin/memorylayer-hook.d.ts.map +1 -0
- package/dist/bin/memorylayer-hook.js +223 -0
- package/dist/bin/memorylayer-hook.js.map +1 -0
- package/dist/bin/memorylayer-mcp.d.ts +16 -0
- package/dist/bin/memorylayer-mcp.d.ts.map +1 -0
- package/dist/bin/memorylayer-mcp.js +72 -0
- package/dist/bin/memorylayer-mcp.js.map +1 -0
- package/examples/client-usage.ts +93 -0
- package/package.json +47 -0
- package/tests/server.test.ts +130 -0
- package/tests/session.test.ts +152 -0
- package/tests/tools.test.ts +191 -0
- package/vitest.config.ts +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# @scitrera/memorylayer-mcp-server
|
|
2
|
+
|
|
3
|
+
TypeScript MCP (Model Context Protocol) server for [MemoryLayer.ai](https://memorylayer.ai).
|
|
4
|
+
|
|
5
|
+
Provides 16 memory tools for LLM agents to store, recall, synthesize, and manage information across sessions.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @scitrera/memorylayer-mcp-server
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### As a Standalone MCP Server
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Set environment variables
|
|
19
|
+
export MEMORYLAYER_URL=http://localhost:61001
|
|
20
|
+
export MEMORYLAYER_WORKSPACE_ID=my-workspace
|
|
21
|
+
|
|
22
|
+
# Run the server
|
|
23
|
+
npx memorylayer-mcp
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Claude Code Configuration (Recommended)
|
|
27
|
+
|
|
28
|
+
> **Detailed setup guide:** See [CLAUDE_CODE_SETUP.md](../docs/CLAUDE_CODE_SETUP.md) for step-by-step instructions.
|
|
29
|
+
|
|
30
|
+
Claude Code runs MCP servers from the project directory, so our server auto-detects the workspace from your git repo or folder name. Add `.mcp.json` to your project root:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"memorylayer": {
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["@scitrera/memorylayer-mcp-server"],
|
|
38
|
+
"env": {
|
|
39
|
+
"MEMORYLAYER_URL": "http://localhost:61001"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Auto-workspace detection**: The server uses your git repo name (or directory name) as the workspace ID. Each project gets isolated memory storage automatically.
|
|
47
|
+
|
|
48
|
+
**Override options:**
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"memorylayer": {
|
|
53
|
+
"command": "npx",
|
|
54
|
+
"args": ["@scitrera/memorylayer-mcp-server"],
|
|
55
|
+
"env": {
|
|
56
|
+
"MEMORYLAYER_URL": "http://localhost:61001",
|
|
57
|
+
"MEMORYLAYER_WORKSPACE_ID": "${WORKSPACE_ID:-my-project}"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Or via CLI:
|
|
65
|
+
```bash
|
|
66
|
+
claude mcp add --transport stdio memorylayer \
|
|
67
|
+
--env MEMORYLAYER_URL=http://localhost:61001 \
|
|
68
|
+
-- npx @scitrera/memorylayer-mcp-server
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Claude Desktop Configuration
|
|
72
|
+
|
|
73
|
+
Add to your Claude Desktop config file (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"mcpServers": {
|
|
78
|
+
"memorylayer": {
|
|
79
|
+
"command": "npx",
|
|
80
|
+
"args": ["@scitrera/memorylayer-mcp-server"],
|
|
81
|
+
"env": {
|
|
82
|
+
"MEMORYLAYER_URL": "http://localhost:61001",
|
|
83
|
+
"MEMORYLAYER_WORKSPACE_ID": "my-project"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Note**: Claude Desktop doesn't change directories per-project, so you should set `MEMORYLAYER_WORKSPACE_ID` explicitly for each project entry.
|
|
91
|
+
|
|
92
|
+
### Programmatic Usage
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { MemoryLayerClient, createServer } from "@scitrera/memorylayer-mcp-server";
|
|
96
|
+
|
|
97
|
+
// Create client (wraps the @scitrera/memorylayer-sdk)
|
|
98
|
+
const client = new MemoryLayerClient({
|
|
99
|
+
baseUrl: "http://localhost:61001",
|
|
100
|
+
workspaceId: "my-workspace",
|
|
101
|
+
apiKey: "optional-api-key"
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Create MCP server
|
|
105
|
+
const server = await createServer(client);
|
|
106
|
+
|
|
107
|
+
// Run server on stdio transport
|
|
108
|
+
await server.run();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Available Tools
|
|
112
|
+
|
|
113
|
+
### Core Memory Tools (5)
|
|
114
|
+
|
|
115
|
+
#### 1. `memory_remember`
|
|
116
|
+
Store a new memory for later recall.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
{
|
|
120
|
+
content: "User prefers TypeScript for new projects",
|
|
121
|
+
type: "semantic", // episodic, semantic, procedural, working
|
|
122
|
+
importance: 0.8, // 0.0 - 1.0
|
|
123
|
+
tags: ["preference", "typescript"],
|
|
124
|
+
subtype: "Preference" // Optional domain classification
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### 2. `memory_recall`
|
|
129
|
+
Search memories by semantic query.
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
{
|
|
133
|
+
query: "What are the user's coding preferences?",
|
|
134
|
+
limit: 10,
|
|
135
|
+
min_relevance: 0.5,
|
|
136
|
+
types: ["semantic"], // Optional filter
|
|
137
|
+
tags: ["preference"] // Optional filter (AND logic)
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### 3. `memory_reflect`
|
|
142
|
+
Synthesize insights across multiple memories.
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
{
|
|
146
|
+
query: "What patterns have we seen with database performance?",
|
|
147
|
+
max_tokens: 500,
|
|
148
|
+
include_sources: true,
|
|
149
|
+
depth: 2 // Association traversal depth
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### 4. `memory_forget`
|
|
154
|
+
Delete or decay outdated information.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
{
|
|
158
|
+
memory_id: "mem_abc123",
|
|
159
|
+
reason: "Outdated information",
|
|
160
|
+
hard: false // true = permanent delete
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### 5. `memory_associate`
|
|
165
|
+
Link memories with typed relationships.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
{
|
|
169
|
+
source_id: "mem_problem",
|
|
170
|
+
target_id: "mem_solution",
|
|
171
|
+
relationship: "solves", // 60+ relationship types available
|
|
172
|
+
strength: 0.9 // 0.0 - 1.0
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Extended Memory Tools (4)
|
|
177
|
+
|
|
178
|
+
#### 6. `memory_briefing`
|
|
179
|
+
Get a session briefing with recent context.
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
{
|
|
183
|
+
lookback_hours: 24,
|
|
184
|
+
include_contradictions: true
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### 7. `memory_statistics`
|
|
189
|
+
Get workspace analytics and memory usage.
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
{
|
|
193
|
+
include_breakdown: true // Include breakdown by type/subtype
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### 8. `memory_graph_query`
|
|
198
|
+
Multi-hop graph traversal for causal chains.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
{
|
|
202
|
+
start_memory_id: "mem_abc123",
|
|
203
|
+
relationship_types: ["causes", "triggers"],
|
|
204
|
+
max_depth: 3,
|
|
205
|
+
direction: "both", // outgoing, incoming, both
|
|
206
|
+
max_paths: 50
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### 9. `memory_audit`
|
|
211
|
+
Find contradictions and inconsistencies.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
{
|
|
215
|
+
memory_id: "mem_abc123", // Optional - omit to audit entire workspace
|
|
216
|
+
auto_resolve: false // Auto-prefer newer contradicting memories
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Session Management Tools (3)
|
|
221
|
+
|
|
222
|
+
These tools enable working memory that persists across tool calls within a session.
|
|
223
|
+
|
|
224
|
+
#### 10. `memory_session_start`
|
|
225
|
+
Start a new session for working memory tracking.
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
{
|
|
229
|
+
metadata: { task: "debugging" } // Optional metadata
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### 11. `memory_session_end`
|
|
234
|
+
End the current session and optionally commit working memory.
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
{
|
|
238
|
+
commit: true, // Commit to long-term storage
|
|
239
|
+
importance_threshold: 0.5 // Min importance for extracted memories
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
#### 12. `memory_session_status`
|
|
244
|
+
Get current session status including working memory summary.
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
{} // No parameters required
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Environment Variables
|
|
251
|
+
|
|
252
|
+
| Variable | Description | Default |
|
|
253
|
+
|----------|-------------|---------|
|
|
254
|
+
| `MEMORYLAYER_URL` | Base URL for MemoryLayer API | `http://localhost:61001` |
|
|
255
|
+
| `MEMORYLAYER_API_KEY` | API key for authentication | (none) |
|
|
256
|
+
| `MEMORYLAYER_WORKSPACE_ID` | Workspace ID (overrides auto-detection) | (auto-detected) |
|
|
257
|
+
| `MEMORYLAYER_AUTO_WORKSPACE` | Set to `false` to disable auto-detection | `true` |
|
|
258
|
+
|
|
259
|
+
## Memory Types
|
|
260
|
+
|
|
261
|
+
- **Episodic**: Specific events/interactions
|
|
262
|
+
- **Semantic**: Facts, concepts, relationships
|
|
263
|
+
- **Procedural**: How-to knowledge
|
|
264
|
+
- **Working**: Current task context (session-scoped)
|
|
265
|
+
|
|
266
|
+
## Relationship Types (60+)
|
|
267
|
+
|
|
268
|
+
Organized into 11 categories:
|
|
269
|
+
|
|
270
|
+
**Hierarchical**: parent_of, child_of, part_of, contains, instance_of, subtype_of
|
|
271
|
+
**Causal**: causes, triggers, leads_to, prevents
|
|
272
|
+
**Temporal**: precedes, concurrent_with, follows_temporally
|
|
273
|
+
**Similarity**: similar_to, variant_of, related_to
|
|
274
|
+
**Learning**: builds_on, contradicts, confirms, supersedes
|
|
275
|
+
**Refinement**: refines, abstracts, specializes, generalizes
|
|
276
|
+
**Reference**: references, referenced_by
|
|
277
|
+
**Solution**: solves, addresses, alternative_to, improves
|
|
278
|
+
**Context**: occurs_in, applies_to, works_with, requires
|
|
279
|
+
**Workflow**: follows, depends_on, enables, blocks
|
|
280
|
+
**Quality**: effective_for, preferred_over, deprecated_by
|
|
281
|
+
|
|
282
|
+
## Architecture
|
|
283
|
+
|
|
284
|
+
The MCP server wraps the `@scitrera/memorylayer-sdk` TypeScript SDK, providing an MCP-compatible interface for LLM agents.
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
memorylayer-mcp-typescript/
|
|
288
|
+
├── src/
|
|
289
|
+
│ ├── types.ts # TypeScript types for MCP tools
|
|
290
|
+
│ ├── tools.ts # MCP tool definitions (12 tools)
|
|
291
|
+
│ ├── client.ts # Wrapper around @scitrera/memorylayer-sdk
|
|
292
|
+
│ ├── session.ts # Local session state management
|
|
293
|
+
│ ├── handlers.ts # Tool handler implementations
|
|
294
|
+
│ ├── server.ts # MCP server using @modelcontextprotocol/sdk
|
|
295
|
+
│ └── index.ts # Main exports
|
|
296
|
+
├── bin/
|
|
297
|
+
│ └── memorylayer-mcp.ts # CLI entry point
|
|
298
|
+
├── package.json
|
|
299
|
+
├── tsconfig.json
|
|
300
|
+
└── README.md
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Development
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
# Install dependencies
|
|
307
|
+
npm install
|
|
308
|
+
|
|
309
|
+
# Build
|
|
310
|
+
npm run build
|
|
311
|
+
|
|
312
|
+
# Watch mode
|
|
313
|
+
npm run dev
|
|
314
|
+
|
|
315
|
+
# Run locally
|
|
316
|
+
npm start
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Using the SDK Client Directly
|
|
320
|
+
|
|
321
|
+
The MCP server's client is a thin wrapper around the TypeScript SDK. For direct SDK usage without MCP, install `@scitrera/memorylayer-sdk`:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
npm install @scitrera/memorylayer-sdk
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import { MemoryLayerClient } from "@scitrera/memorylayer-sdk";
|
|
329
|
+
|
|
330
|
+
const client = new MemoryLayerClient({
|
|
331
|
+
baseUrl: "http://localhost:61001",
|
|
332
|
+
workspaceId: "my-workspace"
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const memory = await client.remember("Important fact", {
|
|
336
|
+
type: "semantic",
|
|
337
|
+
importance: 0.8
|
|
338
|
+
});
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## License
|
|
342
|
+
|
|
343
|
+
Apache 2.0 License - see LICENSE file for details.
|
|
344
|
+
|
|
345
|
+
## Links
|
|
346
|
+
|
|
347
|
+
- [MemoryLayer.ai](https://memorylayer.ai)
|
|
348
|
+
- [Documentation](https://docs.memorylayer.ai)
|
|
349
|
+
- [TypeScript SDK](https://www.npmjs.com/package/@scitrera/memorylayer-sdk)
|
|
350
|
+
- [Model Context Protocol](https://modelcontextprotocol.io)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for MemoryLayer Claude Code hooks
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* memorylayer-hook <hook-type>
|
|
7
|
+
*
|
|
8
|
+
* Reads HookInput JSON from stdin, writes HookOutput JSON to stdout.
|
|
9
|
+
*
|
|
10
|
+
* Hook types:
|
|
11
|
+
* SessionStart - Called at session start
|
|
12
|
+
* UserPromptSubmit - Called when user submits a prompt
|
|
13
|
+
* PreToolUse - Called before a tool is used
|
|
14
|
+
* PostToolUse - Called after a tool is used
|
|
15
|
+
* PreCompact - Called before context compaction
|
|
16
|
+
* Stop - Called when session ends
|
|
17
|
+
*/
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=memorylayer-hook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memorylayer-hook.d.ts","sourceRoot":"","sources":["../../bin/memorylayer-hook.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for MemoryLayer Claude Code hooks
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* memorylayer-hook <hook-type>
|
|
7
|
+
*
|
|
8
|
+
* Reads HookInput JSON from stdin, writes HookOutput JSON to stdout.
|
|
9
|
+
*
|
|
10
|
+
* Hook types:
|
|
11
|
+
* SessionStart - Called at session start
|
|
12
|
+
* UserPromptSubmit - Called when user submits a prompt
|
|
13
|
+
* PreToolUse - Called before a tool is used
|
|
14
|
+
* PostToolUse - Called after a tool is used
|
|
15
|
+
* PreCompact - Called before context compaction
|
|
16
|
+
* Stop - Called when session ends
|
|
17
|
+
*/
|
|
18
|
+
import { appendFileSync } from "fs";
|
|
19
|
+
import { HookEvent } from "../src/hooks/types.js";
|
|
20
|
+
import { handleSessionStart } from "../src/hooks/handlers/session-start.js";
|
|
21
|
+
import { handleUserPromptSubmit } from "../src/hooks/handlers/user-prompt.js";
|
|
22
|
+
import { handlePreToolUse } from "../src/hooks/handlers/pre-tool.js";
|
|
23
|
+
import { handlePostToolUse } from "../src/hooks/handlers/post-tool.js";
|
|
24
|
+
import { handleStop } from "../src/hooks/handlers/stop.js";
|
|
25
|
+
import { resetRecallStatus, updateSessionInfo } from "../src/hooks/state.js";
|
|
26
|
+
import { getClient } from "../src/hooks/client.js";
|
|
27
|
+
/**
|
|
28
|
+
* Read all input from stdin
|
|
29
|
+
*/
|
|
30
|
+
async function readStdin() {
|
|
31
|
+
const chunks = [];
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
34
|
+
process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
35
|
+
process.stdin.on("error", reject);
|
|
36
|
+
// Timeout after 5 seconds if no input
|
|
37
|
+
setTimeout(() => {
|
|
38
|
+
if (chunks.length === 0) {
|
|
39
|
+
resolve("{}");
|
|
40
|
+
}
|
|
41
|
+
}, 5000);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parse hook type from command line
|
|
46
|
+
*/
|
|
47
|
+
function parseHookType(arg) {
|
|
48
|
+
if (!arg)
|
|
49
|
+
return null;
|
|
50
|
+
const normalized = arg.toLowerCase();
|
|
51
|
+
switch (normalized) {
|
|
52
|
+
case "sessionstart":
|
|
53
|
+
return HookEvent.SessionStart;
|
|
54
|
+
case "userpromptsubmit":
|
|
55
|
+
return HookEvent.UserPromptSubmit;
|
|
56
|
+
case "pretooluse":
|
|
57
|
+
return HookEvent.PreToolUse;
|
|
58
|
+
case "posttooluse":
|
|
59
|
+
return HookEvent.PostToolUse;
|
|
60
|
+
case "precompact":
|
|
61
|
+
return HookEvent.PreCompact;
|
|
62
|
+
case "stop":
|
|
63
|
+
return HookEvent.Stop;
|
|
64
|
+
default:
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Hooks that support hookSpecificOutput with additionalContext */
|
|
69
|
+
const HOOKS_WITH_ADDITIONAL_CONTEXT = new Set([
|
|
70
|
+
HookEvent.SessionStart, // Works empirically even though not in schema
|
|
71
|
+
HookEvent.PreToolUse,
|
|
72
|
+
HookEvent.PostToolUse,
|
|
73
|
+
HookEvent.UserPromptSubmit,
|
|
74
|
+
]);
|
|
75
|
+
/**
|
|
76
|
+
* Build properly formatted hook output
|
|
77
|
+
*/
|
|
78
|
+
function buildOutput(hookType, additionalContext, block = false, reason) {
|
|
79
|
+
const output = {
|
|
80
|
+
continue: !block, // Always continue unless blocking
|
|
81
|
+
};
|
|
82
|
+
// For PreToolUse, we can block
|
|
83
|
+
if (hookType === HookEvent.PreToolUse && block) {
|
|
84
|
+
output.continue = false;
|
|
85
|
+
output.decision = "block";
|
|
86
|
+
output.reason = reason || "Blocked by MemoryLayer hook";
|
|
87
|
+
}
|
|
88
|
+
// Only add hookSpecificOutput for hooks that support it
|
|
89
|
+
// SessionStart, Stop, PreCompact do NOT support hookSpecificOutput
|
|
90
|
+
if (additionalContext && HOOKS_WITH_ADDITIONAL_CONTEXT.has(hookType)) {
|
|
91
|
+
output.hookSpecificOutput = {
|
|
92
|
+
hookEventName: hookType,
|
|
93
|
+
additionalContext,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return output;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Build error output
|
|
100
|
+
*/
|
|
101
|
+
function buildErrorOutput(hookType, error) {
|
|
102
|
+
return {
|
|
103
|
+
hookSpecificOutput: {
|
|
104
|
+
hookEventName: hookType,
|
|
105
|
+
additionalContext: `MemoryLayer hook error: ${error}`,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Convert legacy HookOutput to HandlerResult
|
|
111
|
+
*/
|
|
112
|
+
function legacyToResult(output) {
|
|
113
|
+
// Handle new format (has hookSpecificOutput)
|
|
114
|
+
if (output.hookSpecificOutput) {
|
|
115
|
+
return {
|
|
116
|
+
additionalContext: output.hookSpecificOutput.additionalContext,
|
|
117
|
+
block: output.decision === "block",
|
|
118
|
+
reason: output.reason,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// Handle legacy format (has additionalContext at top level)
|
|
122
|
+
return {
|
|
123
|
+
additionalContext: output.additionalContext,
|
|
124
|
+
block: output.block,
|
|
125
|
+
reason: output.blockReason,
|
|
126
|
+
error: output.error,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Dispatch to appropriate handler
|
|
131
|
+
*/
|
|
132
|
+
async function dispatch(hookType, input) {
|
|
133
|
+
switch (hookType) {
|
|
134
|
+
case HookEvent.SessionStart: {
|
|
135
|
+
// Reset recall status at session start
|
|
136
|
+
resetRecallStatus();
|
|
137
|
+
// Write workspace ID and session ID to CLAUDE_ENV_FILE for subsequent bash commands
|
|
138
|
+
const envFile = process.env.CLAUDE_ENV_FILE;
|
|
139
|
+
if (envFile) {
|
|
140
|
+
try {
|
|
141
|
+
const client = getClient();
|
|
142
|
+
const workspaceId = client.getWorkspaceId();
|
|
143
|
+
appendFileSync(envFile, `export MEMORYLAYER_WORKSPACE_ID="${workspaceId}"\n`);
|
|
144
|
+
// Start a server-side session and export its ID
|
|
145
|
+
const sessionResult = await client.startSession({ ttl_seconds: 3600 });
|
|
146
|
+
appendFileSync(envFile, `export MEMORYLAYER_SESSION_ID="${sessionResult.session_id}"\n`);
|
|
147
|
+
// Also save to hook state so the Stop handler can access it
|
|
148
|
+
updateSessionInfo(workspaceId, sessionResult.session_id);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Ignore errors - memory features will still work via MCP tools
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return legacyToResult(await handleSessionStart(input));
|
|
155
|
+
}
|
|
156
|
+
case HookEvent.UserPromptSubmit:
|
|
157
|
+
// Reset recall status for new user turn
|
|
158
|
+
resetRecallStatus();
|
|
159
|
+
return legacyToResult(await handleUserPromptSubmit(input));
|
|
160
|
+
case HookEvent.PreToolUse:
|
|
161
|
+
return legacyToResult(await handlePreToolUse(input));
|
|
162
|
+
case HookEvent.PostToolUse:
|
|
163
|
+
return legacyToResult(await handlePostToolUse(input));
|
|
164
|
+
case HookEvent.PreCompact:
|
|
165
|
+
// PreCompact: Can't inject instructions at this point, just continue
|
|
166
|
+
return {};
|
|
167
|
+
case HookEvent.Stop:
|
|
168
|
+
// Stop: commit working memory and end session (side effects only, no context injection)
|
|
169
|
+
return legacyToResult(await handleStop());
|
|
170
|
+
default:
|
|
171
|
+
return {};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Main entry point
|
|
176
|
+
*/
|
|
177
|
+
async function main() {
|
|
178
|
+
const hookTypeArg = process.argv[2];
|
|
179
|
+
const hookType = parseHookType(hookTypeArg);
|
|
180
|
+
if (!hookType) {
|
|
181
|
+
const output = {
|
|
182
|
+
error: `Invalid or missing hook type. Usage: memorylayer-hook <hook-type>\nValid types: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PreCompact, Stop`,
|
|
183
|
+
};
|
|
184
|
+
console.log(JSON.stringify(output));
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
// Read input from stdin
|
|
189
|
+
const stdinData = await readStdin();
|
|
190
|
+
let input;
|
|
191
|
+
try {
|
|
192
|
+
input = stdinData.trim() ? JSON.parse(stdinData) : { hook_type: hookType };
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
input = { hook_type: hookType };
|
|
196
|
+
}
|
|
197
|
+
// Add hook type to input
|
|
198
|
+
input.hook_type = hookType;
|
|
199
|
+
// Dispatch to handler
|
|
200
|
+
const result = await dispatch(hookType, input);
|
|
201
|
+
// Handle errors from handlers
|
|
202
|
+
if (result.error) {
|
|
203
|
+
const output = buildErrorOutput(hookType, result.error);
|
|
204
|
+
console.log(JSON.stringify(output));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
// Build properly formatted output
|
|
208
|
+
const output = buildOutput(hookType, result.additionalContext, result.block, result.reason);
|
|
209
|
+
// Write output to stdout
|
|
210
|
+
console.log(JSON.stringify(output));
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
const output = buildErrorOutput(hookType, error instanceof Error ? error.message : String(error));
|
|
214
|
+
console.log(JSON.stringify(output));
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Run
|
|
219
|
+
main().catch((error) => {
|
|
220
|
+
console.error("Fatal error:", error);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
});
|
|
223
|
+
//# sourceMappingURL=memorylayer-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memorylayer-hook.js","sourceRoot":"","sources":["../../bin/memorylayer-hook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for MemoryLayer MCP server
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* memorylayer-mcp
|
|
7
|
+
*
|
|
8
|
+
* Environment variables:
|
|
9
|
+
* MEMORYLAYER_URL - Base URL for MemoryLayer API (default: http://localhost:61001)
|
|
10
|
+
* MEMORYLAYER_API_KEY - API key for authentication (optional)
|
|
11
|
+
* MEMORYLAYER_WORKSPACE_ID - Workspace ID (default: auto-detected from directory)
|
|
12
|
+
* MEMORYLAYER_AUTO_WORKSPACE - Set to "false" to disable auto-detection
|
|
13
|
+
* MEMORYLAYER_SESSION_MODE - Set to "false" to disable session/working memory (default: true)
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=memorylayer-mcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memorylayer-mcp.d.ts","sourceRoot":"","sources":["../../bin/memorylayer-mcp.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for MemoryLayer MCP server
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* memorylayer-mcp
|
|
7
|
+
*
|
|
8
|
+
* Environment variables:
|
|
9
|
+
* MEMORYLAYER_URL - Base URL for MemoryLayer API (default: http://localhost:61001)
|
|
10
|
+
* MEMORYLAYER_API_KEY - API key for authentication (optional)
|
|
11
|
+
* MEMORYLAYER_WORKSPACE_ID - Workspace ID (default: auto-detected from directory)
|
|
12
|
+
* MEMORYLAYER_AUTO_WORKSPACE - Set to "false" to disable auto-detection
|
|
13
|
+
* MEMORYLAYER_SESSION_MODE - Set to "false" to disable session/working memory (default: true)
|
|
14
|
+
*/
|
|
15
|
+
import { MemoryLayerClient } from "../src/client.js";
|
|
16
|
+
import { createServer } from "../src/server.js";
|
|
17
|
+
import { detectWorkspaceId } from "../src/workspace.js";
|
|
18
|
+
async function main() {
|
|
19
|
+
// Parse environment variables
|
|
20
|
+
const baseUrl = process.env.MEMORYLAYER_URL || "http://localhost:61001";
|
|
21
|
+
const apiKey = process.env.MEMORYLAYER_API_KEY;
|
|
22
|
+
const autoWorkspace = process.env.MEMORYLAYER_AUTO_WORKSPACE !== "false";
|
|
23
|
+
const sessionMode = process.env.MEMORYLAYER_SESSION_MODE !== "false";
|
|
24
|
+
// Log working directory for debugging workspace detection issues
|
|
25
|
+
console.error(`MCP server starting in directory: ${process.cwd()}`);
|
|
26
|
+
// Determine workspace ID
|
|
27
|
+
let workspaceId;
|
|
28
|
+
if (process.env.MEMORYLAYER_WORKSPACE_ID) {
|
|
29
|
+
workspaceId = process.env.MEMORYLAYER_WORKSPACE_ID;
|
|
30
|
+
console.error(`Using explicit workspace from env: ${workspaceId}`);
|
|
31
|
+
}
|
|
32
|
+
else if (autoWorkspace) {
|
|
33
|
+
workspaceId = detectWorkspaceId();
|
|
34
|
+
console.error(`Auto-detected workspace: ${workspaceId}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
workspaceId = "default";
|
|
38
|
+
console.error(`Using default workspace: ${workspaceId}`);
|
|
39
|
+
}
|
|
40
|
+
// Create client
|
|
41
|
+
const client = new MemoryLayerClient({
|
|
42
|
+
baseUrl,
|
|
43
|
+
apiKey,
|
|
44
|
+
workspaceId
|
|
45
|
+
});
|
|
46
|
+
// Create and run server
|
|
47
|
+
const server = await createServer(client, { workspaceId, sessionMode });
|
|
48
|
+
console.error("MemoryLayer MCP Server Manifest:", JSON.stringify(server.getManifest(), null, 2));
|
|
49
|
+
await server.run();
|
|
50
|
+
}
|
|
51
|
+
// Handle errors and signals
|
|
52
|
+
process.on("SIGINT", () => {
|
|
53
|
+
console.error("Received SIGINT, shutting down gracefully");
|
|
54
|
+
process.exit(0);
|
|
55
|
+
});
|
|
56
|
+
process.on("SIGTERM", () => {
|
|
57
|
+
console.error("Received SIGTERM, shutting down gracefully");
|
|
58
|
+
process.exit(0);
|
|
59
|
+
});
|
|
60
|
+
process.on("uncaughtException", (error) => {
|
|
61
|
+
console.error("Uncaught exception:", error);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
64
|
+
process.on("unhandledRejection", (reason) => {
|
|
65
|
+
console.error("Unhandled rejection:", reason);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
});
|
|
68
|
+
main().catch((error) => {
|
|
69
|
+
console.error("Fatal error:", error);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=memorylayer-mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memorylayer-mcp.js","sourceRoot":"","sources":["../../bin/memorylayer-mcp.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,KAAK,UAAU,IAAI;IACjB,8BAA8B;IAC9B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,wBAAwB,CAAC;IACxE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;IAC/C,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B,KAAK,OAAO,CAAC;IACzE,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,KAAK,OAAO,CAAC;IAErE,iEAAiE;IACjE,OAAO,CAAC,KAAK,CAAC,qCAAqC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAEpE,yBAAyB;IACzB,IAAI,WAAmB,CAAC;IACxB,IAAI,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,CAAC;QACzC,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;QACnD,OAAO,CAAC,KAAK,CAAC,sCAAsC,WAAW,EAAE,CAAC,CAAC;IACrE,CAAC;SAAM,IAAI,aAAa,EAAE,CAAC;QACzB,WAAW,GAAG,iBAAiB,EAAE,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,4BAA4B,WAAW,EAAE,CAAC,CAAC;IAC3D,CAAC;SAAM,CAAC;QACN,WAAW,GAAG,SAAS,CAAC;QACxB,OAAO,CAAC,KAAK,CAAC,4BAA4B,WAAW,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,gBAAgB;IAChB,MAAM,MAAM,GAAG,IAAI,iBAAiB,CAAC;QACnC,OAAO;QACP,MAAM;QACN,WAAW;KACZ,CAAC,CAAC;IAEH,wBAAwB;IACxB,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAAC;IAExE,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAEjG,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;AACrB,CAAC;AAED,4BAA4B;AAC5B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;IACxB,OAAO,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IACzB,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,KAAK,EAAE,EAAE;IACxC,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;IAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,EAAE;IAC1C,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example usage of the MemoryLayer client
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
import { MemoryLayerClient, MemoryType, RelationshipType } from "@scitrera/memorylayer-mcp-server";
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
// Create client instance
|
|
10
|
+
const client = new MemoryLayerClient({
|
|
11
|
+
baseUrl: "http://localhost:61001",
|
|
12
|
+
workspaceId: "example-workspace"
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// 1. Store some memories
|
|
16
|
+
console.log("1. Storing memories...");
|
|
17
|
+
|
|
18
|
+
const problemMemory = await client.remember({
|
|
19
|
+
content: "User encountered authentication timeout errors when API response takes >30s",
|
|
20
|
+
type: MemoryType.EPISODIC,
|
|
21
|
+
importance: 0.7,
|
|
22
|
+
tags: ["authentication", "timeout", "problem"]
|
|
23
|
+
});
|
|
24
|
+
console.log(`Stored problem memory: ${problemMemory.id}`);
|
|
25
|
+
|
|
26
|
+
const solutionMemory = await client.remember({
|
|
27
|
+
content: "Increased authentication timeout to 60s and added retry logic with exponential backoff",
|
|
28
|
+
type: MemoryType.PROCEDURAL,
|
|
29
|
+
importance: 0.9,
|
|
30
|
+
tags: ["authentication", "timeout", "solution"]
|
|
31
|
+
});
|
|
32
|
+
console.log(`Stored solution memory: ${solutionMemory.id}`);
|
|
33
|
+
|
|
34
|
+
// 2. Create an association between problem and solution
|
|
35
|
+
console.log("\n2. Creating association...");
|
|
36
|
+
|
|
37
|
+
const association = await client.associate({
|
|
38
|
+
source_id: solutionMemory.id,
|
|
39
|
+
target_id: problemMemory.id,
|
|
40
|
+
relationship: RelationshipType.SOLVES,
|
|
41
|
+
strength: 0.95
|
|
42
|
+
});
|
|
43
|
+
console.log(`Created association: ${association.id}`);
|
|
44
|
+
|
|
45
|
+
// 3. Recall memories about authentication
|
|
46
|
+
console.log("\n3. Recalling memories about authentication...");
|
|
47
|
+
|
|
48
|
+
const recallResult = await client.recall({
|
|
49
|
+
query: "authentication timeout issues",
|
|
50
|
+
limit: 5,
|
|
51
|
+
min_relevance: 0.5
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log(`Found ${recallResult.memories.length} memories:`);
|
|
55
|
+
for (const memory of recallResult.memories) {
|
|
56
|
+
console.log(` - [${memory.type}] ${memory.content.substring(0, 80)}...`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 4. Reflect on authentication patterns
|
|
60
|
+
console.log("\n4. Reflecting on authentication patterns...");
|
|
61
|
+
|
|
62
|
+
const reflectResult = await client.reflect({
|
|
63
|
+
query: "What have we learned about handling authentication timeouts?",
|
|
64
|
+
max_tokens: 300,
|
|
65
|
+
include_sources: true
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
console.log("Reflection:");
|
|
69
|
+
console.log(reflectResult.reflection);
|
|
70
|
+
console.log(`\nBased on ${reflectResult.source_memories.length} source memories`);
|
|
71
|
+
|
|
72
|
+
// 5. Get workspace statistics
|
|
73
|
+
console.log("\n5. Getting workspace statistics...");
|
|
74
|
+
|
|
75
|
+
const stats = await client.getStatistics(true);
|
|
76
|
+
console.log(`Total memories: ${stats.total_memories}`);
|
|
77
|
+
console.log(`Total associations: ${stats.total_associations}`);
|
|
78
|
+
|
|
79
|
+
if (stats.breakdown) {
|
|
80
|
+
console.log("\nMemories by type:");
|
|
81
|
+
for (const [type, count] of Object.entries(stats.breakdown.by_type)) {
|
|
82
|
+
console.log(` ${type}: ${count}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 6. Get a briefing
|
|
87
|
+
console.log("\n6. Getting session briefing...");
|
|
88
|
+
|
|
89
|
+
const briefing = await client.getBriefing(24, true);
|
|
90
|
+
console.log(briefing.briefing);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scitrera/memorylayer-mcp-server",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "MCP (Model Context Protocol) server for MemoryLayer.ai",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"memorylayer-mcp": "dist/bin/memorylayer-mcp.js",
|
|
10
|
+
"memorylayer-hook": "dist/bin/memorylayer-hook.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"start": "node dist/bin/memorylayer-mcp.js",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"test:coverage": "vitest run --coverage",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"model-context-protocol",
|
|
24
|
+
"memory",
|
|
25
|
+
"ai",
|
|
26
|
+
"llm",
|
|
27
|
+
"agent"
|
|
28
|
+
],
|
|
29
|
+
"author": "Scitrera LLC <> (https://scitrera.ai)",
|
|
30
|
+
"license": "Apache-2.0",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
33
|
+
"@scitrera/memorylayer-sdk": "^0.0.3"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^20.0.0",
|
|
37
|
+
"typescript": "^5.3.0",
|
|
38
|
+
"vitest": "^1.0.0"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/scitrera/memorylayer"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for MCP Server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
6
|
+
import { MCPServer, createServer } from "../src/server.js";
|
|
7
|
+
import { MemoryLayerClient } from "../src/client.js";
|
|
8
|
+
import { TOOLS, SESSION_TOOLS, CONTEXT_ENVIRONMENT_TOOLS } from "../src/tools.js";
|
|
9
|
+
|
|
10
|
+
// Mock the SDK client
|
|
11
|
+
vi.mock("../src/client.js", () => {
|
|
12
|
+
return {
|
|
13
|
+
MemoryLayerClient: vi.fn().mockImplementation(() => ({
|
|
14
|
+
getWorkspaceId: vi.fn().mockReturnValue("test-workspace"),
|
|
15
|
+
remember: vi.fn().mockResolvedValue({ id: "mem-123", type: "semantic", importance: 0.5, tags: [] }),
|
|
16
|
+
recall: vi.fn().mockResolvedValue({ memories: [], total_count: 0, search_latency_ms: 10, mode_used: "semantic" }),
|
|
17
|
+
startSession: vi.fn().mockResolvedValue({ session_id: "server-session-123" }),
|
|
18
|
+
endSession: vi.fn().mockResolvedValue({ memories_extracted: 2 }),
|
|
19
|
+
})),
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("MCPServer", () => {
|
|
24
|
+
let client: MemoryLayerClient;
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
vi.clearAllMocks();
|
|
28
|
+
client = new MemoryLayerClient({ baseUrl: "http://localhost:61001" });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("constructor", () => {
|
|
32
|
+
it("should create server with default options", () => {
|
|
33
|
+
const server = new MCPServer(client);
|
|
34
|
+
expect(server).toBeDefined();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should enable session mode by default", () => {
|
|
38
|
+
const server = new MCPServer(client);
|
|
39
|
+
const manifest = server.getManifest();
|
|
40
|
+
expect(manifest.name).toBe("memorylayer");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should accept custom options", () => {
|
|
44
|
+
const server = new MCPServer(client, {
|
|
45
|
+
workspaceId: "custom-workspace",
|
|
46
|
+
sessionMode: false,
|
|
47
|
+
});
|
|
48
|
+
expect(server).toBeDefined();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("getManifest", () => {
|
|
53
|
+
it("should return server manifest", () => {
|
|
54
|
+
const server = new MCPServer(client);
|
|
55
|
+
const manifest = server.getManifest();
|
|
56
|
+
|
|
57
|
+
expect(manifest.name).toBe("memorylayer");
|
|
58
|
+
expect(manifest.version).toBe("0.1.0");
|
|
59
|
+
expect(manifest.description).toContain("MemoryLayer.ai");
|
|
60
|
+
expect(manifest.capabilities).toBeDefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should list core tools in capabilities", () => {
|
|
64
|
+
const server = new MCPServer(client);
|
|
65
|
+
const manifest = server.getManifest();
|
|
66
|
+
const toolNames = manifest.capabilities?.tools as string[];
|
|
67
|
+
|
|
68
|
+
expect(toolNames).toContain("memory_remember");
|
|
69
|
+
expect(toolNames).toContain("memory_recall");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("tool listing", () => {
|
|
74
|
+
it("should include session tools when session mode enabled", () => {
|
|
75
|
+
const server = new MCPServer(client, { sessionMode: true });
|
|
76
|
+
const manifest = server.getManifest();
|
|
77
|
+
|
|
78
|
+
// The manifest only shows TOOLS, but the handler includes SESSION_TOOLS
|
|
79
|
+
// This tests that core tools are present
|
|
80
|
+
const toolNames = manifest.capabilities?.tools as string[];
|
|
81
|
+
expect(toolNames.length).toBeGreaterThan(0);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("createServer", () => {
|
|
87
|
+
let client: MemoryLayerClient;
|
|
88
|
+
|
|
89
|
+
beforeEach(() => {
|
|
90
|
+
vi.clearAllMocks();
|
|
91
|
+
client = new MemoryLayerClient({ baseUrl: "http://localhost:61001" });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should create server instance", async () => {
|
|
95
|
+
const server = await createServer(client);
|
|
96
|
+
expect(server).toBeInstanceOf(MCPServer);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should pass options to server", async () => {
|
|
100
|
+
const server = await createServer(client, {
|
|
101
|
+
workspaceId: "my-workspace",
|
|
102
|
+
sessionMode: false,
|
|
103
|
+
});
|
|
104
|
+
expect(server).toBeInstanceOf(MCPServer);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("Tool counts", () => {
|
|
109
|
+
it("should have expected number of core tools", () => {
|
|
110
|
+
// memory_remember, memory_recall, memory_reflect, memory_forget,
|
|
111
|
+
// memory_associate, memory_briefing, memory_statistics, memory_graph_query, memory_audit
|
|
112
|
+
expect(TOOLS.length).toBe(9);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should have expected number of session tools", () => {
|
|
116
|
+
// memory_session_start, memory_session_end, memory_session_commit, memory_session_status
|
|
117
|
+
expect(SESSION_TOOLS.length).toBe(4);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should have expected number of context environment tools", () => {
|
|
121
|
+
// memory_context_exec, memory_context_inspect, memory_context_load,
|
|
122
|
+
// memory_context_inject, memory_context_query, memory_context_rlm,
|
|
123
|
+
// memory_context_status, memory_context_checkpoint
|
|
124
|
+
expect(CONTEXT_ENVIRONMENT_TOOLS.length).toBe(8);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should have 21 total tools when combined", () => {
|
|
128
|
+
expect(TOOLS.length + SESSION_TOOLS.length + CONTEXT_ENVIRONMENT_TOOLS.length).toBe(21);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for SessionManager
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
6
|
+
import { SessionManager } from "../src/session.js";
|
|
7
|
+
|
|
8
|
+
describe("SessionManager", () => {
|
|
9
|
+
let manager: SessionManager;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
manager = new SessionManager();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("constructor", () => {
|
|
16
|
+
it("should be enabled by default", () => {
|
|
17
|
+
expect(manager.isEnabled).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should respect enabled config", () => {
|
|
21
|
+
const disabled = new SessionManager({ enabled: false });
|
|
22
|
+
expect(disabled.isEnabled).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should have no active session initially", () => {
|
|
26
|
+
expect(manager.hasActiveSession).toBe(false);
|
|
27
|
+
expect(manager.currentSession).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("startSession", () => {
|
|
32
|
+
it("should create a new session", () => {
|
|
33
|
+
const session = manager.startSession("test-workspace");
|
|
34
|
+
|
|
35
|
+
expect(session).toBeDefined();
|
|
36
|
+
expect(session.id).toMatch(/^local_\d+_[a-z0-9]+$/);
|
|
37
|
+
expect(session.workspaceId).toBe("test-workspace");
|
|
38
|
+
expect(session.committed).toBe(false);
|
|
39
|
+
expect(session.workingMemory.size).toBe(0);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should set hasActiveSession to true", () => {
|
|
43
|
+
manager.startSession("test-workspace");
|
|
44
|
+
expect(manager.hasActiveSession).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should store server session ID if provided", () => {
|
|
48
|
+
const session = manager.startSession("test-workspace", "server-123");
|
|
49
|
+
expect(session.serverSessionId).toBe("server-123");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should replace existing session", () => {
|
|
53
|
+
const first = manager.startSession("workspace-1");
|
|
54
|
+
const second = manager.startSession("workspace-2");
|
|
55
|
+
|
|
56
|
+
expect(manager.currentSession?.id).toBe(second.id);
|
|
57
|
+
expect(manager.currentSession?.workspaceId).toBe("workspace-2");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("endSession", () => {
|
|
62
|
+
it("should return null if no active session", () => {
|
|
63
|
+
const result = manager.endSession();
|
|
64
|
+
expect(result).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should return the ended session", () => {
|
|
68
|
+
const started = manager.startSession("test-workspace");
|
|
69
|
+
const ended = manager.endSession();
|
|
70
|
+
|
|
71
|
+
expect(ended?.id).toBe(started.id);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should clear the active session", () => {
|
|
75
|
+
manager.startSession("test-workspace");
|
|
76
|
+
manager.endSession();
|
|
77
|
+
|
|
78
|
+
expect(manager.hasActiveSession).toBe(false);
|
|
79
|
+
expect(manager.currentSession).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("markCommitted", () => {
|
|
84
|
+
it("should mark session as committed", () => {
|
|
85
|
+
manager.startSession("test-workspace");
|
|
86
|
+
manager.markCommitted();
|
|
87
|
+
|
|
88
|
+
expect(manager.currentSession?.committed).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should do nothing if no active session", () => {
|
|
92
|
+
// Should not throw
|
|
93
|
+
manager.markCommitted();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("working memory", () => {
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
manager.startSession("test-workspace");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("getAllWorkingMemory", () => {
|
|
103
|
+
it("should return empty array for new session", () => {
|
|
104
|
+
const entries = manager.getAllWorkingMemory();
|
|
105
|
+
expect(entries).toEqual([]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should return empty array if no session", () => {
|
|
109
|
+
manager.endSession();
|
|
110
|
+
const entries = manager.getAllWorkingMemory();
|
|
111
|
+
expect(entries).toEqual([]);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("clearWorkingMemory", () => {
|
|
116
|
+
it("should not throw on empty working memory", () => {
|
|
117
|
+
expect(() => manager.clearWorkingMemory()).not.toThrow();
|
|
118
|
+
expect(manager.getAllWorkingMemory()).toHaveLength(0);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("getSessionSummary", () => {
|
|
124
|
+
it("should return inactive status when no session", () => {
|
|
125
|
+
const summary = manager.getSessionSummary();
|
|
126
|
+
expect(summary.active).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should return session info when active", () => {
|
|
130
|
+
manager.startSession("test-workspace", "server-123");
|
|
131
|
+
|
|
132
|
+
const summary = manager.getSessionSummary();
|
|
133
|
+
|
|
134
|
+
expect(summary.active).toBe(true);
|
|
135
|
+
expect(summary.workspaceId).toBe("test-workspace");
|
|
136
|
+
expect(summary.serverSessionId).toBe("server-123");
|
|
137
|
+
expect(summary.workingMemoryCount).toBe(0);
|
|
138
|
+
expect(summary.committed).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("getTtlSeconds", () => {
|
|
143
|
+
it("should return default TTL", () => {
|
|
144
|
+
expect(manager.getTtlSeconds()).toBe(3600);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should return custom TTL", () => {
|
|
148
|
+
const custom = new SessionManager({ ttlSeconds: 7200 });
|
|
149
|
+
expect(custom.getTtlSeconds()).toBe(7200);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for MCP tool definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from "vitest";
|
|
6
|
+
import { TOOLS, SESSION_TOOLS, CONTEXT_ENVIRONMENT_TOOLS, CORE_TOOLS, EXTENDED_TOOLS } from "../src/tools.js";
|
|
7
|
+
|
|
8
|
+
describe("Tool Definitions", () => {
|
|
9
|
+
describe("TOOLS", () => {
|
|
10
|
+
it("should export core memory tools", () => {
|
|
11
|
+
const toolNames = TOOLS.map(t => t.name);
|
|
12
|
+
|
|
13
|
+
expect(toolNames).toContain("memory_remember");
|
|
14
|
+
expect(toolNames).toContain("memory_recall");
|
|
15
|
+
expect(toolNames).toContain("memory_reflect");
|
|
16
|
+
expect(toolNames).toContain("memory_forget");
|
|
17
|
+
expect(toolNames).toContain("memory_associate");
|
|
18
|
+
expect(toolNames).toContain("memory_briefing");
|
|
19
|
+
expect(toolNames).toContain("memory_statistics");
|
|
20
|
+
expect(toolNames).toContain("memory_graph_query");
|
|
21
|
+
expect(toolNames).toContain("memory_audit");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should have valid input schemas", () => {
|
|
25
|
+
for (const tool of TOOLS) {
|
|
26
|
+
expect(tool.inputSchema).toBeDefined();
|
|
27
|
+
expect(tool.inputSchema.type).toBe("object");
|
|
28
|
+
expect(tool.inputSchema.properties).toBeDefined();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should have descriptions for all tools", () => {
|
|
33
|
+
for (const tool of TOOLS) {
|
|
34
|
+
expect(tool.description).toBeDefined();
|
|
35
|
+
expect(tool.description.length).toBeGreaterThan(10);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("SESSION_TOOLS", () => {
|
|
41
|
+
it("should export session management tools", () => {
|
|
42
|
+
const toolNames = SESSION_TOOLS.map(t => t.name);
|
|
43
|
+
|
|
44
|
+
expect(toolNames).toContain("memory_session_start");
|
|
45
|
+
expect(toolNames).toContain("memory_session_end");
|
|
46
|
+
expect(toolNames).toContain("memory_session_commit");
|
|
47
|
+
expect(toolNames).toContain("memory_session_status");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should have valid input schemas", () => {
|
|
51
|
+
for (const tool of SESSION_TOOLS) {
|
|
52
|
+
expect(tool.inputSchema).toBeDefined();
|
|
53
|
+
expect(tool.inputSchema.type).toBe("object");
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should have descriptions for all tools", () => {
|
|
58
|
+
for (const tool of SESSION_TOOLS) {
|
|
59
|
+
expect(tool.description).toBeDefined();
|
|
60
|
+
expect(tool.description.length).toBeGreaterThan(10);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("CONTEXT_ENVIRONMENT_TOOLS", () => {
|
|
66
|
+
it("should export context environment tools", () => {
|
|
67
|
+
const toolNames = CONTEXT_ENVIRONMENT_TOOLS.map(t => t.name);
|
|
68
|
+
|
|
69
|
+
expect(toolNames).toContain("memory_context_exec");
|
|
70
|
+
expect(toolNames).toContain("memory_context_inspect");
|
|
71
|
+
expect(toolNames).toContain("memory_context_load");
|
|
72
|
+
expect(toolNames).toContain("memory_context_inject");
|
|
73
|
+
expect(toolNames).toContain("memory_context_query");
|
|
74
|
+
expect(toolNames).toContain("memory_context_rlm");
|
|
75
|
+
expect(toolNames).toContain("memory_context_status");
|
|
76
|
+
expect(toolNames).toContain("memory_context_checkpoint");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should have valid input schemas", () => {
|
|
80
|
+
for (const tool of CONTEXT_ENVIRONMENT_TOOLS) {
|
|
81
|
+
expect(tool.inputSchema).toBeDefined();
|
|
82
|
+
expect(tool.inputSchema.type).toBe("object");
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should have descriptions for all tools", () => {
|
|
87
|
+
for (const tool of CONTEXT_ENVIRONMENT_TOOLS) {
|
|
88
|
+
expect(tool.description).toBeDefined();
|
|
89
|
+
expect(tool.description.length).toBeGreaterThan(10);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("memory_remember tool", () => {
|
|
95
|
+
const tool = TOOLS.find(t => t.name === "memory_remember");
|
|
96
|
+
|
|
97
|
+
it("should require content", () => {
|
|
98
|
+
expect(tool?.inputSchema.required).toContain("content");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should have memory type enum", () => {
|
|
102
|
+
const typeProperty = tool?.inputSchema.properties?.type;
|
|
103
|
+
expect(typeProperty?.enum).toContain("episodic");
|
|
104
|
+
expect(typeProperty?.enum).toContain("semantic");
|
|
105
|
+
expect(typeProperty?.enum).toContain("procedural");
|
|
106
|
+
expect(typeProperty?.enum).toContain("working");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should have importance range", () => {
|
|
110
|
+
const importance = tool?.inputSchema.properties?.importance;
|
|
111
|
+
expect(importance?.minimum).toBe(0);
|
|
112
|
+
expect(importance?.maximum).toBe(1);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("memory_recall tool", () => {
|
|
117
|
+
const tool = TOOLS.find(t => t.name === "memory_recall");
|
|
118
|
+
|
|
119
|
+
it("should require query", () => {
|
|
120
|
+
expect(tool?.inputSchema.required).toContain("query");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should have limit constraints", () => {
|
|
124
|
+
const limit = tool?.inputSchema.properties?.limit;
|
|
125
|
+
expect(limit?.minimum).toBe(1);
|
|
126
|
+
expect(limit?.maximum).toBe(100);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("memory_associate tool", () => {
|
|
131
|
+
const tool = TOOLS.find(t => t.name === "memory_associate");
|
|
132
|
+
|
|
133
|
+
it("should require source_id, target_id, and relationship", () => {
|
|
134
|
+
expect(tool?.inputSchema.required).toContain("source_id");
|
|
135
|
+
expect(tool?.inputSchema.required).toContain("target_id");
|
|
136
|
+
expect(tool?.inputSchema.required).toContain("relationship");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should have relationship description", () => {
|
|
140
|
+
const relationship = tool?.inputSchema.properties?.relationship;
|
|
141
|
+
expect(relationship?.type).toBe("string");
|
|
142
|
+
expect(relationship?.description).toBeDefined();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("memory_context_exec tool", () => {
|
|
147
|
+
const tool = CONTEXT_ENVIRONMENT_TOOLS.find(t => t.name === "memory_context_exec");
|
|
148
|
+
|
|
149
|
+
it("should require code", () => {
|
|
150
|
+
expect(tool?.inputSchema.required).toContain("code");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should have return_result option", () => {
|
|
154
|
+
const returnResult = tool?.inputSchema.properties?.return_result;
|
|
155
|
+
expect(returnResult?.type).toBe("boolean");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("memory_session_end tool", () => {
|
|
160
|
+
const tool = SESSION_TOOLS.find(t => t.name === "memory_session_end");
|
|
161
|
+
|
|
162
|
+
it("should have commit option", () => {
|
|
163
|
+
expect(tool?.inputSchema.properties?.commit).toBeDefined();
|
|
164
|
+
expect(tool?.inputSchema.properties?.commit?.type).toBe("boolean");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should have importance_threshold option", () => {
|
|
168
|
+
const threshold = tool?.inputSchema.properties?.importance_threshold;
|
|
169
|
+
expect(threshold?.minimum).toBe(0);
|
|
170
|
+
expect(threshold?.maximum).toBe(1);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("Legacy exports", () => {
|
|
175
|
+
it("should export CORE_TOOLS as filtered subset of TOOLS", () => {
|
|
176
|
+
const coreNames = CORE_TOOLS.map(t => t.name);
|
|
177
|
+
expect(coreNames).toEqual([
|
|
178
|
+
"memory_remember", "memory_recall", "memory_reflect",
|
|
179
|
+
"memory_forget", "memory_associate"
|
|
180
|
+
]);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should export EXTENDED_TOOLS as filtered subset of TOOLS", () => {
|
|
184
|
+
const extNames = EXTENDED_TOOLS.map(t => t.name);
|
|
185
|
+
expect(extNames).toEqual([
|
|
186
|
+
"memory_briefing", "memory_statistics",
|
|
187
|
+
"memory_graph_query", "memory_audit"
|
|
188
|
+
]);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: "node",
|
|
7
|
+
include: ["tests/**/*.test.ts"],
|
|
8
|
+
coverage: {
|
|
9
|
+
provider: "v8",
|
|
10
|
+
reporter: ["text", "json", "html"],
|
|
11
|
+
include: ["src/**/*.ts"],
|
|
12
|
+
exclude: ["src/index.ts", "src/types.ts"],
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
});
|